mirror of
https://github.com/SabreTools/BinaryObjectScanner.git
synced 2026-02-04 13:45:28 +00:00
Compare commits
115 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
092374a143 | ||
|
|
3cfb60430a | ||
|
|
370cc68fa4 | ||
|
|
8fe5046c19 | ||
|
|
37e7604441 | ||
|
|
7651b34855 | ||
|
|
4bf89b1d5f | ||
|
|
0287284909 | ||
|
|
a8453b3f21 | ||
|
|
2552564953 | ||
|
|
0d4d19559a | ||
|
|
52f4132ccb | ||
|
|
cb6440662b | ||
|
|
6293895611 | ||
|
|
f564fb6e9e | ||
|
|
3f2adfcf62 | ||
|
|
2c979f291e | ||
|
|
7e7b2ee64a | ||
|
|
87108405a8 | ||
|
|
9fb055cbff | ||
|
|
e690f0137e | ||
|
|
87c08d6fbd | ||
|
|
8c164d776e | ||
|
|
964271b4e1 | ||
|
|
e99ba48f07 | ||
|
|
62b1627b04 | ||
|
|
3a54997d42 | ||
|
|
7d95a43b4b | ||
|
|
23ea8710c0 | ||
|
|
0b62a52991 | ||
|
|
1143c8a8b7 | ||
|
|
a5b66caae6 | ||
|
|
b0b87d05fd | ||
|
|
cb3c666f64 | ||
|
|
12fdae7944 | ||
|
|
e76bc70ec6 | ||
|
|
e78bb8cb41 | ||
|
|
5153e73f42 | ||
|
|
7f36ff8a2b | ||
|
|
99a8a39dda | ||
|
|
adbf983e65 | ||
|
|
d7639495ac | ||
|
|
fbe09d9082 | ||
|
|
70468b72c3 | ||
|
|
90f4af1121 | ||
|
|
aa37449bbf | ||
|
|
c835e04722 | ||
|
|
29b999b8ed | ||
|
|
9ddd6cc317 | ||
|
|
3a694f0e31 | ||
|
|
080cbda588 | ||
|
|
fd066e8aae | ||
|
|
0fd0cf689a | ||
|
|
f85adda24c | ||
|
|
2d1e8e02aa | ||
|
|
371fbee7a4 | ||
|
|
a5bb95e7c1 | ||
|
|
53b5a443fe | ||
|
|
a230871f75 | ||
|
|
f560ce17e8 | ||
|
|
b96329bd33 | ||
|
|
913f7802de | ||
|
|
a9f61ed51e | ||
|
|
04c0835228 | ||
|
|
af7ff05ecf | ||
|
|
4ccf80189e | ||
|
|
b417229ee6 | ||
|
|
61457582b3 | ||
|
|
ecc1613f49 | ||
|
|
5ea89eefe8 | ||
|
|
661808826a | ||
|
|
342f78ffd0 | ||
|
|
cc6a65d5e4 | ||
|
|
068ee76983 | ||
|
|
b980b33019 | ||
|
|
4327ce7848 | ||
|
|
2c813e7b3d | ||
|
|
d69746f7ef | ||
|
|
ce8f73d30d | ||
|
|
d6602ac8a8 | ||
|
|
6478af03e7 | ||
|
|
f086e63914 | ||
|
|
6fbb6bd8dc | ||
|
|
008629b61f | ||
|
|
b7704dbe57 | ||
|
|
5aff2d0b1b | ||
|
|
771bbeed6a | ||
|
|
53dd1e9aa5 | ||
|
|
6b7ed456ac | ||
|
|
5e2185dffd | ||
|
|
b5d318013b | ||
|
|
07a926e50c | ||
|
|
78bbb63c11 | ||
|
|
1fd613c2b2 | ||
|
|
792833ebc8 | ||
|
|
7392fce770 | ||
|
|
6c621c743d | ||
|
|
50a7883958 | ||
|
|
3882db6fc6 | ||
|
|
c91691f79b | ||
|
|
17fbf1163d | ||
|
|
9a1bbd7e0d | ||
|
|
bf6f3bad46 | ||
|
|
b99b2a53cf | ||
|
|
1fec5c15d4 | ||
|
|
a8b13e60b6 | ||
|
|
4c0c44de6b | ||
|
|
af0623beea | ||
|
|
761b418d21 | ||
|
|
0316edb8cb | ||
|
|
48cf417d60 | ||
|
|
83af2926aa | ||
|
|
ddfa820004 | ||
|
|
80986978cb | ||
|
|
68283554e9 |
@@ -9,9 +9,9 @@
|
||||
<Product>BurnOutSharp</Product>
|
||||
<Copyright>Copyright (c)2022 Matt Nadareski</Copyright>
|
||||
<RepositoryUrl>https://github.com/mnadareski/BurnOutSharp</RepositoryUrl>
|
||||
<Version>2.6</Version>
|
||||
<AssemblyVersion>2.6</AssemblyVersion>
|
||||
<FileVersion>2.6</FileVersion>
|
||||
<Version>2.7</Version>
|
||||
<AssemblyVersion>2.7</AssemblyVersion>
|
||||
<FileVersion>2.7</FileVersion>
|
||||
<IncludeSource>true</IncludeSource>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
</PropertyGroup>
|
||||
|
||||
470
BurnOutSharp.Builders/AACS.cs
Normal file
470
BurnOutSharp.Builders/AACS.cs
Normal file
@@ -0,0 +1,470 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using BurnOutSharp.Models.AACS;
|
||||
using BurnOutSharp.Utilities;
|
||||
|
||||
namespace BurnOutSharp.Builders
|
||||
{
|
||||
public class AACS
|
||||
{
|
||||
#region Byte Data
|
||||
|
||||
/// <summary>
|
||||
/// Parse a byte array into an AACS media key block
|
||||
/// </summary>
|
||||
/// <param name="data">Byte array to parse</param>
|
||||
/// <param name="offset">Offset into the byte array</param>
|
||||
/// <returns>Filled archive on success, null on error</returns>
|
||||
public static MediaKeyBlock ParseMediaKeyBlock(byte[] data, int offset)
|
||||
{
|
||||
// If the data is invalid
|
||||
if (data == null)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (offset < 0 || offset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Create a memory stream and parse that
|
||||
MemoryStream dataStream = new MemoryStream(data, offset, data.Length - offset);
|
||||
return ParseMediaKeyBlock(dataStream);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Stream Data
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into an AACS media key block
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled cmedia key block on success, null on error</returns>
|
||||
public static MediaKeyBlock ParseMediaKeyBlock(Stream data)
|
||||
{
|
||||
// If the data is invalid
|
||||
if (data == null || data.Length == 0 || !data.CanSeek || !data.CanRead)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (data.Position < 0 || data.Position >= data.Length)
|
||||
return null;
|
||||
|
||||
// Cache the current offset
|
||||
int initialOffset = (int)data.Position;
|
||||
|
||||
// Create a new media key block to fill
|
||||
var mediaKeyBlock = new MediaKeyBlock();
|
||||
|
||||
#region Records
|
||||
|
||||
// Create the records list
|
||||
var records = new List<Record>();
|
||||
|
||||
// Try to parse the records
|
||||
while (data.Position < data.Length)
|
||||
{
|
||||
// Try to parse the record
|
||||
var record = ParseRecord(data);
|
||||
if (record == null)
|
||||
return null;
|
||||
|
||||
// Add the record
|
||||
records.Add(record);
|
||||
|
||||
// If we have an end of media key block record
|
||||
if (record.RecordType == RecordType.EndOfMediaKeyBlock)
|
||||
break;
|
||||
|
||||
// Align to the 4-byte boundary if we're not at the end
|
||||
if (data.Position != data.Length)
|
||||
{
|
||||
while ((data.Position % 4) != 0)
|
||||
_ = data.ReadByteValue();
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Set the records
|
||||
mediaKeyBlock.Records = records.ToArray();
|
||||
|
||||
#endregion
|
||||
|
||||
return mediaKeyBlock;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a record
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled record on success, null on error</returns>
|
||||
private static Record ParseRecord(Stream data)
|
||||
{
|
||||
// TODO: Use marshalling here instead of building
|
||||
|
||||
// The first 4 bytes make up the type and length
|
||||
byte[] typeAndLength = data.ReadBytes(4);
|
||||
RecordType type = (RecordType)typeAndLength[0];
|
||||
|
||||
// Remove the first byte and parse as big-endian
|
||||
typeAndLength[0] = 0x00;
|
||||
Array.Reverse(typeAndLength);
|
||||
uint length = BitConverter.ToUInt32(typeAndLength, 0);
|
||||
|
||||
// Create a record based on the type
|
||||
switch (type)
|
||||
{
|
||||
// Recognized record types
|
||||
case RecordType.EndOfMediaKeyBlock: return ParseEndOfMediaKeyBlockRecord(data, type, length);
|
||||
case RecordType.ExplicitSubsetDifference: return ParseExplicitSubsetDifferenceRecord(data, type, length);
|
||||
case RecordType.MediaKeyData: return ParseMediaKeyDataRecord(data, type, length);
|
||||
case RecordType.SubsetDifferenceIndex: return ParseSubsetDifferenceIndexRecord(data, type, length);
|
||||
case RecordType.TypeAndVersion: return ParseTypeAndVersionRecord(data, type, length);
|
||||
case RecordType.DriveRevocationList: return ParseDriveRevocationListRecord(data, type, length);
|
||||
case RecordType.HostRevocationList: return ParseHostRevocationListRecord(data, type, length);
|
||||
case RecordType.VerifyMediaKey: return ParseVerifyMediaKeyRecord(data, type, length);
|
||||
case RecordType.Copyright: return ParseCopyrightRecord(data, type, length);
|
||||
|
||||
// Unrecognized record type
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into an end of media key block record
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled end of media key block record on success, null on error</returns>
|
||||
private static EndOfMediaKeyBlockRecord ParseEndOfMediaKeyBlockRecord(Stream data, RecordType type, uint length)
|
||||
{
|
||||
// Verify we're calling the right parser
|
||||
if (type != RecordType.EndOfMediaKeyBlock)
|
||||
return null;
|
||||
|
||||
// TODO: Use marshalling here instead of building
|
||||
var record = new EndOfMediaKeyBlockRecord();
|
||||
|
||||
record.RecordType = type;
|
||||
record.RecordLength = length;
|
||||
if (length > 4)
|
||||
record.SignatureData = data.ReadBytes((int)(length - 4));
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into an explicit subset-difference record
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled explicit subset-difference record on success, null on error</returns>
|
||||
private static ExplicitSubsetDifferenceRecord ParseExplicitSubsetDifferenceRecord(Stream data, RecordType type, uint length)
|
||||
{
|
||||
// Verify we're calling the right parser
|
||||
if (type != RecordType.ExplicitSubsetDifference)
|
||||
return null;
|
||||
|
||||
// TODO: Use marshalling here instead of building
|
||||
var record = new ExplicitSubsetDifferenceRecord();
|
||||
|
||||
record.RecordType = type;
|
||||
record.RecordLength = length;
|
||||
|
||||
// Cache the current offset
|
||||
long initialOffset = data.Position - 4;
|
||||
|
||||
// Create the subset difference list
|
||||
var subsetDifferences = new List<SubsetDifference>();
|
||||
|
||||
// Try to parse the subset differences
|
||||
while (data.Position < initialOffset + length - 5)
|
||||
{
|
||||
var subsetDifference = new SubsetDifference();
|
||||
|
||||
subsetDifference.Mask = data.ReadByteValue();
|
||||
subsetDifference.Number = data.ReadUInt32BE();
|
||||
|
||||
subsetDifferences.Add(subsetDifference);
|
||||
}
|
||||
|
||||
// Set the subset differences
|
||||
record.SubsetDifferences = subsetDifferences.ToArray();
|
||||
|
||||
// If there's any data left, discard it
|
||||
if (data.Position < initialOffset + length)
|
||||
_ = data.ReadBytes((int)(initialOffset + length - data.Position));
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a media key data record
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled media key data record on success, null on error</returns>
|
||||
private static MediaKeyDataRecord ParseMediaKeyDataRecord(Stream data, RecordType type, uint length)
|
||||
{
|
||||
// Verify we're calling the right parser
|
||||
if (type != RecordType.MediaKeyData)
|
||||
return null;
|
||||
|
||||
// TODO: Use marshalling here instead of building
|
||||
var record = new MediaKeyDataRecord();
|
||||
|
||||
record.RecordType = type;
|
||||
record.RecordLength = length;
|
||||
|
||||
// Cache the current offset
|
||||
long initialOffset = data.Position - 4;
|
||||
|
||||
// Create the media key list
|
||||
var mediaKeys = new List<byte[]>();
|
||||
|
||||
// Try to parse the media keys
|
||||
while (data.Position < initialOffset + length)
|
||||
{
|
||||
byte[] mediaKey = data.ReadBytes(0x10);
|
||||
mediaKeys.Add(mediaKey);
|
||||
}
|
||||
|
||||
// Set the media keys
|
||||
record.MediaKeyData = mediaKeys.ToArray();
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a subset-difference index record
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled subset-difference index record on success, null on error</returns>
|
||||
private static SubsetDifferenceIndexRecord ParseSubsetDifferenceIndexRecord(Stream data, RecordType type, uint length)
|
||||
{
|
||||
// Verify we're calling the right parser
|
||||
if (type != RecordType.SubsetDifferenceIndex)
|
||||
return null;
|
||||
|
||||
// TODO: Use marshalling here instead of building
|
||||
var record = new SubsetDifferenceIndexRecord();
|
||||
|
||||
record.RecordType = type;
|
||||
record.RecordLength = length;
|
||||
|
||||
// Cache the current offset
|
||||
long initialOffset = data.Position - 4;
|
||||
|
||||
record.Span = data.ReadUInt32BE();
|
||||
|
||||
// Create the offset list
|
||||
var offsets = new List<uint>();
|
||||
|
||||
// Try to parse the offsets
|
||||
while (data.Position < initialOffset + length)
|
||||
{
|
||||
uint offset = data.ReadUInt32BE();
|
||||
offsets.Add(offset);
|
||||
}
|
||||
|
||||
// Set the offsets
|
||||
record.Offsets = offsets.ToArray();
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a type and version record
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled type and version record on success, null on error</returns>
|
||||
private static TypeAndVersionRecord ParseTypeAndVersionRecord(Stream data, RecordType type, uint length)
|
||||
{
|
||||
// Verify we're calling the right parser
|
||||
if (type != RecordType.TypeAndVersion)
|
||||
return null;
|
||||
|
||||
// TODO: Use marshalling here instead of building
|
||||
var record = new TypeAndVersionRecord();
|
||||
|
||||
record.RecordType = type;
|
||||
record.RecordLength = length;
|
||||
record.MediaKeyBlockType = (MediaKeyBlockType)data.ReadUInt32BE();
|
||||
record.VersionNumber = data.ReadUInt32BE();
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a drive revocation list record
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled drive revocation list record on success, null on error</returns>
|
||||
private static DriveRevocationListRecord ParseDriveRevocationListRecord(Stream data, RecordType type, uint length)
|
||||
{
|
||||
// Verify we're calling the right parser
|
||||
if (type != RecordType.DriveRevocationList)
|
||||
return null;
|
||||
|
||||
// TODO: Use marshalling here instead of building
|
||||
var record = new DriveRevocationListRecord();
|
||||
|
||||
record.RecordType = type;
|
||||
record.RecordLength = length;
|
||||
|
||||
// Cache the current offset
|
||||
long initialOffset = data.Position - 4;
|
||||
|
||||
record.TotalNumberOfEntries = data.ReadUInt32BE();
|
||||
|
||||
// Create the signature blocks list
|
||||
var blocks = new List<DriveRevocationSignatureBlock>();
|
||||
|
||||
// Try to parse the signature blocks
|
||||
int entryCount = 0;
|
||||
while (entryCount < record.TotalNumberOfEntries && data.Position < initialOffset + length)
|
||||
{
|
||||
var block = new DriveRevocationSignatureBlock();
|
||||
|
||||
block.NumberOfEntries = data.ReadUInt32BE();
|
||||
block.EntryFields = new DriveRevocationListEntry[block.NumberOfEntries];
|
||||
for (int i = 0; i < block.EntryFields.Length; i++)
|
||||
{
|
||||
var entry = new DriveRevocationListEntry();
|
||||
|
||||
entry.Range = data.ReadUInt16BE();
|
||||
entry.DriveID = data.ReadBytes(6);
|
||||
|
||||
block.EntryFields[i] = entry;
|
||||
entryCount++;
|
||||
}
|
||||
|
||||
blocks.Add(block);
|
||||
|
||||
// If we have an empty block
|
||||
if (block.NumberOfEntries == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
// Set the signature blocks
|
||||
record.SignatureBlocks = blocks.ToArray();
|
||||
|
||||
// If there's any data left, discard it
|
||||
if (data.Position < initialOffset + length)
|
||||
_ = data.ReadBytes((int)(initialOffset + length - data.Position));
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a host revocation list record
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled host revocation list record on success, null on error</returns>
|
||||
private static HostRevocationListRecord ParseHostRevocationListRecord(Stream data, RecordType type, uint length)
|
||||
{
|
||||
// Verify we're calling the right parser
|
||||
if (type != RecordType.HostRevocationList)
|
||||
return null;
|
||||
|
||||
// TODO: Use marshalling here instead of building
|
||||
var record = new HostRevocationListRecord();
|
||||
|
||||
record.RecordType = type;
|
||||
record.RecordLength = length;
|
||||
|
||||
// Cache the current offset
|
||||
long initialOffset = data.Position - 4;
|
||||
|
||||
record.TotalNumberOfEntries = data.ReadUInt32BE();
|
||||
|
||||
// Create the signature blocks list
|
||||
var blocks = new List<HostRevocationSignatureBlock>();
|
||||
|
||||
// Try to parse the signature blocks
|
||||
int entryCount = 0;
|
||||
while (entryCount < record.TotalNumberOfEntries && data.Position < initialOffset + length)
|
||||
{
|
||||
var block = new HostRevocationSignatureBlock();
|
||||
|
||||
block.NumberOfEntries = data.ReadUInt32BE();
|
||||
block.EntryFields = new HostRevocationListEntry[block.NumberOfEntries];
|
||||
for (int i = 0; i < block.EntryFields.Length; i++)
|
||||
{
|
||||
var entry = new HostRevocationListEntry();
|
||||
|
||||
entry.Range = data.ReadUInt16BE();
|
||||
entry.HostID = data.ReadBytes(6);
|
||||
|
||||
block.EntryFields[i] = entry;
|
||||
entryCount++;
|
||||
}
|
||||
|
||||
blocks.Add(block);
|
||||
|
||||
// If we have an empty block
|
||||
if (block.NumberOfEntries == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
// Set the signature blocks
|
||||
record.SignatureBlocks = blocks.ToArray();
|
||||
|
||||
// If there's any data left, discard it
|
||||
if (data.Position < initialOffset + length)
|
||||
_ = data.ReadBytes((int)(initialOffset + length - data.Position));
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a verify media key record
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled verify media key record on success, null on error</returns>
|
||||
private static VerifyMediaKeyRecord ParseVerifyMediaKeyRecord(Stream data, RecordType type, uint length)
|
||||
{
|
||||
// Verify we're calling the right parser
|
||||
if (type != RecordType.VerifyMediaKey)
|
||||
return null;
|
||||
|
||||
// TODO: Use marshalling here instead of building
|
||||
var record = new VerifyMediaKeyRecord();
|
||||
|
||||
record.RecordType = type;
|
||||
record.RecordLength = length;
|
||||
record.CiphertextValue = data.ReadBytes(0x10);
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a copyright record
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled copyright record on success, null on error</returns>
|
||||
private static CopyrightRecord ParseCopyrightRecord(Stream data, RecordType type, uint length)
|
||||
{
|
||||
// Verify we're calling the right parser
|
||||
if (type != RecordType.Copyright)
|
||||
return null;
|
||||
|
||||
// TODO: Use marshalling here instead of building
|
||||
var record = new CopyrightRecord();
|
||||
|
||||
record.RecordType = type;
|
||||
record.RecordLength = length;
|
||||
if (length > 4)
|
||||
{
|
||||
byte[] copyright = data.ReadBytes((int)(length - 4));
|
||||
record.Copyright = Encoding.ASCII.GetString(copyright).TrimEnd('\0');
|
||||
}
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
95
BurnOutSharp.Builders/BDPlus.cs
Normal file
95
BurnOutSharp.Builders/BDPlus.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using BurnOutSharp.Models.BDPlus;
|
||||
using BurnOutSharp.Utilities;
|
||||
using static BurnOutSharp.Models.BDPlus.Constants;
|
||||
|
||||
namespace BurnOutSharp.Builders
|
||||
{
|
||||
public class BDPlus
|
||||
{
|
||||
#region Byte Data
|
||||
|
||||
/// <summary>
|
||||
/// Parse a byte array into a BD+ SVM
|
||||
/// </summary>
|
||||
/// <param name="data">Byte array to parse</param>
|
||||
/// <param name="offset">Offset into the byte array</param>
|
||||
/// <returns>Filled BD+ SVM on success, null on error</returns>
|
||||
public static SVM ParseSVM(byte[] data, int offset)
|
||||
{
|
||||
// If the data is invalid
|
||||
if (data == null)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (offset < 0 || offset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Create a memory stream and parse that
|
||||
MemoryStream dataStream = new MemoryStream(data, offset, data.Length - offset);
|
||||
return ParseSVM(dataStream);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Stream Data
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into an BD+ SVM
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled BD+ SVM on success, null on error</returns>
|
||||
public static SVM ParseSVM(Stream data)
|
||||
{
|
||||
// If the data is invalid
|
||||
if (data == null || data.Length == 0 || !data.CanSeek || !data.CanRead)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (data.Position < 0 || data.Position >= data.Length)
|
||||
return null;
|
||||
|
||||
// Cache the current offset
|
||||
int initialOffset = (int)data.Position;
|
||||
|
||||
// Try to parse the SVM
|
||||
return ParseSVMData(data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into an SVM
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled SVM on success, null on error</returns>
|
||||
private static SVM ParseSVMData(Stream data)
|
||||
{
|
||||
// TODO: Use marshalling here instead of building
|
||||
var svm = new SVM();
|
||||
|
||||
byte[] signature = data.ReadBytes(8);
|
||||
svm.Signature = Encoding.ASCII.GetString(signature);
|
||||
if (svm.Signature != SignatureString)
|
||||
return null;
|
||||
|
||||
svm.Unknown1 = data.ReadBytes(5);
|
||||
svm.Year = data.ReadUInt16BE();
|
||||
svm.Month = data.ReadByteValue();
|
||||
if (svm.Month < 1 || svm.Month > 12)
|
||||
return null;
|
||||
|
||||
svm.Day = data.ReadByteValue();
|
||||
if (svm.Day < 1 || svm.Day > 31)
|
||||
return null;
|
||||
|
||||
svm.Unknown2 = data.ReadBytes(4);
|
||||
svm.Length = data.ReadUInt32();
|
||||
// if (svm.Length > 0)
|
||||
// svm.Data = data.ReadBytes((int)svm.Length);
|
||||
|
||||
return svm;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using BurnOutSharp.Models.BFPK;
|
||||
using BurnOutSharp.Utilities;
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
<Product>BurnOutSharp</Product>
|
||||
<Copyright>Copyright (c)2022 Matt Nadareski</Copyright>
|
||||
<RepositoryUrl>https://github.com/mnadareski/BurnOutSharp</RepositoryUrl>
|
||||
<Version>2.6</Version>
|
||||
<AssemblyVersion>2.6</AssemblyVersion>
|
||||
<FileVersion>2.6</FileVersion>
|
||||
<Version>2.7</Version>
|
||||
<AssemblyVersion>2.7</AssemblyVersion>
|
||||
<FileVersion>2.7</FileVersion>
|
||||
<IncludeSource>true</IncludeSource>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
</PropertyGroup>
|
||||
|
||||
391
BurnOutSharp.Builders/CFB.cs
Normal file
391
BurnOutSharp.Builders/CFB.cs
Normal file
@@ -0,0 +1,391 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using BurnOutSharp.Models.CFB;
|
||||
using BurnOutSharp.Utilities;
|
||||
using static BurnOutSharp.Models.CFB.Constants;
|
||||
|
||||
namespace BurnOutSharp.Builders
|
||||
{
|
||||
public class CFB
|
||||
{
|
||||
#region Byte Data
|
||||
|
||||
/// <summary>
|
||||
/// Parse a byte array into a Compound File Binary
|
||||
/// </summary>
|
||||
/// <param name="data">Byte array to parse</param>
|
||||
/// <param name="offset">Offset into the byte array</param>
|
||||
/// <returns>Filled Compound File Binary on success, null on error</returns>
|
||||
public static Binary ParseBinary(byte[] data, int offset)
|
||||
{
|
||||
// If the data is invalid
|
||||
if (data == null)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (offset < 0 || offset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Create a memory stream and parse that
|
||||
MemoryStream dataStream = new MemoryStream(data, offset, data.Length - offset);
|
||||
return ParseBinary(dataStream);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Stream Data
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a Compound File Binary
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled Compound File Binary on success, null on error</returns>
|
||||
public static Binary ParseBinary(Stream data)
|
||||
{
|
||||
// If the data is invalid
|
||||
if (data == null || data.Length == 0 || !data.CanSeek || !data.CanRead)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (data.Position < 0 || data.Position >= data.Length)
|
||||
return null;
|
||||
|
||||
// Cache the current offset
|
||||
int initialOffset = (int)data.Position;
|
||||
|
||||
// Create a new binary to fill
|
||||
var binary = new Binary();
|
||||
|
||||
#region Header
|
||||
|
||||
// Try to parse the file header
|
||||
var fileHeader = ParseFileHeader(data);
|
||||
if (fileHeader == null)
|
||||
return null;
|
||||
|
||||
// Set the file header
|
||||
binary.Header = fileHeader;
|
||||
|
||||
#endregion
|
||||
|
||||
#region DIFAT Sector Numbers
|
||||
|
||||
// Create a DIFAT sector table
|
||||
var difatSectors = new List<SectorNumber>();
|
||||
|
||||
// Add the sectors from the header
|
||||
difatSectors.AddRange(fileHeader.DIFAT);
|
||||
|
||||
// Loop through and add the DIFAT sectors
|
||||
SectorNumber currentSector = (SectorNumber)fileHeader.FirstDIFATSectorLocation;
|
||||
for (int i = 0; i < fileHeader.NumberOfDIFATSectors; i++)
|
||||
{
|
||||
// If we have a readable sector
|
||||
if (currentSector <= SectorNumber.MAXREGSECT)
|
||||
{
|
||||
// Get the new next sector information
|
||||
long sectorOffset = (long)((long)(currentSector + 1) * Math.Pow(2, fileHeader.SectorShift));
|
||||
if (sectorOffset < 0 || sectorOffset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Seek to the next sector
|
||||
data.Seek(sectorOffset, SeekOrigin.Begin);
|
||||
|
||||
// Try to parse the sectors
|
||||
var sectorNumbers = ParseSectorNumbers(data, fileHeader.SectorShift);
|
||||
if (sectorNumbers == null)
|
||||
return null;
|
||||
|
||||
// Add the sector shifts
|
||||
difatSectors.AddRange(sectorNumbers);
|
||||
}
|
||||
|
||||
// Get the next sector from the DIFAT
|
||||
currentSector = difatSectors[i];
|
||||
}
|
||||
|
||||
// Assign the DIFAT sectors table
|
||||
binary.DIFATSectorNumbers = difatSectors.ToArray();
|
||||
|
||||
#endregion
|
||||
|
||||
#region FAT Sector Numbers
|
||||
|
||||
// Create a FAT sector table
|
||||
var fatSectors = new List<SectorNumber>();
|
||||
|
||||
// Loop through and add the FAT sectors
|
||||
currentSector = binary.DIFATSectorNumbers[0];
|
||||
for (int i = 0; i < fileHeader.NumberOfFATSectors; i++)
|
||||
{
|
||||
// If we have a readable sector
|
||||
if (currentSector <= SectorNumber.MAXREGSECT)
|
||||
{
|
||||
// Get the new next sector information
|
||||
long sectorOffset = (long)((long)(currentSector + 1) * Math.Pow(2, fileHeader.SectorShift));
|
||||
if (sectorOffset < 0 || sectorOffset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Seek to the next sector
|
||||
data.Seek(sectorOffset, SeekOrigin.Begin);
|
||||
|
||||
// Try to parse the sectors
|
||||
var sectorNumbers = ParseSectorNumbers(data, fileHeader.SectorShift);
|
||||
if (sectorNumbers == null)
|
||||
return null;
|
||||
|
||||
// Add the sector shifts
|
||||
fatSectors.AddRange(sectorNumbers);
|
||||
}
|
||||
|
||||
// Get the next sector from the DIFAT
|
||||
currentSector = binary.DIFATSectorNumbers[i];
|
||||
}
|
||||
|
||||
// Assign the FAT sectors table
|
||||
binary.FATSectorNumbers = fatSectors.ToArray();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Mini FAT Sector Numbers
|
||||
|
||||
// Create a mini FAT sector table
|
||||
var miniFatSectors = new List<SectorNumber>();
|
||||
|
||||
// Loop through and add the mini FAT sectors
|
||||
currentSector = (SectorNumber)fileHeader.FirstMiniFATSectorLocation;
|
||||
for (int i = 0; i < fileHeader.NumberOfMiniFATSectors; i++)
|
||||
{
|
||||
// If we have a readable sector
|
||||
if (currentSector <= SectorNumber.MAXREGSECT)
|
||||
{
|
||||
// Get the new next sector information
|
||||
long sectorOffset = (long)((long)(currentSector + 1) * Math.Pow(2, fileHeader.SectorShift));
|
||||
if (sectorOffset < 0 || sectorOffset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Seek to the next sector
|
||||
data.Seek(sectorOffset, SeekOrigin.Begin);
|
||||
|
||||
// Try to parse the sectors
|
||||
var sectorNumbers = ParseSectorNumbers(data, fileHeader.SectorShift);
|
||||
if (sectorNumbers == null)
|
||||
return null;
|
||||
|
||||
// Add the sector shifts
|
||||
miniFatSectors.AddRange(sectorNumbers);
|
||||
}
|
||||
|
||||
// Get the next sector from the DIFAT
|
||||
currentSector = binary.DIFATSectorNumbers[i];
|
||||
}
|
||||
|
||||
// Assign the mini FAT sectors table
|
||||
binary.MiniFATSectorNumbers = miniFatSectors.ToArray();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Directory Entries
|
||||
|
||||
// Get the offset of the first directory sector
|
||||
long firstDirectoryOffset = (long)(fileHeader.FirstDirectorySectorLocation * Math.Pow(2, fileHeader.SectorShift));
|
||||
if (firstDirectoryOffset < 0 || firstDirectoryOffset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Seek to the first directory sector
|
||||
data.Seek(firstDirectoryOffset, SeekOrigin.Begin);
|
||||
|
||||
// Create a directory sector table
|
||||
var directorySectors = new List<DirectoryEntry>();
|
||||
|
||||
// Get the number of directory sectors
|
||||
uint directorySectorCount = 0;
|
||||
switch (fileHeader.MajorVersion)
|
||||
{
|
||||
case 3:
|
||||
directorySectorCount = int.MaxValue;
|
||||
break;
|
||||
case 4:
|
||||
directorySectorCount = fileHeader.NumberOfDirectorySectors;
|
||||
break;
|
||||
}
|
||||
|
||||
// Loop through and add the directory sectors
|
||||
currentSector = (SectorNumber)fileHeader.FirstDirectorySectorLocation;
|
||||
for (int i = 0; i < directorySectorCount; i++)
|
||||
{
|
||||
// If we have an end of chain
|
||||
if (currentSector == SectorNumber.ENDOFCHAIN)
|
||||
break;
|
||||
|
||||
// If we have a readable sector
|
||||
if (currentSector <= SectorNumber.MAXREGSECT)
|
||||
{
|
||||
// Get the new next sector information
|
||||
long sectorOffset = (long)((long)(currentSector + 1) * Math.Pow(2, fileHeader.SectorShift));
|
||||
if (sectorOffset < 0 || sectorOffset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Seek to the next sector
|
||||
data.Seek(sectorOffset, SeekOrigin.Begin);
|
||||
|
||||
// Try to parse the sectors
|
||||
var directoryEntries = ParseDirectoryEntries(data, fileHeader.SectorShift, fileHeader.MajorVersion);
|
||||
if (directoryEntries == null)
|
||||
return null;
|
||||
|
||||
// Add the sector shifts
|
||||
directorySectors.AddRange(directoryEntries);
|
||||
}
|
||||
|
||||
// Get the next sector from the DIFAT
|
||||
currentSector = binary.DIFATSectorNumbers[i];
|
||||
}
|
||||
|
||||
// Assign the Directory sectors table
|
||||
binary.DirectoryEntries = directorySectors.ToArray();
|
||||
|
||||
#endregion
|
||||
|
||||
return binary;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a file header
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled file header on success, null on error</returns>
|
||||
private static FileHeader ParseFileHeader(Stream data)
|
||||
{
|
||||
// TODO: Use marshalling here instead of building
|
||||
FileHeader header = new FileHeader();
|
||||
|
||||
header.Signature = data.ReadUInt64();
|
||||
if (header.Signature != SignatureUInt64)
|
||||
return null;
|
||||
|
||||
header.CLSID = data.ReadGuid();
|
||||
header.MinorVersion = data.ReadUInt16();
|
||||
header.MajorVersion = data.ReadUInt16();
|
||||
header.ByteOrder = data.ReadUInt16();
|
||||
if (header.ByteOrder != 0xFFFE)
|
||||
return null;
|
||||
|
||||
header.SectorShift = data.ReadUInt16();
|
||||
if (header.MajorVersion == 3 && header.SectorShift != 0x0009)
|
||||
return null;
|
||||
else if (header.MajorVersion == 4 && header.SectorShift != 0x000C)
|
||||
return null;
|
||||
|
||||
header.MiniSectorShift = data.ReadUInt16();
|
||||
header.Reserved = data.ReadBytes(6);
|
||||
header.NumberOfDirectorySectors = data.ReadUInt32();
|
||||
if (header.MajorVersion == 3 && header.NumberOfDirectorySectors != 0)
|
||||
return null;
|
||||
|
||||
header.NumberOfFATSectors = data.ReadUInt32();
|
||||
header.FirstDirectorySectorLocation = data.ReadUInt32();
|
||||
header.TransactionSignatureNumber = data.ReadUInt32();
|
||||
header.MiniStreamCutoffSize = data.ReadUInt32();
|
||||
if (header.MiniStreamCutoffSize != 0x00001000)
|
||||
return null;
|
||||
|
||||
header.FirstMiniFATSectorLocation = data.ReadUInt32();
|
||||
header.NumberOfMiniFATSectors = data.ReadUInt32();
|
||||
header.FirstDIFATSectorLocation = data.ReadUInt32();
|
||||
header.NumberOfDIFATSectors = data.ReadUInt32();
|
||||
header.DIFAT = new SectorNumber[109];
|
||||
for (int i = 0; i < header.DIFAT.Length; i++)
|
||||
{
|
||||
header.DIFAT[i] = (SectorNumber)data.ReadUInt32();
|
||||
}
|
||||
|
||||
// Skip rest of sector for version 4
|
||||
if (header.MajorVersion == 4)
|
||||
_ = data.ReadBytes(3584);
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a sector full of sector numbers
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <param name="sectorShift">Sector shift from the header</param>
|
||||
/// <returns>Filled sector full of sector numbers on success, null on error</returns>
|
||||
private static SectorNumber[] ParseSectorNumbers(Stream data, ushort sectorShift)
|
||||
{
|
||||
// TODO: Use marshalling here instead of building
|
||||
int sectorCount = (int)(Math.Pow(2, sectorShift) / sizeof(uint));
|
||||
SectorNumber[] sectorNumbers = new SectorNumber[sectorCount];
|
||||
|
||||
for (int i = 0; i < sectorNumbers.Length; i++)
|
||||
{
|
||||
sectorNumbers[i] = (SectorNumber)data.ReadUInt32();
|
||||
}
|
||||
|
||||
return sectorNumbers;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a sector full of directory entries
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <param name="sectorShift">Sector shift from the header</param>
|
||||
/// <param name="majorVersion">Major version from the header</param>
|
||||
/// <returns>Filled sector full of directory entries on success, null on error</returns>
|
||||
private static DirectoryEntry[] ParseDirectoryEntries(Stream data, ushort sectorShift, ushort majorVersion)
|
||||
{
|
||||
// TODO: Use marshalling here instead of building
|
||||
const int directoryEntrySize = 64 + 2 + 1 + 1 + 4 + 4 + 4 + 16 + 4 + 8 + 8 + 4 + 8;
|
||||
int sectorCount = (int)(Math.Pow(2, sectorShift) / directoryEntrySize);
|
||||
DirectoryEntry[] directoryEntries = new DirectoryEntry[sectorCount];
|
||||
|
||||
for (int i = 0; i < directoryEntries.Length; i++)
|
||||
{
|
||||
var directoryEntry = ParseDirectoryEntry(data, majorVersion);
|
||||
if (directoryEntry == null)
|
||||
return null;
|
||||
|
||||
directoryEntries[i] = directoryEntry;
|
||||
}
|
||||
|
||||
return directoryEntries;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a directory entry
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <param name="majorVersion">Major version from the header</param>
|
||||
/// <returns>Filled directory entry on success, null on error</returns>
|
||||
private static DirectoryEntry ParseDirectoryEntry(Stream data, ushort majorVersion)
|
||||
{
|
||||
// TODO: Use marshalling here instead of building
|
||||
DirectoryEntry directoryEntry = new DirectoryEntry();
|
||||
|
||||
byte[] name = data.ReadBytes(64);
|
||||
directoryEntry.Name = Encoding.Unicode.GetString(name).TrimEnd('\0');
|
||||
directoryEntry.NameLength = data.ReadUInt16();
|
||||
directoryEntry.ObjectType = (ObjectType)data.ReadByteValue();
|
||||
directoryEntry.ColorFlag = (ColorFlag)data.ReadByteValue();
|
||||
directoryEntry.LeftSiblingID = (StreamID)data.ReadUInt32();
|
||||
directoryEntry.RightSiblingID = (StreamID)data.ReadUInt32();
|
||||
directoryEntry.ChildID = (StreamID)data.ReadUInt32();
|
||||
directoryEntry.CLSID = data.ReadGuid();
|
||||
directoryEntry.StateBits = data.ReadUInt32();
|
||||
directoryEntry.CreationTime = data.ReadUInt64();
|
||||
directoryEntry.ModifiedTime = data.ReadUInt64();
|
||||
directoryEntry.StartingSectorLocation = data.ReadUInt32();
|
||||
directoryEntry.StreamSize = data.ReadUInt64();
|
||||
if (majorVersion == 3)
|
||||
directoryEntry.StreamSize &= 0x0000FFFF;
|
||||
|
||||
return directoryEntry;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ namespace BurnOutSharp.Builders
|
||||
/// <param name="data">Byte array to parse</param>
|
||||
/// <param name="offset">Offset into the byte array</param>
|
||||
/// <returns>Filled cabinet on success, null on error</returns>
|
||||
public static Header ParseCabinet(byte[] data, int offset)
|
||||
public static Cabinet ParseCabinet(byte[] data, int offset)
|
||||
{
|
||||
// If the data is invalid
|
||||
if (data == null)
|
||||
@@ -42,7 +42,7 @@ namespace BurnOutSharp.Builders
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled cabinet on success, null on error</returns>
|
||||
public static Header ParseCabinet(Stream data)
|
||||
public static Cabinet ParseCabinet(Stream data)
|
||||
{
|
||||
// If the data is invalid
|
||||
if (data == null || data.Length == 0 || !data.CanSeek || !data.CanRead)
|
||||
@@ -56,7 +56,7 @@ namespace BurnOutSharp.Builders
|
||||
int initialOffset = (int)data.Position;
|
||||
|
||||
// Create a new cabinet to fill
|
||||
var header = new Header();
|
||||
var cabinet = new Cabinet();
|
||||
|
||||
#region Common Header
|
||||
|
||||
@@ -66,34 +66,46 @@ namespace BurnOutSharp.Builders
|
||||
return null;
|
||||
|
||||
// Set the cabinet header
|
||||
header.CommonHeader = commonHeader;
|
||||
cabinet.CommonHeader = commonHeader;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Cabinet Descriptor
|
||||
#region Volume Header
|
||||
|
||||
// Get the cabinet descriptor offset
|
||||
uint cabinetDescriptorOffset = commonHeader.CabDescriptorOffset;
|
||||
if (cabinetDescriptorOffset < 0 || cabinetDescriptorOffset >= data.Length)
|
||||
// Try to parse the volume header
|
||||
var volumeHeader = ParseVolumeHeader(data, GetMajorVersion(commonHeader));
|
||||
if (volumeHeader == null)
|
||||
return null;
|
||||
|
||||
// Seek to the cabinet descriptor
|
||||
data.Seek(cabinetDescriptorOffset, SeekOrigin.Begin);
|
||||
// Set the volume header
|
||||
cabinet.VolumeHeader = volumeHeader;
|
||||
|
||||
// Try to parse the cabinet descriptor
|
||||
var cabinetDescriptor = ParseCabinetDescriptor(data);
|
||||
if (cabinetDescriptor == null)
|
||||
#endregion
|
||||
|
||||
#region Descriptor
|
||||
|
||||
// Get the descriptor offset
|
||||
uint descriptorOffset = commonHeader.DescriptorOffset;
|
||||
if (descriptorOffset < 0 || descriptorOffset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Set the cabinet descriptor
|
||||
header.CabinetDescriptor = cabinetDescriptor;
|
||||
// Seek to the descriptor
|
||||
data.Seek(descriptorOffset, SeekOrigin.Begin);
|
||||
|
||||
// Try to parse the descriptor
|
||||
var descriptor = ParseDescriptor(data);
|
||||
if (descriptor == null)
|
||||
return null;
|
||||
|
||||
// Set the descriptor
|
||||
cabinet.Descriptor = descriptor;
|
||||
|
||||
#endregion
|
||||
|
||||
#region File Descriptor Offsets
|
||||
|
||||
// Get the file table offset
|
||||
uint fileTableOffset = commonHeader.CabDescriptorOffset + cabinetDescriptor.FileTableOffset;
|
||||
uint fileTableOffset = commonHeader.DescriptorOffset + descriptor.FileTableOffset;
|
||||
if (fileTableOffset < 0 || fileTableOffset >= data.Length)
|
||||
return null;
|
||||
|
||||
@@ -102,16 +114,16 @@ namespace BurnOutSharp.Builders
|
||||
|
||||
// Get the number of file table items
|
||||
uint fileTableItems;
|
||||
if (header.MajorVersion <= 5)
|
||||
fileTableItems = cabinetDescriptor.DirectoryCount + cabinetDescriptor.FileCount;
|
||||
if (GetMajorVersion(commonHeader) <= 5)
|
||||
fileTableItems = descriptor.DirectoryCount + descriptor.FileCount;
|
||||
else
|
||||
fileTableItems = cabinetDescriptor.DirectoryCount;
|
||||
fileTableItems = descriptor.DirectoryCount;
|
||||
|
||||
// Create and fill the file table
|
||||
header.FileDescriptorOffsets = new uint[fileTableItems];
|
||||
for (int i = 0; i < header.FileDescriptorOffsets.Length; i++)
|
||||
cabinet.FileDescriptorOffsets = new uint[fileTableItems];
|
||||
for (int i = 0; i < cabinet.FileDescriptorOffsets.Length; i++)
|
||||
{
|
||||
header.FileDescriptorOffsets[i] = data.ReadUInt32();
|
||||
cabinet.FileDescriptorOffsets[i] = data.ReadUInt32();
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -119,13 +131,13 @@ namespace BurnOutSharp.Builders
|
||||
#region Directory Descriptors
|
||||
|
||||
// Create and fill the directory descriptors
|
||||
header.DirectoryDescriptors = new FileDescriptor[cabinetDescriptor.DirectoryCount];
|
||||
for (int i = 0; i < cabinetDescriptor.DirectoryCount; i++)
|
||||
cabinet.DirectoryNames = new string[descriptor.DirectoryCount];
|
||||
for (int i = 0; i < descriptor.DirectoryCount; i++)
|
||||
{
|
||||
// Get the directory descriptor offset
|
||||
uint offset = cabinetDescriptorOffset
|
||||
+ cabinetDescriptor.FileTableOffset
|
||||
+ header.FileDescriptorOffsets[i];
|
||||
uint offset = descriptorOffset
|
||||
+ descriptor.FileTableOffset
|
||||
+ cabinet.FileDescriptorOffsets[i];
|
||||
|
||||
// If we have an invalid offset
|
||||
if (offset < 0 || offset >= data.Length)
|
||||
@@ -135,8 +147,8 @@ namespace BurnOutSharp.Builders
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Create and add the file descriptor
|
||||
FileDescriptor directoryDescriptor = ParseDirectoryDescriptor(data, header.MajorVersion);
|
||||
header.DirectoryDescriptors[i] = directoryDescriptor;
|
||||
string directoryName = ParseDirectoryName(data, GetMajorVersion(commonHeader));
|
||||
cabinet.DirectoryNames[i] = directoryName;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -144,22 +156,22 @@ namespace BurnOutSharp.Builders
|
||||
#region File Descriptors
|
||||
|
||||
// Create and fill the file descriptors
|
||||
header.FileDescriptors = new FileDescriptor[cabinetDescriptor.FileCount];
|
||||
for (int i = 0; i < cabinetDescriptor.FileCount; i++)
|
||||
cabinet.FileDescriptors = new FileDescriptor[descriptor.FileCount];
|
||||
for (int i = 0; i < descriptor.FileCount; i++)
|
||||
{
|
||||
// Get the file descriptor offset
|
||||
uint offset;
|
||||
if (header.MajorVersion <= 5)
|
||||
if (GetMajorVersion(commonHeader) <= 5)
|
||||
{
|
||||
offset = cabinetDescriptorOffset
|
||||
+ cabinetDescriptor.FileTableOffset
|
||||
+ header.FileDescriptorOffsets[cabinetDescriptor.DirectoryCount + i];
|
||||
offset = descriptorOffset
|
||||
+ descriptor.FileTableOffset
|
||||
+ cabinet.FileDescriptorOffsets[descriptor.DirectoryCount + i];
|
||||
}
|
||||
else
|
||||
{
|
||||
offset = cabinetDescriptorOffset
|
||||
+ cabinetDescriptor.FileTableOffset
|
||||
+ cabinetDescriptor.FileTableOffset2
|
||||
offset = descriptorOffset
|
||||
+ descriptor.FileTableOffset
|
||||
+ descriptor.FileTableOffset2
|
||||
+ (uint)(i * 0x57);
|
||||
}
|
||||
|
||||
@@ -171,8 +183,8 @@ namespace BurnOutSharp.Builders
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Create and add the file descriptor
|
||||
FileDescriptor fileDescriptor = ParseFileDescriptor(data, header.MajorVersion, cabinetDescriptorOffset + cabinetDescriptor.FileTableOffset);
|
||||
header.FileDescriptors[i] = fileDescriptor;
|
||||
FileDescriptor fileDescriptor = ParseFileDescriptor(data, GetMajorVersion(commonHeader), descriptorOffset + descriptor.FileTableOffset);
|
||||
cabinet.FileDescriptors[i] = fileDescriptor;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -180,16 +192,16 @@ namespace BurnOutSharp.Builders
|
||||
#region File Group Offsets
|
||||
|
||||
// Create and fill the file group offsets
|
||||
header.FileGroupOffsets = new Dictionary<long, OffsetList>();
|
||||
for (int i = 0; i < cabinetDescriptor.FileGroupOffsets.Length; i++)
|
||||
cabinet.FileGroupOffsets = new Dictionary<long, OffsetList>();
|
||||
for (int i = 0; i < descriptor.FileGroupOffsets.Length; i++)
|
||||
{
|
||||
// Get the file group offset
|
||||
uint offset = cabinetDescriptor.FileGroupOffsets[i];
|
||||
uint offset = descriptor.FileGroupOffsets[i];
|
||||
if (offset == 0)
|
||||
continue;
|
||||
|
||||
// Adjust the file group offset
|
||||
offset += commonHeader.CabDescriptorOffset;
|
||||
offset += commonHeader.DescriptorOffset;
|
||||
if (offset < 0 || offset >= data.Length)
|
||||
continue;
|
||||
|
||||
@@ -197,22 +209,22 @@ namespace BurnOutSharp.Builders
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Create and add the offset
|
||||
OffsetList offsetList = ParseOffsetList(data, header.MajorVersion, cabinetDescriptorOffset);
|
||||
header.FileGroupOffsets[cabinetDescriptor.FileGroupOffsets[i]] = offsetList;
|
||||
OffsetList offsetList = ParseOffsetList(data, GetMajorVersion(commonHeader), descriptorOffset);
|
||||
cabinet.FileGroupOffsets[descriptor.FileGroupOffsets[i]] = offsetList;
|
||||
|
||||
// If we have a nonzero next offset
|
||||
uint nextOffset = offsetList.NextOffset;
|
||||
while (nextOffset != 0)
|
||||
{
|
||||
// Get the next offset to read
|
||||
uint internalOffset = nextOffset + commonHeader.CabDescriptorOffset;
|
||||
uint internalOffset = nextOffset + commonHeader.DescriptorOffset;
|
||||
|
||||
// Seek to the file group offset
|
||||
data.Seek(internalOffset, SeekOrigin.Begin);
|
||||
|
||||
// Create and add the offset
|
||||
offsetList = ParseOffsetList(data, header.MajorVersion, cabinetDescriptorOffset);
|
||||
header.FileGroupOffsets[nextOffset] = offsetList;
|
||||
offsetList = ParseOffsetList(data, GetMajorVersion(commonHeader), descriptorOffset);
|
||||
cabinet.FileGroupOffsets[nextOffset] = offsetList;
|
||||
|
||||
// Set the next offset
|
||||
nextOffset = offsetList.NextOffset;
|
||||
@@ -223,43 +235,55 @@ namespace BurnOutSharp.Builders
|
||||
|
||||
#region File Groups
|
||||
|
||||
// Create the file groups array
|
||||
cabinet.FileGroups = new FileGroup[cabinet.FileGroupOffsets.Count];
|
||||
|
||||
// Create and fill the file groups
|
||||
List<FileGroup> fileGroups = new List<FileGroup>();
|
||||
foreach (var kvp in header.FileGroupOffsets)
|
||||
int fileGroupId = 0;
|
||||
foreach (var kvp in cabinet.FileGroupOffsets)
|
||||
{
|
||||
// Get the offset
|
||||
OffsetList list = kvp.Value;
|
||||
if (list == null)
|
||||
{
|
||||
fileGroupId++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we have an invalid offset
|
||||
if (list.DescriptorOffset <= 0)
|
||||
{
|
||||
fileGroupId++;
|
||||
continue;
|
||||
}
|
||||
|
||||
/// Seek to the file group
|
||||
data.Seek(list.DescriptorOffset + cabinetDescriptorOffset, SeekOrigin.Begin);
|
||||
data.Seek(list.DescriptorOffset + descriptorOffset, SeekOrigin.Begin);
|
||||
|
||||
// Try to parse the file group
|
||||
FileGroup fileGroup = ParseFileGroup(data, header.MajorVersion, cabinetDescriptorOffset);
|
||||
var fileGroup = ParseFileGroup(data, GetMajorVersion(commonHeader), descriptorOffset);
|
||||
if (fileGroup == null)
|
||||
return null;
|
||||
|
||||
// Add the file group
|
||||
fileGroups.Add(fileGroup);
|
||||
cabinet.FileGroups[fileGroupId++] = fileGroup;
|
||||
}
|
||||
|
||||
// Set the file groups
|
||||
header.FileGroups = fileGroups.ToArray();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Component Offsets
|
||||
|
||||
// Create and fill the component offsets
|
||||
header.ComponentOffsets = new Dictionary<long, OffsetList>();
|
||||
for (int i = 0; i < cabinetDescriptor.ComponentOffsets.Length; i++)
|
||||
cabinet.ComponentOffsets = new Dictionary<long, OffsetList>();
|
||||
for (int i = 0; i < descriptor.ComponentOffsets.Length; i++)
|
||||
{
|
||||
// Get the component offset
|
||||
uint offset = cabinetDescriptor.ComponentOffsets[i];
|
||||
uint offset = descriptor.ComponentOffsets[i];
|
||||
if (offset == 0)
|
||||
continue;
|
||||
|
||||
// Adjust the component offset
|
||||
offset += commonHeader.CabDescriptorOffset;
|
||||
offset += commonHeader.DescriptorOffset;
|
||||
if (offset < 0 || offset >= data.Length)
|
||||
continue;
|
||||
|
||||
@@ -267,22 +291,22 @@ namespace BurnOutSharp.Builders
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Create and add the offset
|
||||
OffsetList offsetList = ParseOffsetList(data, header.MajorVersion, cabinetDescriptorOffset);
|
||||
header.ComponentOffsets[cabinetDescriptor.ComponentOffsets[i]] = offsetList;
|
||||
OffsetList offsetList = ParseOffsetList(data, GetMajorVersion(commonHeader), descriptorOffset);
|
||||
cabinet.ComponentOffsets[descriptor.ComponentOffsets[i]] = offsetList;
|
||||
|
||||
// If we have a nonzero next offset
|
||||
uint nextOffset = offsetList.NextOffset;
|
||||
while (nextOffset != 0)
|
||||
{
|
||||
// Get the next offset to read
|
||||
uint internalOffset = nextOffset + commonHeader.CabDescriptorOffset;
|
||||
uint internalOffset = nextOffset + commonHeader.DescriptorOffset;
|
||||
|
||||
// Seek to the file group offset
|
||||
data.Seek(internalOffset, SeekOrigin.Begin);
|
||||
|
||||
// Create and add the offset
|
||||
offsetList = ParseOffsetList(data, header.MajorVersion, cabinetDescriptorOffset);
|
||||
header.ComponentOffsets[nextOffset] = offsetList;
|
||||
offsetList = ParseOffsetList(data, GetMajorVersion(commonHeader), descriptorOffset);
|
||||
cabinet.ComponentOffsets[nextOffset] = offsetList;
|
||||
|
||||
// Set the next offset
|
||||
nextOffset = offsetList.NextOffset;
|
||||
@@ -293,31 +317,45 @@ namespace BurnOutSharp.Builders
|
||||
|
||||
#region Components
|
||||
|
||||
// Create the components array
|
||||
cabinet.Components = new Component[cabinet.ComponentOffsets.Count];
|
||||
|
||||
// Create and fill the components
|
||||
List<Component> components = new List<Component>();
|
||||
foreach (KeyValuePair<long, OffsetList> kvp in header.ComponentOffsets)
|
||||
int componentId = 0;
|
||||
foreach (KeyValuePair<long, OffsetList> kvp in cabinet.ComponentOffsets)
|
||||
{
|
||||
// Get the offset
|
||||
OffsetList list = kvp.Value;
|
||||
if (list == null)
|
||||
{
|
||||
componentId++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we have an invalid offset
|
||||
if (list.DescriptorOffset <= 0)
|
||||
{
|
||||
componentId++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Seek to the component
|
||||
data.Seek(list.DescriptorOffset + cabinetDescriptorOffset, SeekOrigin.Begin);
|
||||
data.Seek(list.DescriptorOffset + descriptorOffset, SeekOrigin.Begin);
|
||||
|
||||
// Try to parse the component
|
||||
Component component = ParseComponent(data, header.MajorVersion, cabinetDescriptorOffset);
|
||||
var component = ParseComponent(data, GetMajorVersion(commonHeader), descriptorOffset);
|
||||
if (component == null)
|
||||
return null;
|
||||
|
||||
// Add the component
|
||||
components.Add(component);
|
||||
cabinet.Components[componentId++] = component;
|
||||
}
|
||||
|
||||
// Set the components
|
||||
header.Components = components.ToArray();
|
||||
|
||||
#endregion
|
||||
|
||||
return header;
|
||||
// TODO: Parse setup types
|
||||
|
||||
return cabinet;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -336,45 +374,105 @@ namespace BurnOutSharp.Builders
|
||||
|
||||
commonHeader.Version = data.ReadUInt32();
|
||||
commonHeader.VolumeInfo = data.ReadUInt32();
|
||||
commonHeader.CabDescriptorOffset = data.ReadUInt32();
|
||||
commonHeader.CabDescriptorSize = data.ReadUInt32();
|
||||
commonHeader.DescriptorOffset = data.ReadUInt32();
|
||||
commonHeader.DescriptorSize = data.ReadUInt32();
|
||||
|
||||
return commonHeader;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a cabinet descriptor
|
||||
/// Parse a Stream into a volume header
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled cabinet descriptor on success, null on error</returns>
|
||||
private static CabDescriptor ParseCabinetDescriptor(Stream data)
|
||||
/// <param name="majorVersion">Major version of the cabinet</param>
|
||||
/// <returns>Filled volume header on success, null on error</returns>
|
||||
private static VolumeHeader ParseVolumeHeader(Stream data, int majorVersion)
|
||||
{
|
||||
CabDescriptor cabDescriptor = new CabDescriptor();
|
||||
VolumeHeader volumeHeader = new VolumeHeader();
|
||||
|
||||
cabDescriptor.Reserved0 = data.ReadBytes(0x0C);
|
||||
cabDescriptor.FileTableOffset = data.ReadUInt32();
|
||||
cabDescriptor.Reserved1 = data.ReadBytes(0x04);
|
||||
cabDescriptor.FileTableSize = data.ReadUInt32();
|
||||
cabDescriptor.FileTableSize2 = data.ReadUInt32();
|
||||
cabDescriptor.DirectoryCount = data.ReadUInt32();
|
||||
cabDescriptor.Reserved2 = data.ReadBytes(0x08);
|
||||
cabDescriptor.FileCount = data.ReadUInt32();
|
||||
cabDescriptor.FileTableOffset2 = data.ReadUInt32();
|
||||
cabDescriptor.Reserved3 = data.ReadBytes(0x0E);
|
||||
|
||||
cabDescriptor.FileGroupOffsets = new uint[MAX_FILE_GROUP_COUNT];
|
||||
for (int i = 0; i < cabDescriptor.FileGroupOffsets.Length; i++)
|
||||
// Read the descriptor based on version
|
||||
if (majorVersion <= 5)
|
||||
{
|
||||
cabDescriptor.FileGroupOffsets[i] = data.ReadUInt32();
|
||||
volumeHeader.DataOffset = data.ReadUInt32();
|
||||
_ = data.ReadBytes(0x04); // Skip 0x04 bytes, unknown data?
|
||||
volumeHeader.FirstFileIndex = data.ReadUInt32();
|
||||
volumeHeader.LastFileIndex = data.ReadUInt32();
|
||||
volumeHeader.FirstFileOffset = data.ReadUInt32();
|
||||
volumeHeader.FirstFileSizeExpanded = data.ReadUInt32();
|
||||
volumeHeader.FirstFileSizeCompressed = data.ReadUInt32();
|
||||
volumeHeader.LastFileOffset = data.ReadUInt32();
|
||||
volumeHeader.LastFileSizeExpanded = data.ReadUInt32();
|
||||
volumeHeader.LastFileSizeCompressed = data.ReadUInt32();
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Should standard and high values be combined?
|
||||
volumeHeader.DataOffset = data.ReadUInt32();
|
||||
volumeHeader.DataOffsetHigh = data.ReadUInt32();
|
||||
volumeHeader.FirstFileIndex = data.ReadUInt32();
|
||||
volumeHeader.LastFileIndex = data.ReadUInt32();
|
||||
volumeHeader.FirstFileOffset = data.ReadUInt32();
|
||||
volumeHeader.FirstFileOffsetHigh = data.ReadUInt32();
|
||||
volumeHeader.FirstFileSizeExpanded = data.ReadUInt32();
|
||||
volumeHeader.FirstFileSizeExpandedHigh = data.ReadUInt32();
|
||||
volumeHeader.FirstFileSizeCompressed = data.ReadUInt32();
|
||||
volumeHeader.FirstFileSizeCompressedHigh = data.ReadUInt32();
|
||||
volumeHeader.LastFileOffset = data.ReadUInt32();
|
||||
volumeHeader.LastFileOffsetHigh = data.ReadUInt32();
|
||||
volumeHeader.LastFileSizeExpanded = data.ReadUInt32();
|
||||
volumeHeader.LastFileSizeExpandedHigh = data.ReadUInt32();
|
||||
volumeHeader.LastFileSizeCompressed = data.ReadUInt32();
|
||||
volumeHeader.LastFileSizeCompressedHigh = data.ReadUInt32();
|
||||
}
|
||||
|
||||
cabDescriptor.ComponentOffsets = new uint[MAX_COMPONENT_COUNT];
|
||||
for (int i = 0; i < cabDescriptor.ComponentOffsets.Length; i++)
|
||||
return volumeHeader;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a descriptor
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled descriptor on success, null on error</returns>
|
||||
private static Descriptor ParseDescriptor(Stream data)
|
||||
{
|
||||
Descriptor descriptor = new Descriptor();
|
||||
|
||||
descriptor.StringsOffset = data.ReadUInt32();
|
||||
descriptor.Reserved0 = data.ReadBytes(4);
|
||||
descriptor.ComponentListOffset = data.ReadUInt32();
|
||||
descriptor.FileTableOffset = data.ReadUInt32();
|
||||
descriptor.Reserved1 = data.ReadBytes(4);
|
||||
descriptor.FileTableSize = data.ReadUInt32();
|
||||
descriptor.FileTableSize2 = data.ReadUInt32();
|
||||
descriptor.DirectoryCount = data.ReadUInt16();
|
||||
descriptor.Reserved2 = data.ReadBytes(4);
|
||||
descriptor.Reserved3 = data.ReadBytes(2);
|
||||
descriptor.Reserved4 = data.ReadBytes(4);
|
||||
descriptor.FileCount = data.ReadUInt32();
|
||||
descriptor.FileTableOffset2 = data.ReadUInt32();
|
||||
descriptor.ComponentTableInfoCount = data.ReadUInt16();
|
||||
descriptor.ComponentTableOffset = data.ReadUInt32();
|
||||
descriptor.Reserved5 = data.ReadBytes(4);
|
||||
descriptor.Reserved6 = data.ReadBytes(4);
|
||||
|
||||
descriptor.FileGroupOffsets = new uint[MAX_FILE_GROUP_COUNT];
|
||||
for (int i = 0; i < descriptor.FileGroupOffsets.Length; i++)
|
||||
{
|
||||
cabDescriptor.ComponentOffsets[i] = data.ReadUInt32();
|
||||
descriptor.FileGroupOffsets[i] = data.ReadUInt32();
|
||||
}
|
||||
|
||||
return cabDescriptor;
|
||||
descriptor.ComponentOffsets = new uint[MAX_COMPONENT_COUNT];
|
||||
for (int i = 0; i < descriptor.ComponentOffsets.Length; i++)
|
||||
{
|
||||
descriptor.ComponentOffsets[i] = data.ReadUInt32();
|
||||
}
|
||||
|
||||
descriptor.SetupTypesOffset = data.ReadUInt32();
|
||||
descriptor.SetupTableOffset = data.ReadUInt32();
|
||||
descriptor.Reserved7 = data.ReadBytes(4);
|
||||
descriptor.Reserved8 = data.ReadBytes(4);
|
||||
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -423,14 +521,33 @@ namespace BurnOutSharp.Builders
|
||||
|
||||
fileGroup.NameOffset = data.ReadUInt32();
|
||||
|
||||
// Skip bytes based on the version
|
||||
if (majorVersion <= 5)
|
||||
_ = data.ReadBytes(0x48);
|
||||
else
|
||||
_ = data.ReadBytes(0x12);
|
||||
fileGroup.ExpandedSize = data.ReadUInt32();
|
||||
fileGroup.Reserved0 = data.ReadBytes(4);
|
||||
fileGroup.CompressedSize = data.ReadUInt32();
|
||||
fileGroup.Reserved1 = data.ReadBytes(4);
|
||||
fileGroup.Reserved2 = data.ReadBytes(2);
|
||||
fileGroup.Attribute1 = data.ReadUInt16();
|
||||
fileGroup.Attribute2 = data.ReadUInt16();
|
||||
|
||||
fileGroup.FirstFile = data.ReadUInt16();
|
||||
// TODO: Figure out what data lives in this area for V5 and below
|
||||
if (majorVersion <= 5)
|
||||
data.Seek(0x36, SeekOrigin.Current);
|
||||
|
||||
fileGroup.FirstFile = data.ReadUInt32();
|
||||
fileGroup.LastFile = data.ReadUInt32();
|
||||
fileGroup.UnknownOffset = data.ReadUInt32();
|
||||
fileGroup.Var4Offset = data.ReadUInt32();
|
||||
fileGroup.Var1Offset = data.ReadUInt32();
|
||||
fileGroup.HTTPLocationOffset = data.ReadUInt32();
|
||||
fileGroup.FTPLocationOffset = data.ReadUInt32();
|
||||
fileGroup.MiscOffset = data.ReadUInt32();
|
||||
fileGroup.Var2Offset = data.ReadUInt32();
|
||||
fileGroup.TargetDirectoryOffset = data.ReadUInt32();
|
||||
fileGroup.Reserved3 = data.ReadBytes(2);
|
||||
fileGroup.Reserved4 = data.ReadBytes(2);
|
||||
fileGroup.Reserved5 = data.ReadBytes(2);
|
||||
fileGroup.Reserved6 = data.ReadBytes(2);
|
||||
fileGroup.Reserved7 = data.ReadBytes(2);
|
||||
|
||||
// Cache the current position
|
||||
long currentPosition = data.Position;
|
||||
@@ -465,20 +582,64 @@ namespace BurnOutSharp.Builders
|
||||
{
|
||||
Component component = new Component();
|
||||
|
||||
component.IdentifierOffset = data.ReadUInt32();
|
||||
component.DescriptorOffset = data.ReadUInt32();
|
||||
component.DisplayNameOffset = data.ReadUInt32();
|
||||
component.Reserved0 = data.ReadBytes(2);
|
||||
component.ReservedOffset0 = data.ReadUInt32();
|
||||
component.ReservedOffset1 = data.ReadUInt32();
|
||||
component.ComponentIndex = data.ReadUInt16();
|
||||
component.NameOffset = data.ReadUInt32();
|
||||
|
||||
// Skip bytes based on the version
|
||||
if (majorVersion <= 5)
|
||||
_ = data.ReadBytes(0x6C);
|
||||
else
|
||||
_ = data.ReadBytes(0x6B);
|
||||
|
||||
component.ReservedOffset2 = data.ReadUInt32();
|
||||
component.ReservedOffset3 = data.ReadUInt32();
|
||||
component.ReservedOffset4 = data.ReadUInt32();
|
||||
component.Reserved1 = data.ReadBytes(32);
|
||||
component.CLSIDOffset = data.ReadUInt32();
|
||||
component.Reserved2 = data.ReadBytes(28);
|
||||
component.Reserved3 = data.ReadBytes(majorVersion <= 5 ? 2 : 1);
|
||||
component.DependsCount = data.ReadUInt16();
|
||||
component.DependsOffset = data.ReadUInt32();
|
||||
component.FileGroupCount = data.ReadUInt16();
|
||||
component.FileGroupTableOffset = data.ReadUInt32();
|
||||
component.FileGroupNamesOffset = data.ReadUInt32();
|
||||
component.X3Count = data.ReadUInt16();
|
||||
component.X3Offset = data.ReadUInt32();
|
||||
component.SubComponentsCount = data.ReadUInt16();
|
||||
component.SubComponentsOffset = data.ReadUInt32();
|
||||
component.NextComponentOffset = data.ReadUInt32();
|
||||
component.ReservedOffset5 = data.ReadUInt32();
|
||||
component.ReservedOffset6 = data.ReadUInt32();
|
||||
component.ReservedOffset7 = data.ReadUInt32();
|
||||
component.ReservedOffset8 = data.ReadUInt32();
|
||||
|
||||
// Cache the current position
|
||||
long currentPosition = data.Position;
|
||||
|
||||
// Read the identifier, if possible
|
||||
if (component.IdentifierOffset != 0)
|
||||
{
|
||||
// Seek to the identifier
|
||||
data.Seek(component.IdentifierOffset + descriptorOffset, SeekOrigin.Begin);
|
||||
|
||||
// Read the string
|
||||
if (majorVersion >= 17)
|
||||
component.Identifier = data.ReadString(Encoding.Unicode);
|
||||
else
|
||||
component.Identifier = data.ReadString(Encoding.ASCII);
|
||||
}
|
||||
|
||||
// Read the display name, if possible
|
||||
if (component.DisplayNameOffset != 0)
|
||||
{
|
||||
// Seek to the name
|
||||
data.Seek(component.DisplayNameOffset + descriptorOffset, SeekOrigin.Begin);
|
||||
|
||||
// Read the string
|
||||
if (majorVersion >= 17)
|
||||
component.DisplayName = data.ReadString(Encoding.Unicode);
|
||||
else
|
||||
component.DisplayName = data.ReadString(Encoding.ASCII);
|
||||
}
|
||||
|
||||
// Read the name, if possible
|
||||
if (component.NameOffset != 0)
|
||||
{
|
||||
@@ -492,20 +653,42 @@ namespace BurnOutSharp.Builders
|
||||
component.Name = data.ReadString(Encoding.ASCII);
|
||||
}
|
||||
|
||||
// Read the file group table, if possible
|
||||
if (component.FileGroupCount != 0 && component.FileGroupTableOffset != 0)
|
||||
// Read the CLSID, if possible
|
||||
if (component.CLSIDOffset != 0)
|
||||
{
|
||||
// Seek to the CLSID
|
||||
data.Seek(component.CLSIDOffset + descriptorOffset, SeekOrigin.Begin);
|
||||
|
||||
// Read the GUID
|
||||
component.CLSID = data.ReadGuid();
|
||||
}
|
||||
|
||||
// Read the file group names, if possible
|
||||
if (component.FileGroupCount != 0 && component.FileGroupNamesOffset != 0)
|
||||
{
|
||||
// Seek to the file group table offset
|
||||
data.Seek(component.FileGroupTableOffset + descriptorOffset, SeekOrigin.Begin);
|
||||
data.Seek(component.FileGroupNamesOffset + descriptorOffset, SeekOrigin.Begin);
|
||||
|
||||
// Read the file group table
|
||||
// Read the file group names table
|
||||
component.FileGroupNames = new string[component.FileGroupCount];
|
||||
for (int j = 0; j < component.FileGroupCount; j++)
|
||||
{
|
||||
// Get the name offset
|
||||
uint nameOffset = data.ReadUInt32();
|
||||
|
||||
// Cache the current offset
|
||||
long preNameOffset = data.Position;
|
||||
|
||||
// Seek to the name offset
|
||||
data.Seek(nameOffset + descriptorOffset, SeekOrigin.Begin);
|
||||
|
||||
if (majorVersion >= 17)
|
||||
component.FileGroupNames[j] = data.ReadString(Encoding.Unicode);
|
||||
else
|
||||
component.FileGroupNames[j] = data.ReadString(Encoding.ASCII);
|
||||
|
||||
// Seek back to the original position
|
||||
data.Seek(preNameOffset, SeekOrigin.Begin);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -516,22 +699,18 @@ namespace BurnOutSharp.Builders
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a directory descriptor
|
||||
/// Parse a Stream into a directory name
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <param name="majorVersion">Major version of the cabinet</param>
|
||||
/// <returns>Filled directory descriptor on success, null on error</returns>
|
||||
private static FileDescriptor ParseDirectoryDescriptor(Stream data, int majorVersion)
|
||||
/// <returns>Filled directory name on success, null on error</returns>
|
||||
private static string ParseDirectoryName(Stream data, int majorVersion)
|
||||
{
|
||||
FileDescriptor fileDescriptor = new FileDescriptor();
|
||||
|
||||
// Read the string
|
||||
if (majorVersion >= 17)
|
||||
fileDescriptor.Name = data.ReadString(Encoding.Unicode);
|
||||
return data.ReadString(Encoding.Unicode);
|
||||
else
|
||||
fileDescriptor.Name = data.ReadString(Encoding.ASCII);
|
||||
|
||||
return fileDescriptor;
|
||||
return data.ReadString(Encoding.ASCII);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -599,51 +778,29 @@ namespace BurnOutSharp.Builders
|
||||
return fileDescriptor;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helpers
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a volume header
|
||||
/// Get the major version of the cabinet
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <param name="majorVersion">Major version of the cabinet</param>
|
||||
/// <returns>Filled volume header on success, null on error</returns>
|
||||
private static VolumeHeader ParseVolumeHeader(Stream data, int majorVersion)
|
||||
/// <remarks>This should live in the wrapper but is needed during parsing</remarks>
|
||||
private static int GetMajorVersion(CommonHeader commonHeader)
|
||||
{
|
||||
VolumeHeader volumeHeader = new VolumeHeader();
|
||||
|
||||
// Read the descriptor based on version
|
||||
if (majorVersion <= 5)
|
||||
uint majorVersion = commonHeader.Version;
|
||||
if (majorVersion >> 24 == 1)
|
||||
{
|
||||
volumeHeader.DataOffset = data.ReadUInt32();
|
||||
_ = data.ReadBytes(0x04); // Skip 0x04 bytes, unknown data?
|
||||
volumeHeader.FirstFileIndex = data.ReadUInt32();
|
||||
volumeHeader.LastFileIndex = data.ReadUInt32();
|
||||
volumeHeader.FirstFileOffset = data.ReadUInt32();
|
||||
volumeHeader.FirstFileSizeExpanded = data.ReadUInt32();
|
||||
volumeHeader.FirstFileSizeCompressed = data.ReadUInt32();
|
||||
volumeHeader.LastFileOffset = data.ReadUInt32();
|
||||
volumeHeader.LastFileSizeExpanded = data.ReadUInt32();
|
||||
volumeHeader.LastFileSizeCompressed = data.ReadUInt32();
|
||||
majorVersion = (majorVersion >> 12) & 0x0F;
|
||||
}
|
||||
else
|
||||
else if (majorVersion >> 24 == 2 || majorVersion >> 24 == 4)
|
||||
{
|
||||
volumeHeader.DataOffset = data.ReadUInt32();
|
||||
volumeHeader.DataOffsetHigh = data.ReadUInt32();
|
||||
volumeHeader.FirstFileIndex = data.ReadUInt32();
|
||||
volumeHeader.LastFileIndex = data.ReadUInt32();
|
||||
volumeHeader.FirstFileOffset = data.ReadUInt32();
|
||||
volumeHeader.FirstFileOffsetHigh = data.ReadUInt32();
|
||||
volumeHeader.FirstFileSizeExpanded = data.ReadUInt32();
|
||||
volumeHeader.FirstFileSizeExpandedHigh = data.ReadUInt32();
|
||||
volumeHeader.FirstFileSizeCompressed = data.ReadUInt32();
|
||||
volumeHeader.FirstFileSizeCompressedHigh = data.ReadUInt32();
|
||||
volumeHeader.LastFileOffset = data.ReadUInt32();
|
||||
volumeHeader.LastFileOffsetHigh = data.ReadUInt32();
|
||||
volumeHeader.LastFileSizeExpanded = data.ReadUInt32();
|
||||
volumeHeader.LastFileSizeExpandedHigh = data.ReadUInt32();
|
||||
volumeHeader.LastFileSizeCompressed = data.ReadUInt32();
|
||||
volumeHeader.LastFileSizeCompressedHigh = data.ReadUInt32();
|
||||
majorVersion = majorVersion & 0xFFFF;
|
||||
if (majorVersion != 0)
|
||||
majorVersion /= 100;
|
||||
}
|
||||
|
||||
return volumeHeader;
|
||||
return (int)majorVersion;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using BurnOutSharp.Models.LinearExecutable;
|
||||
using BurnOutSharp.Utilities;
|
||||
using static BurnOutSharp.Models.LinearExecutable.Constants;
|
||||
|
||||
namespace BurnOutSharp.Builders
|
||||
{
|
||||
@@ -53,6 +57,8 @@ namespace BurnOutSharp.Builders
|
||||
// Create a new executable to fill
|
||||
var executable = new Executable();
|
||||
|
||||
#region MS-DOS Stub
|
||||
|
||||
// Parse the MS-DOS stub
|
||||
var stub = MSDOS.ParseExecutable(data);
|
||||
if (stub?.Header == null || stub.Header.NewExeHeaderAddr == 0)
|
||||
@@ -61,19 +67,875 @@ namespace BurnOutSharp.Builders
|
||||
// Set the MS-DOS stub
|
||||
executable.Stub = stub;
|
||||
|
||||
// TODO: Implement LE/LX parsing
|
||||
return null;
|
||||
#endregion
|
||||
|
||||
#region Information Block
|
||||
|
||||
// Try to parse the executable header
|
||||
data.Seek(initialOffset + stub.Header.NewExeHeaderAddr, SeekOrigin.Begin);
|
||||
var informationBlock = ParseInformationBlock(data);
|
||||
if (informationBlock == null)
|
||||
return null;
|
||||
|
||||
// Set the executable header
|
||||
executable.InformationBlock = informationBlock;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Object Table
|
||||
|
||||
// Get the object table offset
|
||||
long offset = informationBlock.ObjectTableOffset + stub.Header.NewExeHeaderAddr;
|
||||
if (offset > stub.Header.NewExeHeaderAddr && offset < data.Length)
|
||||
{
|
||||
// Seek to the object table
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Create the object table
|
||||
executable.ObjectTable = new ObjectTableEntry[informationBlock.ObjectTableCount];
|
||||
|
||||
// Try to parse the object table
|
||||
for (int i = 0; i < executable.ObjectTable.Length; i++)
|
||||
{
|
||||
var entry = ParseObjectTableEntry(data);
|
||||
if (entry == null)
|
||||
return null;
|
||||
|
||||
executable.ObjectTable[i] = entry;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Object Page Map
|
||||
|
||||
// Get the object page map offset
|
||||
offset = informationBlock.ObjectPageMapOffset + stub.Header.NewExeHeaderAddr;
|
||||
if (offset > stub.Header.NewExeHeaderAddr && offset < data.Length)
|
||||
{
|
||||
// Seek to the object page map
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Create the object page map
|
||||
executable.ObjectPageMap = new ObjectPageMapEntry[informationBlock.ObjectTableCount];
|
||||
|
||||
// Try to parse the object page map
|
||||
for (int i = 0; i < executable.ObjectPageMap.Length; i++)
|
||||
{
|
||||
var entry = ParseObjectPageMapEntry(data);
|
||||
if (entry == null)
|
||||
return null;
|
||||
|
||||
executable.ObjectPageMap[i] = entry;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Object Iterate Data Map
|
||||
|
||||
offset = informationBlock.ObjectIterateDataMapOffset + stub.Header.NewExeHeaderAddr;
|
||||
if (offset > stub.Header.NewExeHeaderAddr && offset < data.Length)
|
||||
{
|
||||
// Seek to the object page map
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// TODO: Implement when model found
|
||||
// No model has been found in the documentation about what
|
||||
// each of the entries looks like for this map.
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Resource Table
|
||||
|
||||
// Get the resource table offset
|
||||
offset = informationBlock.ResourceTableOffset + stub.Header.NewExeHeaderAddr;
|
||||
if (offset > stub.Header.NewExeHeaderAddr && offset < data.Length)
|
||||
{
|
||||
// Seek to the resource table
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Create the resource table
|
||||
executable.ResourceTable = new ResourceTableEntry[informationBlock.ResourceTableCount];
|
||||
|
||||
// Try to parse the resource table
|
||||
for (int i = 0; i < executable.ResourceTable.Length; i++)
|
||||
{
|
||||
var entry = ParseResourceTableEntry(data);
|
||||
if (entry == null)
|
||||
return null;
|
||||
|
||||
executable.ResourceTable[i] = entry;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Resident Names Table
|
||||
|
||||
// Get the resident names table offset
|
||||
offset = informationBlock.ResidentNamesTableOffset + stub.Header.NewExeHeaderAddr;
|
||||
if (offset > stub.Header.NewExeHeaderAddr && offset < data.Length)
|
||||
{
|
||||
// Seek to the resident names table
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Create the resident names table
|
||||
var residentNamesTable = new List<ResidentNamesTableEntry>();
|
||||
|
||||
// Try to parse the resident names table
|
||||
while (true)
|
||||
{
|
||||
var entry = ParseResidentNamesTableEntry(data);
|
||||
residentNamesTable.Add(entry);
|
||||
|
||||
// If we have a 0-length entry
|
||||
if (entry.Length == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
// Assign the resident names table
|
||||
executable.ResidentNamesTable = residentNamesTable.ToArray();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Entry Table
|
||||
|
||||
// Get the entry table offset
|
||||
offset = informationBlock.EntryTableOffset + stub.Header.NewExeHeaderAddr;
|
||||
if (offset > stub.Header.NewExeHeaderAddr && offset < data.Length)
|
||||
{
|
||||
// Seek to the entry table
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Create the entry table
|
||||
var entryTable = new List<EntryTableBundle>();
|
||||
|
||||
// Try to parse the entry table
|
||||
while (true)
|
||||
{
|
||||
var bundle = ParseEntryTableBundle(data);
|
||||
entryTable.Add(bundle);
|
||||
|
||||
// If we have a 0-length entry
|
||||
if (bundle.Entries == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
// Assign the entry table
|
||||
executable.EntryTable = entryTable.ToArray();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Module Format Directives Table
|
||||
|
||||
// Get the module format directives table offset
|
||||
offset = informationBlock.ModuleDirectivesTableOffset + stub.Header.NewExeHeaderAddr;
|
||||
if (offset > stub.Header.NewExeHeaderAddr && offset < data.Length)
|
||||
{
|
||||
// Seek to the module format directives table
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Create the module format directives table
|
||||
executable.ModuleFormatDirectivesTable = new ModuleFormatDirectivesTableEntry[informationBlock.ModuleDirectivesCount];
|
||||
|
||||
// Try to parse the module format directives table
|
||||
for (int i = 0; i < executable.ModuleFormatDirectivesTable.Length; i++)
|
||||
{
|
||||
var entry = ParseModuleFormatDirectivesTableEntry(data);
|
||||
if (entry == null)
|
||||
return null;
|
||||
|
||||
executable.ModuleFormatDirectivesTable[i] = entry;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Verify Record Directive Table
|
||||
|
||||
// TODO: Figure out where the offset to this table is stored
|
||||
// The documentation suggests it's either part of or immediately following
|
||||
// the Module Format Directives Table
|
||||
|
||||
#endregion
|
||||
|
||||
#region Fix-up Page Table
|
||||
|
||||
// Get the fix-up page table offset
|
||||
offset = informationBlock.FixupPageTableOffset + stub.Header.NewExeHeaderAddr;
|
||||
if (offset > stub.Header.NewExeHeaderAddr && offset < data.Length)
|
||||
{
|
||||
// Seek to the fix-up page table
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Create the fix-up page table
|
||||
executable.FixupPageTable = new FixupPageTableEntry[executable.ObjectPageMap.Length + 1];
|
||||
|
||||
// Try to parse the fix-up page table
|
||||
for (int i = 0; i < executable.FixupPageTable.Length; i++)
|
||||
{
|
||||
var entry = ParseFixupPageTableEntry(data);
|
||||
if (entry == null)
|
||||
return null;
|
||||
|
||||
executable.FixupPageTable[i] = entry;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Fix-up Record Table
|
||||
|
||||
// Get the fix-up record table offset
|
||||
offset = informationBlock.FixupRecordTableOffset + stub.Header.NewExeHeaderAddr;
|
||||
if (offset > stub.Header.NewExeHeaderAddr && offset < data.Length)
|
||||
{
|
||||
// Seek to the fix-up record table
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Create the fix-up record table
|
||||
executable.FixupRecordTable = new FixupRecordTableEntry[executable.ObjectPageMap.Length + 1];
|
||||
|
||||
// Try to parse the fix-up record table
|
||||
for (int i = 0; i < executable.FixupRecordTable.Length; i++)
|
||||
{
|
||||
var entry = ParseFixupRecordTableEntry(data);
|
||||
if (entry == null)
|
||||
return null;
|
||||
|
||||
executable.FixupRecordTable[i] = entry;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Imported Module Name Table
|
||||
|
||||
// Get the imported module name table offset
|
||||
offset = informationBlock.ImportedModulesNameTableOffset + stub.Header.NewExeHeaderAddr;
|
||||
if (offset > stub.Header.NewExeHeaderAddr && offset < data.Length)
|
||||
{
|
||||
// Seek to the imported module name table
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Create the imported module name table
|
||||
executable.ImportModuleNameTable = new ImportModuleNameTableEntry[informationBlock.ImportedModulesCount];
|
||||
|
||||
// Try to parse the imported module name table
|
||||
for (int i = 0; i < executable.ImportModuleNameTable.Length; i++)
|
||||
{
|
||||
var entry = ParseImportModuleNameTableEntry(data);
|
||||
if (entry == null)
|
||||
return null;
|
||||
|
||||
executable.ImportModuleNameTable[i] = entry;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Imported Module Procedure Name Table
|
||||
|
||||
// Get the imported module procedure name table offset
|
||||
offset = informationBlock.ImportProcedureNameTableOffset + stub.Header.NewExeHeaderAddr;
|
||||
if (offset > stub.Header.NewExeHeaderAddr && offset < data.Length)
|
||||
{
|
||||
// Seek to the imported module procedure name table
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Get the size of the imported module procedure name table
|
||||
long tableSize = informationBlock.FixupPageTableOffset
|
||||
+ informationBlock.FixupSectionSize
|
||||
- informationBlock.ImportProcedureNameTableOffset;
|
||||
|
||||
// Create the imported module procedure name table
|
||||
var importModuleProcedureNameTable = new List<ImportModuleProcedureNameTableEntry>();
|
||||
|
||||
// Try to parse the imported module procedure name table
|
||||
while (data.Position < offset + tableSize)
|
||||
{
|
||||
var entry = ParseImportModuleProcedureNameTableEntry(data);
|
||||
if (entry == null)
|
||||
return null;
|
||||
|
||||
importModuleProcedureNameTable.Add(entry);
|
||||
}
|
||||
|
||||
// Assign the resident names table
|
||||
executable.ImportModuleProcedureNameTable = importModuleProcedureNameTable.ToArray();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Per-Page Checksum Table
|
||||
|
||||
// Get the per-page checksum table offset
|
||||
offset = informationBlock.PerPageChecksumTableOffset + stub.Header.NewExeHeaderAddr;
|
||||
if (offset > stub.Header.NewExeHeaderAddr && offset < data.Length)
|
||||
{
|
||||
// Seek to the per-page checksum name table
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Create the per-page checksum name table
|
||||
executable.PerPageChecksumTable = new PerPageChecksumTableEntry[informationBlock.ModuleNumberPages];
|
||||
|
||||
// Try to parse the per-page checksum name table
|
||||
for (int i = 0; i < executable.PerPageChecksumTable.Length; i++)
|
||||
{
|
||||
var entry = ParsePerPageChecksumTableEntry(data);
|
||||
if (entry == null)
|
||||
return null;
|
||||
|
||||
executable.PerPageChecksumTable[i] = entry;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Non-Resident Names Table
|
||||
|
||||
// Get the non-resident names table offset
|
||||
offset = informationBlock.NonResidentNamesTableOffset + stub.Header.NewExeHeaderAddr;
|
||||
if (offset > stub.Header.NewExeHeaderAddr && offset < data.Length)
|
||||
{
|
||||
// Seek to the non-resident names table
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Create the non-resident names table
|
||||
var nonResidentNamesTable = new List<NonResidentNamesTableEntry>();
|
||||
|
||||
// Try to parse the non-resident names table
|
||||
while (true)
|
||||
{
|
||||
var entry = ParseNonResidentNameTableEntry(data);
|
||||
nonResidentNamesTable.Add(entry);
|
||||
|
||||
// If we have a 0-length entry
|
||||
if (entry.Length == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
// Assign the non-resident names table
|
||||
executable.NonResidentNamesTable = nonResidentNamesTable.ToArray();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Debug Information
|
||||
|
||||
// Get the debug information offset
|
||||
offset = informationBlock.DebugInformationOffset + stub.Header.NewExeHeaderAddr;
|
||||
if (offset > stub.Header.NewExeHeaderAddr && offset < data.Length)
|
||||
{
|
||||
// Seek to the debug information
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Try to parse the debug information
|
||||
var debugInformation = ParseDebugInformation(data, informationBlock.DebugInformationLength);
|
||||
if (debugInformation == null)
|
||||
return null;
|
||||
|
||||
// Set the debug information
|
||||
executable.DebugInformation = debugInformation;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
return executable;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a Linear Executable information block
|
||||
/// Parse a Stream into an information block
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled executable header on success, null on error</returns>
|
||||
/// <returns>Filled information block on success, null on error</returns>
|
||||
private static InformationBlock ParseInformationBlock(Stream data)
|
||||
{
|
||||
// TODO: Implement LE/LX information block parsing
|
||||
return null;
|
||||
// TODO: Use marshalling here instead of building
|
||||
var informationBlock = new InformationBlock();
|
||||
|
||||
byte[] magic = data.ReadBytes(2);
|
||||
informationBlock.Signature = Encoding.ASCII.GetString(magic);
|
||||
if (informationBlock.Signature != LESignatureString && informationBlock.Signature != LXSignatureString)
|
||||
return null;
|
||||
|
||||
informationBlock.ByteOrder = (ByteOrder)data.ReadByteValue();
|
||||
informationBlock.WordOrder = (WordOrder)data.ReadByteValue();
|
||||
informationBlock.ExecutableFormatLevel = data.ReadUInt32();
|
||||
informationBlock.CPUType = (CPUType)data.ReadUInt16();
|
||||
informationBlock.ModuleOS = (OperatingSystem)data.ReadUInt16();
|
||||
informationBlock.ModuleVersion = data.ReadUInt32();
|
||||
informationBlock.ModuleTypeFlags = (ModuleFlags)data.ReadUInt32();
|
||||
informationBlock.ModuleNumberPages = data.ReadUInt32();
|
||||
informationBlock.InitialObjectCS = data.ReadUInt32();
|
||||
informationBlock.InitialEIP = data.ReadUInt32();
|
||||
informationBlock.InitialObjectSS = data.ReadUInt32();
|
||||
informationBlock.InitialESP = data.ReadUInt32();
|
||||
informationBlock.MemoryPageSize = data.ReadUInt32();
|
||||
informationBlock.BytesOnLastPage = data.ReadUInt32();
|
||||
informationBlock.FixupSectionSize = data.ReadUInt32();
|
||||
informationBlock.FixupSectionChecksum = data.ReadUInt32();
|
||||
informationBlock.LoaderSectionSize = data.ReadUInt32();
|
||||
informationBlock.LoaderSectionChecksum = data.ReadUInt32();
|
||||
informationBlock.ObjectTableOffset = data.ReadUInt32();
|
||||
informationBlock.ObjectTableCount = data.ReadUInt32();
|
||||
informationBlock.ObjectPageMapOffset = data.ReadUInt32();
|
||||
informationBlock.ObjectIterateDataMapOffset = data.ReadUInt32();
|
||||
informationBlock.ResourceTableOffset = data.ReadUInt32();
|
||||
informationBlock.ResourceTableCount = data.ReadUInt32();
|
||||
informationBlock.ResidentNamesTableOffset = data.ReadUInt32();
|
||||
informationBlock.EntryTableOffset = data.ReadUInt32();
|
||||
informationBlock.ModuleDirectivesTableOffset = data.ReadUInt32();
|
||||
informationBlock.ModuleDirectivesCount = data.ReadUInt32();
|
||||
informationBlock.FixupPageTableOffset = data.ReadUInt32();
|
||||
informationBlock.FixupRecordTableOffset = data.ReadUInt32();
|
||||
informationBlock.ImportedModulesNameTableOffset = data.ReadUInt32();
|
||||
informationBlock.ImportedModulesCount = data.ReadUInt32();
|
||||
informationBlock.ImportProcedureNameTableOffset = data.ReadUInt32();
|
||||
informationBlock.PerPageChecksumTableOffset = data.ReadUInt32();
|
||||
informationBlock.DataPagesOffset = data.ReadUInt32();
|
||||
informationBlock.PreloadPageCount = data.ReadUInt32();
|
||||
informationBlock.NonResidentNamesTableOffset = data.ReadUInt32();
|
||||
informationBlock.NonResidentNamesTableLength = data.ReadUInt32();
|
||||
informationBlock.NonResidentNamesTableChecksum = data.ReadUInt32();
|
||||
informationBlock.AutomaticDataObject = data.ReadUInt32();
|
||||
informationBlock.DebugInformationOffset = data.ReadUInt32();
|
||||
informationBlock.DebugInformationLength = data.ReadUInt32();
|
||||
informationBlock.PreloadInstancePagesNumber = data.ReadUInt32();
|
||||
informationBlock.DemandInstancePagesNumber = data.ReadUInt32();
|
||||
informationBlock.ExtraHeapAllocation = data.ReadUInt32();
|
||||
|
||||
return informationBlock;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into an object table entry
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled object table entry on success, null on error</returns>
|
||||
private static ObjectTableEntry ParseObjectTableEntry(Stream data)
|
||||
{
|
||||
// TODO: Use marshalling here instead of building
|
||||
var entry = new ObjectTableEntry();
|
||||
|
||||
entry.VirtualSegmentSize = data.ReadUInt32();
|
||||
entry.RelocationBaseAddress = data.ReadUInt32();
|
||||
entry.ObjectFlags = (ObjectFlags)data.ReadUInt16();
|
||||
entry.PageTableIndex = data.ReadUInt32();
|
||||
entry.PageTableEntries = data.ReadUInt32();
|
||||
entry.Reserved = data.ReadUInt32();
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into an object page map entry
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled object page map entry on success, null on error</returns>
|
||||
private static ObjectPageMapEntry ParseObjectPageMapEntry(Stream data)
|
||||
{
|
||||
// TODO: Use marshalling here instead of building
|
||||
var entry = new ObjectPageMapEntry();
|
||||
|
||||
entry.PageDataOffset = data.ReadUInt32();
|
||||
entry.DataSize = data.ReadUInt16();
|
||||
entry.Flags = (ObjectPageFlags)data.ReadUInt16();
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a resource table entry
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled resource table entry on success, null on error</returns>
|
||||
private static ResourceTableEntry ParseResourceTableEntry(Stream data)
|
||||
{
|
||||
// TODO: Use marshalling here instead of building
|
||||
var entry = new ResourceTableEntry();
|
||||
|
||||
entry.TypeID = (ResourceTableEntryType)data.ReadUInt32();
|
||||
entry.NameID = data.ReadUInt16();
|
||||
entry.ResourceSize = data.ReadUInt32();
|
||||
entry.ObjectNumber = data.ReadUInt16();
|
||||
entry.Offset = data.ReadUInt32();
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a resident names table entry
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled resident names table entry on success, null on error</returns>
|
||||
private static ResidentNamesTableEntry ParseResidentNamesTableEntry(Stream data)
|
||||
{
|
||||
// TODO: Use marshalling here instead of building
|
||||
var entry = new ResidentNamesTableEntry();
|
||||
|
||||
entry.Length = data.ReadByteValue();
|
||||
if (entry.Length > 0)
|
||||
{
|
||||
byte[] name = data.ReadBytes(entry.Length);
|
||||
entry.Name = Encoding.ASCII.GetString(name).TrimEnd('\0');
|
||||
}
|
||||
entry.OrdinalNumber = data.ReadUInt16();
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into an entry table bundle
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled entry table bundle on success, null on error</returns>
|
||||
private static EntryTableBundle ParseEntryTableBundle(Stream data)
|
||||
{
|
||||
// TODO: Use marshalling here instead of building
|
||||
var bundle = new EntryTableBundle();
|
||||
|
||||
bundle.Entries = data.ReadByteValue();
|
||||
if (bundle.Entries == 0)
|
||||
return bundle;
|
||||
|
||||
bundle.BundleType = (BundleType)data.ReadByteValue();
|
||||
bundle.TableEntries = new EntryTableEntry[bundle.Entries];
|
||||
for (int i = 0; i < bundle.Entries; i++)
|
||||
{
|
||||
var entry = new EntryTableEntry();
|
||||
|
||||
switch (bundle.BundleType & ~BundleType.ParameterTypingInformationPresent)
|
||||
{
|
||||
case BundleType.UnusedEntry:
|
||||
// Empty entry with no information
|
||||
break;
|
||||
|
||||
case BundleType.SixteenBitEntry:
|
||||
entry.SixteenBitObjectNumber = data.ReadUInt16();
|
||||
entry.SixteenBitEntryFlags = (EntryFlags)data.ReadByteValue();
|
||||
entry.SixteenBitOffset = data.ReadUInt16();
|
||||
break;
|
||||
|
||||
case BundleType.TwoEightySixCallGateEntry:
|
||||
entry.TwoEightySixObjectNumber = data.ReadUInt16();
|
||||
entry.TwoEightySixEntryFlags = (EntryFlags)data.ReadByteValue();
|
||||
entry.TwoEightySixOffset = data.ReadUInt16();
|
||||
entry.TwoEightySixCallgate = data.ReadUInt16();
|
||||
break;
|
||||
|
||||
case BundleType.ThirtyTwoBitEntry:
|
||||
entry.ThirtyTwoBitObjectNumber = data.ReadUInt16();
|
||||
entry.ThirtyTwoBitEntryFlags = (EntryFlags)data.ReadByteValue();
|
||||
entry.ThirtyTwoBitOffset = data.ReadUInt32();
|
||||
break;
|
||||
|
||||
case BundleType.ForwarderEntry:
|
||||
entry.ForwarderReserved = data.ReadUInt16();
|
||||
entry.ForwarderFlags = (ForwarderFlags)data.ReadByteValue();
|
||||
entry.ForwarderModuleOrdinalNumber = data.ReadUInt16();
|
||||
entry.ProcedureNameOffset = data.ReadUInt32();
|
||||
entry.ImportOrdinalNumber = data.ReadUInt32();
|
||||
break;
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
bundle.TableEntries[i] = entry;
|
||||
}
|
||||
|
||||
return bundle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a module format directives table entry
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled module format directives table entry on success, null on error</returns>
|
||||
private static ModuleFormatDirectivesTableEntry ParseModuleFormatDirectivesTableEntry(Stream data)
|
||||
{
|
||||
// TODO: Use marshalling here instead of building
|
||||
var entry = new ModuleFormatDirectivesTableEntry();
|
||||
|
||||
entry.DirectiveNumber = (DirectiveNumber)data.ReadUInt16();
|
||||
entry.DirectiveDataLength = data.ReadUInt16();
|
||||
entry.DirectiveDataOffset = data.ReadUInt32();
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a verify record directive table entry
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled verify record directive table entry on success, null on error</returns>
|
||||
private static VerifyRecordDirectiveTableEntry ParseVerifyRecordDirectiveTableEntry(Stream data)
|
||||
{
|
||||
// TODO: Use marshalling here instead of building
|
||||
var entry = new VerifyRecordDirectiveTableEntry();
|
||||
|
||||
entry.EntryCount = data.ReadUInt16();
|
||||
entry.OrdinalIndex = data.ReadUInt16();
|
||||
entry.Version = data.ReadUInt16();
|
||||
entry.ObjectEntriesCount = data.ReadUInt16();
|
||||
entry.ObjectNumberInModule = data.ReadUInt16();
|
||||
entry.ObjectLoadBaseAddress = data.ReadUInt16();
|
||||
entry.ObjectVirtualAddressSize = data.ReadUInt16();
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a fix-up page table entry
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled fix-up page table entry on success, null on error</returns>
|
||||
private static FixupPageTableEntry ParseFixupPageTableEntry(Stream data)
|
||||
{
|
||||
// TODO: Use marshalling here instead of building
|
||||
var entry = new FixupPageTableEntry();
|
||||
|
||||
entry.Offset = data.ReadUInt32();
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a fix-up record table entry
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled fix-up record table entry on success, null on error</returns>
|
||||
private static FixupRecordTableEntry ParseFixupRecordTableEntry(Stream data)
|
||||
{
|
||||
// TODO: Use marshalling here instead of building
|
||||
var entry = new FixupRecordTableEntry();
|
||||
|
||||
entry.SourceType = (FixupRecordSourceType)data.ReadByteValue();
|
||||
entry.TargetFlags = (FixupRecordTargetFlags)data.ReadByteValue();
|
||||
|
||||
// Source list flag
|
||||
if (entry.SourceType.HasFlag(FixupRecordSourceType.SourceListFlag))
|
||||
entry.SourceOffsetListCount = data.ReadByteValue();
|
||||
else
|
||||
entry.SourceOffset = data.ReadUInt16();
|
||||
|
||||
// OBJECT / TRGOFF
|
||||
if (entry.TargetFlags.HasFlag(FixupRecordTargetFlags.InternalReference))
|
||||
{
|
||||
// 16-bit Object Number/Module Ordinal Flag
|
||||
if (entry.TargetFlags.HasFlag(FixupRecordTargetFlags.SixteenBitObjectNumberModuleOrdinalFlag))
|
||||
entry.TargetObjectNumberWORD = data.ReadUInt16();
|
||||
else
|
||||
entry.TargetObjectNumberByte = data.ReadByteValue();
|
||||
|
||||
// 16-bit Selector fixup
|
||||
if (!entry.SourceType.HasFlag(FixupRecordSourceType.SixteenBitSelectorFixup))
|
||||
{
|
||||
// 32-bit Target Offset Flag
|
||||
if (entry.TargetFlags.HasFlag(FixupRecordTargetFlags.ThirtyTwoBitTargetOffsetFlag))
|
||||
entry.TargetOffsetDWORD = data.ReadUInt32();
|
||||
else
|
||||
entry.TargetOffsetWORD = data.ReadUInt16();
|
||||
}
|
||||
}
|
||||
|
||||
// MOD ORD# / IMPORT ORD / ADDITIVE
|
||||
else if (entry.TargetFlags.HasFlag(FixupRecordTargetFlags.ImportedReferenceByOrdinal))
|
||||
{
|
||||
// 16-bit Object Number/Module Ordinal Flag
|
||||
if (entry.TargetFlags.HasFlag(FixupRecordTargetFlags.SixteenBitObjectNumberModuleOrdinalFlag))
|
||||
entry.OrdinalIndexImportModuleNameTableWORD = data.ReadUInt16();
|
||||
else
|
||||
entry.OrdinalIndexImportModuleNameTableByte = data.ReadByteValue();
|
||||
|
||||
// 8-bit Ordinal Flag & 32-bit Target Offset Flag
|
||||
if (entry.TargetFlags.HasFlag(FixupRecordTargetFlags.EightBitOrdinalFlag))
|
||||
entry.ImportedOrdinalNumberByte = data.ReadByteValue();
|
||||
else if (entry.TargetFlags.HasFlag(FixupRecordTargetFlags.ThirtyTwoBitTargetOffsetFlag))
|
||||
entry.ImportedOrdinalNumberDWORD = data.ReadUInt32();
|
||||
else
|
||||
entry.ImportedOrdinalNumberWORD = data.ReadUInt16();
|
||||
|
||||
// Additive Fixup Flag
|
||||
if (entry.TargetFlags.HasFlag(FixupRecordTargetFlags.AdditiveFixupFlag))
|
||||
{
|
||||
// 32-bit Additive Flag
|
||||
if (entry.TargetFlags.HasFlag(FixupRecordTargetFlags.ThirtyTwoBitAdditiveFixupFlag))
|
||||
entry.AdditiveFixupValueDWORD = data.ReadUInt32();
|
||||
else
|
||||
entry.AdditiveFixupValueWORD = data.ReadUInt16();
|
||||
}
|
||||
}
|
||||
|
||||
// MOD ORD# / PROCEDURE NAME OFFSET / ADDITIVE
|
||||
else if (entry.TargetFlags.HasFlag(FixupRecordTargetFlags.ImportedReferenceByName))
|
||||
{
|
||||
// 16-bit Object Number/Module Ordinal Flag
|
||||
if (entry.TargetFlags.HasFlag(FixupRecordTargetFlags.SixteenBitObjectNumberModuleOrdinalFlag))
|
||||
entry.OrdinalIndexImportModuleNameTableWORD = data.ReadUInt16();
|
||||
else
|
||||
entry.OrdinalIndexImportModuleNameTableByte = data.ReadByteValue();
|
||||
|
||||
// 32-bit Target Offset Flag
|
||||
if (entry.TargetFlags.HasFlag(FixupRecordTargetFlags.ThirtyTwoBitTargetOffsetFlag))
|
||||
entry.OffsetImportProcedureNameTableDWORD = data.ReadUInt32();
|
||||
else
|
||||
entry.OffsetImportProcedureNameTableWORD = data.ReadUInt16();
|
||||
|
||||
// Additive Fixup Flag
|
||||
if (entry.TargetFlags.HasFlag(FixupRecordTargetFlags.AdditiveFixupFlag))
|
||||
{
|
||||
// 32-bit Additive Flag
|
||||
if (entry.TargetFlags.HasFlag(FixupRecordTargetFlags.ThirtyTwoBitAdditiveFixupFlag))
|
||||
entry.AdditiveFixupValueDWORD = data.ReadUInt32();
|
||||
else
|
||||
entry.AdditiveFixupValueWORD = data.ReadUInt16();
|
||||
}
|
||||
}
|
||||
|
||||
// ORD # / ADDITIVE
|
||||
else if (entry.TargetFlags.HasFlag(FixupRecordTargetFlags.InternalReferenceViaEntryTable))
|
||||
{
|
||||
// 16-bit Object Number/Module Ordinal Flag
|
||||
if (entry.TargetFlags.HasFlag(FixupRecordTargetFlags.SixteenBitObjectNumberModuleOrdinalFlag))
|
||||
entry.OrdinalIndexImportModuleNameTableWORD = data.ReadUInt16();
|
||||
else
|
||||
entry.OrdinalIndexImportModuleNameTableByte = data.ReadByteValue();
|
||||
|
||||
// Additive Fixup Flag
|
||||
if (entry.TargetFlags.HasFlag(FixupRecordTargetFlags.AdditiveFixupFlag))
|
||||
{
|
||||
// 32-bit Additive Flag
|
||||
if (entry.TargetFlags.HasFlag(FixupRecordTargetFlags.ThirtyTwoBitAdditiveFixupFlag))
|
||||
entry.AdditiveFixupValueDWORD = data.ReadUInt32();
|
||||
else
|
||||
entry.AdditiveFixupValueWORD = data.ReadUInt16();
|
||||
}
|
||||
}
|
||||
|
||||
// No other top-level flags recognized
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
#region SCROFFn
|
||||
|
||||
if (entry.SourceType.HasFlag(FixupRecordSourceType.SourceListFlag))
|
||||
{
|
||||
entry.SourceOffsetList = new ushort[entry.SourceOffsetListCount];
|
||||
for (int i = 0; i < entry.SourceOffsetList.Length; i++)
|
||||
{
|
||||
entry.SourceOffsetList[i] = data.ReadUInt16();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a import module name table entry
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled import module name table entry on success, null on error</returns>
|
||||
private static ImportModuleNameTableEntry ParseImportModuleNameTableEntry(Stream data)
|
||||
{
|
||||
// TODO: Use marshalling here instead of building
|
||||
var entry = new ImportModuleNameTableEntry();
|
||||
|
||||
entry.Length = data.ReadByteValue();
|
||||
if (entry.Length > 0)
|
||||
{
|
||||
byte[] name = data.ReadBytes(entry.Length);
|
||||
entry.Name = Encoding.ASCII.GetString(name).TrimEnd('\0');
|
||||
}
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a import module name table entry
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled import module name table entry on success, null on error</returns>
|
||||
private static ImportModuleProcedureNameTableEntry ParseImportModuleProcedureNameTableEntry(Stream data)
|
||||
{
|
||||
// TODO: Use marshalling here instead of building
|
||||
var entry = new ImportModuleProcedureNameTableEntry();
|
||||
|
||||
entry.Length = data.ReadByteValue();
|
||||
if (entry.Length > 0)
|
||||
{
|
||||
byte[] name = data.ReadBytes(entry.Length);
|
||||
entry.Name = Encoding.ASCII.GetString(name).TrimEnd('\0');
|
||||
}
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a per-page checksum table entry
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled per-page checksum table entry on success, null on error</returns>
|
||||
private static PerPageChecksumTableEntry ParsePerPageChecksumTableEntry(Stream data)
|
||||
{
|
||||
// TODO: Use marshalling here instead of building
|
||||
var entry = new PerPageChecksumTableEntry();
|
||||
|
||||
entry.Checksum = data.ReadUInt32();
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a non-resident names table entry
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled non-resident names table entry on success, null on error</returns>
|
||||
private static NonResidentNamesTableEntry ParseNonResidentNameTableEntry(Stream data)
|
||||
{
|
||||
// TODO: Use marshalling here instead of building
|
||||
var entry = new NonResidentNamesTableEntry();
|
||||
|
||||
entry.Length = data.ReadByteValue();
|
||||
if (entry.Length > 0)
|
||||
{
|
||||
byte[] name = data.ReadBytes(entry.Length);
|
||||
entry.Name = Encoding.ASCII.GetString(name).TrimEnd('\0');
|
||||
}
|
||||
entry.OrdinalNumber = data.ReadUInt16();
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a debug information
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <param name="size">Total size of the debug information</param>
|
||||
/// <returns>Filled debug information on success, null on error</returns>
|
||||
private static DebugInformation ParseDebugInformation(Stream data, long size)
|
||||
{
|
||||
// TODO: Use marshalling here instead of building
|
||||
var debugInformation = new DebugInformation();
|
||||
|
||||
byte[] signature = data.ReadBytes(3);
|
||||
debugInformation.Signature = Encoding.ASCII.GetString(signature);
|
||||
if (debugInformation.Signature != DebugInformationSignatureString)
|
||||
return null;
|
||||
|
||||
debugInformation.FormatType = (DebugFormatType)data.ReadByteValue();
|
||||
debugInformation.DebuggerData = data.ReadBytes((int)(size - 4));
|
||||
|
||||
return debugInformation;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
1223
BurnOutSharp.Builders/N3DS.cs
Normal file
1223
BurnOutSharp.Builders/N3DS.cs
Normal file
File diff suppressed because it is too large
Load Diff
393
BurnOutSharp.Builders/Nitro.cs
Normal file
393
BurnOutSharp.Builders/Nitro.cs
Normal file
@@ -0,0 +1,393 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using BurnOutSharp.Models.Nitro;
|
||||
using BurnOutSharp.Utilities;
|
||||
|
||||
namespace BurnOutSharp.Builders
|
||||
{
|
||||
public class Nitro
|
||||
{
|
||||
#region Byte Data
|
||||
|
||||
/// <summary>
|
||||
/// Parse a byte array into a NDS cart image
|
||||
/// </summary>
|
||||
/// <param name="data">Byte array to parse</param>
|
||||
/// <param name="offset">Offset into the byte array</param>
|
||||
/// <returns>Filled cart image on success, null on error</returns>
|
||||
public static Cart ParseCart(byte[] data, int offset)
|
||||
{
|
||||
// If the data is invalid
|
||||
if (data == null)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (offset < 0 || offset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Create a memory stream and parse that
|
||||
MemoryStream dataStream = new MemoryStream(data, offset, data.Length - offset);
|
||||
return ParseCart(dataStream);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Stream Data
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a NDS cart image
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled cart image on success, null on error</returns>
|
||||
public static Cart ParseCart(Stream data)
|
||||
{
|
||||
// If the data is invalid
|
||||
if (data == null || data.Length == 0 || !data.CanSeek || !data.CanRead)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (data.Position < 0 || data.Position >= data.Length)
|
||||
return null;
|
||||
|
||||
// Cache the current offset
|
||||
int initialOffset = (int)data.Position;
|
||||
|
||||
// Create a new cart image to fill
|
||||
var cart = new Cart();
|
||||
|
||||
#region Header
|
||||
|
||||
// Try to parse the header
|
||||
var header = ParseCommonHeader(data);
|
||||
if (header == null)
|
||||
return null;
|
||||
|
||||
// Set the cart image header
|
||||
cart.CommonHeader = header;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Extended DSi Header
|
||||
|
||||
// If we have a DSi-compatible cartridge
|
||||
if (header.UnitCode == Unitcode.NDSPlusDSi || header.UnitCode == Unitcode.DSi)
|
||||
{
|
||||
var extendedDSiHeader = ParseExtendedDSiHeader(data);
|
||||
if (extendedDSiHeader == null)
|
||||
return null;
|
||||
|
||||
cart.ExtendedDSiHeader = extendedDSiHeader;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Secure Area
|
||||
|
||||
// Try to get the secure area offset
|
||||
long secureAreaOffset = 0x4000;
|
||||
if (secureAreaOffset > data.Length)
|
||||
return null;
|
||||
|
||||
// Seek to the secure area
|
||||
data.Seek(secureAreaOffset, SeekOrigin.Begin);
|
||||
|
||||
// Read the secure area without processing
|
||||
cart.SecureArea = data.ReadBytes(0x800);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Name Table
|
||||
|
||||
// Try to get the name table offset
|
||||
long nameTableOffset = header.FileNameTableOffset;
|
||||
if (nameTableOffset < 0 || nameTableOffset > data.Length)
|
||||
return null;
|
||||
|
||||
// Seek to the name table
|
||||
data.Seek(nameTableOffset, SeekOrigin.Begin);
|
||||
|
||||
// Try to parse the name table
|
||||
var nameTable = ParseNameTable(data);
|
||||
if (nameTable == null)
|
||||
return null;
|
||||
|
||||
// Set the name table
|
||||
cart.NameTable = nameTable;
|
||||
|
||||
#endregion
|
||||
|
||||
#region File Allocation Table
|
||||
|
||||
// Try to get the file allocation table offset
|
||||
long fileAllocationTableOffset = header.FileAllocationTableOffset;
|
||||
if (fileAllocationTableOffset < 0 || fileAllocationTableOffset > data.Length)
|
||||
return null;
|
||||
|
||||
// Seek to the file allocation table
|
||||
data.Seek(fileAllocationTableOffset, SeekOrigin.Begin);
|
||||
|
||||
// Create the file allocation table
|
||||
var fileAllocationTable = new List<FileAllocationTableEntry>();
|
||||
|
||||
// Try to parse the file allocation table
|
||||
while (data.Position - fileAllocationTableOffset < header.FileAllocationTableLength)
|
||||
{
|
||||
var entry = ParseFileAllocationTableEntry(data);
|
||||
fileAllocationTable.Add(entry);
|
||||
}
|
||||
|
||||
// Set the file allocation table
|
||||
cart.FileAllocationTable = fileAllocationTable.ToArray();
|
||||
|
||||
#endregion
|
||||
|
||||
// TODO: Read and optionally parse out the other areas
|
||||
// Look for offsets and lengths in the header pieces
|
||||
|
||||
return cart;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a common header
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled common header on success, null on error</returns>
|
||||
private static CommonHeader ParseCommonHeader(Stream data)
|
||||
{
|
||||
// TODO: Use marshalling here instead of building
|
||||
CommonHeader commonHeader = new CommonHeader();
|
||||
|
||||
byte[] gameTitle = data.ReadBytes(12);
|
||||
commonHeader.GameTitle = Encoding.ASCII.GetString(gameTitle).TrimEnd('\0');
|
||||
commonHeader.GameCode = data.ReadUInt32();
|
||||
byte[] makerCode = data.ReadBytes(2);
|
||||
commonHeader.MakerCode = Encoding.ASCII.GetString(bytes: makerCode).TrimEnd('\0');
|
||||
commonHeader.UnitCode = (Unitcode)data.ReadByteValue();
|
||||
commonHeader.EncryptionSeedSelect = data.ReadByteValue();
|
||||
commonHeader.DeviceCapacity = data.ReadByteValue();
|
||||
commonHeader.Reserved1 = data.ReadBytes(7);
|
||||
commonHeader.GameRevision = data.ReadUInt16();
|
||||
commonHeader.RomVersion = data.ReadByteValue();
|
||||
commonHeader.InternalFlags = data.ReadByteValue();
|
||||
commonHeader.ARM9RomOffset = data.ReadUInt32();
|
||||
commonHeader.ARM9EntryAddress = data.ReadUInt32();
|
||||
commonHeader.ARM9LoadAddress = data.ReadUInt32();
|
||||
commonHeader.ARM9Size = data.ReadUInt32();
|
||||
commonHeader.ARM7RomOffset = data.ReadUInt32();
|
||||
commonHeader.ARM7EntryAddress = data.ReadUInt32();
|
||||
commonHeader.ARM7LoadAddress = data.ReadUInt32();
|
||||
commonHeader.ARM7Size = data.ReadUInt32();
|
||||
commonHeader.FileNameTableOffset = data.ReadUInt32();
|
||||
commonHeader.FileNameTableLength = data.ReadUInt32();
|
||||
commonHeader.FileAllocationTableOffset = data.ReadUInt32();
|
||||
commonHeader.FileAllocationTableLength = data.ReadUInt32();
|
||||
commonHeader.ARM9OverlayOffset = data.ReadUInt32();
|
||||
commonHeader.ARM9OverlayLength = data.ReadUInt32();
|
||||
commonHeader.ARM7OverlayOffset = data.ReadUInt32();
|
||||
commonHeader.ARM7OverlayLength = data.ReadUInt32();
|
||||
commonHeader.NormalCardControlRegisterSettings = data.ReadUInt32();
|
||||
commonHeader.SecureCardControlRegisterSettings = data.ReadUInt32();
|
||||
commonHeader.IconBannerOffset = data.ReadUInt32();
|
||||
commonHeader.SecureAreaCRC = data.ReadUInt16();
|
||||
commonHeader.SecureTransferTimeout = data.ReadUInt16();
|
||||
commonHeader.ARM9Autoload = data.ReadUInt32();
|
||||
commonHeader.ARM7Autoload = data.ReadUInt32();
|
||||
commonHeader.SecureDisable = data.ReadBytes(8);
|
||||
commonHeader.NTRRegionRomSize = data.ReadUInt32();
|
||||
commonHeader.HeaderSize = data.ReadUInt32();
|
||||
commonHeader.Reserved2 = data.ReadBytes(56);
|
||||
commonHeader.NintendoLogo = data.ReadBytes(156);
|
||||
commonHeader.NintendoLogoCRC = data.ReadUInt16();
|
||||
commonHeader.HeaderCRC = data.ReadUInt16();
|
||||
commonHeader.DebuggerReserved = data.ReadBytes(0x20);
|
||||
|
||||
return commonHeader;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into an extended DSi header
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled extended DSi header on success, null on error</returns>
|
||||
private static ExtendedDSiHeader ParseExtendedDSiHeader(Stream data)
|
||||
{
|
||||
// TODO: Use marshalling here instead of building
|
||||
ExtendedDSiHeader extendedDSiHeader = new ExtendedDSiHeader();
|
||||
|
||||
extendedDSiHeader.GlobalMBK15Settings = new uint[5];
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
extendedDSiHeader.GlobalMBK15Settings[i] = data.ReadUInt32();
|
||||
}
|
||||
extendedDSiHeader.LocalMBK68SettingsARM9 = new uint[3];
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
extendedDSiHeader.LocalMBK68SettingsARM9[i] = data.ReadUInt32();
|
||||
}
|
||||
extendedDSiHeader.LocalMBK68SettingsARM7 = new uint[3];
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
extendedDSiHeader.LocalMBK68SettingsARM7[i] = data.ReadUInt32();
|
||||
}
|
||||
extendedDSiHeader.GlobalMBK9Setting = data.ReadUInt32();
|
||||
extendedDSiHeader.RegionFlags = data.ReadUInt32();
|
||||
extendedDSiHeader.AccessControl = data.ReadUInt32();
|
||||
extendedDSiHeader.ARM7SCFGEXTMask = data.ReadUInt32();
|
||||
extendedDSiHeader.ReservedFlags = data.ReadUInt32();
|
||||
extendedDSiHeader.ARM9iRomOffset = data.ReadUInt32();
|
||||
extendedDSiHeader.Reserved3 = data.ReadUInt32();
|
||||
extendedDSiHeader.ARM9iLoadAddress = data.ReadUInt32();
|
||||
extendedDSiHeader.ARM9iSize = data.ReadUInt32();
|
||||
extendedDSiHeader.ARM7iRomOffset = data.ReadUInt32();
|
||||
extendedDSiHeader.Reserved4 = data.ReadUInt32();
|
||||
extendedDSiHeader.ARM7iLoadAddress = data.ReadUInt32();
|
||||
extendedDSiHeader.ARM7iSize = data.ReadUInt32();
|
||||
extendedDSiHeader.DigestNTRRegionOffset = data.ReadUInt32();
|
||||
extendedDSiHeader.DigestNTRRegionLength = data.ReadUInt32();
|
||||
extendedDSiHeader.DigestTWLRegionOffset = data.ReadUInt32();
|
||||
extendedDSiHeader.DigestTWLRegionLength = data.ReadUInt32();
|
||||
extendedDSiHeader.DigestSectorHashtableRegionOffset = data.ReadUInt32();
|
||||
extendedDSiHeader.DigestSectorHashtableRegionLength = data.ReadUInt32();
|
||||
extendedDSiHeader.DigestBlockHashtableRegionOffset = data.ReadUInt32();
|
||||
extendedDSiHeader.DigestBlockHashtableRegionLength = data.ReadUInt32();
|
||||
extendedDSiHeader.DigestSectorSize = data.ReadUInt32();
|
||||
extendedDSiHeader.DigestBlockSectorCount = data.ReadUInt32();
|
||||
extendedDSiHeader.IconBannerSize = data.ReadUInt32();
|
||||
extendedDSiHeader.Unknown1 = data.ReadUInt32();
|
||||
extendedDSiHeader.ModcryptArea1Offset = data.ReadUInt32();
|
||||
extendedDSiHeader.ModcryptArea1Size = data.ReadUInt32();
|
||||
extendedDSiHeader.ModcryptArea2Offset = data.ReadUInt32();
|
||||
extendedDSiHeader.ModcryptArea2Size = data.ReadUInt32();
|
||||
extendedDSiHeader.TitleID = data.ReadBytes(8);
|
||||
extendedDSiHeader.DSiWarePublicSavSize = data.ReadUInt32();
|
||||
extendedDSiHeader.DSiWarePrivateSavSize = data.ReadUInt32();
|
||||
extendedDSiHeader.ReservedZero = data.ReadBytes(176);
|
||||
extendedDSiHeader.Unknown2 = data.ReadBytes(0x10);
|
||||
extendedDSiHeader.ARM9WithSecureAreaSHA1HMACHash = data.ReadBytes(20);
|
||||
extendedDSiHeader.ARM7SHA1HMACHash = data.ReadBytes(20);
|
||||
extendedDSiHeader.DigestMasterSHA1HMACHash = data.ReadBytes(20);
|
||||
extendedDSiHeader.BannerSHA1HMACHash = data.ReadBytes(20);
|
||||
extendedDSiHeader.ARM9iDecryptedSHA1HMACHash = data.ReadBytes(20);
|
||||
extendedDSiHeader.ARM7iDecryptedSHA1HMACHash = data.ReadBytes(20);
|
||||
extendedDSiHeader.Reserved5 = data.ReadBytes(40);
|
||||
extendedDSiHeader.ARM9NoSecureAreaSHA1HMACHash = data.ReadBytes(20);
|
||||
extendedDSiHeader.Reserved6 = data.ReadBytes(2636);
|
||||
extendedDSiHeader.ReservedAndUnchecked = data.ReadBytes(0x180);
|
||||
extendedDSiHeader.RSASignature = data.ReadBytes(0x80);
|
||||
|
||||
return extendedDSiHeader;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a name table
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled name table on success, null on error</returns>
|
||||
private static NameTable ParseNameTable(Stream data)
|
||||
{
|
||||
// TODO: Use marshalling here instead of building
|
||||
NameTable nameTable = new NameTable();
|
||||
|
||||
// Create a variable-length table
|
||||
var folderAllocationTable = new List<FolderAllocationTableEntry>();
|
||||
int entryCount = int.MaxValue;
|
||||
while (entryCount > 0)
|
||||
{
|
||||
var entry = ParseFolderAllocationTableEntry(data);
|
||||
folderAllocationTable.Add(entry);
|
||||
|
||||
// If we have the root entry
|
||||
if (entryCount == int.MaxValue)
|
||||
entryCount = (entry.Unknown << 8) | entry.ParentFolderIndex;
|
||||
|
||||
// Decrement the entry count
|
||||
entryCount--;
|
||||
}
|
||||
|
||||
// Assign the folder allocation table
|
||||
nameTable.FolderAllocationTable = folderAllocationTable.ToArray();
|
||||
|
||||
// Create a variable-length table
|
||||
var nameList = new List<NameListEntry>();
|
||||
while (true)
|
||||
{
|
||||
var entry = ParseNameListEntry(data);
|
||||
if (entry == null)
|
||||
break;
|
||||
|
||||
nameList.Add(entry);
|
||||
}
|
||||
|
||||
// Assign the name list
|
||||
nameTable.NameList = nameList.ToArray();
|
||||
|
||||
return nameTable;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a folder allocation table entry
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled folder allocation table entry on success, null on error</returns>
|
||||
private static FolderAllocationTableEntry ParseFolderAllocationTableEntry(Stream data)
|
||||
{
|
||||
// TODO: Use marshalling here instead of building
|
||||
FolderAllocationTableEntry entry = new FolderAllocationTableEntry();
|
||||
|
||||
entry.StartOffset = data.ReadUInt32();
|
||||
entry.FirstFileIndex = data.ReadUInt16();
|
||||
entry.ParentFolderIndex = data.ReadByteValue();
|
||||
entry.Unknown = data.ReadByteValue();
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a name list entry
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled name list entry on success, null on error</returns>
|
||||
private static NameListEntry ParseNameListEntry(Stream data)
|
||||
{
|
||||
// TODO: Use marshalling here instead of building
|
||||
NameListEntry entry = new NameListEntry();
|
||||
|
||||
byte flagAndSize = data.ReadByteValue();
|
||||
if (flagAndSize == 0xFF)
|
||||
return null;
|
||||
|
||||
entry.Folder = (flagAndSize & 0x80) != 0;
|
||||
|
||||
byte size = (byte)(flagAndSize & ~0x80);
|
||||
if (size > 0)
|
||||
{
|
||||
byte[] name = data.ReadBytes(size);
|
||||
entry.Name = Encoding.UTF8.GetString(name);
|
||||
}
|
||||
|
||||
if (entry.Folder)
|
||||
entry.Index = data.ReadUInt16();
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a name list entry
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled name list entry on success, null on error</returns>
|
||||
private static FileAllocationTableEntry ParseFileAllocationTableEntry(Stream data)
|
||||
{
|
||||
// TODO: Use marshalling here instead of building
|
||||
FileAllocationTableEntry entry = new FileAllocationTableEntry();
|
||||
|
||||
entry.StartOffset = data.ReadUInt32();
|
||||
entry.EndOffset = data.ReadUInt32();
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
211
BurnOutSharp.Builders/PFF.cs
Normal file
211
BurnOutSharp.Builders/PFF.cs
Normal file
@@ -0,0 +1,211 @@
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using BurnOutSharp.Models.PFF;
|
||||
using BurnOutSharp.Utilities;
|
||||
using static BurnOutSharp.Models.PFF.Constants;
|
||||
|
||||
namespace BurnOutSharp.Builders
|
||||
{
|
||||
public class PFF
|
||||
{
|
||||
#region Byte Data
|
||||
|
||||
/// <summary>
|
||||
/// Parse a byte array into a PFF archive
|
||||
/// </summary>
|
||||
/// <param name="data">Byte array to parse</param>
|
||||
/// <param name="offset">Offset into the byte array</param>
|
||||
/// <returns>Filled archive on success, null on error</returns>
|
||||
public static Archive ParseArchive(byte[] data, int offset)
|
||||
{
|
||||
// If the data is invalid
|
||||
if (data == null)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (offset < 0 || offset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Create a memory stream and parse that
|
||||
MemoryStream dataStream = new MemoryStream(data, offset, data.Length - offset);
|
||||
return ParseArchive(dataStream);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Stream Data
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a PFF archive
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled archive on success, null on error</returns>
|
||||
public static Archive ParseArchive(Stream data)
|
||||
{
|
||||
// If the data is invalid
|
||||
if (data == null || data.Length == 0 || !data.CanSeek || !data.CanRead)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (data.Position < 0 || data.Position >= data.Length)
|
||||
return null;
|
||||
|
||||
// Cache the current offset
|
||||
int initialOffset = (int)data.Position;
|
||||
|
||||
// Create a new archive to fill
|
||||
var archive = new Archive();
|
||||
|
||||
#region Header
|
||||
|
||||
// Try to parse the header
|
||||
var header = ParseHeader(data);
|
||||
if (header == null)
|
||||
return null;
|
||||
|
||||
// Set the archive header
|
||||
archive.Header = header;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Segments
|
||||
|
||||
// Get the segments
|
||||
long offset = header.FileListOffset;
|
||||
if (offset < 0 || offset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Seek to the segments
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Create the segments array
|
||||
archive.Segments = new Segment[header.NumberOfFiles];
|
||||
|
||||
// Read all segments in turn
|
||||
for (int i = 0; i < header.NumberOfFiles; i++)
|
||||
{
|
||||
var file = ParseSegment(data, header.FileSegmentSize);
|
||||
if (file == null)
|
||||
return null;
|
||||
|
||||
archive.Segments[i] = file;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Footer
|
||||
|
||||
// Get the footer offset
|
||||
offset = header.FileListOffset + (header.FileSegmentSize * header.NumberOfFiles);
|
||||
if (offset < 0 || offset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Seek to the footer
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Try to parse the footer
|
||||
var footer = ParseFooter(data);
|
||||
if (footer == null)
|
||||
return null;
|
||||
|
||||
// Set the archive footer
|
||||
archive.Footer = footer;
|
||||
|
||||
#endregion
|
||||
|
||||
return archive;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a header
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled header on success, null on error</returns>
|
||||
private static Header ParseHeader(Stream data)
|
||||
{
|
||||
// TODO: Use marshalling here instead of building
|
||||
Header header = new Header();
|
||||
|
||||
header.HeaderSize = data.ReadUInt32();
|
||||
byte[] signature = data.ReadBytes(4);
|
||||
header.Signature = Encoding.ASCII.GetString(signature);
|
||||
header.NumberOfFiles = data.ReadUInt32();
|
||||
header.FileSegmentSize = data.ReadUInt32();
|
||||
switch (header.Signature)
|
||||
{
|
||||
case Version0SignatureString:
|
||||
if (header.FileSegmentSize != Version0HSegmentSize)
|
||||
return null;
|
||||
break;
|
||||
|
||||
case Version2SignatureString:
|
||||
if (header.FileSegmentSize != Version2SegmentSize)
|
||||
return null;
|
||||
break;
|
||||
|
||||
// Version 3 can sometimes have Version 2 segment sizes
|
||||
case Version3SignatureString:
|
||||
if (header.FileSegmentSize != Version2SegmentSize && header.FileSegmentSize != Version3SegmentSize)
|
||||
return null;
|
||||
break;
|
||||
|
||||
case Version4SignatureString:
|
||||
if (header.FileSegmentSize != Version4SegmentSize)
|
||||
return null;
|
||||
break;
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
header.FileListOffset = data.ReadUInt32();
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a footer
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled footer on success, null on error</returns>
|
||||
private static Footer ParseFooter(Stream data)
|
||||
{
|
||||
// TODO: Use marshalling here instead of building
|
||||
Footer footer = new Footer();
|
||||
|
||||
footer.SystemIP = data.ReadUInt32();
|
||||
footer.Reserved = data.ReadUInt32();
|
||||
byte[] kingTag = data.ReadBytes(4);
|
||||
footer.KingTag = Encoding.ASCII.GetString(kingTag);
|
||||
|
||||
return footer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a file entry
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <param name="segmentSize">PFF segment size</param>
|
||||
/// <returns>Filled file entry on success, null on error</returns>
|
||||
private static Segment ParseSegment(Stream data, uint segmentSize)
|
||||
{
|
||||
// TODO: Use marshalling here instead of building
|
||||
Segment segment = new Segment();
|
||||
|
||||
segment.Deleted = data.ReadUInt32();
|
||||
segment.FileLocation = data.ReadUInt32();
|
||||
segment.FileSize = data.ReadUInt32();
|
||||
segment.PackedDate = data.ReadUInt32();
|
||||
byte[] fileName = data.ReadBytes(0x10);
|
||||
segment.FileName = Encoding.ASCII.GetString(fileName).TrimEnd('\0');
|
||||
if (segmentSize > Version2SegmentSize)
|
||||
segment.ModifiedDate = data.ReadUInt32();
|
||||
if (segmentSize > Version3SegmentSize)
|
||||
segment.CompressionLevel = data.ReadUInt32();
|
||||
|
||||
return segment;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
463
BurnOutSharp.Builders/PlayJ.cs
Normal file
463
BurnOutSharp.Builders/PlayJ.cs
Normal file
@@ -0,0 +1,463 @@
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using BurnOutSharp.Models.PlayJ;
|
||||
using BurnOutSharp.Utilities;
|
||||
using static BurnOutSharp.Models.PlayJ.Constants;
|
||||
|
||||
namespace BurnOutSharp.Builders
|
||||
{
|
||||
public class PlayJ
|
||||
{
|
||||
#region Byte Data
|
||||
|
||||
/// <summary>
|
||||
/// Parse a byte array into a PlayJ playlist
|
||||
/// </summary>
|
||||
/// <param name="data">Byte array to parse</param>
|
||||
/// <param name="offset">Offset into the byte array</param>
|
||||
/// <returns>Filled playlist on success, null on error</returns>
|
||||
public static Playlist ParsePlaylist(byte[] data, int offset)
|
||||
{
|
||||
// If the data is invalid
|
||||
if (data == null)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (offset < 0 || offset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Create a memory stream and parse that
|
||||
MemoryStream dataStream = new MemoryStream(data, offset, data.Length - offset);
|
||||
return ParsePlaylist(dataStream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a byte array into a PlayJ audio file
|
||||
/// </summary>
|
||||
/// <param name="data">Byte array to parse</param>
|
||||
/// <param name="offset">Offset into the byte array</param>
|
||||
/// <returns>Filled audio file on success, null on error</returns>
|
||||
public static AudioFile ParseAudioFile(byte[] data, int offset)
|
||||
{
|
||||
// If the data is invalid
|
||||
if (data == null)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (offset < 0 || offset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Create a memory stream and parse that
|
||||
MemoryStream dataStream = new MemoryStream(data, offset, data.Length - offset);
|
||||
return ParseAudioFile(dataStream);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Stream Data
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a PlayJ playlist
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled playlist on success, null on error</returns>
|
||||
public static Playlist ParsePlaylist(Stream data)
|
||||
{
|
||||
// If the data is invalid
|
||||
if (data == null || data.Length == 0 || !data.CanSeek || !data.CanRead)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (data.Position < 0 || data.Position >= data.Length)
|
||||
return null;
|
||||
|
||||
// Cache the current offset
|
||||
int initialOffset = (int)data.Position;
|
||||
|
||||
// Create a new playlist to fill
|
||||
var playlist = new Playlist();
|
||||
|
||||
#region Playlist Header
|
||||
|
||||
// Try to parse the playlist header
|
||||
var playlistHeader = ParsePlaylistHeader(data);
|
||||
if (playlistHeader == null)
|
||||
return null;
|
||||
|
||||
// Set the playlist header
|
||||
playlist.Header = playlistHeader;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Audio Files
|
||||
|
||||
// Create the audio files array
|
||||
playlist.AudioFiles = new AudioFile[playlistHeader.TrackCount];
|
||||
|
||||
// Try to parse the audio files
|
||||
for (int i = 0; i < playlist.AudioFiles.Length; i++)
|
||||
{
|
||||
long currentOffset = data.Position;
|
||||
var entryHeader = ParseAudioFile(data, currentOffset);
|
||||
if (entryHeader == null)
|
||||
return null;
|
||||
|
||||
playlist.AudioFiles[i] = entryHeader;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
return playlist;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a PlayJ audio file
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <param name="adjust">Offset to adjust all seeking by</param>
|
||||
/// <returns>Filled audio file on success, null on error</returns>
|
||||
public static AudioFile ParseAudioFile(Stream data, long adjust = 0)
|
||||
{
|
||||
// If the data is invalid
|
||||
if (data == null || data.Length == 0 || !data.CanSeek || !data.CanRead)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (data.Position < 0 || data.Position >= data.Length)
|
||||
return null;
|
||||
|
||||
// Cache the current offset
|
||||
int initialOffset = (int)data.Position;
|
||||
|
||||
// Create a new audio file to fill
|
||||
var audioFile = new AudioFile();
|
||||
|
||||
#region Audio Header
|
||||
|
||||
// Try to parse the audio header
|
||||
var audioHeader = ParseAudioHeader(data);
|
||||
if (audioHeader == null)
|
||||
return null;
|
||||
|
||||
// Set the audio header
|
||||
audioFile.Header = audioHeader;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Unknown Block 1
|
||||
|
||||
uint unknownOffset1 = (audioHeader.Version == 0x00000000)
|
||||
? (audioHeader as AudioHeaderV1).UnknownOffset1
|
||||
: (audioHeader as AudioHeaderV2).UnknownOffset1 + 0x54;
|
||||
|
||||
// If we have an unknown block 1 offset
|
||||
if (unknownOffset1 > 0)
|
||||
{
|
||||
// Get the unknown block 1 offset
|
||||
long offset = unknownOffset1 + adjust;
|
||||
if (offset < 0 || offset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Seek to the unknown block 1
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
// Try to parse the unknown block 1
|
||||
var unknownBlock1 = ParseUnknownBlock1(data);
|
||||
if (unknownBlock1 == null)
|
||||
return null;
|
||||
|
||||
// Set the unknown block 1
|
||||
audioFile.UnknownBlock1 = unknownBlock1;
|
||||
|
||||
#endregion
|
||||
|
||||
#region V1 Only
|
||||
|
||||
// If we have a V1 file
|
||||
if (audioHeader.Version == 0x00000000)
|
||||
{
|
||||
#region Unknown Value 2
|
||||
|
||||
// Get the V1 unknown offset 2
|
||||
uint? unknownOffset2 = (audioHeader as AudioHeaderV1)?.UnknownOffset2;
|
||||
|
||||
// If we have an unknown value 2 offset
|
||||
if (unknownOffset2 != null && unknownOffset2 > 0)
|
||||
{
|
||||
// Get the unknown value 2 offset
|
||||
long offset = unknownOffset2.Value + adjust;
|
||||
if (offset < 0 || offset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Seek to the unknown value 2
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
// Set the unknown value 2
|
||||
audioFile.UnknownValue2 = data.ReadUInt32();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Unknown Block 3
|
||||
|
||||
// Get the V1 unknown offset 3
|
||||
uint? unknownOffset3 = (audioHeader as AudioHeaderV1)?.UnknownOffset3;
|
||||
|
||||
// If we have an unknown block 3 offset
|
||||
if (unknownOffset3 != null && unknownOffset3 > 0)
|
||||
{
|
||||
// Get the unknown block 3 offset
|
||||
long offset = unknownOffset3.Value + adjust;
|
||||
if (offset < 0 || offset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Seek to the unknown block 3
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
// Try to parse the unknown block 3
|
||||
var unknownBlock3 = ParseUnknownBlock3(data);
|
||||
if (unknownBlock3 == null)
|
||||
return null;
|
||||
|
||||
// Set the unknown block 3
|
||||
audioFile.UnknownBlock3 = unknownBlock3;
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region V2 Only
|
||||
|
||||
// If we have a V2 file
|
||||
if (audioHeader.Version == 0x0000000A)
|
||||
{
|
||||
#region Data Files Count
|
||||
|
||||
// Set the data files count
|
||||
audioFile.DataFilesCount = data.ReadUInt32();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Data Files
|
||||
|
||||
// Create the data files array
|
||||
audioFile.DataFiles = new DataFile[audioFile.DataFilesCount];
|
||||
|
||||
// Try to parse the data files
|
||||
for (int i = 0; i < audioFile.DataFiles.Length; i++)
|
||||
{
|
||||
var dataFile = ParseDataFile(data);
|
||||
if (dataFile == null)
|
||||
return null;
|
||||
|
||||
audioFile.DataFiles[i] = dataFile;
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
return audioFile;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a playlist header
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled playlist header on success, null on error</returns>
|
||||
private static PlaylistHeader ParsePlaylistHeader(Stream data)
|
||||
{
|
||||
// TODO: Use marshalling here instead of building
|
||||
PlaylistHeader playlistHeader = new PlaylistHeader();
|
||||
|
||||
playlistHeader.TrackCount = data.ReadUInt32();
|
||||
playlistHeader.Data = data.ReadBytes(52);
|
||||
|
||||
return playlistHeader;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into an audio header
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled audio header on success, null on error</returns>
|
||||
private static AudioHeader ParseAudioHeader(Stream data)
|
||||
{
|
||||
// Cache the current offset
|
||||
long initialOffset = data.Position;
|
||||
|
||||
// TODO: Use marshalling here instead of building
|
||||
AudioHeader audioHeader;
|
||||
|
||||
// Get the common header pieces
|
||||
uint signature = data.ReadUInt32();
|
||||
if (signature != SignatureUInt32)
|
||||
return null;
|
||||
|
||||
uint version = data.ReadUInt32();
|
||||
|
||||
// Build the header according to version
|
||||
uint unknownOffset1;
|
||||
switch (version)
|
||||
{
|
||||
// Version 1
|
||||
case 0x00000000:
|
||||
AudioHeaderV1 v1 = new AudioHeaderV1();
|
||||
|
||||
v1.Signature = signature;
|
||||
v1.Version = version;
|
||||
v1.TrackID = data.ReadUInt32();
|
||||
v1.UnknownOffset1 = data.ReadUInt32();
|
||||
v1.UnknownOffset2 = data.ReadUInt32();
|
||||
v1.UnknownOffset3 = data.ReadUInt32();
|
||||
v1.Unknown1 = data.ReadUInt32();
|
||||
v1.Unknown2 = data.ReadUInt32();
|
||||
v1.Year = data.ReadUInt32();
|
||||
v1.TrackNumber = data.ReadByteValue();
|
||||
v1.Subgenre = (Subgenre)data.ReadByteValue();
|
||||
v1.Duration = data.ReadUInt32();
|
||||
|
||||
audioHeader = v1;
|
||||
unknownOffset1 = v1.UnknownOffset1;
|
||||
break;
|
||||
|
||||
// Version 2
|
||||
case 0x0000000A:
|
||||
AudioHeaderV2 v2 = new AudioHeaderV2();
|
||||
|
||||
v2.Signature = signature;
|
||||
v2.Version = version;
|
||||
v2.Unknown1 = data.ReadUInt32();
|
||||
v2.Unknown2 = data.ReadUInt32();
|
||||
v2.Unknown3 = data.ReadUInt32();
|
||||
v2.Unknown4 = data.ReadUInt32();
|
||||
v2.Unknown5 = data.ReadUInt32();
|
||||
v2.Unknown6 = data.ReadUInt32();
|
||||
v2.UnknownOffset1 = data.ReadUInt32();
|
||||
v2.Unknown7 = data.ReadUInt32();
|
||||
v2.Unknown8 = data.ReadUInt32();
|
||||
v2.Unknown9 = data.ReadUInt32();
|
||||
v2.UnknownOffset2 = data.ReadUInt32();
|
||||
v2.Unknown10 = data.ReadUInt32();
|
||||
v2.Unknown11 = data.ReadUInt32();
|
||||
v2.Unknown12 = data.ReadUInt32();
|
||||
v2.Unknown13 = data.ReadUInt32();
|
||||
v2.Unknown14 = data.ReadUInt32();
|
||||
v2.Unknown15 = data.ReadUInt32();
|
||||
v2.Unknown16 = data.ReadUInt32();
|
||||
v2.Unknown17 = data.ReadUInt32();
|
||||
v2.TrackID = data.ReadUInt32();
|
||||
v2.Year = data.ReadUInt32();
|
||||
v2.TrackNumber = data.ReadUInt32();
|
||||
v2.Unknown18 = data.ReadUInt32();
|
||||
|
||||
audioHeader = v2;
|
||||
unknownOffset1 = v2.UnknownOffset1 + 0x54;
|
||||
break;
|
||||
|
||||
// No other version are recognized
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
audioHeader.TrackLength = data.ReadUInt16();
|
||||
byte[] track = data.ReadBytes(audioHeader.TrackLength);
|
||||
if (track != null)
|
||||
audioHeader.Track = Encoding.ASCII.GetString(track);
|
||||
|
||||
audioHeader.ArtistLength = data.ReadUInt16();
|
||||
byte[] artist = data.ReadBytes(audioHeader.ArtistLength);
|
||||
if (artist != null)
|
||||
audioHeader.Artist = Encoding.ASCII.GetString(artist);
|
||||
|
||||
audioHeader.AlbumLength = data.ReadUInt16();
|
||||
byte[] album = data.ReadBytes(audioHeader.AlbumLength);
|
||||
if (album != null)
|
||||
audioHeader.Album = Encoding.ASCII.GetString(album);
|
||||
|
||||
audioHeader.WriterLength = data.ReadUInt16();
|
||||
byte[] writer = data.ReadBytes(audioHeader.WriterLength);
|
||||
if (writer != null)
|
||||
audioHeader.Writer = Encoding.ASCII.GetString(writer);
|
||||
|
||||
audioHeader.PublisherLength = data.ReadUInt16();
|
||||
byte[] publisher = data.ReadBytes(audioHeader.PublisherLength);
|
||||
if (publisher != null)
|
||||
audioHeader.Publisher = Encoding.ASCII.GetString(publisher);
|
||||
|
||||
audioHeader.LabelLength = data.ReadUInt16();
|
||||
byte[] label = data.ReadBytes(audioHeader.LabelLength);
|
||||
if (label != null)
|
||||
audioHeader.Label = Encoding.ASCII.GetString(label);
|
||||
|
||||
if (data.Position - initialOffset < unknownOffset1)
|
||||
{
|
||||
audioHeader.CommentsLength = data.ReadUInt16();
|
||||
byte[] comments = data.ReadBytes(audioHeader.CommentsLength);
|
||||
if (comments != null)
|
||||
audioHeader.Comments = Encoding.ASCII.GetString(comments);
|
||||
}
|
||||
|
||||
return audioHeader;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into an unknown block 1
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled unknown block 1 on success, null on error</returns>
|
||||
private static UnknownBlock1 ParseUnknownBlock1(Stream data)
|
||||
{
|
||||
// TODO: Use marshalling here instead of building
|
||||
UnknownBlock1 unknownBlock1 = new UnknownBlock1();
|
||||
|
||||
unknownBlock1.Length = data.ReadUInt32();
|
||||
unknownBlock1.Data = data.ReadBytes((int)unknownBlock1.Length);
|
||||
|
||||
return unknownBlock1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into an unknown block 3
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled unknown block 3 on success, null on error</returns>
|
||||
private static UnknownBlock3 ParseUnknownBlock3(Stream data)
|
||||
{
|
||||
// TODO: Use marshalling here instead of building
|
||||
UnknownBlock3 unknownBlock3 = new UnknownBlock3();
|
||||
|
||||
// No-op because we don't even know the length
|
||||
|
||||
return unknownBlock3;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a data file
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled data file on success, null on error</returns>
|
||||
private static DataFile ParseDataFile(Stream data)
|
||||
{
|
||||
// TODO: Use marshalling here instead of building
|
||||
DataFile dataFile = new DataFile();
|
||||
|
||||
dataFile.FileNameLength = data.ReadUInt16();
|
||||
byte[] fileName = data.ReadBytes(dataFile.FileNameLength);
|
||||
if (fileName != null)
|
||||
dataFile.FileName = Encoding.ASCII.GetString(fileName);
|
||||
|
||||
dataFile.DataLength = data.ReadUInt32();
|
||||
dataFile.Data = data.ReadBytes((int)dataFile.DataLength);
|
||||
|
||||
return dataFile;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -9,9 +9,9 @@
|
||||
<Product>BurnOutSharp</Product>
|
||||
<Copyright>Copyright (c)2022 Matt Nadareski</Copyright>
|
||||
<RepositoryUrl>https://github.com/mnadareski/BurnOutSharp</RepositoryUrl>
|
||||
<Version>2.6</Version>
|
||||
<AssemblyVersion>2.6</AssemblyVersion>
|
||||
<FileVersion>2.6</FileVersion>
|
||||
<Version>2.7</Version>
|
||||
<AssemblyVersion>2.7</AssemblyVersion>
|
||||
<FileVersion>2.7</FileVersion>
|
||||
<IncludeSource>true</IncludeSource>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
<Product>BurnOutSharp</Product>
|
||||
<Copyright>Copyright (c)2018-2022 Matt Nadareski</Copyright>
|
||||
<RepositoryUrl>https://github.com/mnadareski/BurnOutSharp</RepositoryUrl>
|
||||
<Version>2.6</Version>
|
||||
<AssemblyVersion>2.6</AssemblyVersion>
|
||||
<FileVersion>2.6</FileVersion>
|
||||
<Version>2.7</Version>
|
||||
<AssemblyVersion>2.7</AssemblyVersion>
|
||||
<FileVersion>2.7</FileVersion>
|
||||
<IncludeSource>true</IncludeSource>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
</PropertyGroup>
|
||||
|
||||
13
BurnOutSharp.Models/AACS/CopyrightRecord.cs
Normal file
13
BurnOutSharp.Models/AACS/CopyrightRecord.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace BurnOutSharp.Models.AACS
|
||||
{
|
||||
/// <summary>
|
||||
/// This record type is undocumented but found in real media key blocks
|
||||
/// </summary>
|
||||
public sealed class CopyrightRecord : Record
|
||||
{
|
||||
/// <summary>
|
||||
/// Null-terminated ASCII string representing the copyright
|
||||
/// </summary>
|
||||
public string Copyright;
|
||||
}
|
||||
}
|
||||
21
BurnOutSharp.Models/AACS/DriveRevocationListEntry.cs
Normal file
21
BurnOutSharp.Models/AACS/DriveRevocationListEntry.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
namespace BurnOutSharp.Models.AACS
|
||||
{
|
||||
/// <see href="https://aacsla.com/wp-content/uploads/2019/02/AACS_Spec_Common_Final_0953.pdf"/>
|
||||
public sealed class DriveRevocationListEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// A 2-byte Range value indicates the range of revoked ID’s starting
|
||||
/// from the ID contained in the record. A value of zero in the Range
|
||||
/// field indicates that only one ID is being revoked, a value of one
|
||||
/// in the Range field indicates two ID’s are being revoked, and so on.
|
||||
/// </summary>
|
||||
public ushort Range;
|
||||
|
||||
/// <summary>
|
||||
/// A 6-byte Drive ID value identifying the Licensed Drive being revoked
|
||||
/// (or the first in a range of Licensed Drives being revoked, in the
|
||||
/// case of a non-zero Range value).
|
||||
/// </summary>
|
||||
public byte[] DriveID;
|
||||
}
|
||||
}
|
||||
26
BurnOutSharp.Models/AACS/DriveRevocationListRecord.cs
Normal file
26
BurnOutSharp.Models/AACS/DriveRevocationListRecord.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
namespace BurnOutSharp.Models.AACS
|
||||
{
|
||||
/// <summary>
|
||||
/// A properly formatted type 3 or type 4 Media Key Block contains exactly
|
||||
/// one Drive Revocation List Record. It follows the Host Revocation List
|
||||
/// Record, although it may not immediately follow it.
|
||||
///
|
||||
/// The Drive Revocation List Record is identical to the Host Revocation
|
||||
/// List Record, except it has type 2016, and it contains Drive Revocation
|
||||
/// List Entries, not Host Revocation List Entries. The Drive Revocation List
|
||||
/// Entries refer to Drive IDs in the Drive Certificates.
|
||||
/// </summary>
|
||||
/// <see href="https://aacsla.com/wp-content/uploads/2019/02/AACS_Spec_Common_Final_0953.pdf"/>
|
||||
public sealed class DriveRevocationListRecord : Record
|
||||
{
|
||||
/// <summary>
|
||||
/// The total number of Drive Revocation List Entry fields that follow.
|
||||
/// </summary>
|
||||
public uint TotalNumberOfEntries;
|
||||
|
||||
/// <summary>
|
||||
/// Revocation list entries
|
||||
/// </summary>
|
||||
public DriveRevocationSignatureBlock[] SignatureBlocks;
|
||||
}
|
||||
}
|
||||
17
BurnOutSharp.Models/AACS/DriveRevocationSignatureBlock.cs
Normal file
17
BurnOutSharp.Models/AACS/DriveRevocationSignatureBlock.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace BurnOutSharp.Models.AACS
|
||||
{
|
||||
/// <see href="https://aacsla.com/wp-content/uploads/2019/02/AACS_Spec_Common_Final_0953.pdf"/>
|
||||
public sealed class DriveRevocationSignatureBlock
|
||||
{
|
||||
/// <summary>
|
||||
/// The number of Drive Revocation List Entry fields in the signature block.
|
||||
/// </summary>
|
||||
public uint NumberOfEntries;
|
||||
|
||||
/// <summary>
|
||||
/// A list of 8-byte Host Drive List Entry fields, the length of this
|
||||
/// list being equal to the number in the signature block.
|
||||
/// </summary>
|
||||
public DriveRevocationListEntry[] EntryFields;
|
||||
}
|
||||
}
|
||||
23
BurnOutSharp.Models/AACS/EndOfMediaKeyBlockRecord.cs
Normal file
23
BurnOutSharp.Models/AACS/EndOfMediaKeyBlockRecord.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
namespace BurnOutSharp.Models.AACS
|
||||
{
|
||||
/// <summary>
|
||||
/// A properly formatted MKB shall contain an End of Media Key Block Record.
|
||||
/// When a device encounters this Record it stops processing the MKB, using
|
||||
/// whatever Km value it has calculated up to that point as the final Km for
|
||||
/// that MKB (pending possible checks for correctness of the key, as
|
||||
/// described previously).
|
||||
/// </summary>
|
||||
/// <see href="https://aacsla.com/wp-content/uploads/2019/02/AACS_Spec_Common_Final_0953.pdf"/>
|
||||
public sealed class EndOfMediaKeyBlockRecord : Record
|
||||
{
|
||||
/// <summary>
|
||||
/// AACS LA’s signature on the data in the Media Key Block up to,
|
||||
/// but not including, this record. Devices depending on the Version
|
||||
/// Number in the Type and Version Record must verify the signature.
|
||||
/// Other devices may ignore the signature data. If any device
|
||||
/// determines that the signature does not verify or is omitted, it
|
||||
/// must refuse to use the Media Key.
|
||||
/// </summary>
|
||||
public byte[] SignatureData;
|
||||
}
|
||||
}
|
||||
46
BurnOutSharp.Models/AACS/Enums.cs
Normal file
46
BurnOutSharp.Models/AACS/Enums.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
namespace BurnOutSharp.Models.AACS
|
||||
{
|
||||
public enum MediaKeyBlockType : uint
|
||||
{
|
||||
/// <summary>
|
||||
/// (Type 3). This is a normal Media Key Block suitable for being recorded
|
||||
/// on a AACS Recordable Media. Both Class I and Class II Licensed Products
|
||||
/// use it to directly calculate the Media Key.
|
||||
/// </summary>
|
||||
Type3 = 0x00031003,
|
||||
|
||||
/// <summary>
|
||||
/// (Type 4). This is a Media Key Block that has been designed to use Key
|
||||
/// Conversion Data (KCD). Thus, it is suitable only for pre-recorded media
|
||||
/// from which the KCD is derived. Both Class I and Class II Licensed Products
|
||||
/// use it to directly calculate the Media Key.
|
||||
/// </summary>
|
||||
Type4 = 0x00041003,
|
||||
|
||||
/// <summary>
|
||||
/// (Type 10). This is a Class II Media Key Block (one that has the functionality
|
||||
/// of a Sequence Key Block). This can only be processed by Class II Licensed
|
||||
/// Products; Class I Licensed Products are revoked in Type 10 Media Key Blocks
|
||||
/// and cannot process them. This type does not contain the Host Revocation List
|
||||
/// Record, the Drive Revocation List Record, and the Media Key Data Record, as
|
||||
/// described in the following sections. It does contain the records shown in
|
||||
/// Section 3.2.5.2, which are only processed by Class II Licensed Products.
|
||||
/// </summary>
|
||||
Type10 = 0x000A1003,
|
||||
}
|
||||
|
||||
public enum RecordType : byte
|
||||
{
|
||||
EndOfMediaKeyBlock = 0x02,
|
||||
ExplicitSubsetDifference = 0x04,
|
||||
MediaKeyData = 0x05,
|
||||
SubsetDifferenceIndex = 0x07,
|
||||
TypeAndVersion = 0x10,
|
||||
DriveRevocationList = 0x20,
|
||||
HostRevocationList = 0x21,
|
||||
VerifyMediaKey = 0x81,
|
||||
|
||||
// Not documented
|
||||
Copyright = 0x7F,
|
||||
}
|
||||
}
|
||||
11
BurnOutSharp.Models/AACS/ExplicitSubsetDifferenceRecord.cs
Normal file
11
BurnOutSharp.Models/AACS/ExplicitSubsetDifferenceRecord.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace BurnOutSharp.Models.AACS
|
||||
{
|
||||
/// <see href="https://aacsla.com/wp-content/uploads/2019/02/AACS_Spec_Common_Final_0953.pdf"/>
|
||||
public sealed class ExplicitSubsetDifferenceRecord : Record
|
||||
{
|
||||
/// <summary>
|
||||
/// In this record, each subset-difference is encoded with 5 bytes.
|
||||
/// </summary>
|
||||
public SubsetDifference[] SubsetDifferences;
|
||||
}
|
||||
}
|
||||
21
BurnOutSharp.Models/AACS/HostRevocationListEntry.cs
Normal file
21
BurnOutSharp.Models/AACS/HostRevocationListEntry.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
namespace BurnOutSharp.Models.AACS
|
||||
{
|
||||
/// <see href="https://aacsla.com/wp-content/uploads/2019/02/AACS_Spec_Common_Final_0953.pdf"/>
|
||||
public sealed class HostRevocationListEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// A 2-byte Range value indicates the range of revoked ID’s starting
|
||||
/// from the ID contained in the record. A value of zero in the Range
|
||||
/// field indicates that only one ID is being revoked, a value of one
|
||||
/// in the Range field indicates two ID’s are being revoked, and so on.
|
||||
/// </summary>
|
||||
public ushort Range;
|
||||
|
||||
/// <summary>
|
||||
/// A 6-byte Host ID value identifying the host being revoked (or the
|
||||
/// first in a range of hosts being revoked, in the case of a non-zero
|
||||
/// Range value).
|
||||
/// </summary>
|
||||
public byte[] HostID;
|
||||
}
|
||||
}
|
||||
29
BurnOutSharp.Models/AACS/HostRevocationListRecord.cs
Normal file
29
BurnOutSharp.Models/AACS/HostRevocationListRecord.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
namespace BurnOutSharp.Models.AACS
|
||||
{
|
||||
/// <summary>
|
||||
/// A properly formatted type 3 or type 4 Media Key Block shall have exactly
|
||||
/// one Host Revocation List Record as its second record. This record provides
|
||||
/// a list of hosts that have been revoked by the AACS LA. The AACS specification
|
||||
/// is applicable to PC-based system where a Licensed Drive and PC Host act
|
||||
/// together as the Recording Device and/or Playback Device for AACS Content.
|
||||
/// AACS uses a drive-host authentication protocol for the host to verify the
|
||||
/// integrity of the data received from the Licensed Drive, and for the Licensed
|
||||
/// Drive to check the validity of the host application. The Type and Version
|
||||
/// Record and the Host Revocation List Record are guaranteed to be the first two
|
||||
/// records of a Media Key Block, to make it easier for Licensed Drives to extract
|
||||
/// this data from an arbitrary Media Key Block.
|
||||
/// </summary>
|
||||
/// <see href="https://aacsla.com/wp-content/uploads/2019/02/AACS_Spec_Common_Final_0953.pdf"/>
|
||||
public sealed class HostRevocationListRecord : Record
|
||||
{
|
||||
/// <summary>
|
||||
/// The total number of Host Revocation List Entry fields that follow.
|
||||
/// </summary>
|
||||
public uint TotalNumberOfEntries;
|
||||
|
||||
/// <summary>
|
||||
/// Revocation list entries
|
||||
/// </summary>
|
||||
public HostRevocationSignatureBlock[] SignatureBlocks;
|
||||
}
|
||||
}
|
||||
17
BurnOutSharp.Models/AACS/HostRevocationSignatureBlock.cs
Normal file
17
BurnOutSharp.Models/AACS/HostRevocationSignatureBlock.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace BurnOutSharp.Models.AACS
|
||||
{
|
||||
/// <see href="https://aacsla.com/wp-content/uploads/2019/02/AACS_Spec_Common_Final_0953.pdf"/>
|
||||
public sealed class HostRevocationSignatureBlock
|
||||
{
|
||||
/// <summary>
|
||||
/// The number of Host Revocation List Entry fields in the signature block.
|
||||
/// </summary>
|
||||
public uint NumberOfEntries;
|
||||
|
||||
/// <summary>
|
||||
/// A list of 8-byte Host Revocation List Entry fields, the length of this
|
||||
/// list being equal to the number in the signature block.
|
||||
/// </summary>
|
||||
public HostRevocationListEntry[] EntryFields;
|
||||
}
|
||||
}
|
||||
14
BurnOutSharp.Models/AACS/MediaKeyBlock.cs
Normal file
14
BurnOutSharp.Models/AACS/MediaKeyBlock.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace BurnOutSharp.Models.AACS
|
||||
{
|
||||
/// <summary>
|
||||
/// A Media Key Block is formatted as a sequence of contiguous Records.
|
||||
/// </summary>
|
||||
/// <see href="https://aacsla.com/wp-content/uploads/2019/02/AACS_Spec_Common_Final_0953.pdf"/>
|
||||
public sealed class MediaKeyBlock
|
||||
{
|
||||
/// <summary>
|
||||
/// Records
|
||||
/// </summary>
|
||||
public Record[] Records { get; set; }
|
||||
}
|
||||
}
|
||||
18
BurnOutSharp.Models/AACS/MediaKeyDataRecord.cs
Normal file
18
BurnOutSharp.Models/AACS/MediaKeyDataRecord.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
namespace BurnOutSharp.Models.AACS
|
||||
{
|
||||
/// <summary>
|
||||
/// This record gives the associated encrypted media key data for the
|
||||
/// subset-differences identified in the Explicit Subset-Difference Record.
|
||||
/// </summary>
|
||||
/// <see href="https://aacsla.com/wp-content/uploads/2019/02/AACS_Spec_Common_Final_0953.pdf"/>
|
||||
public sealed class MediaKeyDataRecord : Record
|
||||
{
|
||||
/// <summary>
|
||||
/// Each subset difference has its associated 16 bytes in this
|
||||
/// record, in the same order it is encountered in the subset-difference
|
||||
/// record. This 16 bytes is the ciphertext value C in the media
|
||||
/// key calculation.
|
||||
/// </summary>
|
||||
public byte[][] MediaKeyData;
|
||||
}
|
||||
}
|
||||
28
BurnOutSharp.Models/AACS/Record.cs
Normal file
28
BurnOutSharp.Models/AACS/Record.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
namespace BurnOutSharp.Models.AACS
|
||||
{
|
||||
/// <summary>
|
||||
/// Each Record begins with a one-byte Record Type field, followed by a
|
||||
/// three-byte Record Length field.
|
||||
///
|
||||
/// The following subsections describe the currently defined Record types,
|
||||
/// and how a device processes each. All multi-byte integers, including
|
||||
/// the length field, are “Big Endian”; in other words, the most significant
|
||||
/// byte comes first in the record.
|
||||
/// </summary>
|
||||
/// <see href="https://aacsla.com/wp-content/uploads/2019/02/AACS_Spec_Common_Final_0953.pdf"/>
|
||||
public abstract class Record
|
||||
{
|
||||
/// <summary>
|
||||
/// The Record Type field value indicates the type of the Record.
|
||||
/// </summary>
|
||||
public RecordType RecordType;
|
||||
|
||||
/// <summary>
|
||||
/// The Record Length field value indicates the number of bytes in
|
||||
/// the Record, including the Record Type and the Record Length
|
||||
/// fields themselves. Record lengths are always multiples of 4 bytes.
|
||||
/// </summary>
|
||||
// <remarks>UInt24 not UInt32</remarks>
|
||||
public uint RecordLength;
|
||||
}
|
||||
}
|
||||
20
BurnOutSharp.Models/AACS/SubsetDifference.cs
Normal file
20
BurnOutSharp.Models/AACS/SubsetDifference.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
namespace BurnOutSharp.Models.AACS
|
||||
{
|
||||
/// <see href="https://aacsla.com/wp-content/uploads/2019/02/AACS_Spec_Common_Final_0953.pdf"/>
|
||||
public sealed class SubsetDifference
|
||||
{
|
||||
/// <summary>
|
||||
/// The mask for u is given by the first byte. That byte is
|
||||
/// treated as a number, the number of low-order 0-bits in
|
||||
/// the mask. For example, the value 0x01 denotes a mask of
|
||||
/// 0xFFFFFFFE; value 0x0A denotes a mask of 0xFFFFFC00.
|
||||
/// </summary>
|
||||
public byte Mask;
|
||||
|
||||
/// <summary>
|
||||
/// The last 4 bytes are the uv number, most significant
|
||||
/// byte first.
|
||||
/// </summary>
|
||||
public uint Number;
|
||||
}
|
||||
}
|
||||
26
BurnOutSharp.Models/AACS/SubsetDifferenceIndexRecord.cs
Normal file
26
BurnOutSharp.Models/AACS/SubsetDifferenceIndexRecord.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
namespace BurnOutSharp.Models.AACS
|
||||
{
|
||||
/// <summary>
|
||||
/// This is a speed-up record which can be ignored by devices not wishing to
|
||||
/// take advantage of it. It is a lookup table which allows devices to quickly
|
||||
/// find their subset-difference in the Explicit Subset-Difference record,
|
||||
/// without processing the entire record. This Subset-Difference Index record
|
||||
/// is always present, and always precedes the Explicit Subset-Difference record
|
||||
/// in the MKB, although it does not necessarily immediately precede it.
|
||||
/// </summary>
|
||||
/// <see href="https://aacsla.com/wp-content/uploads/2019/02/AACS_Spec_Common_Final_0953.pdf"/>
|
||||
public sealed class SubsetDifferenceIndexRecord : Record
|
||||
{
|
||||
/// <summary>
|
||||
/// The number of devices per index offset.
|
||||
/// </summary>
|
||||
public uint Span;
|
||||
|
||||
/// <summary>
|
||||
/// These offsets refer to the offset within the following Explicit
|
||||
/// Subset-Difference record, with 0 being the start of the record.
|
||||
/// </summary>
|
||||
// <remarks>UInt24 not UInt32</remarks>
|
||||
public uint[] Offsets;
|
||||
}
|
||||
}
|
||||
32
BurnOutSharp.Models/AACS/TypeAndVersionRecord.cs
Normal file
32
BurnOutSharp.Models/AACS/TypeAndVersionRecord.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
namespace BurnOutSharp.Models.AACS
|
||||
{
|
||||
/// <summary>
|
||||
/// Devices, except for recording devices which are writing Media Key Block
|
||||
/// Extensions, may ignore this record. Recording devices shall verify the
|
||||
/// signature (see End of Media Key Block record) and use the Version Number
|
||||
/// in this record to determine if a new Media Key BLock Extension is, in
|
||||
/// fact, more recent than the Media Key Block Extension that is currently
|
||||
/// on the media.
|
||||
/// </summary>
|
||||
/// <see href="https://aacsla.com/wp-content/uploads/2019/02/AACS_Spec_Common_Final_0953.pdf"/>
|
||||
public sealed class TypeAndVersionRecord : Record
|
||||
{
|
||||
/// <summary>
|
||||
/// For AACS applications, the MKBType field is one of three values.
|
||||
/// It is not an error for a Type 3 Media Key Block to be used for
|
||||
/// controlling access to AACS Content on pre- recorded media. In
|
||||
/// this case, the device shall not use the KCD.
|
||||
/// </summary>
|
||||
public MediaKeyBlockType MediaKeyBlockType;
|
||||
|
||||
/// <summary>
|
||||
/// The Version Number is a 32-bit unsigned integer. Each time the
|
||||
/// licensing agency changes the revocation, it increments the version
|
||||
/// number and inserts the new value in subsequent Media Key Blocks.
|
||||
/// Thus, larger values indicate more recent Media Key Blocks. The
|
||||
/// Version Numbers begin at 1; 0 is a special value used for test
|
||||
/// Media Key Blocks.
|
||||
/// </summary>
|
||||
public uint VersionNumber;
|
||||
}
|
||||
}
|
||||
24
BurnOutSharp.Models/AACS/VerifyMediaKeyRecord.cs
Normal file
24
BurnOutSharp.Models/AACS/VerifyMediaKeyRecord.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
namespace BurnOutSharp.Models.AACS
|
||||
{
|
||||
/// <summary>
|
||||
/// A properly formatted MKB shall have exactly one Verify Media Key Record
|
||||
/// as its first record. The presence of the Verify Media Key Record in an MKB
|
||||
/// is mandatory, but the use of the Record by a device is not mandatory. The
|
||||
/// device may use the Verify Media Key Record to verify the correctness of a
|
||||
/// given MKB, or of its processing of it. If everything is correct, the device
|
||||
/// should observe the condition:
|
||||
/// [AES_128D(vKm, C]msb_64 == 0x0123456789ABCDEF)]
|
||||
/// where Km is the Media Key value.
|
||||
/// </summary>
|
||||
/// <see href="https://aacsla.com/wp-content/uploads/2019/02/AACS_Spec_Common_Final_0953.pdf"/>
|
||||
public sealed class VerifyMediaKeyRecord : Record
|
||||
{
|
||||
/// <summary>
|
||||
/// Bytes 4 through 19 of the Record contain the ciphertext value
|
||||
/// Cv = AES-128E (Km, 0x0123456789ABCDEF || 0xXXXXXXXXXXXXXXXX)
|
||||
/// where 0xXXXXXXXXXXXXXXXX is an arbitrary 8-byte value, and Km is
|
||||
/// the correct final Media Key value.
|
||||
/// </summary>
|
||||
public byte[] CiphertextValue;
|
||||
}
|
||||
}
|
||||
7
BurnOutSharp.Models/BDPlus/Constants.cs
Normal file
7
BurnOutSharp.Models/BDPlus/Constants.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace BurnOutSharp.Models.BDPlus
|
||||
{
|
||||
public static class Constants
|
||||
{
|
||||
public const string SignatureString = "BDSVM_CC";
|
||||
}
|
||||
}
|
||||
46
BurnOutSharp.Models/BDPlus/SVM.cs
Normal file
46
BurnOutSharp.Models/BDPlus/SVM.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
namespace BurnOutSharp.Models.BDPlus
|
||||
{
|
||||
/// <see href="https://github.com/mwgoldsmith/bdplus/blob/master/src/libbdplus/bdsvm/loader.c"/>
|
||||
public sealed class SVM
|
||||
{
|
||||
/// <summary>
|
||||
/// "BDSVM_CC"
|
||||
/// </summary>
|
||||
public string Signature;
|
||||
|
||||
/// <summary>
|
||||
/// 5 bytes of unknown data
|
||||
/// </summary>
|
||||
public byte[] Unknown1;
|
||||
|
||||
/// <summary>
|
||||
/// Version year
|
||||
/// </summary>
|
||||
public ushort Year;
|
||||
|
||||
/// <summary>
|
||||
/// Version month
|
||||
/// </summary>
|
||||
public byte Month;
|
||||
|
||||
/// <summary>
|
||||
/// Version day
|
||||
/// </summary>
|
||||
public byte Day;
|
||||
|
||||
/// <summary>
|
||||
/// 4 bytes of unknown data
|
||||
/// </summary>
|
||||
public byte[] Unknown2;
|
||||
|
||||
/// <summary>
|
||||
/// Length
|
||||
/// </summary>
|
||||
public uint Length;
|
||||
|
||||
/// <summary>
|
||||
/// Length bytes of data
|
||||
/// </summary>
|
||||
public byte[] Data;
|
||||
}
|
||||
}
|
||||
@@ -9,9 +9,9 @@
|
||||
<Product>BurnOutSharp</Product>
|
||||
<Copyright>Copyright (c)2022 Matt Nadareski</Copyright>
|
||||
<RepositoryUrl>https://github.com/mnadareski/BurnOutSharp</RepositoryUrl>
|
||||
<Version>2.6</Version>
|
||||
<AssemblyVersion>2.6</AssemblyVersion>
|
||||
<FileVersion>2.6</FileVersion>
|
||||
<Version>2.7</Version>
|
||||
<AssemblyVersion>2.7</AssemblyVersion>
|
||||
<FileVersion>2.7</FileVersion>
|
||||
<IncludeSource>true</IncludeSource>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
</PropertyGroup>
|
||||
|
||||
92
BurnOutSharp.Models/CFB/Binary.cs
Normal file
92
BurnOutSharp.Models/CFB/Binary.cs
Normal file
@@ -0,0 +1,92 @@
|
||||
namespace BurnOutSharp.Models.CFB
|
||||
{
|
||||
/// <summary>
|
||||
/// Microsoft Compound File Binary (CFB) file format, also known as the
|
||||
/// Object Linking and Embedding (OLE) or Component Object Model (COM)
|
||||
/// structured storage compound file implementation binary file format.
|
||||
/// This structure name can be shortened to compound file.
|
||||
/// </summary>
|
||||
/// <see href="https://winprotocoldoc.blob.core.windows.net/productionwindowsarchives/MS-CFB/%5bMS-CFB%5d.pdf"/>
|
||||
public sealed class Binary
|
||||
{
|
||||
/// <summary>
|
||||
/// Compound file header
|
||||
/// </summary>
|
||||
public FileHeader Header { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The FAT is the main allocator for space within a compound file.
|
||||
/// Every sector in the file is represented within the FAT in some
|
||||
/// fashion, including those sectors that are unallocated (free).
|
||||
/// The FAT is a sector chain that is made up of one or more FAT sectors.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If Header Major Version is 3, there MUST be 128 fields specified to fill a 512-byte sector.
|
||||
///
|
||||
/// If Header Major Version is 4, there MUST be 1,024 fields specified to fill a 4,096-byte sector
|
||||
/// </remarks>
|
||||
public SectorNumber[] FATSectorNumbers { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The mini FAT is used to allocate space in the mini stream.
|
||||
/// The mini stream is divided intosmaller, equal-length sectors,
|
||||
/// and the sector size that is used for the mini stream is specified
|
||||
/// from the Compound File Header (64 bytes).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If Header Major Version is 3, there MUST be 128 fields specified to fill a 512-byte sector.
|
||||
///
|
||||
/// If Header Major Version is 4, there MUST be 1,024 fields specified to fill a 4,096-byte sector
|
||||
/// </remarks>
|
||||
public SectorNumber[] MiniFATSectorNumbers { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The DIFAT array is used to represent storage of the FAT sectors.
|
||||
/// The DIFAT is represented by an array of 32-bit sector numbers.
|
||||
/// The DIFAT array is stored both in the header and in DIFAT sectors.
|
||||
/// In the header, the DIFAT array occupies 109 entries, and in each
|
||||
/// DIFAT sector, the DIFAT array occupies the entire sector minus
|
||||
/// 4 bytes. (The last field is for chaining the DIFAT sector chain.)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If Header Major Version is 3, there MUST be 127 fields specified to
|
||||
/// fill a 512-byte sector minus the "Next DIFAT Sector Location" field.
|
||||
///
|
||||
/// If Header Major Version is 4, there MUST be 1,023 fields specified
|
||||
/// to fill a 4,096-byte sector minus the "Next DIFAT Sector Location" field.
|
||||
/// </remarks>
|
||||
public SectorNumber[] DIFATSectorNumbers { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The directory entry array is an array of directory entries that
|
||||
/// are grouped into a directory sector. Each storage object or stream
|
||||
/// object within a compound file is represented by a single directory
|
||||
/// entry. The space for the directory sectors that are holding the
|
||||
/// array is allocated from the FAT.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The first entry in the first sector of the directory chain (also
|
||||
/// referred to as the first element of the directory array, or stream
|
||||
/// ID #0) is known as the root directory entry, and it is reserved for
|
||||
/// two purposes. First, it provides a root parent for all objects that
|
||||
/// are stationed at the root of the compound file. Second, its function
|
||||
/// is overloaded to store the size and starting sector for the mini stream.
|
||||
///
|
||||
/// The root directory entry behaves as both a stream and a storage object.
|
||||
/// The root directory entry's Name field MUST contain the null-terminated
|
||||
/// string "Root Entry" in Unicode UTF-16.
|
||||
///
|
||||
/// The object class GUID (CLSID) that is stored in the root directory
|
||||
/// entry can be used for COM activation of the document's application.
|
||||
///
|
||||
/// The time stamps for the root storage are not maintained in the root
|
||||
/// directory entry. Rather, the root storage's creation and modification
|
||||
/// time stamps are normally stored on the file itself in the file system.
|
||||
///
|
||||
/// The Creation Time field in the root storage directory entry MUST be
|
||||
/// all zeroes. The Modified Time field in the root storage directory
|
||||
/// entry MAY be all zeroes.
|
||||
/// <remarks>
|
||||
public DirectoryEntry[] DirectoryEntries { get; set; }
|
||||
}
|
||||
}
|
||||
52
BurnOutSharp.Models/CFB/Constants.cs
Normal file
52
BurnOutSharp.Models/CFB/Constants.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using System;
|
||||
|
||||
namespace BurnOutSharp.Models.CFB
|
||||
{
|
||||
public static class Constants
|
||||
{
|
||||
public static readonly byte[] SignatureBytes = new byte[] { 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1 };
|
||||
|
||||
public const ulong SignatureUInt64 = 0xE11AB1A1E011CFD0;
|
||||
|
||||
|
||||
/// <see href="https://devblogs.microsoft.com/setup/identifying-windows-installer-file-types/"/>
|
||||
#region Class IDs
|
||||
|
||||
/// <summary>
|
||||
/// Installer Package (msi), Merge Module (msm), Patch Creation Properties (pcp)
|
||||
/// </summary>
|
||||
public static readonly Guid InstallerPackage = new Guid("000c1084-0000-0000-c000-000000000046");
|
||||
|
||||
/// <summary>
|
||||
/// Patch Package (msp)
|
||||
/// </summary>
|
||||
public static readonly Guid PatchPackage = new Guid("000C1086-0000-0000-C000-000000000046");
|
||||
|
||||
/// <summary>
|
||||
/// Transform (mst)
|
||||
/// </summary>
|
||||
public static readonly Guid Transform = new Guid("000C1082-0000-0000-C000-000000000046");
|
||||
|
||||
#endregion
|
||||
|
||||
/// <see href="https://learn.microsoft.com/en-us/windows/win32/stg/predefined-property-set-format-identifiers"/>
|
||||
#region Property Set Format IDs
|
||||
|
||||
/// <summary>
|
||||
/// The Summary Information Property Set
|
||||
/// </summary>
|
||||
public static readonly Guid FMTID_SummaryInformation = new Guid("F29F85E0-4FF9-1068-AB91-08002B27B3D9");
|
||||
|
||||
/// <summary>
|
||||
/// The DocumentSummaryInformation and UserDefined Property Sets
|
||||
/// </summary>
|
||||
public static readonly Guid FMTID_DocSummaryInformation = new Guid("D5CDD502-2E9C-101B-9397-08002B2CF9AE");
|
||||
|
||||
/// <summary>
|
||||
/// The DocumentSummaryInformation and UserDefined Property Sets
|
||||
/// </summary>
|
||||
public static readonly Guid FMTID_UserDefinedProperties = new Guid("D5CDD505-2E9C-101B-9397-08002B2CF9AE");
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
133
BurnOutSharp.Models/CFB/DirectoryEntry.cs
Normal file
133
BurnOutSharp.Models/CFB/DirectoryEntry.cs
Normal file
@@ -0,0 +1,133 @@
|
||||
using System;
|
||||
|
||||
namespace BurnOutSharp.Models.CFB
|
||||
{
|
||||
/// <see href="https://winprotocoldoc.blob.core.windows.net/productionwindowsarchives/MS-CFB/%5bMS-CFB%5d.pdf"/>
|
||||
public sealed class DirectoryEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// This field MUST contain a Unicode string for the storage or stream
|
||||
/// name encoded in UTF-16. The name MUST be terminated with a UTF-16
|
||||
/// terminating null character. Thus, storage and stream names are limited
|
||||
/// to 32 UTF-16 code points, including the terminating null character.
|
||||
/// When locating an object in the compound file except for the root
|
||||
/// storage, the directory entry name is compared by using a special
|
||||
/// case-insensitive uppercase mapping, described in Red-Black Tree.
|
||||
/// The following characters are illegal and MUST NOT be part of the
|
||||
/// name: '/', '\', ':', '!'.
|
||||
/// </summary>
|
||||
public string Name;
|
||||
|
||||
/// <summary>
|
||||
/// This field MUST be 0x00, 0x01, 0x02, or 0x05, depending on the
|
||||
/// actual type of object. All other values are not valid.
|
||||
/// </summary>
|
||||
public ushort NameLength;
|
||||
|
||||
/// <summary>
|
||||
/// This field MUST match the length of the Directory Entry Name Unicode
|
||||
/// string in bytes. The length MUST be a multiple of 2 and include the
|
||||
/// terminating null character in the count. This length MUST NOT exceed 64,
|
||||
/// the maximum size of the Directory Entry Name field.
|
||||
/// </summary>
|
||||
public ObjectType ObjectType;
|
||||
|
||||
/// <summary>
|
||||
/// This field MUST be 0x00 (red) or 0x01 (black). All other values are not valid.
|
||||
/// </summary>
|
||||
public ColorFlag ColorFlag;
|
||||
|
||||
/// <summary>
|
||||
/// This field contains the stream ID of the left sibling. If there
|
||||
/// is no left sibling, the field MUST be set to NOSTREAM (0xFFFFFFFF).
|
||||
/// </summary>
|
||||
public StreamID LeftSiblingID;
|
||||
|
||||
/// <summary>
|
||||
/// This field contains the stream ID of the right sibling. If there
|
||||
/// is no right sibling, the field MUST be set to NOSTREAM (0xFFFFFFFF).
|
||||
/// </summary>
|
||||
public StreamID RightSiblingID;
|
||||
|
||||
/// <summary>
|
||||
/// This field contains the stream ID of a child object. If there is no
|
||||
/// child object, including all entries for stream objects, the field
|
||||
/// MUST be set to NOSTREAM (0xFFFFFFFF).
|
||||
/// </summary>
|
||||
public StreamID ChildID;
|
||||
|
||||
/// <summary>
|
||||
/// This field contains an object class GUID, if this entry is for a
|
||||
/// storage object or root storage object. For a stream object, this field
|
||||
/// MUST be set to all zeroes. A value containing all zeroes in a storage
|
||||
/// or root storage directory entry is valid, and indicates that no object
|
||||
/// class is associated with the storage. If an implementation of the file
|
||||
/// format enables applications to create storage objects without explicitly
|
||||
/// setting an object class GUID, it MUST write all zeroes by default. If
|
||||
/// this value is not all zeroes, the object class GUID can be used as a
|
||||
/// parameter to start applications.
|
||||
/// </summary>
|
||||
public Guid CLSID;
|
||||
|
||||
/// <summary>
|
||||
/// This field contains the user-defined flags if this entry is for a storage
|
||||
/// object or root storage object. For a stream object, this field SHOULD be
|
||||
/// set to all zeroes because many implementations provide no way for
|
||||
/// applications to retrieve state bits from a stream object. If an
|
||||
/// implementation of the file format enables applications to create storage
|
||||
/// objects without explicitly setting state bits, it MUST write all zeroes
|
||||
/// by default.
|
||||
/// </summary>
|
||||
public uint StateBits;
|
||||
|
||||
/// <summary>
|
||||
/// This field contains the creation time for a storage object, or all zeroes
|
||||
/// to indicate that the creation time of the storage object was not recorded.
|
||||
/// The Windows FILETIME structure is used to represent this field in UTC.
|
||||
/// For a stream object, this field MUST be all zeroes. For a root storage
|
||||
/// object, this field MUST be all zeroes, and the creation time is retrieved
|
||||
/// or set on the compound file itself.
|
||||
/// </summary>
|
||||
public ulong CreationTime;
|
||||
|
||||
/// <summary>
|
||||
/// This field contains the modification time for a storage object, or all
|
||||
/// zeroes to indicate that the modified time of the storage object was not
|
||||
/// recorded. The Windows FILETIME structure is used to represent this field
|
||||
/// in UTC. For a stream object, this field MUST be all zeroes. For a root
|
||||
/// storage object, this field MAY<2> be set to all zeroes, and the modified
|
||||
/// time is retrieved or set on the compound file itself.
|
||||
/// </summary>
|
||||
public ulong ModifiedTime;
|
||||
|
||||
/// <summary>
|
||||
/// This field contains the first sector location if this is a stream object.
|
||||
/// For a root storage object, this field MUST contain the first sector of the
|
||||
/// mini stream, if the mini stream exists. For a storage object, this field MUST
|
||||
/// be set to all zeroes.
|
||||
/// </summary>
|
||||
public uint StartingSectorLocation;
|
||||
|
||||
/// <summary>
|
||||
/// This 64-bit integer field contains the size of the user-defined data if this
|
||||
/// is a stream object. For a root storage object, this field contains the size
|
||||
/// of the mini stream. For a storage object, this field MUST be set to all zeroes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// For a version 3 compound file 512-byte sector size, the value of this field MUST
|
||||
/// be less than or equal to 0x80000000. (Equivalently, this requirement can be stated:
|
||||
/// the size of a stream or of the mini stream in a version 3 compound file MUST be
|
||||
/// less than or equal to 2 gigabytes (GB).) Note that as a consequence of this
|
||||
/// requirement, the most significant 32 bits of this field MUST be zero in a version
|
||||
/// 3 compound file. However, implementers should be aware that some older
|
||||
/// implementations did not initialize the most significant 32 bits of this field,
|
||||
/// and these bits might therefore be nonzero in files that are otherwise valid
|
||||
/// version 3 compound files. Although this document does not normatively specify
|
||||
/// parser behavior, it is recommended that parsers ignore the most significant 32 bits
|
||||
/// of this field in version 3 compound files, treating it as if its value were zero,
|
||||
/// unless there is a specific reason to do otherwise (for example, a parser whose
|
||||
/// purpose is to verify the correctness of a compound file).
|
||||
/// </remarks>
|
||||
public ulong StreamSize;
|
||||
}
|
||||
}
|
||||
474
BurnOutSharp.Models/CFB/Enums.cs
Normal file
474
BurnOutSharp.Models/CFB/Enums.cs
Normal file
@@ -0,0 +1,474 @@
|
||||
namespace BurnOutSharp.Models.CFB
|
||||
{
|
||||
public enum ColorFlag : byte
|
||||
{
|
||||
Red = 0x00,
|
||||
Black = 0x01,
|
||||
}
|
||||
|
||||
public enum ObjectType : byte
|
||||
{
|
||||
Unknown = 0x00,
|
||||
StorageObject = 0x01,
|
||||
StreamObject = 0x02,
|
||||
RootStorageObject = 0x05,
|
||||
}
|
||||
|
||||
public enum SectorNumber : uint
|
||||
{
|
||||
/// <summary>
|
||||
/// Regular sector number.
|
||||
/// </summary>
|
||||
REGSECT = 0x00000000, // 0x00000000 - 0xFFFFFFF9
|
||||
|
||||
/// <summary>
|
||||
/// Maximum regular sector number.
|
||||
/// </summary>
|
||||
MAXREGSECT = 0xFFFFFFFA,
|
||||
|
||||
/// <summary>
|
||||
/// Reserved for future use.
|
||||
/// </summary>
|
||||
NotApplicable = 0xFFFFFFFB,
|
||||
|
||||
/// <summary>
|
||||
/// Specifies a DIFAT sector in the FAT.
|
||||
/// </summary>
|
||||
DIFSECT = 0xFFFFFFFC,
|
||||
|
||||
/// <summary>
|
||||
/// Specifies a FAT sector in the FAT.
|
||||
/// </summary>
|
||||
FATSECT = 0xFFFFFFFD,
|
||||
|
||||
/// <summary>
|
||||
/// End of a linked chain of sectors.
|
||||
/// </summary>
|
||||
ENDOFCHAIN = 0xFFFFFFFE,
|
||||
|
||||
/// <summary>
|
||||
/// Specifies an unallocated sector in the FAT, Mini FAT, or DIFAT.
|
||||
/// </summary>
|
||||
FREESECT = 0xFFFFFFFF,
|
||||
}
|
||||
|
||||
public enum StreamID : uint
|
||||
{
|
||||
/// <summary>
|
||||
/// Regular stream ID to identify the directory entry.
|
||||
/// </summary>
|
||||
REGSID = 0x00000000, // 0x00000000 - 0xFFFFFFF9
|
||||
|
||||
/// <summary>
|
||||
/// Maximum regular stream ID.
|
||||
/// </summary>
|
||||
MAXREGSID = 0xFFFFFFFA,
|
||||
|
||||
/// <summary>
|
||||
/// Terminator or empty pointer.
|
||||
/// </summary>
|
||||
NOSTREAM = 0xFFFFFFFF,
|
||||
}
|
||||
|
||||
/// <see href="https://learn.microsoft.com/en-us/windows/win32/stg/the-summary-information-property-set"/>
|
||||
public enum SummaryInformationProperty : uint
|
||||
{
|
||||
/// <summary>
|
||||
/// Title
|
||||
/// </summary>
|
||||
PIDSI_TITLE = 0x00000002,
|
||||
|
||||
/// <summary>
|
||||
/// Subject
|
||||
/// </summary>
|
||||
PIDSI_SUBJECT = 0x00000003,
|
||||
|
||||
/// <summary>
|
||||
/// Author
|
||||
/// </summary>
|
||||
PIDSI_AUTHOR = 0x00000004,
|
||||
|
||||
/// <summary>
|
||||
/// Keywords
|
||||
/// </summary>
|
||||
PIDSI_KEYWORDS = 0x00000005,
|
||||
|
||||
/// <summary>
|
||||
/// Comments
|
||||
/// </summary>
|
||||
PIDSI_COMMENTS = 0x00000006,
|
||||
|
||||
/// <summary>
|
||||
/// Template
|
||||
/// </summary>
|
||||
PIDSI_TEMPLATE = 0x00000007,
|
||||
|
||||
/// <summary>
|
||||
/// Last Saved By
|
||||
/// </summary>
|
||||
PIDSI_LASTAUTHOR = 0x00000008,
|
||||
|
||||
/// <summary>
|
||||
/// Revision Number
|
||||
/// </summary>
|
||||
PIDSI_REVNUMBER = 0x00000009,
|
||||
|
||||
/// <summary>
|
||||
/// Total Editing Time
|
||||
/// </summary>
|
||||
PIDSI_EDITTIME = 0x0000000A,
|
||||
|
||||
/// <summary>
|
||||
/// Last Printed
|
||||
/// </summary>
|
||||
PIDSI_LASTPRINTED = 0x0000000B,
|
||||
|
||||
/// <summary>
|
||||
/// Create Time/Date
|
||||
/// </summary>
|
||||
PIDSI_CREATE_DTM = 0x0000000C,
|
||||
|
||||
/// <summary>
|
||||
/// Last saved Time/Date
|
||||
/// </summary>
|
||||
PIDSI_LASTSAVE_DTM = 0x0000000D,
|
||||
|
||||
/// <summary>
|
||||
/// Number of Pages
|
||||
/// </summary>
|
||||
PIDSI_PAGECOUNT = 0x0000000E,
|
||||
|
||||
/// <summary>
|
||||
/// Number of Words
|
||||
/// </summary>
|
||||
PIDSI_WORDCOUNT = 0x0000000F,
|
||||
|
||||
/// <summary>
|
||||
/// Number of Characters
|
||||
/// </summary>
|
||||
PIDSI_CHARCOUNT = 0x00000010,
|
||||
|
||||
/// <summary>
|
||||
/// Thumbnail
|
||||
/// </summary>
|
||||
PIDSI_THUMBNAIL = 0x00000011,
|
||||
|
||||
/// <summary>
|
||||
/// Name of Creating Application
|
||||
/// </summary>
|
||||
PIDSI_APPNAME = 0x00000012,
|
||||
|
||||
/// <summary>
|
||||
/// Security
|
||||
/// </summary>
|
||||
PIDSI_SECURITY = 0x00000013,
|
||||
}
|
||||
|
||||
/// <remarks>Also includes the DocumentSummaryInformation set</remarks>
|
||||
/// <see href="https://learn.microsoft.com/en-us/windows/win32/stg/the-documentsummaryinformation-and-userdefined-property-sets"/>
|
||||
public enum UserDefinedProperty : uint
|
||||
{
|
||||
/// <summary>
|
||||
/// Category - A text string typed by the user that indicates what
|
||||
/// category the file belongs to (memo, proposal, and so on). It
|
||||
/// is useful for finding files of same type.
|
||||
/// </summary>
|
||||
PIDDSI_CATEGORY = 0x00000002,
|
||||
|
||||
/// <summary>
|
||||
/// PresentationTarget - Target format for presentation (35mm,
|
||||
/// printer, video, and so on).
|
||||
/// </summary>
|
||||
PIDDSI_PRESFORMAT = 0x00000003,
|
||||
|
||||
/// <summary>
|
||||
/// Bytes - Number of bytes.
|
||||
/// </summary>
|
||||
PIDDSI_BYTECOUNT = 0x00000004,
|
||||
|
||||
/// <summary>
|
||||
/// Lines - Number of lines.
|
||||
/// </summary>
|
||||
PIDDSI_LINECOUNT = 0x00000005,
|
||||
|
||||
/// <summary>
|
||||
/// Paragraphs - Number of paragraphs.
|
||||
/// </summary>
|
||||
PIDDSI_PARCOUNT = 0x00000006,
|
||||
|
||||
/// <summary>
|
||||
/// Slides - Number of slides.
|
||||
/// </summary>
|
||||
PIDDSI_SLIDECOUNT = 0x00000007,
|
||||
|
||||
/// <summary>
|
||||
/// Notes - Number of pages that contain notes.
|
||||
/// </summary>
|
||||
PIDDSI_NOTECOUNT = 0x00000008,
|
||||
|
||||
/// <summary>
|
||||
/// HiddenSlides - Number of slides that are hidden.
|
||||
/// </summary>
|
||||
PIDDSI_HIDDENCOUNT = 0x00000009,
|
||||
|
||||
/// <summary>
|
||||
/// MMClips - Number of sound or video clips.
|
||||
/// </summary>
|
||||
PIDDSI_MMCLIPCOUNT = 0x0000000A,
|
||||
|
||||
/// <summary>
|
||||
/// ScaleCrop - Set to True (-1) when scaling of the thumbnail
|
||||
|
||||
/// is desired. If not set, cropping is desired.
|
||||
/// </summary>
|
||||
PIDDSI_SCALE = 0x0000000B,
|
||||
|
||||
/// <summary>
|
||||
/// HeadingPairs - Internally used property indicating the
|
||||
/// grouping of different document parts and the number of
|
||||
/// items in each group. The titles of the document parts are
|
||||
/// stored in the TitlesofParts property. The HeadingPairs
|
||||
/// property is stored as a vector of variants, in repeating
|
||||
/// pairs of VT_LPSTR (or VT_LPWSTR) and VT_I4 values. The
|
||||
/// VT_LPSTR value represents a heading name, and the VT_I4
|
||||
/// value indicates the count of document parts under that heading.
|
||||
/// </summary>
|
||||
PIDDSI_HEADINGPAIR = 0x0000000C,
|
||||
|
||||
/// <summary>
|
||||
/// TitlesofParts - Names of document parts.
|
||||
/// </summary>
|
||||
PIDDSI_DOCPARTS = 0x0000000D,
|
||||
|
||||
/// <summary>
|
||||
/// Manager - Manager of the project.
|
||||
/// </summary>
|
||||
PIDDSI_MANAGER = 0x0000000E,
|
||||
|
||||
/// <summary>
|
||||
/// Company - Company name.
|
||||
/// </summary>
|
||||
PIDDSI_COMPANY = 0x0000000F,
|
||||
|
||||
/// <summary>
|
||||
/// LinksUpToDate - Boolean value to indicate whether the custom
|
||||
/// links are hampered by excessive noise, for all applications.
|
||||
/// </summary>
|
||||
PIDDSI_LINKSDIRTY = 0x00000010,
|
||||
}
|
||||
|
||||
/// <see href="https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-oaut/3fe7db9f-5803-4dc4-9d14-5425d3f5461f"/>
|
||||
public enum VariantType : ushort
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of the contained field is undefined. When this flag is
|
||||
/// specified, the VARIANT MUST NOT contain a data field.
|
||||
/// </summary>
|
||||
VT_EMPTY = 0x0000,
|
||||
|
||||
/// <summary>
|
||||
/// The type of the contained field is NULL. When this flag is
|
||||
/// specified, the VARIANT MUST NOT contain a data field.
|
||||
/// </summary>
|
||||
VT_NULL = 0x0001,
|
||||
|
||||
/// <summary>
|
||||
/// Either the specified type, or the type of the element or contained
|
||||
/// field MUST be a 2-byte signed integer.
|
||||
/// </summary>
|
||||
VT_I2 = 0x0002,
|
||||
|
||||
/// <summary>
|
||||
/// Either the specified type, or the type of the element or contained
|
||||
/// field MUST be a 4-byte signed integer.
|
||||
/// </summary>
|
||||
VT_I4 = 0x0003,
|
||||
|
||||
/// <summary>
|
||||
/// Either the specified type, or the type of the element or contained
|
||||
/// field MUST be a 4-byte IEEE floating-point number.
|
||||
/// </summary>
|
||||
VT_R4 = 0x0004,
|
||||
|
||||
/// <summary>
|
||||
/// Either the specified type, or the type of the element or contained
|
||||
/// field MUST be an 8-byte IEEE floating-point number.
|
||||
/// </summary>
|
||||
VT_R8 = 0x0005,
|
||||
|
||||
/// <summary>
|
||||
/// Either the specified type, or the type of the element or contained
|
||||
/// field MUST be CURRENCY.
|
||||
/// </summary>
|
||||
VT_CY = 0x0006,
|
||||
|
||||
/// <summary>
|
||||
/// Either the specified type, or the type of the element or contained
|
||||
/// field MUST be DATE.
|
||||
/// </summary>
|
||||
VT_DATE = 0x0007,
|
||||
|
||||
/// <summary>
|
||||
/// Either the specified type, or the type of the element or contained
|
||||
/// field MUST be BSTR.
|
||||
/// </summary>
|
||||
VT_BSTR = 0x0008,
|
||||
|
||||
/// <summary>
|
||||
/// Either the specified type, or the type of the element or contained
|
||||
/// field MUST be a pointer to IDispatch.
|
||||
/// </summary>
|
||||
VT_DISPATCH = 0x0009,
|
||||
|
||||
/// <summary>
|
||||
/// Either the specified type, or the type of the element or contained
|
||||
/// field MUST be HRESULT.
|
||||
/// </summary>
|
||||
VT_ERROR = 0x000A,
|
||||
|
||||
/// <summary>
|
||||
/// Either the specified type, or the type of the element or contained
|
||||
/// field MUST be VARIANT_BOOL.
|
||||
/// </summary>
|
||||
VT_BOOL = 0x000B,
|
||||
|
||||
/// <summary>
|
||||
/// Either the specified type, or the type of the element or contained
|
||||
/// field MUST be VARIANT. It MUST appear with the bit flag VT_BYREF.
|
||||
/// </summary>
|
||||
VT_VARIANT = 0x000C,
|
||||
|
||||
/// <summary>
|
||||
/// Either the specified type, or the type of the element or contained
|
||||
/// field MUST be a pointer to IUnknown.
|
||||
/// </summary>
|
||||
VT_UNKNOWN = 0x000D,
|
||||
|
||||
/// <summary>
|
||||
/// Either the specified type, or the type of the element or contained
|
||||
/// field MUST be DECIMAL.
|
||||
/// </summary>
|
||||
VT_DECIMAL = 0x000E,
|
||||
|
||||
/// <summary>
|
||||
/// Either the specified type, or the type of the element or contained
|
||||
/// field MUST be a 1-byte integer.
|
||||
/// </summary>
|
||||
VT_I1 = 0x0010,
|
||||
|
||||
/// <summary>
|
||||
/// Either the specified type, or the type of the element or contained
|
||||
/// field MUST be a 1-byte unsigned integer.
|
||||
/// </summary>
|
||||
VT_UI1 = 0x0011,
|
||||
|
||||
/// <summary>
|
||||
/// Either the specified type, or the type of the element or contained
|
||||
/// field MUST be a 2-byte unsigned integer.
|
||||
/// </summary>
|
||||
VT_UI2 = 0x0012,
|
||||
|
||||
/// <summary>
|
||||
/// Either the specified type, or the type of the element or contained
|
||||
/// field MUST be a 4-byte unsigned integer.
|
||||
/// </summary>
|
||||
VT_UI4 = 0x0013,
|
||||
|
||||
/// <summary>
|
||||
/// Either the specified type, or the type of the element or contained
|
||||
/// field MUST be an 8-byte signed integer.
|
||||
/// </summary>
|
||||
VT_I8 = 0x0014,
|
||||
|
||||
/// <summary>
|
||||
/// Either the specified type, or the type of the element or contained
|
||||
/// field MUST be an 8-byte unsigned integer.
|
||||
/// </summary>
|
||||
VT_UI8 = 0x0015,
|
||||
|
||||
/// <summary>
|
||||
/// Either the specified type, or the type of the element or contained
|
||||
/// field MUST be a 4-byte signed integer.
|
||||
/// </summary>
|
||||
VT_INT = 0x0016,
|
||||
|
||||
/// <summary>
|
||||
/// Either the specified type, or the type of the element or contained
|
||||
/// field MUST be a 4-byte unsigned integer.
|
||||
/// </summary>
|
||||
VT_UINT = 0x0017,
|
||||
|
||||
/// <summary>
|
||||
/// The specified type MUST be void.
|
||||
/// </summary>
|
||||
VT_VOID = 0x0018,
|
||||
|
||||
/// <summary>
|
||||
/// The specified type MUST be HRESULT.
|
||||
/// </summary>
|
||||
VT_HRESULT = 0x0019,
|
||||
|
||||
/// <summary>
|
||||
/// The specified type MUST be a unique pointer.
|
||||
/// </summary>
|
||||
VT_PTR = 0x001A,
|
||||
|
||||
/// <summary>
|
||||
/// The specified type MUST be SAFEARRAY.
|
||||
/// </summary>
|
||||
VT_SAFEARRAY = 0x001B,
|
||||
|
||||
/// <summary>
|
||||
/// The specified type MUST be a fixed-size array.
|
||||
/// </summary>
|
||||
VT_CARRAY = 0x001C,
|
||||
|
||||
/// <summary>
|
||||
/// The specified type MUST be user defined.
|
||||
/// </summary>
|
||||
VT_USERDEFINED = 0x001D,
|
||||
|
||||
/// <summary>
|
||||
/// The specified type MUST be a NULL-terminated string.
|
||||
/// </summary>
|
||||
VT_LPSTR = 0x001E,
|
||||
|
||||
/// <summary>
|
||||
/// The specified type MUST be a zero-terminated string of
|
||||
/// UNICODE characters.
|
||||
/// </summary>
|
||||
VT_LPWSTR = 0x001F,
|
||||
|
||||
/// <summary>
|
||||
/// The type of the element or contained field MUST be a BRECORD.
|
||||
/// </summary>
|
||||
VT_RECORD = 0x0024,
|
||||
|
||||
/// <summary>
|
||||
/// The specified type MUST be either a 4-byte or an 8-byte signed
|
||||
/// integer. The size of the integer is platform specific and
|
||||
/// determines the system pointer size value.
|
||||
/// </summary>
|
||||
VT_INT_PTR = 0x0025,
|
||||
|
||||
/// <summary>
|
||||
/// The specified type MUST be either a 4 byte or an 8 byte unsigned
|
||||
/// integer. The size of the integer is platform specific and
|
||||
/// determines the system pointer size value
|
||||
/// </summary>
|
||||
VT_UINT_PTR = 0x0026,
|
||||
|
||||
/// <summary>
|
||||
/// The type of the element or contained field MUST be a SAFEARRAY.
|
||||
/// </summary>
|
||||
VT_ARRAY = 0x2000,
|
||||
|
||||
/// <summary>
|
||||
/// The type of the element or contained field MUST be a pointer to
|
||||
/// one of the types listed in the previous rows of this table. If
|
||||
/// present, this bit flag MUST appear in a VARIANT discriminant
|
||||
/// with one of the previous flags.
|
||||
/// </summary>
|
||||
VT_BYREF = 0x4000
|
||||
}
|
||||
}
|
||||
127
BurnOutSharp.Models/CFB/FileHeader.cs
Normal file
127
BurnOutSharp.Models/CFB/FileHeader.cs
Normal file
@@ -0,0 +1,127 @@
|
||||
using System;
|
||||
|
||||
namespace BurnOutSharp.Models.CFB
|
||||
{
|
||||
/// <see href="https://winprotocoldoc.blob.core.windows.net/productionwindowsarchives/MS-CFB/%5bMS-CFB%5d.pdf"/>
|
||||
public sealed class FileHeader
|
||||
{
|
||||
/// <summary>
|
||||
/// Iddentification signature for the compound file structure, and MUST be
|
||||
/// set to the value 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1.
|
||||
/// </summary>
|
||||
public ulong Signature;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved and unused class ID that MUST be set to all zeroes (CLSID_NULL)
|
||||
/// </summary>
|
||||
public Guid CLSID;
|
||||
|
||||
/// <summary>
|
||||
/// Version number for nonbreaking changes. This field SHOULD be set to
|
||||
/// 0x003E if the major version field is either 0x0003 or 0x0004.
|
||||
/// </summary>
|
||||
public ushort MinorVersion;
|
||||
|
||||
/// <summary>
|
||||
/// Version number for breaking changes. This field MUST be set to either
|
||||
/// 0x0003 (version 3) or 0x0004 (version 4).
|
||||
/// </summary>
|
||||
public ushort MajorVersion;
|
||||
|
||||
/// <summary>
|
||||
/// This field MUST be set to 0xFFFE. This field is a byte order mark for
|
||||
/// all integer fields, specifying little-endian byte order.
|
||||
/// </summary>
|
||||
public ushort ByteOrder;
|
||||
|
||||
/// <summary>
|
||||
/// This field MUST be set to 0x0009, or 0x000c, depending on the Major
|
||||
/// Version field. This field specifies the sector size of the compound file
|
||||
/// as a power of 2.
|
||||
///
|
||||
/// If Major Version is 3, the Sector Shift MUST be 0x0009, specifying a
|
||||
/// sector size of 512 bytes.
|
||||
///
|
||||
/// If Major Version is 4, the Sector Shift MUST be 0x000C, specifying a
|
||||
/// sector size of 4096 bytes.
|
||||
/// </summary>
|
||||
public ushort SectorShift;
|
||||
|
||||
/// <summary>
|
||||
/// This field MUST be set to 0x0006. This field specifies the sector size
|
||||
/// of the Mini Stream as a power of 2. The sector size of the Mini Stream
|
||||
/// MUST be 64 bytes.
|
||||
/// </summary>
|
||||
public ushort MiniSectorShift;
|
||||
|
||||
/// <summary>
|
||||
/// This field MUST be set to all zeroes.
|
||||
/// </summary>
|
||||
public byte[] Reserved;
|
||||
|
||||
/// <summary>
|
||||
/// This integer field contains the count of the number of directory sectors
|
||||
/// in the compound file.
|
||||
///
|
||||
/// If Major Version is 3, the Number of Directory Sectors MUST be zero. This
|
||||
/// field is not supported for version 3 compound files.
|
||||
/// </summary>
|
||||
public uint NumberOfDirectorySectors;
|
||||
|
||||
/// <summary>
|
||||
/// This integer field contains the count of the number of FAT sectors in the
|
||||
/// compound file.
|
||||
/// </summary>
|
||||
public uint NumberOfFATSectors;
|
||||
|
||||
/// <summary>
|
||||
/// This integer field contains the starting sector number for the directory stream.
|
||||
/// </summary>
|
||||
public uint FirstDirectorySectorLocation;
|
||||
|
||||
/// <summary>
|
||||
/// This integer field MAY contain a sequence number that is incremented every time
|
||||
/// the compound file is saved by an implementation that supports file transactions.
|
||||
/// This is the field that MUST be set to all zeroes if file transactions are not
|
||||
/// implemented.
|
||||
/// </summary>
|
||||
public uint TransactionSignatureNumber;
|
||||
|
||||
/// <summary>
|
||||
/// This integer field MUST be set to 0x00001000. This field specifies the maximum
|
||||
/// size of a user-defined data stream that is allocated from the mini FAT and mini
|
||||
/// stream, and that cutoff is 4,096 bytes. Any user-defined data stream that is
|
||||
/// greater than or equal to this cutoff size must be allocated as normal sectors from
|
||||
/// the FAT.
|
||||
/// </summary>
|
||||
public uint MiniStreamCutoffSize;
|
||||
|
||||
/// <summary>
|
||||
/// This integer field contains the starting sector number for the mini FAT.
|
||||
/// </summary>
|
||||
public uint FirstMiniFATSectorLocation;
|
||||
|
||||
/// <summary>
|
||||
/// This integer field contains the count of the number of mini FAT sectors in the
|
||||
/// compound file.
|
||||
/// </summary>
|
||||
public uint NumberOfMiniFATSectors;
|
||||
|
||||
/// <summary>
|
||||
/// This integer field contains the starting sector number for the DIFAT.
|
||||
/// </summary>
|
||||
public uint FirstDIFATSectorLocation;
|
||||
|
||||
/// <summary>
|
||||
/// This integer field contains the count of the number of DIFAT sectors in the
|
||||
/// compound file.
|
||||
/// </summary>
|
||||
public uint NumberOfDIFATSectors;
|
||||
|
||||
/// <summary>
|
||||
/// This array of 32-bit integer fields contains the first 109 FAT sector
|
||||
/// locations of the compound file
|
||||
/// </summary>
|
||||
public SectorNumber[] DIFAT;
|
||||
}
|
||||
}
|
||||
82
BurnOutSharp.Models/CFB/SummaryInformation.cs
Normal file
82
BurnOutSharp.Models/CFB/SummaryInformation.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using System;
|
||||
|
||||
namespace BurnOutSharp.Models.CFB
|
||||
{
|
||||
/// <see href="https://github.com/GNOME/msitools/blob/master/libmsi/libmsi-summary-info.c"/>
|
||||
public sealed class SummaryInformation
|
||||
{
|
||||
#region Set Header
|
||||
|
||||
/// <summary>
|
||||
/// This field MUST be set to 0xFFFE. This field is a byte order mark for
|
||||
/// all integer fields, specifying little-endian byte order.
|
||||
/// </summary>
|
||||
public ushort ByteOrder;
|
||||
|
||||
/// <summary>
|
||||
/// Format
|
||||
/// </summary>
|
||||
public ushort Format;
|
||||
|
||||
/// <summary>
|
||||
/// Build
|
||||
/// </summary>
|
||||
public ushort Build;
|
||||
|
||||
/// <summary>
|
||||
/// Platform ID
|
||||
/// </summary>
|
||||
public ushort PlatformID;
|
||||
|
||||
/// <summary>
|
||||
/// CLSID
|
||||
/// </summary>
|
||||
public Guid CLSID;
|
||||
|
||||
/// <summary>
|
||||
/// 4 bytes of reserved data
|
||||
/// </summary>
|
||||
public byte[] Reserved;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Format Header
|
||||
|
||||
/// <summary>
|
||||
/// Format ID, should be <see cref="Constants.FMTID_SummaryInformation"/>
|
||||
/// </summary>
|
||||
public Guid FormatID;
|
||||
|
||||
/// <summary>
|
||||
/// 16 bytes of unknown data
|
||||
/// </summary>
|
||||
public byte[] Unknown;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Section Header
|
||||
|
||||
/// <summary>
|
||||
/// Location of the section
|
||||
/// </summary>
|
||||
public uint Offset;
|
||||
|
||||
/// <summary>
|
||||
/// Section count(?)
|
||||
/// </summary>
|
||||
public uint SectionCount;
|
||||
|
||||
/// <summary>
|
||||
/// Property count
|
||||
/// </summary>
|
||||
public uint PropertyCount;
|
||||
|
||||
/// <summary>
|
||||
/// Properties
|
||||
/// </summary>
|
||||
/// <remarks>Each Variant might be followed by an index and offset value</remarks>
|
||||
public Variant[] Properties;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
45
BurnOutSharp.Models/CFB/Variant.cs
Normal file
45
BurnOutSharp.Models/CFB/Variant.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
namespace BurnOutSharp.Models.CFB
|
||||
{
|
||||
/// <summary>
|
||||
/// VARIANT is a container for a union that can hold many types of data.
|
||||
/// </summary>
|
||||
/// <see href="https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-oaut/b2ee2b50-665e-43e6-a92c-8f2a29fd7add"/>
|
||||
public sealed class Variant
|
||||
{
|
||||
/// <summary>
|
||||
/// MUST be set to the size, in quad words (64 bits), of the structure.
|
||||
/// </summary>
|
||||
public uint Size;
|
||||
|
||||
/// <summary>
|
||||
/// MUST be set to 0 and MUST be ignored by the recipient.
|
||||
/// </summary>
|
||||
public uint RpcReserved;
|
||||
|
||||
/// <summary>
|
||||
/// MUST be set to one of the values specified with a "V".
|
||||
/// </summary>
|
||||
public VariantType VariantType;
|
||||
|
||||
/// <summary>
|
||||
/// MAY be set to 0 and MUST be ignored by the recipient.
|
||||
/// </summary>
|
||||
public ushort Reserved1;
|
||||
|
||||
/// <summary>
|
||||
/// MAY be set to 0 and MUST be ignored by the recipient.
|
||||
/// </summary>
|
||||
public ushort Reserved2;
|
||||
|
||||
/// <summary>
|
||||
/// MAY be set to 0 and MUST be ignored by the recipient.
|
||||
/// </summary>
|
||||
public ushort Reserved3;
|
||||
|
||||
/// <summary>
|
||||
/// MUST contain an instance of the type, according to the value
|
||||
/// in the <see cref="VariantType"/> field.
|
||||
/// </summary>
|
||||
public object Union;
|
||||
}
|
||||
}
|
||||
31
BurnOutSharp.Models/DVD/AudioSubPictureAttributesTable.cs
Normal file
31
BurnOutSharp.Models/DVD/AudioSubPictureAttributesTable.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
namespace BurnOutSharp.Models.DVD
|
||||
{
|
||||
/// <see href="https://dvd.sourceforge.net/dvdinfo/ifo.html"/>
|
||||
public sealed class AudioSubPictureAttributesTable
|
||||
{
|
||||
/// <summary>
|
||||
/// Number of title sets
|
||||
/// </summary>
|
||||
public ushort NumberOfTitleSets;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved;
|
||||
|
||||
/// <summary>
|
||||
/// End address (last byte of last VTS_ATRT)
|
||||
/// </summary>
|
||||
public uint EndAddress;
|
||||
|
||||
/// <summary>
|
||||
/// Offset to VTS_ATRT n
|
||||
/// </summary>
|
||||
public uint[] Offsets;
|
||||
|
||||
/// <summary>
|
||||
/// Entries
|
||||
/// </summary>
|
||||
public AudioSubPictureAttributesTableEntry[] Entries;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
namespace BurnOutSharp.Models.DVD
|
||||
{
|
||||
/// <see href="https://dvd.sourceforge.net/dvdinfo/ifo.html"/>
|
||||
public sealed class AudioSubPictureAttributesTableEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// End address (EA)
|
||||
/// </summary>
|
||||
public uint EndAddress;
|
||||
|
||||
/// <summary>
|
||||
/// VTS_CAT (copy of offset 022-025 of the VTS IFO file)
|
||||
/// 0=unspecified, 1=Karaoke
|
||||
/// </summary>
|
||||
public uint Category;
|
||||
|
||||
/// <summary>
|
||||
/// Copy of VTS attributes (offset 100 and on from the VTS IFO
|
||||
/// file, usually 0x300 bytes long)
|
||||
/// </summary>
|
||||
public byte[] AttributesCopy;
|
||||
}
|
||||
}
|
||||
26
BurnOutSharp.Models/DVD/CellAddressTable.cs
Normal file
26
BurnOutSharp.Models/DVD/CellAddressTable.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
namespace BurnOutSharp.Models.DVD
|
||||
{
|
||||
/// <see href="https://dvd.sourceforge.net/dvdinfo/ifo.html"/>
|
||||
public sealed class CellAddressTable
|
||||
{
|
||||
/// <summary>
|
||||
/// Number of VOB IDs
|
||||
/// </summary>
|
||||
public ushort NumberOfVOBIDs;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved;
|
||||
|
||||
/// <summary>
|
||||
/// End address (last byte of last entry)
|
||||
/// </summary>
|
||||
public uint EndAddress;
|
||||
|
||||
/// <summary>
|
||||
/// 12-byte entries
|
||||
/// </summary>
|
||||
public CellAddressTableEntry[] Entries;
|
||||
}
|
||||
}
|
||||
31
BurnOutSharp.Models/DVD/CellAddressTableEntry.cs
Normal file
31
BurnOutSharp.Models/DVD/CellAddressTableEntry.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
namespace BurnOutSharp.Models.DVD
|
||||
{
|
||||
/// <see href="https://dvd.sourceforge.net/dvdinfo/ifo.html"/>
|
||||
public sealed class CellAddressTableEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// VOBidn
|
||||
/// </summary>
|
||||
public ushort VOBIdentity;
|
||||
|
||||
/// <summary>
|
||||
/// CELLidn
|
||||
/// </summary>
|
||||
public byte CellIdentity;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte Reserved;
|
||||
|
||||
/// <summary>
|
||||
/// Starting sector within VOB
|
||||
/// </summary>
|
||||
public uint StartingSectorWithinVOB;
|
||||
|
||||
/// <summary>
|
||||
/// Ending sector within VOB
|
||||
/// </summary>
|
||||
public uint EndingSectorWithinVOB;
|
||||
}
|
||||
}
|
||||
9
BurnOutSharp.Models/DVD/Constants.cs
Normal file
9
BurnOutSharp.Models/DVD/Constants.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace BurnOutSharp.Models.DVD
|
||||
{
|
||||
public static class Constants
|
||||
{
|
||||
public const string VideoManagerIFOSignature = "DVDVIDEO-VMG";
|
||||
|
||||
public const string VideoTitleSetIFOSignature = "DVDVIDEO-VTS";
|
||||
}
|
||||
}
|
||||
56
BurnOutSharp.Models/DVD/Enums.cs
Normal file
56
BurnOutSharp.Models/DVD/Enums.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
|
||||
namespace BurnOutSharp.Models.DVD
|
||||
{
|
||||
[Flags]
|
||||
public enum ProgramChainCategory : byte
|
||||
{
|
||||
MenuTypeTitle = 0x02,
|
||||
Entry = 0x80,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum TitleType : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Uop0 Time play or search
|
||||
/// </summary>
|
||||
Uop0TimePlayOrSearch = 0x01,
|
||||
|
||||
/// <summary>
|
||||
/// Uop1 PTT play or search
|
||||
/// </summary>
|
||||
Uop1PTTPlayOrSearch = 0x02,
|
||||
|
||||
/// <summary>
|
||||
/// Jump/Link/Call commands - exist
|
||||
/// </summary>
|
||||
JumpLinkCallExist = 0x04,
|
||||
|
||||
/// <summary>
|
||||
/// Jump/Link/Call commands - button
|
||||
/// </summary>
|
||||
JumpLinkCallButton = 0x08,
|
||||
|
||||
/// <summary>
|
||||
/// Jump/Link/Call commands - pre/post
|
||||
/// </summary>
|
||||
JumpLinkCallPrePost = 0x10,
|
||||
|
||||
/// <summary>
|
||||
/// Jump/Link/Call commands - cell
|
||||
/// </summary>
|
||||
JumpLinkCallCell = 0x20,
|
||||
|
||||
/// <summary>
|
||||
/// 0=one_sequential_pgc
|
||||
/// 1=not one_sequential (random, shuffle, stills, loops, or more than one pgc)
|
||||
/// </summary>
|
||||
ComplexPGC = 0x40,
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
Reserved = 0x80,
|
||||
}
|
||||
}
|
||||
32
BurnOutSharp.Models/DVD/LanguageUnitTable.cs
Normal file
32
BurnOutSharp.Models/DVD/LanguageUnitTable.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
namespace BurnOutSharp.Models.DVD
|
||||
{
|
||||
/// <see href="https://dvd.sourceforge.net/dvdinfo/ifo_vmg.html"/>
|
||||
public sealed class LanguageUnitTable
|
||||
{
|
||||
/// <summary>
|
||||
/// Number of Language Units
|
||||
/// </summary>
|
||||
public ushort NumberOfLanguageUnits;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved;
|
||||
|
||||
/// <summary>
|
||||
/// End address (last byte of last PGC in last LU)
|
||||
/// relative to VMGM_PGCI_UT
|
||||
/// </summary>
|
||||
public uint EndAddress;
|
||||
|
||||
/// <summary>
|
||||
/// Language Units
|
||||
/// </summary>
|
||||
public LanguageUnitTableEntry[] Entries;
|
||||
|
||||
/// <summary>
|
||||
/// Program Chains
|
||||
/// </summary>
|
||||
public ProgramChainTable[] ProgramChains;
|
||||
}
|
||||
}
|
||||
26
BurnOutSharp.Models/DVD/LanguageUnitTableEntry.cs
Normal file
26
BurnOutSharp.Models/DVD/LanguageUnitTableEntry.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
namespace BurnOutSharp.Models.DVD
|
||||
{
|
||||
/// <see href="https://dvd.sourceforge.net/dvdinfo/ifo_vmg.html"/>
|
||||
public sealed class LanguageUnitTableEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// ISO639 language code
|
||||
/// </summary>
|
||||
public ushort ISO639LanguageCode;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved for language code extension
|
||||
/// </summary>
|
||||
public byte Reserved;
|
||||
|
||||
/// <summary>
|
||||
/// Menu existence flag [80 = title]
|
||||
/// </summary>
|
||||
public byte MenuExistenceFlag;
|
||||
|
||||
/// <summary>
|
||||
/// Offset to VMGM_LU, relative to VMGM_PGCI_UT
|
||||
/// </summary>
|
||||
public uint LanguageUnitOffset;
|
||||
}
|
||||
}
|
||||
37
BurnOutSharp.Models/DVD/ParentalManagementMasksTable.cs
Normal file
37
BurnOutSharp.Models/DVD/ParentalManagementMasksTable.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
namespace BurnOutSharp.Models.DVD
|
||||
{
|
||||
/// <summary>
|
||||
/// The VMG_PTL_MAIT is searched by country, and points to
|
||||
/// the table for each country.
|
||||
/// </summary>
|
||||
/// <see href="https://dvd.sourceforge.net/dvdinfo/ifo_vmg.html"/>
|
||||
public sealed class ParentalManagementMasksTable
|
||||
{
|
||||
/// <summary>
|
||||
/// Number of countries
|
||||
/// </summary>
|
||||
public ushort NumberOfCountries;
|
||||
|
||||
/// <summary>
|
||||
/// Number of title sets (NTs)
|
||||
/// </summary>
|
||||
public ushort NumberOfTitleSets;
|
||||
|
||||
/// <summary>
|
||||
/// End address (last byte of last PTL_MAIT)
|
||||
/// </summary>
|
||||
public uint EndAddress;
|
||||
|
||||
/// <summary>
|
||||
/// Entries
|
||||
/// </summary>
|
||||
public ParentalManagementMasksTableEntry[] Entries;
|
||||
|
||||
/// <summary>
|
||||
/// The PTL_MAIT contains the 16-bit masks for the VMG and
|
||||
/// all title sets for parental management level 8 followed
|
||||
/// by the masks for level 7, and so on to level 1.
|
||||
/// </summary>
|
||||
public byte[][] BitMasks;
|
||||
}
|
||||
}
|
||||
21
BurnOutSharp.Models/DVD/ParentalManagementMasksTableEntry.cs
Normal file
21
BurnOutSharp.Models/DVD/ParentalManagementMasksTableEntry.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
namespace BurnOutSharp.Models.DVD
|
||||
{
|
||||
/// <see href="https://dvd.sourceforge.net/dvdinfo/ifo_vmg.html"/>
|
||||
public sealed class ParentalManagementMasksTableEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// Country code
|
||||
/// </summary>
|
||||
public ushort CountryCode;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved;
|
||||
|
||||
/// <summary>
|
||||
/// Offset to PTL_MAIT
|
||||
/// </summary>
|
||||
public uint Offset;
|
||||
}
|
||||
}
|
||||
27
BurnOutSharp.Models/DVD/ProgramChainTable.cs
Normal file
27
BurnOutSharp.Models/DVD/ProgramChainTable.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
namespace BurnOutSharp.Models.DVD
|
||||
{
|
||||
/// <see href="https://dvd.sourceforge.net/dvdinfo/ifo_vmg.html"/>
|
||||
public sealed class ProgramChainTable
|
||||
{
|
||||
/// <summary>
|
||||
/// Number of Program Chains
|
||||
/// </summary>
|
||||
public ushort NumberOfProgramChains;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved;
|
||||
|
||||
/// <summary>
|
||||
/// End address (last byte of last PGC in this LU)
|
||||
/// relative to VMGM_LU
|
||||
/// </summary>
|
||||
public uint EndAddress;
|
||||
|
||||
/// <summary>
|
||||
/// Program Chains
|
||||
/// </summary>
|
||||
public ProgramChainTableEntry[] Entries;
|
||||
}
|
||||
}
|
||||
26
BurnOutSharp.Models/DVD/ProgramChainTableEntry.cs
Normal file
26
BurnOutSharp.Models/DVD/ProgramChainTableEntry.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
namespace BurnOutSharp.Models.DVD
|
||||
{
|
||||
/// <see href="https://dvd.sourceforge.net/dvdinfo/ifo_vmg.html"/>
|
||||
public sealed class ProgramChainTableEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// PGC category
|
||||
/// </summary>
|
||||
public ProgramChainCategory Category;
|
||||
|
||||
/// <summary>
|
||||
/// Unknown
|
||||
/// </summary>
|
||||
public byte Unknown;
|
||||
|
||||
/// <summary>
|
||||
/// Parental management mask
|
||||
/// </summary>
|
||||
public ushort ParentalManagementMask;
|
||||
|
||||
/// <summary>
|
||||
/// Offset to VMGM_PGC, relative to VMGM_LU
|
||||
/// </summary>
|
||||
public uint Offset;
|
||||
}
|
||||
}
|
||||
26
BurnOutSharp.Models/DVD/TitlesTable.cs
Normal file
26
BurnOutSharp.Models/DVD/TitlesTable.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
namespace BurnOutSharp.Models.DVD
|
||||
{
|
||||
/// <see href="https://dvd.sourceforge.net/dvdinfo/ifo_vmg.html"/>
|
||||
public sealed class TitlesTable
|
||||
{
|
||||
/// <summary>
|
||||
/// Number of titles
|
||||
/// </summary>
|
||||
public ushort NumberOfTitles;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved;
|
||||
|
||||
/// <summary>
|
||||
/// End address (last byte of last entry)
|
||||
/// </summary>
|
||||
public uint EndAddress;
|
||||
|
||||
/// <summary>
|
||||
/// 12-byte entries
|
||||
/// </summary>
|
||||
public TitlesTableEntry[] Entries;
|
||||
}
|
||||
}
|
||||
42
BurnOutSharp.Models/DVD/TitlesTableEntry.cs
Normal file
42
BurnOutSharp.Models/DVD/TitlesTableEntry.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
namespace BurnOutSharp.Models.DVD
|
||||
{
|
||||
/// <see href="https://dvd.sourceforge.net/dvdinfo/ifo_vmg.html"/>
|
||||
public sealed class TitlesTableEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// Title type
|
||||
/// </summary>
|
||||
public TitleType TitleType;
|
||||
|
||||
/// <summary>
|
||||
/// Number of angles
|
||||
/// </summary>
|
||||
public byte NumberOfAngles;
|
||||
|
||||
/// <summary>
|
||||
/// Number of chapters (PTTs)
|
||||
/// </summary>
|
||||
public ushort NumberOfChapters;
|
||||
|
||||
/// <summary>
|
||||
/// Parental management mask
|
||||
/// </summary>
|
||||
public ushort ParentalManagementMask;
|
||||
|
||||
/// <summary>
|
||||
/// Video Title Set number (VTSN)
|
||||
/// </summary>
|
||||
public byte VideoTitleSetNumber;
|
||||
|
||||
/// <summary>
|
||||
/// Title number within VTS (VTS_TTN)
|
||||
/// </summary>
|
||||
public byte TitleNumberWithinVTS;
|
||||
|
||||
/// <summary>
|
||||
/// Start sector for VTS, referenced to whole disk
|
||||
/// (video_ts.ifo starts at sector 00000000)
|
||||
/// </summary>
|
||||
public uint VTSStartSector;
|
||||
}
|
||||
}
|
||||
16
BurnOutSharp.Models/DVD/VOBUAddressMap.cs
Normal file
16
BurnOutSharp.Models/DVD/VOBUAddressMap.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace BurnOutSharp.Models.DVD
|
||||
{
|
||||
/// <see href="https://dvd.sourceforge.net/dvdinfo/ifo.html"/>
|
||||
public sealed class VOBUAddressMap
|
||||
{
|
||||
/// <summary>
|
||||
/// End address (last byte of last entry)
|
||||
/// </summary>
|
||||
public uint EndAddress;
|
||||
|
||||
/// <summary>
|
||||
/// Starting sector within VOB of nth VOBU
|
||||
/// </summary>
|
||||
public uint[] StartingSectors;
|
||||
}
|
||||
}
|
||||
150
BurnOutSharp.Models/DVD/VideoManagerIFO.cs
Normal file
150
BurnOutSharp.Models/DVD/VideoManagerIFO.cs
Normal file
@@ -0,0 +1,150 @@
|
||||
namespace BurnOutSharp.Models.DVD
|
||||
{
|
||||
/// <see href="https://dvd.sourceforge.net/dvdinfo/ifo.html"/>
|
||||
public sealed class VideoManagerIFO
|
||||
{
|
||||
/// <summary>
|
||||
/// "DVDVIDEO-VMG"
|
||||
/// </summary>
|
||||
public string Signature;
|
||||
|
||||
/// <summary>
|
||||
/// Last sector of VMG set (last sector of BUP)
|
||||
/// </summary>
|
||||
public uint LastVMGSetSector;
|
||||
|
||||
/// <summary>
|
||||
/// Last sector of IFO
|
||||
/// </summary>
|
||||
public uint LastIFOSector;
|
||||
|
||||
/// <summary>
|
||||
/// Version number
|
||||
/// - Byte 0 - Reserved, should be 0
|
||||
/// - Byte 1, Bits 7-4 - Major version number
|
||||
/// - Byte 1, Bits 3-0 - Minor version number
|
||||
/// </summary>
|
||||
public ushort VersionNumber;
|
||||
|
||||
/// <summary>
|
||||
/// VMG category
|
||||
/// </summary>
|
||||
/// <remarks>byte1=prohibited region mask</remarks>
|
||||
public uint VMGCategory;
|
||||
|
||||
/// <summary>
|
||||
/// Number of volumes
|
||||
/// </summary>
|
||||
public ushort NumberOfVolumes;
|
||||
|
||||
/// <summary>
|
||||
/// Volume number
|
||||
/// </summary>
|
||||
public ushort VolumeNumber;
|
||||
|
||||
/// <summary>
|
||||
/// Side ID
|
||||
/// </summary>
|
||||
public byte SideID;
|
||||
|
||||
/// <summary>
|
||||
/// Number of title sets
|
||||
/// </summary>
|
||||
public ushort NumberOfTitleSets;
|
||||
|
||||
/// <summary>
|
||||
/// Provider ID
|
||||
/// </summary>
|
||||
public byte[] ProviderID;
|
||||
|
||||
/// <summary>
|
||||
/// VMG POS
|
||||
/// </summary>
|
||||
public ulong VMGPOS;
|
||||
|
||||
/// <summary>
|
||||
/// End byte address of VMGI_MAT
|
||||
/// </summary>
|
||||
public uint InformationManagementTableEndByteAddress;
|
||||
|
||||
/// <summary>
|
||||
/// Start address of FP_PGC (First Play program chain)
|
||||
/// </summary>
|
||||
public uint FirstPlayProgramChainStartAddress;
|
||||
|
||||
/// <summary>
|
||||
/// Start sector of Menu VOB
|
||||
/// </summary>
|
||||
public uint MenuVOBStartSector;
|
||||
|
||||
/// <summary>
|
||||
/// Sector pointer to TT_SRPT (table of titles)
|
||||
/// </summary>
|
||||
public uint TableOfTitlesSectorPointer;
|
||||
|
||||
/// <summary>
|
||||
/// Sector pointer to VMGM_PGCI_UT (Menu Program Chain table)
|
||||
/// </summary>
|
||||
public uint MenuProgramChainTableSectorPointer;
|
||||
|
||||
/// <summary>
|
||||
/// Sector pointer to VMG_PTL_MAIT (Parental Management masks)
|
||||
/// </summary>
|
||||
public uint ParentalManagementMasksSectorPointer;
|
||||
|
||||
/// <summary>
|
||||
/// Sector pointer to VMG_VTS_ATRT (copies of VTS audio/sub-picture attributes)
|
||||
/// </summary>
|
||||
public uint AudioSubPictureAttributesSectorPointer;
|
||||
|
||||
/// <summary>
|
||||
/// Sector pointer to VMG_TXTDT_MG (text data)
|
||||
/// </summary>
|
||||
public uint TextDataSectorPointer;
|
||||
|
||||
/// <summary>
|
||||
/// Sector pointer to VMGM_C_ADT (menu cell address table)
|
||||
/// </summary>
|
||||
public uint MenuCellAddressTableSectorPointer;
|
||||
|
||||
/// <summary>
|
||||
/// Sector pointer to VMGM_VOBU_ADMAP (menu VOBU address map)
|
||||
/// </summary>
|
||||
public uint MenuVOBUAddressMapSectorPointer;
|
||||
|
||||
/// <summary>
|
||||
/// Video attributes of VMGM_VOBS
|
||||
/// </summary>
|
||||
public byte[] VideoAttributes;
|
||||
|
||||
/// <summary>
|
||||
/// Number of audio streams in VMGM_VOBS
|
||||
/// </summary>
|
||||
public ushort NumberOfAudioStreams;
|
||||
|
||||
/// <summary>
|
||||
/// Audio attributes of VMGM_VOBS
|
||||
/// </summary>
|
||||
public byte[][] AudioAttributes;
|
||||
|
||||
/// <summary>
|
||||
/// Unknown
|
||||
/// </summary>
|
||||
public byte[] Unknown;
|
||||
|
||||
/// <summary>
|
||||
/// Number of subpicture streams in VMGM_VOBS (0 or 1)
|
||||
/// </summary>
|
||||
public ushort NumberOfSubpictureStreams;
|
||||
|
||||
/// <summary>
|
||||
/// Subpicture attributes of VMGM_VOBS
|
||||
/// </summary>
|
||||
public byte[] SubpictureAttributes;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved;
|
||||
}
|
||||
}
|
||||
160
BurnOutSharp.Models/DVD/VideoTitleSetIFO.cs
Normal file
160
BurnOutSharp.Models/DVD/VideoTitleSetIFO.cs
Normal file
@@ -0,0 +1,160 @@
|
||||
namespace BurnOutSharp.Models.DVD
|
||||
{
|
||||
/// <see href="https://dvd.sourceforge.net/dvdinfo/ifo.html"/>
|
||||
public sealed class VideoTitleSetIFO
|
||||
{
|
||||
/// <summary>
|
||||
/// "DVDVIDEO-VTS"
|
||||
/// </summary>
|
||||
public string Signature;
|
||||
|
||||
/// <summary>
|
||||
/// Last sector of title set (last sector of BUP)
|
||||
/// </summary>
|
||||
public uint LastTitleSetSector;
|
||||
|
||||
/// <summary>
|
||||
/// Last sector of IFO
|
||||
/// </summary>
|
||||
public uint LastIFOSector;
|
||||
|
||||
/// <summary>
|
||||
/// Version number
|
||||
/// - Byte 0 - Reserved, should be 0
|
||||
/// - Byte 1, Bits 7-4 - Major version number
|
||||
/// - Byte 1, Bits 3-0 - Minor version number
|
||||
/// </summary>
|
||||
public ushort VersionNumber;
|
||||
|
||||
/// <summary>
|
||||
/// VTS category
|
||||
/// </summary>
|
||||
/// <remarks>0=unspecified, 1=Karaoke</remarks>
|
||||
public uint VMGCategory;
|
||||
|
||||
/// <summary>
|
||||
/// Number of volumes
|
||||
/// </summary>
|
||||
public ushort NumberOfVolumes;
|
||||
|
||||
/// <summary>
|
||||
/// Volume number
|
||||
/// </summary>
|
||||
public ushort VolumeNumber;
|
||||
|
||||
/// <summary>
|
||||
/// Side ID
|
||||
/// </summary>
|
||||
public byte SideID;
|
||||
|
||||
/// <summary>
|
||||
/// Number of title sets
|
||||
/// </summary>
|
||||
public ushort NumberOfTitleSets;
|
||||
|
||||
/// <summary>
|
||||
/// Provider ID
|
||||
/// </summary>
|
||||
public byte[] ProviderID;
|
||||
|
||||
/// <summary>
|
||||
/// VMG POS
|
||||
/// </summary>
|
||||
public ulong VMGPOS;
|
||||
|
||||
/// <summary>
|
||||
/// End byte address of VTS_MAT
|
||||
/// </summary>
|
||||
public uint InformationManagementTableEndByteAddress;
|
||||
|
||||
/// <summary>
|
||||
/// Start address of FP_PGC (First Play program chain)
|
||||
/// </summary>
|
||||
public uint FirstPlayProgramChainStartAddress;
|
||||
|
||||
/// <summary>
|
||||
/// Start sector of Menu VOB
|
||||
/// </summary>
|
||||
public uint MenuVOBStartSector;
|
||||
|
||||
/// <summary>
|
||||
/// Start sector of Title VOB
|
||||
/// </summary>
|
||||
public uint TitleVOBStartSector;
|
||||
|
||||
/// <summary>
|
||||
/// Sector pointer to VTS_PTT_SRPT (table of Titles and Chapters)
|
||||
/// </summary>
|
||||
public uint TableOfTitlesAndChaptersSectorPointer;
|
||||
|
||||
/// <summary>
|
||||
/// Sector pointer to VTS_PGCI (Title Program Chain table)
|
||||
/// </summary>
|
||||
public uint TitleProgramChainTableSectorPointer;
|
||||
|
||||
/// <summary>
|
||||
/// Sector pointer to VTSM_PGCI_UT (Menu Program Chain table)
|
||||
/// </summary>
|
||||
public uint MenuProgramChainTableSectorPointer;
|
||||
|
||||
/// <summary>
|
||||
/// Sector pointer to VTS_TMAPTI (time map)
|
||||
/// </summary>
|
||||
public uint TimeMapSectorPointer;
|
||||
|
||||
/// <summary>
|
||||
/// Sector pointer to VTSM_C_ADT (menu cell address table)
|
||||
/// </summary>
|
||||
public uint MenuCellAddressTableSectorPointer;
|
||||
|
||||
/// <summary>
|
||||
/// Sector pointer to VTSM_VOBU_ADMAP (menu VOBU address map)
|
||||
/// </summary>
|
||||
public uint MenuVOBUAddressMapSectorPointer;
|
||||
|
||||
/// <summary>
|
||||
/// Sector pointer to VTS_C_ADT (title set cell address table)
|
||||
/// </summary>
|
||||
public uint TitleSetCellAddressTableSectorPointer;
|
||||
|
||||
/// <summary>
|
||||
/// Sector pointer to VTS_VOBU_ADMAP (title set VOBU address map)
|
||||
/// </summary>
|
||||
public uint TitleSetVOBUAddressMapSectorPointer;
|
||||
|
||||
/// <summary>
|
||||
/// Video attributes of VTSM_VOBS
|
||||
/// </summary>
|
||||
public byte[] VideoAttributes;
|
||||
|
||||
/// <summary>
|
||||
/// Number of audio streams in VTSM_VOBS
|
||||
/// </summary>
|
||||
public ushort NumberOfAudioStreams;
|
||||
|
||||
/// <summary>
|
||||
/// Audio attributes of VTSM_VOBS
|
||||
/// </summary>
|
||||
public byte[][] AudioAttributes;
|
||||
|
||||
/// <summary>
|
||||
/// Unknown
|
||||
/// </summary>
|
||||
public byte[] Unknown;
|
||||
|
||||
/// <summary>
|
||||
/// Number of subpicture streams in VTSM_VOBS (0 or 1)
|
||||
/// </summary>
|
||||
public ushort NumberOfSubpictureStreams;
|
||||
|
||||
/// <summary>
|
||||
/// Subpicture attributes of VTSM_VOBS
|
||||
/// </summary>
|
||||
public byte[] SubpictureAttributes;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved;
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
namespace BurnOutSharp.Models.InstallShieldCabinet
|
||||
{
|
||||
/// <see href="https://github.com/twogood/unshield/blob/main/lib/cabfile.h"/>
|
||||
public sealed class CabDescriptor
|
||||
{
|
||||
public byte[] Reserved0;
|
||||
|
||||
public uint FileTableOffset;
|
||||
|
||||
public byte[] Reserved1;
|
||||
|
||||
public uint FileTableSize;
|
||||
|
||||
public uint FileTableSize2;
|
||||
|
||||
public uint DirectoryCount;
|
||||
|
||||
public byte[] Reserved2;
|
||||
|
||||
public uint FileCount;
|
||||
|
||||
public uint FileTableOffset2;
|
||||
|
||||
public byte[] Reserved3;
|
||||
|
||||
public uint[] FileGroupOffsets;
|
||||
|
||||
public uint[] ComponentOffsets;
|
||||
}
|
||||
}
|
||||
75
BurnOutSharp.Models/InstallShieldCabinet/Cabinet.cs
Normal file
75
BurnOutSharp.Models/InstallShieldCabinet/Cabinet.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BurnOutSharp.Models.InstallShieldCabinet
|
||||
{
|
||||
/// <see href="https://github.com/twogood/unshield/blob/main/lib/cabfile.h"/>
|
||||
/// <see href="https://github.com/twogood/unshield/blob/main/lib/internal.h"/>
|
||||
public sealed class Cabinet
|
||||
{
|
||||
#region Headers
|
||||
|
||||
/// <summary>
|
||||
/// Common header
|
||||
/// </summary>
|
||||
public CommonHeader CommonHeader { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Volume header
|
||||
/// </summary>
|
||||
public VolumeHeader VolumeHeader { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Descriptor
|
||||
/// </summary>
|
||||
public Descriptor Descriptor { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region File Descriptors
|
||||
|
||||
/// <summary>
|
||||
/// Offsets to all file descriptors
|
||||
/// </summary>
|
||||
public uint[] FileDescriptorOffsets { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Directory names
|
||||
/// </summary>
|
||||
public string[] DirectoryNames { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Standard file descriptors
|
||||
/// </summary>
|
||||
public FileDescriptor[] FileDescriptors { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region File Groups
|
||||
|
||||
/// <summary>
|
||||
/// File group offset to offset list mapping
|
||||
/// </summary>
|
||||
public Dictionary<long, OffsetList> FileGroupOffsets { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// File groups
|
||||
/// </summary>
|
||||
public FileGroup[] FileGroups { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Components
|
||||
|
||||
/// <summary>
|
||||
/// Component offset to offset list mapping
|
||||
/// </summary>
|
||||
public Dictionary<long, OffsetList> ComponentOffsets { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Components
|
||||
/// </summary>
|
||||
public Component[] Components { get; set; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -3,14 +3,29 @@ namespace BurnOutSharp.Models.InstallShieldCabinet
|
||||
/// <see href="https://github.com/twogood/unshield/blob/main/lib/cabfile.h"/>
|
||||
public sealed class CommonHeader
|
||||
{
|
||||
/// <summary>
|
||||
/// "ISc("
|
||||
/// </summary>
|
||||
public string Signature;
|
||||
|
||||
/// <summary>
|
||||
/// Encoded version
|
||||
/// </summary>
|
||||
public uint Version;
|
||||
|
||||
/// <summary>
|
||||
/// Volume information
|
||||
/// </summary>
|
||||
public uint VolumeInfo;
|
||||
|
||||
public uint CabDescriptorOffset;
|
||||
/// <summary>
|
||||
/// Offset to cabinet descriptor
|
||||
/// </summary>
|
||||
public uint DescriptorOffset;
|
||||
|
||||
public uint CabDescriptorSize;
|
||||
/// <summary>
|
||||
/// Cabinet descriptor size
|
||||
/// </summary>
|
||||
public uint DescriptorSize;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,173 @@
|
||||
using System;
|
||||
|
||||
namespace BurnOutSharp.Models.InstallShieldCabinet
|
||||
{
|
||||
/// <see href="https://github.com/twogood/unshield/blob/main/lib/libunshield.h"/>
|
||||
public sealed class Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Offset to the component identifier
|
||||
/// </summary>
|
||||
public uint IdentifierOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Component identifier
|
||||
/// </summary>
|
||||
public string Identifier;
|
||||
|
||||
/// <summary>
|
||||
/// Offset to the component descriptor
|
||||
/// </summary>
|
||||
public uint DescriptorOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Offset to the display name
|
||||
/// </summary>
|
||||
public uint DisplayNameOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Display name
|
||||
/// </summary>
|
||||
public string DisplayName;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved0;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved offset
|
||||
/// </summary>
|
||||
public uint ReservedOffset0;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved offset
|
||||
/// </summary>
|
||||
public uint ReservedOffset1;
|
||||
|
||||
/// <summary>
|
||||
/// Component index
|
||||
/// </summary>
|
||||
public ushort ComponentIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Offset to the component name
|
||||
/// </summary>
|
||||
public uint NameOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Component name
|
||||
/// </summary>
|
||||
public string Name;
|
||||
|
||||
public ushort FileGroupCount;
|
||||
/// <summary>
|
||||
/// Reserved offset
|
||||
/// </summary>
|
||||
public uint ReservedOffset2;
|
||||
|
||||
public uint FileGroupTableOffset;
|
||||
/// <summary>
|
||||
/// Reserved offset
|
||||
/// </summary>
|
||||
public uint ReservedOffset3;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved offset
|
||||
/// </summary>
|
||||
public uint ReservedOffset4;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved1;
|
||||
|
||||
/// <summary>
|
||||
/// Offset to the component CLSID
|
||||
/// </summary>
|
||||
public uint CLSIDOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Component CLSID
|
||||
/// </summary>
|
||||
public Guid CLSID;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved2;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved3;
|
||||
|
||||
/// <summary>
|
||||
/// Number of depends(?)
|
||||
/// </summary>
|
||||
public ushort DependsCount;
|
||||
|
||||
/// <summary>
|
||||
/// Offset to depends(?)
|
||||
/// </summary>
|
||||
public uint DependsOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Number of file groups
|
||||
/// </summary>
|
||||
public uint FileGroupCount;
|
||||
|
||||
/// <summary>
|
||||
/// Offset to the file group names
|
||||
/// </summary>
|
||||
public uint FileGroupNamesOffset;
|
||||
|
||||
/// <summary>
|
||||
/// File group names
|
||||
/// </summary>
|
||||
public string[] FileGroupNames;
|
||||
|
||||
/// <summary>
|
||||
/// Number of X3(?)
|
||||
/// </summary>
|
||||
public ushort X3Count;
|
||||
|
||||
/// <summary>
|
||||
/// Offset to X3(?)
|
||||
/// </summary>
|
||||
public uint X3Offset;
|
||||
|
||||
/// <summary>
|
||||
/// Number of sub-components
|
||||
/// </summary>
|
||||
public ushort SubComponentsCount;
|
||||
|
||||
/// <summary>
|
||||
/// Offset to the sub-components
|
||||
/// </summary>
|
||||
public uint SubComponentsOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Offset to the next component
|
||||
/// </summary>
|
||||
public uint NextComponentOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved offset
|
||||
/// </summary>
|
||||
public uint ReservedOffset5;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved offset
|
||||
/// </summary>
|
||||
public uint ReservedOffset6;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved offset
|
||||
/// </summary>
|
||||
public uint ReservedOffset7;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved offset
|
||||
/// </summary>
|
||||
public uint ReservedOffset8;
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,12 @@ namespace BurnOutSharp.Models.InstallShieldCabinet
|
||||
|
||||
public const uint SignatureUInt32 = 0x28635349;
|
||||
|
||||
public const int COMMON_HEADER_SIZE = 20;
|
||||
|
||||
public const int VOLUME_HEADER_SIZE_V5 = 40;
|
||||
|
||||
public const int VOLUME_HEADER_SIZE_V6 = 64;
|
||||
|
||||
// TODO: Determine how the value "71" was chosen here
|
||||
public const int MAX_FILE_GROUP_COUNT = 71;
|
||||
|
||||
|
||||
121
BurnOutSharp.Models/InstallShieldCabinet/Descriptor.cs
Normal file
121
BurnOutSharp.Models/InstallShieldCabinet/Descriptor.cs
Normal file
@@ -0,0 +1,121 @@
|
||||
namespace BurnOutSharp.Models.InstallShieldCabinet
|
||||
{
|
||||
/// <see href="https://github.com/twogood/unshield/blob/main/lib/cabfile.h"/>
|
||||
public sealed class Descriptor
|
||||
{
|
||||
/// <summary>
|
||||
/// Offset to the descriptor strings
|
||||
/// </summary>
|
||||
public uint StringsOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved0;
|
||||
|
||||
/// <summary>
|
||||
/// Offset to the component list
|
||||
/// </summary>
|
||||
public uint ComponentListOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Offset to the file table
|
||||
/// </summary>
|
||||
public uint FileTableOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved1;
|
||||
|
||||
/// <summary>
|
||||
/// Size of the file table
|
||||
/// </summary>
|
||||
public uint FileTableSize;
|
||||
|
||||
/// <summary>
|
||||
/// Redundant size of the file table
|
||||
/// </summary>
|
||||
public uint FileTableSize2;
|
||||
|
||||
/// <summary>
|
||||
/// Number of directories
|
||||
/// </summary>
|
||||
public ushort DirectoryCount;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved2;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved3;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved4;
|
||||
|
||||
/// <summary>
|
||||
/// Number of files
|
||||
/// </summary>
|
||||
public uint FileCount;
|
||||
|
||||
/// <summary>
|
||||
/// Redundant offset to the file table
|
||||
/// </summary>
|
||||
public uint FileTableOffset2;
|
||||
|
||||
/// <summary>
|
||||
/// Number of component table infos
|
||||
/// </summary>
|
||||
public ushort ComponentTableInfoCount;
|
||||
|
||||
/// <summary>
|
||||
/// Offset to the component table
|
||||
/// </summary>
|
||||
public uint ComponentTableOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved5;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved6;
|
||||
|
||||
/// <summary>
|
||||
/// Offsets to the file groups
|
||||
/// </summary>
|
||||
public uint[] FileGroupOffsets;
|
||||
|
||||
/// <summary>
|
||||
/// Offsets to the components
|
||||
/// </summary>
|
||||
public uint[] ComponentOffsets;
|
||||
|
||||
/// <summary>
|
||||
/// Offset to the setup types
|
||||
/// </summary>
|
||||
public uint SetupTypesOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Offset to the setup table
|
||||
/// </summary>
|
||||
public uint SetupTableOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved7;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved8;
|
||||
}
|
||||
}
|
||||
@@ -3,28 +3,64 @@ namespace BurnOutSharp.Models.InstallShieldCabinet
|
||||
/// <see href="https://github.com/twogood/unshield/blob/main/lib/cabfile.h"/>
|
||||
public sealed class FileDescriptor
|
||||
{
|
||||
/// <summary>
|
||||
/// Offset to the file descriptor name
|
||||
/// </summary>
|
||||
public uint NameOffset;
|
||||
|
||||
/// <summary>
|
||||
/// File descriptor name
|
||||
/// </summary>
|
||||
public string Name;
|
||||
|
||||
/// <summary>
|
||||
/// Directory index
|
||||
/// </summary>
|
||||
public uint DirectoryIndex;
|
||||
|
||||
/// <summary>
|
||||
/// File flags
|
||||
/// </summary>
|
||||
public FileFlags Flags;
|
||||
|
||||
/// <summary>
|
||||
/// Size of the entry when expanded
|
||||
/// </summary>
|
||||
public ulong ExpandedSize;
|
||||
|
||||
/// <summary>
|
||||
/// Size of the entry when compressed
|
||||
/// </summary>
|
||||
public ulong CompressedSize;
|
||||
|
||||
/// <summary>
|
||||
/// Offset to the entry data
|
||||
/// </summary>
|
||||
public ulong DataOffset;
|
||||
|
||||
/// <summary>
|
||||
/// MD5 of the entry data
|
||||
/// </summary>
|
||||
public byte[] MD5;
|
||||
|
||||
/// <summary>
|
||||
/// Volume number
|
||||
/// </summary>
|
||||
public ushort Volume;
|
||||
|
||||
/// <summary>
|
||||
/// Link previous
|
||||
/// </summary>
|
||||
public uint LinkPrevious;
|
||||
|
||||
/// <summary>
|
||||
/// Link next
|
||||
/// </summary>
|
||||
public uint LinkNext;
|
||||
|
||||
/// <summary>
|
||||
/// Link flags
|
||||
/// </summary>
|
||||
public LinkFlags LinkFlags;
|
||||
}
|
||||
}
|
||||
@@ -3,12 +3,124 @@ namespace BurnOutSharp.Models.InstallShieldCabinet
|
||||
/// <see href="https://github.com/twogood/unshield/blob/main/lib/libunshield.h"/>
|
||||
public sealed class FileGroup
|
||||
{
|
||||
/// <summary>
|
||||
/// Offset to the file group name
|
||||
/// </summary>
|
||||
public uint NameOffset;
|
||||
|
||||
/// <summary>
|
||||
/// File group name
|
||||
/// </summary>
|
||||
public string Name;
|
||||
|
||||
/// <summary>
|
||||
/// Size of the expanded data
|
||||
/// </summary>
|
||||
public uint ExpandedSize;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved0;
|
||||
|
||||
/// <summary>
|
||||
/// Size of the compressed data
|
||||
/// </summary>
|
||||
public uint CompressedSize;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved1;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved2;
|
||||
|
||||
/// <summary>
|
||||
/// Attribute(?)
|
||||
/// </summary>
|
||||
public ushort Attribute1;
|
||||
|
||||
/// <summary>
|
||||
/// Attribute(?)
|
||||
/// </summary>
|
||||
public ushort Attribute2;
|
||||
|
||||
/// <summary>
|
||||
/// Index of the first file
|
||||
/// </summary>
|
||||
public uint FirstFile;
|
||||
|
||||
/// <summary>
|
||||
/// Index of the last file
|
||||
/// </summary>
|
||||
public uint LastFile;
|
||||
|
||||
/// <summary>
|
||||
/// Unknown offset(?)
|
||||
/// </summary>
|
||||
public uint UnknownOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Var 4 offset(?)
|
||||
/// </summary>
|
||||
public uint Var4Offset;
|
||||
|
||||
/// <summary>
|
||||
/// Var 1 offset(?)
|
||||
/// </summary>
|
||||
public uint Var1Offset;
|
||||
|
||||
/// <summary>
|
||||
/// Offset to the HTTP location
|
||||
/// </summary>
|
||||
public uint HTTPLocationOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Offset to the FTP location
|
||||
/// </summary>
|
||||
public uint FTPLocationOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Misc offset(?)
|
||||
/// </summary>
|
||||
public uint MiscOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Var 2 offset(?)
|
||||
/// </summary>
|
||||
public uint Var2Offset;
|
||||
|
||||
/// <summary>
|
||||
/// Offset to the target directory
|
||||
/// </summary>
|
||||
public uint TargetDirectoryOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved3;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved4;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved5;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved6;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved7;
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BurnOutSharp.Models.InstallShieldCabinet
|
||||
{
|
||||
/// <see href="https://github.com/twogood/unshield/blob/main/lib/internal.h"/>
|
||||
public sealed class Header
|
||||
{
|
||||
// TODO: Move to wrapper, when exists
|
||||
public int MajorVersion
|
||||
{
|
||||
get
|
||||
{
|
||||
uint majorVersion = CommonHeader.Version;
|
||||
if (majorVersion >> 24 == 1)
|
||||
{
|
||||
majorVersion = (majorVersion >> 12) & 0x0F;
|
||||
}
|
||||
else if (majorVersion >> 24 == 2 || majorVersion >> 24 == 4)
|
||||
{
|
||||
majorVersion = majorVersion & 0xFFFF;
|
||||
if (majorVersion != 0)
|
||||
majorVersion /= 100;
|
||||
}
|
||||
|
||||
return (int)majorVersion;
|
||||
}
|
||||
}
|
||||
|
||||
#region Headers
|
||||
|
||||
public CommonHeader CommonHeader { get; set; }
|
||||
|
||||
public CabDescriptor CabinetDescriptor { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region File Descriptors
|
||||
|
||||
public uint[] FileDescriptorOffsets { get; set; }
|
||||
|
||||
public FileDescriptor[] DirectoryDescriptors { get; set; }
|
||||
|
||||
public FileDescriptor[] FileDescriptors { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region File Groups
|
||||
|
||||
public Dictionary<long, OffsetList> FileGroupOffsets { get; set; }
|
||||
|
||||
public FileGroup[] FileGroups { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Components
|
||||
|
||||
public Dictionary<long, OffsetList> ComponentOffsets { get; set; }
|
||||
|
||||
public Component[] Components { get; set; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
namespace BurnOutSharp.Models.InstallShieldCabinet
|
||||
{
|
||||
/// <see href="https://github.com/twogood/unshield/blob/main/lib/cabfile.h"/>
|
||||
/// TODO: Should standard and high values be combined?
|
||||
public sealed class VolumeHeader
|
||||
{
|
||||
public uint DataOffset;
|
||||
|
||||
@@ -27,10 +27,12 @@ namespace BurnOutSharp.Models.LinearExecutable
|
||||
/// </summary>
|
||||
public DebugFormatType FormatType;
|
||||
|
||||
// DEBUGGER DATA = Debugger specific data.
|
||||
// The format of the debugger data is defined by the debugger that is being used.
|
||||
// The values defined for the type field are not enforced by the system. It is
|
||||
// the responsibility of the linker or debugging tools to follow the convention
|
||||
// for the type field that is defined here.
|
||||
/// <summary>
|
||||
/// The format of the debugger data is defined by the debugger that is being used.
|
||||
/// The values defined for the type field are not enforced by the system. It is
|
||||
/// the responsibility of the linker or debugging tools to follow the convention
|
||||
/// for the type field that is defined here.
|
||||
/// </summary>
|
||||
public byte[] DebuggerData;
|
||||
}
|
||||
}
|
||||
|
||||
59
BurnOutSharp.Models/LinearExecutable/EntryTableBundle.cs
Normal file
59
BurnOutSharp.Models/LinearExecutable/EntryTableBundle.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace BurnOutSharp.Models.LinearExecutable
|
||||
{
|
||||
/// <summary>
|
||||
/// The entry table contains object and offset information that is used to resolve
|
||||
/// fixup references to the entry points within this module. Not all entry points
|
||||
/// in the entry table will be exported, some entry points will only be used
|
||||
/// within the module. An ordinal number is used to index into the entry table.
|
||||
/// The entry table entries are numbered starting from one.
|
||||
///
|
||||
/// The list of entries are compressed into 'bundles', where possible. The entries
|
||||
/// within each bundle are all the same size. A bundle starts with a count field
|
||||
/// which indicates the number of entries in the bundle. The count is followed by
|
||||
/// a type field which identifies the bundle format. This provides both a means
|
||||
/// for saving space as well as a mechanism for extending the bundle types.
|
||||
/// </summary>
|
||||
/// <see href="https://faydoc.tripod.com/formats/exe-LE.htm"/>
|
||||
/// <see href="http://www.edm2.com/index.php/LX_-_Linear_eXecutable_Module_Format_Description"/>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public sealed class EntryTableBundle
|
||||
{
|
||||
/// <summary>
|
||||
/// Number of entries.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is the number of entries in this bundle.
|
||||
///
|
||||
/// A zero value for the number of entries identifies the end of the
|
||||
/// entry table. There is no further bundle information when the number
|
||||
/// of entries is zero. In other words the entry table is terminated by
|
||||
/// a single zero byte.
|
||||
///
|
||||
/// For <see cref="BundleType.UnusedEntry"/>, this is the number of unused
|
||||
/// entries to skip.
|
||||
/// For <see cref="BundleType.SixteenBitEntry"/>, this is the number of 16-bit
|
||||
/// entries in this bundle. The flags and offset value are repeated this
|
||||
/// number of times.
|
||||
/// For <see cref="BundleType.TwoEightySixCallGateEntry"/>, this is the number
|
||||
/// of 286 call gate entries in this bundle. The flags, callgate, and offset
|
||||
/// value are repeated this number of times.
|
||||
/// For <see cref="BundleType.ThirtyTwoBitEntry"/>, this is the number
|
||||
/// of 32-bit entries in this bundle. The flags and offset value are repeated
|
||||
/// this number of times.
|
||||
/// For <see cref="BundleType.ForwarderEntry"/>, this field is reserved for future use.
|
||||
/// </remarks>
|
||||
public byte Entries;
|
||||
|
||||
/// <summary>
|
||||
/// This defines the bundle type which determines the contents of the BUNDLE INFO.
|
||||
/// </summary>
|
||||
public BundleType BundleType;
|
||||
|
||||
/// <summary>
|
||||
/// Table entries in the bundle
|
||||
/// </summary>
|
||||
public EntryTableEntry[] TableEntries;
|
||||
}
|
||||
}
|
||||
@@ -2,55 +2,11 @@
|
||||
|
||||
namespace BurnOutSharp.Models.LinearExecutable
|
||||
{
|
||||
/// <summary>
|
||||
/// The entry table contains object and offset information that is used to resolve
|
||||
/// fixup references to the entry points within this module. Not all entry points
|
||||
/// in the entry table will be exported, some entry points will only be used
|
||||
/// within the module. An ordinal number is used to index into the entry table.
|
||||
/// The entry table entries are numbered starting from one.
|
||||
///
|
||||
/// The list of entries are compressed into 'bundles', where possible. The entries
|
||||
/// within each bundle are all the same size. A bundle starts with a count field
|
||||
/// which indicates the number of entries in the bundle. The count is followed by
|
||||
/// a type field which identifies the bundle format. This provides both a means
|
||||
/// for saving space as well as a mechanism for extending the bundle types.
|
||||
/// </summary>
|
||||
/// <see href="https://faydoc.tripod.com/formats/exe-LE.htm"/>
|
||||
/// <see href="http://www.edm2.com/index.php/LX_-_Linear_eXecutable_Module_Format_Description"/>
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public sealed class EntryTableEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// Number of entries.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is the number of entries in this bundle.
|
||||
///
|
||||
/// A zero value for the number of entries identifies the end of the
|
||||
/// entry table. There is no further bundle information when the number
|
||||
/// of entries is zero. In other words the entry table is terminated by
|
||||
/// a single zero byte.
|
||||
///
|
||||
/// For <see cref="BundleType.UnusedEntry"/>, this is the number of unused
|
||||
/// entries to skip.
|
||||
/// For <see cref="BundleType.SixteenBitEntry"/>, this is the number of 16-bit
|
||||
/// entries in this bundle. The flags and offset value are repeated this
|
||||
/// number of times.
|
||||
/// For <see cref="BundleType.TwoEightySixCallGateEntry"/>, this is the number
|
||||
/// of 286 call gate entries in this bundle. The flags, callgate, and offset
|
||||
/// value are repeated this number of times.
|
||||
/// For <see cref="BundleType.ThirtyTwoBitEntry"/>, this is the number
|
||||
/// of 32-bit entries in this bundle. The flags and offset value are repeated
|
||||
/// this number of times.
|
||||
/// For <see cref="BundleType.ForwarderEntry"/>, this field is reserved for future use.
|
||||
/// </remarks>
|
||||
[FieldOffset(0)] public byte Entries;
|
||||
|
||||
/// <summary>
|
||||
/// This defines the bundle type which determines the contents of the BUNDLE INFO.
|
||||
/// </summary>
|
||||
[FieldOffset(1)] public BundleType BundleType;
|
||||
|
||||
#region 16-bit Entry
|
||||
|
||||
/// <summary>
|
||||
@@ -59,7 +15,7 @@ namespace BurnOutSharp.Models.LinearExecutable
|
||||
/// <remarks>
|
||||
/// This is the object number for the entries in this bundle.
|
||||
/// </remarks>
|
||||
[FieldOffset(2)] public ushort SixteenBitObjectNumber;
|
||||
[FieldOffset(0)] public ushort SixteenBitObjectNumber;
|
||||
|
||||
/// <summary>
|
||||
/// Entry flags.
|
||||
@@ -67,7 +23,7 @@ namespace BurnOutSharp.Models.LinearExecutable
|
||||
/// <remarks>
|
||||
/// These are the flags for this entry point.
|
||||
/// </remarks>
|
||||
[FieldOffset(4)] public EntryFlags SixteenBitEntryFlags;
|
||||
[FieldOffset(2)] public EntryFlags SixteenBitEntryFlags;
|
||||
|
||||
/// <summary>
|
||||
/// Offset in object.
|
||||
@@ -75,7 +31,7 @@ namespace BurnOutSharp.Models.LinearExecutable
|
||||
/// <remarks>
|
||||
/// This is the offset in the object for the entry point defined at this ordinal number.
|
||||
/// </remarks>
|
||||
[FieldOffset(5)] public ushort SixteenBitOffset;
|
||||
[FieldOffset(3)] public ushort SixteenBitOffset;
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -87,7 +43,7 @@ namespace BurnOutSharp.Models.LinearExecutable
|
||||
/// <remarks>
|
||||
/// This is the object number for the entries in this bundle.
|
||||
/// </remarks>
|
||||
[FieldOffset(2)] public ushort TwoEightySixObjectNumber;
|
||||
[FieldOffset(0)] public ushort TwoEightySixObjectNumber;
|
||||
|
||||
/// <summary>
|
||||
/// Entry flags.
|
||||
@@ -95,7 +51,7 @@ namespace BurnOutSharp.Models.LinearExecutable
|
||||
/// <remarks>
|
||||
/// These are the flags for this entry point.
|
||||
/// </remarks>
|
||||
[FieldOffset(4)] public EntryFlags TwoEightySixEntryFlags;
|
||||
[FieldOffset(2)] public EntryFlags TwoEightySixEntryFlags;
|
||||
|
||||
/// <summary>
|
||||
/// Offset in object.
|
||||
@@ -103,7 +59,7 @@ namespace BurnOutSharp.Models.LinearExecutable
|
||||
/// <remarks>
|
||||
/// This is the offset in the object for the entry point defined at this ordinal number.
|
||||
/// </remarks>
|
||||
[FieldOffset(5)] public ushort TwoEightySixOffset;
|
||||
[FieldOffset(3)] public ushort TwoEightySixOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Callgate selector.
|
||||
@@ -115,7 +71,7 @@ namespace BurnOutSharp.Models.LinearExecutable
|
||||
/// offset is place in the relocation fixup address. The segment number and offset
|
||||
/// in segment is placed in the LDT callgate.
|
||||
/// </remarks>
|
||||
[FieldOffset(7)] public ushort TwoEightySixCallgate;
|
||||
[FieldOffset(5)] public ushort TwoEightySixCallgate;
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -127,7 +83,7 @@ namespace BurnOutSharp.Models.LinearExecutable
|
||||
/// <remarks>
|
||||
/// This is the object number for the entries in this bundle.
|
||||
/// </remarks>
|
||||
[FieldOffset(2)] public ushort ThirtyTwoBitObjectNumber;
|
||||
[FieldOffset(0)] public ushort ThirtyTwoBitObjectNumber;
|
||||
|
||||
/// <summary>
|
||||
/// Entry flags.
|
||||
@@ -135,7 +91,7 @@ namespace BurnOutSharp.Models.LinearExecutable
|
||||
/// <remarks>
|
||||
/// These are the flags for this entry point.
|
||||
/// </remarks>
|
||||
[FieldOffset(4)] public EntryFlags ThirtyTwoBitEntryFlags;
|
||||
[FieldOffset(2)] public EntryFlags ThirtyTwoBitEntryFlags;
|
||||
|
||||
/// <summary>
|
||||
/// Offset in object.
|
||||
@@ -143,7 +99,7 @@ namespace BurnOutSharp.Models.LinearExecutable
|
||||
/// <remarks>
|
||||
/// This is the offset in the object for the entry point defined at this ordinal number.
|
||||
/// </remarks>
|
||||
[FieldOffset(5)] public uint ThirtyTwoBitOffset;
|
||||
[FieldOffset(3)] public uint ThirtyTwoBitOffset;
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -155,7 +111,7 @@ namespace BurnOutSharp.Models.LinearExecutable
|
||||
/// <remarks>
|
||||
/// This field is reserved for future use.
|
||||
/// </remarks>
|
||||
[FieldOffset(2)] public ushort ForwarderReserved;
|
||||
[FieldOffset(0)] public ushort ForwarderReserved;
|
||||
|
||||
/// <summary>
|
||||
/// Forwarder flags.
|
||||
@@ -163,7 +119,7 @@ namespace BurnOutSharp.Models.LinearExecutable
|
||||
/// <remarks>
|
||||
/// These are the flags for this entry point.
|
||||
/// </remarks>
|
||||
[FieldOffset(4)] public ForwarderFlags ForwarderFlags;
|
||||
[FieldOffset(2)] public ForwarderFlags ForwarderFlags;
|
||||
|
||||
/// <summary>
|
||||
/// Module Ordinal Number
|
||||
@@ -171,7 +127,7 @@ namespace BurnOutSharp.Models.LinearExecutable
|
||||
/// <remarks>
|
||||
/// This is the index into the Import Module Name Table for this forwarder.
|
||||
/// </remarks>
|
||||
[FieldOffset(5)] public ushort ForwarderModuleOrdinalNumber;
|
||||
[FieldOffset(3)] public ushort ForwarderModuleOrdinalNumber;
|
||||
|
||||
/// <summary>
|
||||
/// Procedure Name Offset
|
||||
@@ -199,7 +155,7 @@ namespace BurnOutSharp.Models.LinearExecutable
|
||||
/// single libraries, one could provide entry points for the three libraries
|
||||
/// that are forwarders pointing to the common implementation.
|
||||
/// </remarks>
|
||||
[FieldOffset(7)] public uint ProcedureNameOffset;
|
||||
[FieldOffset(5)] public uint ProcedureNameOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Import Ordinal Number
|
||||
@@ -227,8 +183,8 @@ namespace BurnOutSharp.Models.LinearExecutable
|
||||
/// single libraries, one could provide entry points for the three libraries
|
||||
/// that are forwarders pointing to the common implementation.
|
||||
/// </remarks>
|
||||
[FieldOffset(7)] public uint ImportOrdinalNumber;
|
||||
[FieldOffset(5)] public uint ImportOrdinalNumber;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,11 +26,11 @@ namespace BurnOutSharp.Models.LinearExecutable
|
||||
public ObjectTableEntry[] ObjectTable { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Object page table
|
||||
/// Object page map
|
||||
/// </summary>
|
||||
public ObjectPageTableEntry[] ObjectPageTable { get; set; }
|
||||
public ObjectPageMapEntry[] ObjectPageMap { get; set; }
|
||||
|
||||
// TODO: Object iterate data map table [Does this exist?]
|
||||
// TODO: Object iterate data map table (Undefined)
|
||||
|
||||
/// <summary>
|
||||
/// Resource table
|
||||
@@ -40,12 +40,12 @@ namespace BurnOutSharp.Models.LinearExecutable
|
||||
/// <summary>
|
||||
/// Resident Name table
|
||||
/// </summary>
|
||||
public ResidentNameTableEntry[] ResidentNameTable { get; set; }
|
||||
public ResidentNamesTableEntry[] ResidentNamesTable { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Entry table
|
||||
/// </summary>
|
||||
public EntryTableEntry[] EntryTable { get; set; }
|
||||
public EntryTableBundle[] EntryTable { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Module format directives table (optional)
|
||||
@@ -57,11 +57,6 @@ namespace BurnOutSharp.Models.LinearExecutable
|
||||
/// </summary>
|
||||
public VerifyRecordDirectiveTableEntry[] VerifyRecordDirectiveTable { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Per-Page checksum table
|
||||
/// </summary>
|
||||
public PerPageChecksumTableEntry[] PerPageChecksumTable { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Fix-up page table
|
||||
/// </summary>
|
||||
@@ -82,14 +77,15 @@ namespace BurnOutSharp.Models.LinearExecutable
|
||||
/// </summary>
|
||||
public ImportModuleProcedureNameTableEntry[] ImportModuleProcedureNameTable { get; set; }
|
||||
|
||||
// TODO: Preload Pages
|
||||
// TODO: Demand Load Pages
|
||||
// TODO: Iterated Pages
|
||||
/// <summary>
|
||||
/// Per-Page checksum table
|
||||
/// </summary>
|
||||
public PerPageChecksumTableEntry[] PerPageChecksumTable { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Non-Resident Name table
|
||||
/// </summary>
|
||||
public NonResidentNameTableEntry[] NonResidentNameTable { get; set; }
|
||||
public NonResidentNamesTableEntry[] NonResidentNamesTable { get; set; }
|
||||
|
||||
// TODO: Non-resident directives data (Undefined)
|
||||
|
||||
|
||||
@@ -36,6 +36,6 @@ namespace BurnOutSharp.Models.LinearExecutable
|
||||
/// This is a variable length string with it's length defined in bytes by
|
||||
/// the LEN field. The string is case sensitive and is not null terminated.
|
||||
/// </remarks>
|
||||
public byte[] Name;
|
||||
public string Name;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,6 @@ namespace BurnOutSharp.Models.LinearExecutable
|
||||
/// This is a variable length string with it's length defined in bytes by
|
||||
/// the LEN field. The string is case sensitive and is not null terminated.
|
||||
/// </remarks>
|
||||
public byte[] Name;
|
||||
public string Name;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace BurnOutSharp.Models.LinearExecutable
|
||||
/// The signature word is used by the loader to identify the EXE
|
||||
/// file as a valid 32-bit Linear Executable Module Format.
|
||||
/// </remarks>
|
||||
public char[] Signature;
|
||||
public string Signature;
|
||||
|
||||
/// <summary>
|
||||
/// Byte Ordering.
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace BurnOutSharp.Models.LinearExecutable
|
||||
/// <see href="https://faydoc.tripod.com/formats/exe-LE.htm"/>
|
||||
/// <see href="http://www.edm2.com/index.php/LX_-_Linear_eXecutable_Module_Format_Description"/>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public sealed class ResidentNameTableEntry
|
||||
public sealed class NonResidentNamesTableEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// String Length.
|
||||
@@ -51,7 +51,7 @@ namespace BurnOutSharp.Models.LinearExecutable
|
||||
/// This is a variable length string with it's length defined in bytes by the LEN field.
|
||||
/// The string is case case sensitive and is not null terminated.
|
||||
/// </remarks>
|
||||
public byte[] Name;
|
||||
public string Name;
|
||||
|
||||
/// <summary>
|
||||
/// Ordinal number.
|
||||
@@ -16,7 +16,7 @@ namespace BurnOutSharp.Models.LinearExecutable
|
||||
/// <see href="https://faydoc.tripod.com/formats/exe-LE.htm"/>
|
||||
/// <see href="http://www.edm2.com/index.php/LX_-_Linear_eXecutable_Module_Format_Description"/>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public sealed class ObjectPageTableEntry
|
||||
public sealed class ObjectPageMapEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// Offset to the page data in the EXE file.
|
||||
@@ -11,12 +11,12 @@ namespace BurnOutSharp.Models.LinearExecutable
|
||||
/// information in the entry table.
|
||||
///
|
||||
/// The resident name table is kept resident in system memory while the module is
|
||||
/// loaded.It is intended to contain the exported entry point names that are
|
||||
/// frequently dynamically linked to by name.Non-resident names are not kept in
|
||||
/// loaded. It is intended to contain the exported entry point names that are
|
||||
/// frequently dynamically linked to by name. Non-resident names are not kept in
|
||||
/// memory and are read from the EXE file when a dynamic link reference is made.
|
||||
/// Exported entry point names that are infrequently dynamically linked to by name
|
||||
/// or are commonly referenced by ordinal number should be placed in the
|
||||
/// non-resident name table.The trade off made for references by name is performance
|
||||
/// non-resident name table. The trade off made for references by name is performance
|
||||
/// vs memory usage.
|
||||
///
|
||||
/// Import references by name require these tables to be searched to obtain the entry
|
||||
@@ -28,7 +28,7 @@ namespace BurnOutSharp.Models.LinearExecutable
|
||||
/// <see href="https://faydoc.tripod.com/formats/exe-LE.htm"/>
|
||||
/// <see href="http://www.edm2.com/index.php/LX_-_Linear_eXecutable_Module_Format_Description"/>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public sealed class NonResidentNameTableEntry
|
||||
public sealed class ResidentNamesTableEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// String Length.
|
||||
@@ -51,7 +51,7 @@ namespace BurnOutSharp.Models.LinearExecutable
|
||||
/// This is a variable length string with it's length defined in bytes by the LEN field.
|
||||
/// The string is case case sensitive and is not null terminated.
|
||||
/// </remarks>
|
||||
public byte[] Name;
|
||||
public string Name;
|
||||
|
||||
/// <summary>
|
||||
/// Ordinal number.
|
||||
44
BurnOutSharp.Models/N3DS/ARM11KernelCapabilities.cs
Normal file
44
BurnOutSharp.Models/N3DS/ARM11KernelCapabilities.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
namespace BurnOutSharp.Models.N3DS
|
||||
{
|
||||
/// <summary>
|
||||
/// The kernel capability descriptors are passed to svcCreateProcess.
|
||||
/// </summary>
|
||||
/// <see href="https://www.3dbrew.org/wiki/NCCH/Extended_Header#ARM11_Kernel_Capabilities"/>
|
||||
public sealed class ARM11KernelCapabilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Descriptors
|
||||
/// -------------------
|
||||
/// Pattern of bits 20-31 Type Fields
|
||||
/// 0b1110xxxxxxxx Interrupt info
|
||||
/// 0b11110xxxxxxx System call mask Bits 24-26: System call mask table index; Bits 0-23: mask
|
||||
/// 0b1111110xxxxx Kernel release version Bits 8-15: Major version; Bits 0-7: Minor version
|
||||
/// 0b11111110xxxx Handle table size Bits 0-18: size
|
||||
/// 0b111111110xxx Kernel flags
|
||||
/// 0b11111111100x Map address range Describes a memory mapping like the 0b111111111110 descriptor, but an entire range rather than a single page is mapped.Another 0b11111111100x descriptor must follow this one to denote the(exclusive) end of the address range to map.
|
||||
/// 0b111111111110 Map memory page Bits 0-19: page index to map(virtual address >> 12; the physical address is determined per-page according to Memory layout); Bit 20: Map read-only(otherwise read-write)
|
||||
///
|
||||
/// ARM11 Kernel Flags
|
||||
/// -------------------
|
||||
/// Bit Description
|
||||
/// 0 Allow debug
|
||||
/// 1 Force debug
|
||||
/// 2 Allow non-alphanum
|
||||
/// 3 Shared page writing
|
||||
/// 4 Privilege priority
|
||||
/// 5 Allow main() args
|
||||
/// 6 Shared device memory
|
||||
/// 7 Runnable on sleep
|
||||
/// 8-11 Memory type(1: application, 2: system, 3: base)
|
||||
/// 12 Special memory
|
||||
/// 13 Process has access to CPU core 2 (New3DS only)
|
||||
/// </summary>
|
||||
/// TODO: Make enum for flag values
|
||||
public uint[] Descriptors;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved;
|
||||
}
|
||||
}
|
||||
66
BurnOutSharp.Models/N3DS/ARM11LocalSystemCapabilities.cs
Normal file
66
BurnOutSharp.Models/N3DS/ARM11LocalSystemCapabilities.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
namespace BurnOutSharp.Models.N3DS
|
||||
{
|
||||
/// <see href="https://www.3dbrew.org/wiki/NCCH/Extended_Header#ARM11_Local_System_Capabilities"/>
|
||||
public sealed class ARM11LocalSystemCapabilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Program ID
|
||||
/// </summary>
|
||||
public ulong ProgramID;
|
||||
|
||||
/// <summary>
|
||||
/// Core version (The Title ID low of the required FIRM)
|
||||
/// </summary>
|
||||
public uint CoreVersion;
|
||||
|
||||
/// <summary>
|
||||
/// Flag1 (implemented starting from 8.0.0-18).
|
||||
/// </summary>
|
||||
public ARM11LSCFlag1 Flag1;
|
||||
|
||||
/// <summary>
|
||||
/// Flag2 (implemented starting from 8.0.0-18).
|
||||
/// </summary>
|
||||
public ARM11LSCFlag2 Flag2;
|
||||
|
||||
/// <summary>
|
||||
/// Flag0
|
||||
/// </summary>
|
||||
public ARM11LSCFlag0 Flag0;
|
||||
|
||||
/// <summary>
|
||||
/// Priority
|
||||
/// </summary>
|
||||
public byte Priority;
|
||||
|
||||
/// <summary>
|
||||
/// Resource limit descriptors. The first byte here controls the maximum allowed CpuTime.
|
||||
/// </summary>
|
||||
public ushort[] ResourceLimitDescriptors;
|
||||
|
||||
/// <summary>
|
||||
/// Storage info
|
||||
/// </summary>
|
||||
public StorageInfo StorageInfo;
|
||||
|
||||
/// <summary>
|
||||
/// Service access control
|
||||
/// </summary>
|
||||
public ulong[] ServiceAccessControl;
|
||||
|
||||
/// <summary>
|
||||
/// Extended service access control, support for this was implemented with 9.3.0-X.
|
||||
/// </summary>
|
||||
public ulong[] ExtendedServiceAccessControl;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved;
|
||||
|
||||
/// <summary>
|
||||
/// Resource limit category. (0 = APPLICATION, 1 = SYS_APPLET, 2 = LIB_APPLET, 3 = OTHER (sysmodules running under the BASE memregion))
|
||||
/// </summary>
|
||||
public ResourceLimitCategory ResourceLimitCategory;
|
||||
}
|
||||
}
|
||||
17
BurnOutSharp.Models/N3DS/ARM9AccessControl.cs
Normal file
17
BurnOutSharp.Models/N3DS/ARM9AccessControl.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace BurnOutSharp.Models.N3DS
|
||||
{
|
||||
/// <see href="https://www.3dbrew.org/wiki/NCCH/Extended_Header#ARM9_Access_Control"/>
|
||||
public sealed class ARM9AccessControl
|
||||
{
|
||||
/// <summary>
|
||||
/// Descriptors
|
||||
/// </summary>
|
||||
public byte[] Descriptors;
|
||||
|
||||
/// <summary>
|
||||
/// ARM9 Descriptor Version. Originally this value had to be ≥ 2.
|
||||
/// Starting with 9.3.0-X this value has to be either value 2 or value 3.
|
||||
/// </summary>
|
||||
public byte DescriptorVersion;
|
||||
}
|
||||
}
|
||||
21
BurnOutSharp.Models/N3DS/AccessControlInfo.cs
Normal file
21
BurnOutSharp.Models/N3DS/AccessControlInfo.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
namespace BurnOutSharp.Models.N3DS
|
||||
{
|
||||
/// <see href="https://www.3dbrew.org/wiki/NCCH/Extended_Header#Access_Control_Info"/>
|
||||
public sealed class AccessControlInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// ARM11 local system capabilities
|
||||
/// </summary>
|
||||
public ARM11LocalSystemCapabilities ARM11LocalSystemCapabilities;
|
||||
|
||||
/// <summary>
|
||||
/// ARM11 kernel capabilities
|
||||
/// </summary>
|
||||
public ARM11KernelCapabilities ARM11KernelCapabilities;
|
||||
|
||||
/// <summary>
|
||||
/// ARM9 access control
|
||||
/// </summary>
|
||||
public ARM9AccessControl ARM9AccessControl;
|
||||
}
|
||||
}
|
||||
56
BurnOutSharp.Models/N3DS/CIA.cs
Normal file
56
BurnOutSharp.Models/N3DS/CIA.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
namespace BurnOutSharp.Models.N3DS
|
||||
{
|
||||
/// <summary>
|
||||
/// CIA stands for CTR Importable Archive. This format allows the installation of
|
||||
/// titles to the 3DS. CIA files and titles on Nintendo's CDN contain identical data.
|
||||
/// As a consequence, valid CIA files can be generated from CDN content. This also
|
||||
/// means CIA files can contain anything that titles on Nintendo's CDN can contain.
|
||||
///
|
||||
/// Under normal circumstances CIA files are used where downloading a title is
|
||||
/// impractical or not possible. Such as distributing a Download Play child, or
|
||||
/// installing forced Gamecard updates. Those CIA(s) are stored by the titles in
|
||||
/// question, in an auxiliary CFA file.
|
||||
/// </summary>
|
||||
/// <see href="https://www.3dbrew.org/wiki/CIA"/>
|
||||
public class CIA
|
||||
{
|
||||
/// <summary>
|
||||
/// CIA header
|
||||
/// </summary>
|
||||
public CIAHeader Header { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Certificate chain
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// https://www.3dbrew.org/wiki/CIA#Certificate_Chain
|
||||
/// </remarks>
|
||||
public Certificate[] CertificateChain { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Ticket
|
||||
/// </summary>
|
||||
public Ticket Ticket { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// TMD file data
|
||||
/// </summary>
|
||||
public TitleMetadata TMDFileData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Content file data
|
||||
/// </summary>
|
||||
public NCCHHeader[] Partitions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Content file data
|
||||
/// </summary>
|
||||
/// TODO: Parse the content file data
|
||||
public byte[] ContentFileData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Meta file data (Not a necessary component)
|
||||
/// </summary>
|
||||
public MetaData MetaData { get; set; }
|
||||
}
|
||||
}
|
||||
51
BurnOutSharp.Models/N3DS/CIAHeader.cs
Normal file
51
BurnOutSharp.Models/N3DS/CIAHeader.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
namespace BurnOutSharp.Models.N3DS
|
||||
{
|
||||
/// <see href="https://www.3dbrew.org/wiki/CIA#CIA_Header"/>
|
||||
public sealed class CIAHeader
|
||||
{
|
||||
/// <summary>
|
||||
/// Archive header size, usually 0x2020 bytes
|
||||
/// </summary>
|
||||
public uint HeaderSize;
|
||||
|
||||
/// <summary>
|
||||
/// Type
|
||||
/// </summary>
|
||||
public ushort Type;
|
||||
|
||||
/// <summary>
|
||||
/// Version
|
||||
/// </summary>
|
||||
public ushort Version;
|
||||
|
||||
/// <summary>
|
||||
/// Certificate chain size
|
||||
/// </summary>
|
||||
public uint CertificateChainSize;
|
||||
|
||||
/// <summary>
|
||||
/// Ticket size
|
||||
/// </summary>
|
||||
public uint TicketSize;
|
||||
|
||||
/// <summary>
|
||||
/// TMD file size
|
||||
/// </summary>
|
||||
public uint TMDFileSize;
|
||||
|
||||
/// <summary>
|
||||
/// Meta size (0 if no Meta data is present)
|
||||
/// </summary>
|
||||
public uint MetaSize;
|
||||
|
||||
/// <summary>
|
||||
/// Content size
|
||||
/// </summary>
|
||||
public ulong ContentSize;
|
||||
|
||||
/// <summary>
|
||||
/// Content Index
|
||||
/// </summary>
|
||||
public byte[] ContentIndex;
|
||||
}
|
||||
}
|
||||
61
BurnOutSharp.Models/N3DS/CardInfoHeader.cs
Normal file
61
BurnOutSharp.Models/N3DS/CardInfoHeader.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
namespace BurnOutSharp.Models.N3DS
|
||||
{
|
||||
/// <see href="https://www.3dbrew.org/wiki/NCSD#Card_Info_Header"/>
|
||||
public sealed class CardInfoHeader
|
||||
{
|
||||
/// <summary>
|
||||
/// CARD2: Writable Address In Media Units (For 'On-Chip' Savedata). CARD1: Always 0xFFFFFFFF.
|
||||
/// </summary>
|
||||
public uint WritableAddressMediaUnits;
|
||||
|
||||
/// <summary>
|
||||
/// Card Info Bitmask
|
||||
/// </summary>
|
||||
public uint CardInfoBitmask;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved1;
|
||||
|
||||
/// <summary>
|
||||
/// Filled size of cartridge
|
||||
/// </summary>
|
||||
public uint FilledSize;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved2;
|
||||
|
||||
/// <summary>
|
||||
/// Title version
|
||||
/// </summary>
|
||||
public ushort TitleVersion;
|
||||
|
||||
/// <summary>
|
||||
/// Card revision
|
||||
/// </summary>
|
||||
public ushort CardRevision;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved3;
|
||||
|
||||
/// <summary>
|
||||
/// Title ID of CVer in included update partition
|
||||
/// </summary>
|
||||
public byte[] CVerTitleID;
|
||||
|
||||
/// <summary>
|
||||
/// Version number of CVer in included update partition
|
||||
/// </summary>
|
||||
public ushort CVerVersionNumber;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved4;
|
||||
}
|
||||
}
|
||||
43
BurnOutSharp.Models/N3DS/Cart.cs
Normal file
43
BurnOutSharp.Models/N3DS/Cart.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
namespace BurnOutSharp.Models.N3DS
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a 3DS cart image
|
||||
/// </summary>
|
||||
public class Cart
|
||||
{
|
||||
/// <summary>
|
||||
/// 3DS cart header
|
||||
/// </summary>
|
||||
public NCSDHeader Header { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 3DS card info header
|
||||
/// </summary>
|
||||
public CardInfoHeader CardInfoHeader { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 3DS development card info header
|
||||
/// </summary>
|
||||
public DevelopmentCardInfoHeader DevelopmentCardInfoHeader { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// NCCH partitions
|
||||
/// </summary>
|
||||
public NCCHHeader[] Partitions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// NCCH extended headers
|
||||
/// </summary>
|
||||
public NCCHExtendedHeader[] ExtendedHeaders { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// ExeFS headers associated with each partition
|
||||
/// </summary>
|
||||
public ExeFSHeader[] ExeFSHeaders { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// RomFS headers associated with each partition
|
||||
/// </summary>
|
||||
public RomFSHeader[] RomFSHeaders { get; set; }
|
||||
}
|
||||
}
|
||||
92
BurnOutSharp.Models/N3DS/Certificate.cs
Normal file
92
BurnOutSharp.Models/N3DS/Certificate.cs
Normal file
@@ -0,0 +1,92 @@
|
||||
namespace BurnOutSharp.Models.N3DS
|
||||
{
|
||||
/// <summary>
|
||||
/// Certificates contain cryptography information for verifying Signatures.
|
||||
/// These certificates are also signed. The parent/child relationship between
|
||||
/// certificates, makes all the certificates effectively signed by 'Root',
|
||||
/// the public key for which is stored in NATIVE_FIRM.
|
||||
/// </summary>
|
||||
/// <see href="https://www.3dbrew.org/wiki/Certificates"/>
|
||||
public sealed class Certificate
|
||||
{
|
||||
/// <summary>
|
||||
/// Signature Type
|
||||
/// </summary>
|
||||
public SignatureType SignatureType;
|
||||
|
||||
/// <summary>
|
||||
/// Signature size
|
||||
/// </summary>
|
||||
public ushort SignatureSize;
|
||||
|
||||
/// <summary>
|
||||
/// Padding size
|
||||
/// </summary>
|
||||
public byte PaddingSize;
|
||||
|
||||
/// <summary>
|
||||
/// Signature
|
||||
/// </summary>
|
||||
public byte[] Signature;
|
||||
|
||||
/// <summary>
|
||||
/// Padding to align next data to 0x40 bytes
|
||||
/// </summary>
|
||||
public byte[] Padding;
|
||||
|
||||
/// <summary>
|
||||
/// Issuer
|
||||
/// </summary>
|
||||
public string Issuer;
|
||||
|
||||
/// <summary>
|
||||
/// Key Type
|
||||
/// </summary>
|
||||
public PublicKeyType KeyType;
|
||||
|
||||
/// <summary>
|
||||
/// Name
|
||||
/// </summary>
|
||||
public string Name;
|
||||
|
||||
/// <summary>
|
||||
/// Expiration time as UNIX Timestamp, used at least for CTCert
|
||||
/// </summary>
|
||||
public uint ExpirationTime;
|
||||
|
||||
// This contains the Public Key (i.e. Modulus & Public Exponent)
|
||||
#region RSA-4096 and RSA-2048
|
||||
|
||||
/// <summary>
|
||||
/// Modulus
|
||||
/// </summary>
|
||||
public byte[] RSAModulus;
|
||||
|
||||
/// <summary>
|
||||
/// Public Exponent
|
||||
/// </summary>
|
||||
public uint RSAPublicExponent;
|
||||
|
||||
/// <summary>
|
||||
/// Padding
|
||||
/// </summary>
|
||||
public byte[] RSAPadding;
|
||||
|
||||
#endregion
|
||||
|
||||
// This contains the ECC public key, and is as follows:
|
||||
#region ECC
|
||||
|
||||
/// <summary>
|
||||
/// Public Key
|
||||
/// </summary>
|
||||
public byte[] ECCPublicKey;
|
||||
|
||||
/// <summary>
|
||||
/// Padding
|
||||
/// </summary>
|
||||
public byte[] ECCPadding;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
21
BurnOutSharp.Models/N3DS/CodeSetInfo.cs
Normal file
21
BurnOutSharp.Models/N3DS/CodeSetInfo.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
namespace BurnOutSharp.Models.N3DS
|
||||
{
|
||||
/// <see href="https://www.3dbrew.org/wiki/NCCH/Extended_Header#Code_Set_Info"/>
|
||||
public sealed class CodeSetInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Address
|
||||
/// </summary>
|
||||
public uint Address;
|
||||
|
||||
/// <summary>
|
||||
/// Physical region size (in page-multiples)
|
||||
/// </summary>
|
||||
public uint PhysicalRegionSizeInPages;
|
||||
|
||||
/// <summary>
|
||||
/// Size (in bytes)
|
||||
/// </summary>
|
||||
public uint SizeInBytes;
|
||||
}
|
||||
}
|
||||
25
BurnOutSharp.Models/N3DS/Constants.cs
Normal file
25
BurnOutSharp.Models/N3DS/Constants.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
namespace BurnOutSharp.Models.N3DS
|
||||
{
|
||||
public static class Constants
|
||||
{
|
||||
// ExeFS
|
||||
public static readonly byte[] CodeSegmentName = new byte[] { 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x00, 0x00, 0x00 }; // .code\0\0\0
|
||||
|
||||
// NCCH
|
||||
public const string NCCHMagicNumber = "NCCH";
|
||||
|
||||
// NCSD
|
||||
public const string NCSDMagicNumber = "NCSD";
|
||||
|
||||
// RomFS
|
||||
public const string RomFSMagicNumber = "IVFC";
|
||||
public const uint RomFSSecondMagicNumber = 0x10000;
|
||||
|
||||
// Setup Keys and IVs
|
||||
public static byte[] PlainCounter = new byte[] { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
||||
public static byte[] ExefsCounter = new byte[] { 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
||||
public static byte[] RomfsCounter = new byte[] { 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
||||
|
||||
public const int CXTExtendedDataHeaderLength = 0x800;
|
||||
}
|
||||
}
|
||||
38
BurnOutSharp.Models/N3DS/ContentChunkRecord.cs
Normal file
38
BurnOutSharp.Models/N3DS/ContentChunkRecord.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
namespace BurnOutSharp.Models.N3DS
|
||||
{
|
||||
/// <summary>
|
||||
/// There is one of these for each content contained in this title.
|
||||
/// (Determined by "Content Count" in the TMD Header).
|
||||
/// </summary>
|
||||
/// <see href="https://www.3dbrew.org/wiki/Title_metadata#Content_chunk_records"/>
|
||||
public sealed class ContentChunkRecord
|
||||
{
|
||||
/// <summary>
|
||||
/// Content id
|
||||
/// </summary>
|
||||
public uint ContentId;
|
||||
|
||||
/// <summary>
|
||||
/// Content index
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This does not apply to DLC.
|
||||
/// </remarks>
|
||||
public ContentIndex ContentIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Content type
|
||||
/// </summary>
|
||||
public TMDContentType ContentType;
|
||||
|
||||
/// <summary>
|
||||
/// Content size
|
||||
/// </summary>
|
||||
public ulong ContentSize;
|
||||
|
||||
/// <summary>
|
||||
/// SHA-256 hash
|
||||
/// </summary>
|
||||
public byte[] SHA256Hash;
|
||||
}
|
||||
}
|
||||
24
BurnOutSharp.Models/N3DS/ContentInfoRecord.cs
Normal file
24
BurnOutSharp.Models/N3DS/ContentInfoRecord.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
namespace BurnOutSharp.Models.N3DS
|
||||
{
|
||||
/// <summary>
|
||||
/// There are 64 of these records, usually only the first is used.
|
||||
/// </summary>
|
||||
/// <see href="https://www.3dbrew.org/wiki/Title_metadata#Content_Info_Records"/>
|
||||
public sealed class ContentInfoRecord
|
||||
{
|
||||
/// <summary>
|
||||
/// Content index offset
|
||||
/// </summary>
|
||||
public ushort ContentIndexOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Content command count [k]
|
||||
/// </summary>
|
||||
public ushort ContentCommandCount;
|
||||
|
||||
/// <summary>
|
||||
/// SHA-256 hash of the next k content records that have not been hashed yet
|
||||
/// </summary>
|
||||
public byte[] UnhashedContentRecordsSHA256Hash;
|
||||
}
|
||||
}
|
||||
31
BurnOutSharp.Models/N3DS/DevelopmentCardInfoHeader.cs
Normal file
31
BurnOutSharp.Models/N3DS/DevelopmentCardInfoHeader.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
namespace BurnOutSharp.Models.N3DS
|
||||
{
|
||||
/// <see href="https://www.3dbrew.org/wiki/NCSD#Development_Card_Info_Header_Extension"/>
|
||||
public sealed class DevelopmentCardInfoHeader
|
||||
{
|
||||
/// <summary>
|
||||
/// InitialData
|
||||
/// </summary>
|
||||
public InitialData InitialData;
|
||||
|
||||
/// <summary>
|
||||
/// CardDeviceReserved1
|
||||
/// </summary>
|
||||
public byte[] CardDeviceReserved1;
|
||||
|
||||
/// <summary>
|
||||
/// TitleKey
|
||||
/// </summary>
|
||||
public byte[] TitleKey;
|
||||
|
||||
/// <summary>
|
||||
/// CardDeviceReserved2
|
||||
/// </summary>
|
||||
public byte[] CardDeviceReserved2;
|
||||
|
||||
/// <summary>
|
||||
/// TestData
|
||||
/// </summary>
|
||||
public TestData TestData;
|
||||
}
|
||||
}
|
||||
230
BurnOutSharp.Models/N3DS/Enums.cs
Normal file
230
BurnOutSharp.Models/N3DS/Enums.cs
Normal file
@@ -0,0 +1,230 @@
|
||||
using System;
|
||||
|
||||
namespace BurnOutSharp.Models.N3DS
|
||||
{
|
||||
// TODO: Fix this, I don't think it's correct
|
||||
[Flags]
|
||||
public enum ARM9AccessControlDescriptors : byte
|
||||
{
|
||||
MountNandRoot = 0x01,
|
||||
MountNandroWriteAccess = 0x02,
|
||||
MountTwlnRoot = 0x04,
|
||||
MountWnandRoot = 0x08,
|
||||
MountCardSPI = 0x0F,
|
||||
UseSDIF3 = 0x10,
|
||||
CreateSeed = 0x20,
|
||||
UseCardSPI = 0x40,
|
||||
SDApplication = 0x80,
|
||||
MountSdmcWriteAccess = 0xF0,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum ARM11LSCFlag0 : byte
|
||||
{
|
||||
IdealProcessor = 0x01 | 0x02,
|
||||
|
||||
AffinityMask = 0x04 | 0x08,
|
||||
|
||||
/// <summary>
|
||||
/// Value Description
|
||||
/// 0 Prod (64MB of usable application memory)
|
||||
/// 1 Undefined (unusable)
|
||||
/// 2 Dev1 (96MB of usable application memory)
|
||||
/// 3 Dev2 (80MB of usable application memory)
|
||||
/// 4 Dev3 (72MB of usable application memory)
|
||||
/// 5 Dev4 (32MB of usable application memory)
|
||||
/// 6-7 Undefined Same as Prod?
|
||||
/// </summary>
|
||||
Old3DSSystemMode = 0x0F | 0x10 | 0x20 | 0x40,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum ARM11LSCFlag1 : byte
|
||||
{
|
||||
EnableL2Cache = 0x01,
|
||||
Cpuspeed_804MHz = 0x02,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum ARM11LSCFlag2 : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Value Description
|
||||
/// 0 Legacy (use Old3DS system mode)
|
||||
/// 1 Prod (124MB of usable application memory)
|
||||
/// 2 Dev1 (178MB of usable application memory)
|
||||
/// 3 Dev2 (124MB of usable application memory)
|
||||
/// 4-7 Undefined Same as Prod?
|
||||
/// </summary>
|
||||
New3DSSystemMode = 0x01 | 0x02 | 0x04 | 0x08,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum BitMasks : byte
|
||||
{
|
||||
FixedCryptoKey = 0x01,
|
||||
NoMountRomFs = 0x02,
|
||||
NoCrypto = 0x04,
|
||||
NewKeyYGenerator = 0x20,
|
||||
}
|
||||
|
||||
public enum ContentIndex : ushort
|
||||
{
|
||||
/// <summary>
|
||||
/// Main Content (.CXI for 3DS executable content/.CFA for 3DS Data Archives/.SRL for TWL content)
|
||||
/// </summary>
|
||||
MainContent = 0x0000,
|
||||
|
||||
/// <summary>
|
||||
/// Home Menu Manual (.CFA)
|
||||
/// </summary>
|
||||
HomeMenuManual = 0x0001,
|
||||
|
||||
/// <summary>
|
||||
/// DLP Child Container (.CFA)
|
||||
/// </summary>
|
||||
DLPChildContainer = 0x0002,
|
||||
}
|
||||
|
||||
public enum ContentPlatform : byte
|
||||
{
|
||||
CTR = 0x01,
|
||||
Snake = 0x02, // New3DS
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum ContentType : byte
|
||||
{
|
||||
Data = 0x01,
|
||||
Executable = 0x02,
|
||||
SystemUpdate = 0x04,
|
||||
Manual = 0x08,
|
||||
Child = 0x04 | 0x08,
|
||||
Trial = 0x10,
|
||||
}
|
||||
|
||||
public enum CryptoMethod : byte
|
||||
{
|
||||
Original = 0x00,
|
||||
Seven = 0x01,
|
||||
NineThree = 0x0A,
|
||||
NineSix = 0x0B,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum FilesystemAccessInfo : ulong
|
||||
{
|
||||
CategorySystemApplication = 0x1,
|
||||
CategoryHardwareCheck = 0x2,
|
||||
CategoryFilesystemTool = 0x4,
|
||||
Debug = 0x8,
|
||||
TWLCardBackup = 0x10,
|
||||
TWLNANDData = 0x20,
|
||||
BOSS = 0x40,
|
||||
sdmcRoot = 0x80,
|
||||
Core = 0x100,
|
||||
nandRootroReadOnly = 0x200,
|
||||
nandRootrw = 0x400,
|
||||
nandrootroWriteAccess = 0x800,
|
||||
CategorySystemSettings = 0x1000,
|
||||
Cardboard = 0x2000,
|
||||
ExportImportIVS = 0x4000,
|
||||
sdmcRootWriteOnly = 0x8000,
|
||||
SwitchCleanup = 0x10000, // Introduced in 3.0.0?
|
||||
SavedataMove = 0x20000, // Introduced in 5.0.0
|
||||
Shop = 0x40000, // Introduced in 5.0.0
|
||||
Shell = 0x80000, // Introduced in 5.0.0
|
||||
CategoryHomeMenu = 0x100000, // Introduced in 6.0.0
|
||||
SeedDB = 0x200000, // Introduced in 9.6.0-X FIRM. Home Menu has this bit set starting with 9.6.0-X.
|
||||
}
|
||||
|
||||
public enum FilesystemType : ulong
|
||||
{
|
||||
None = 0,
|
||||
Normal = 1,
|
||||
FIRM = 3,
|
||||
AGB_FIRMSave = 4,
|
||||
}
|
||||
|
||||
public enum MediaCardDeviceType : byte
|
||||
{
|
||||
NORFlash = 0x01,
|
||||
None = 0x02,
|
||||
BT = 0x03,
|
||||
}
|
||||
|
||||
public enum MediaPlatformIndex : byte
|
||||
{
|
||||
CTR = 0x01,
|
||||
}
|
||||
|
||||
public enum MediaTypeIndex : byte
|
||||
{
|
||||
InnerDevice = 0x00,
|
||||
Card1 = 0x01,
|
||||
Card2 = 0x02,
|
||||
ExtendedDevice = 0x03,
|
||||
}
|
||||
|
||||
public enum NCCHFlags
|
||||
{
|
||||
CryptoMethod = 0x03,
|
||||
ContentPlatform = 0x04,
|
||||
ContentTypeBitMask = 0x05,
|
||||
ContentUnitSize = 0x06,
|
||||
BitMasks = 0x07,
|
||||
}
|
||||
|
||||
public enum NCSDFlags
|
||||
{
|
||||
BackupWriteWaitTime = 0x00,
|
||||
MediaCardDevice3X = 0x03,
|
||||
MediaPlatformIndex = 0x04,
|
||||
MediaTypeIndex = 0x05,
|
||||
MediaUnitSize = 0x06,
|
||||
MediaCardDevice2X = 0x07,
|
||||
}
|
||||
|
||||
public enum PublicKeyType : uint
|
||||
{
|
||||
RSA_4096 = 0x00000000,
|
||||
RSA_2048 = 0x01000000,
|
||||
EllipticCurve = 0x02000000,
|
||||
}
|
||||
|
||||
public enum ResourceLimitCategory
|
||||
{
|
||||
APPLICATION = 0,
|
||||
SYS_APPLET = 1,
|
||||
LIB_APPLET = 2,
|
||||
OTHER = 3,
|
||||
}
|
||||
|
||||
// Note: These are reversed because of how C# reads values
|
||||
public enum SignatureType : uint
|
||||
{
|
||||
RSA_4096_SHA1 = 0x00000100,
|
||||
RSA_2048_SHA1 = 0x01000100,
|
||||
ECDSA_SHA1 = 0x02000100,
|
||||
RSA_4096_SHA256 = 0x03000100,
|
||||
RSA_2048_SHA256 = 0x04000100,
|
||||
ECDSA_SHA256 = 0x05000100,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum StorageInfoOtherAttributes : byte
|
||||
{
|
||||
NotUseROMFS = 0x01,
|
||||
UseExtendedSavedataAccess = 0x02,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum TMDContentType : ushort
|
||||
{
|
||||
Encrypted = 0x0001,
|
||||
Disc = 0x0002,
|
||||
CFM = 0x0004,
|
||||
Optional = 0x4000,
|
||||
Shared = 0x8000,
|
||||
}
|
||||
}
|
||||
33
BurnOutSharp.Models/N3DS/ExeFSFileHeader.cs
Normal file
33
BurnOutSharp.Models/N3DS/ExeFSFileHeader.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
namespace BurnOutSharp.Models.N3DS
|
||||
{
|
||||
/// <summary>
|
||||
/// There are a maximum of 10 file headers in the ExeFS format. (This maximum
|
||||
/// number of file headers is disputable, with makerom indicating a maximum of
|
||||
/// 8 sections and makecia indicating a maximum of 10. From a non-SDK point of
|
||||
/// view, the ExeFS header format can hold no more than 10 file headers within
|
||||
/// the currently define size of 0x200 bytes.)
|
||||
/// </summary>
|
||||
/// <see href="https://www.3dbrew.org/wiki/ExeFS#File_headers"/>
|
||||
public sealed class ExeFSFileHeader
|
||||
{
|
||||
/// <summary>
|
||||
/// File name
|
||||
/// </summary>
|
||||
public string FileName;
|
||||
|
||||
/// <summary>
|
||||
/// File offset
|
||||
/// </summary>
|
||||
public uint FileOffset;
|
||||
|
||||
/// <summary>
|
||||
/// File size
|
||||
/// </summary>
|
||||
public uint FileSize;
|
||||
|
||||
/// <summary>
|
||||
/// SHA256 hash calculated over the entire file contents
|
||||
/// </summary>
|
||||
public byte[] FileHash;
|
||||
}
|
||||
}
|
||||
32
BurnOutSharp.Models/N3DS/ExeFSHeader.cs
Normal file
32
BurnOutSharp.Models/N3DS/ExeFSHeader.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
namespace BurnOutSharp.Models.N3DS
|
||||
{
|
||||
/// <summary>
|
||||
/// ExeFS or Executable Filesystem contains information related to the
|
||||
/// executable program, and is the part of the CXI format.
|
||||
///
|
||||
/// The ExeFS usually contains one or more of the following files:
|
||||
/// - .code Contains the code binary, which can be optionally reverse-LZSS compressed via an exheader flag.
|
||||
/// - logo Contains distribution licensing Binary data.
|
||||
/// - banner Contains the banner which homemenu uses for this CXI.
|
||||
/// - icon Contains the icon which homemenu displays for this CXI.
|
||||
/// </summary>
|
||||
/// <see href="https://www.3dbrew.org/wiki/ExeFS"/>
|
||||
public sealed class ExeFSHeader
|
||||
{
|
||||
/// <summary>
|
||||
/// File headers (10 headers maximum, 16 bytes each)
|
||||
/// </summary>
|
||||
public ExeFSFileHeader[] FileHeaders;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved;
|
||||
|
||||
/// <summary>
|
||||
/// File hashes (10 hashes maximum, 32 bytes each, one for each header)
|
||||
/// </summary>
|
||||
/// <remarks>SHA-256 hashes</remarks>
|
||||
public byte[][] FileHashes;
|
||||
}
|
||||
}
|
||||
36
BurnOutSharp.Models/N3DS/InitialData.cs
Normal file
36
BurnOutSharp.Models/N3DS/InitialData.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
namespace BurnOutSharp.Models.N3DS
|
||||
{
|
||||
/// <see href="https://www.3dbrew.org/wiki/NCSD#InitialData"/>
|
||||
public sealed class InitialData
|
||||
{
|
||||
/// <summary>
|
||||
/// Card seed keyY (first u64 is Media ID (same as first NCCH partitionId))
|
||||
/// </summary>
|
||||
public byte[] CardSeedKeyY;
|
||||
|
||||
/// <summary>
|
||||
/// Encrypted card seed (AES-CCM, keyslot 0x3B for retail cards, see CTRCARD_SECSEED)
|
||||
/// </summary>
|
||||
public byte[] EncryptedCardSeed;
|
||||
|
||||
/// <summary>
|
||||
/// Card seed AES-MAC
|
||||
/// </summary>
|
||||
public byte[] CardSeedAESMAC;
|
||||
|
||||
/// <summary>
|
||||
/// Card seed nonce
|
||||
/// </summary>
|
||||
public byte[] CardSeedNonce;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved3
|
||||
/// </summary>
|
||||
public byte[] Reserved;
|
||||
|
||||
/// <summary>
|
||||
/// Copy of first NCCH header (excluding RSA signature)
|
||||
/// </summary>
|
||||
public NCCHHeader BackupHeader;
|
||||
}
|
||||
}
|
||||
32
BurnOutSharp.Models/N3DS/MetaData.cs
Normal file
32
BurnOutSharp.Models/N3DS/MetaData.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
namespace BurnOutSharp.Models.N3DS
|
||||
{
|
||||
/// <see href="https://www.3dbrew.org/wiki/CIA#Meta"/>
|
||||
public sealed class MetaData
|
||||
{
|
||||
/// <summary>
|
||||
/// Title ID dependency list - Taken from the application's ExHeader
|
||||
/// </summary>
|
||||
/// TODO: Determine numeric format of each entry
|
||||
public byte[] TitleIDDependencyList;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved1;
|
||||
|
||||
/// <summary>
|
||||
/// Core Version
|
||||
/// </summary>
|
||||
public uint CoreVersion;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved2;
|
||||
|
||||
/// <summary>
|
||||
/// Icon Data(.ICN) - Taken from the application's ExeFS
|
||||
/// </summary>
|
||||
public byte[] IconData;
|
||||
}
|
||||
}
|
||||
36
BurnOutSharp.Models/N3DS/NCCHExtendedHeader.cs
Normal file
36
BurnOutSharp.Models/N3DS/NCCHExtendedHeader.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
namespace BurnOutSharp.Models.N3DS
|
||||
{
|
||||
/// <summary>
|
||||
/// The exheader has two sections:
|
||||
/// - The actual exheader data, containing System Control Info (SCI) and Access Control Info (ACI);
|
||||
/// - A signed copy of NCCH HDR public key, and exheader ACI. This version of the ACI is used as limitation to the actual ACI.
|
||||
/// </summary>
|
||||
/// <see href="https://www.3dbrew.org/wiki/NCCH/Extended_Header"/>
|
||||
public sealed class NCCHExtendedHeader
|
||||
{
|
||||
/// <summary>
|
||||
/// SCI
|
||||
/// </summary>
|
||||
public SystemControlInfo SCI;
|
||||
|
||||
/// <summary>
|
||||
/// ACI
|
||||
/// </summary>
|
||||
public AccessControlInfo ACI;
|
||||
|
||||
/// <summary>
|
||||
/// AccessDesc signature (RSA-2048-SHA256)
|
||||
/// </summary>
|
||||
public byte[] AccessDescSignature;
|
||||
|
||||
/// <summary>
|
||||
/// NCCH HDR RSA-2048 public key
|
||||
/// </summary>
|
||||
public byte[] NCCHHDRPublicKey;
|
||||
|
||||
/// <summary>
|
||||
/// ACI (for limitation of first ACI)
|
||||
/// </summary>
|
||||
public AccessControlInfo ACIForLimitations;
|
||||
}
|
||||
}
|
||||
156
BurnOutSharp.Models/N3DS/NCCHHeader.cs
Normal file
156
BurnOutSharp.Models/N3DS/NCCHHeader.cs
Normal file
@@ -0,0 +1,156 @@
|
||||
namespace BurnOutSharp.Models.N3DS
|
||||
{
|
||||
/// <see href="https://www.3dbrew.org/wiki/NCCH#NCCH_Header"/>
|
||||
public sealed class NCCHHeader
|
||||
{
|
||||
/// <summary>
|
||||
/// RSA-2048 signature of the NCCH header, using SHA-256.
|
||||
/// </summary>
|
||||
public byte[] RSA2048Signature;
|
||||
|
||||
/// <summary>
|
||||
/// Magic ID, always 'NCCH'
|
||||
/// </summary>
|
||||
public string MagicID;
|
||||
|
||||
/// <summary>
|
||||
/// Content size, in media units (1 media unit = 0x200 bytes)
|
||||
/// </summary>
|
||||
public uint ContentSizeInMediaUnits;
|
||||
|
||||
/// <summary>
|
||||
/// Partition ID
|
||||
/// </summary>
|
||||
public ulong PartitionId;
|
||||
|
||||
/// <summary>
|
||||
/// Maker code
|
||||
/// </summary>
|
||||
public ushort MakerCode;
|
||||
|
||||
/// <summary>
|
||||
/// Version
|
||||
/// </summary>
|
||||
public ushort Version;
|
||||
|
||||
/// <summary>
|
||||
/// When ncchflag[7] = 0x20 starting with FIRM 9.6.0-X, this is compared with the first output u32 from a
|
||||
/// SHA256 hash. The data used for that hash is 0x18-bytes: [0x10-long title-unique content lock seed]
|
||||
/// [programID from NCCH + 0x118]. This hash is only used for verification of the content lock seed, and
|
||||
/// is not the actual keyY.
|
||||
/// </summary>
|
||||
public uint VerificationHash;
|
||||
|
||||
/// <summary>
|
||||
/// Program ID
|
||||
/// </summary>
|
||||
public byte[] ProgramId;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved1;
|
||||
|
||||
/// <summary>
|
||||
/// Logo Region SHA-256 hash. (For applications built with SDK 5+) (Supported from firmware: 5.0.0-11)
|
||||
/// </summary>
|
||||
public byte[] LogoRegionHash;
|
||||
|
||||
/// <summary>
|
||||
/// Product code
|
||||
/// </summary>
|
||||
public string ProductCode;
|
||||
|
||||
/// <summary>
|
||||
/// Extended header SHA-256 hash (SHA256 of 2x Alignment Size, beginning at 0x0 of ExHeader)
|
||||
/// </summary>
|
||||
public byte[] ExtendedHeaderHash;
|
||||
|
||||
/// <summary>
|
||||
/// Extended header size, in bytes
|
||||
/// </summary>
|
||||
public uint ExtendedHeaderSizeInBytes;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved2;
|
||||
|
||||
/// <summary>
|
||||
/// Flags
|
||||
/// </summary>
|
||||
public NCCHHeaderFlags Flags;
|
||||
|
||||
/// <summary>
|
||||
/// Plain region offset, in media units
|
||||
/// </summary>
|
||||
public uint PlainRegionOffsetInMediaUnits;
|
||||
|
||||
/// <summary>
|
||||
/// Plain region size, in media units
|
||||
/// </summary>
|
||||
public uint PlainRegionSizeInMediaUnits;
|
||||
|
||||
/// <summary>
|
||||
/// Logo Region offset, in media units (For applications built with SDK 5+) (Supported from firmware: 5.0.0-11)
|
||||
/// </summary>
|
||||
public uint LogoRegionOffsetInMediaUnits;
|
||||
|
||||
/// <summary>
|
||||
/// Logo Region size, in media units (For applications built with SDK 5+) (Supported from firmware: 5.0.0-11)
|
||||
/// </summary>
|
||||
public uint LogoRegionSizeInMediaUnits;
|
||||
|
||||
/// <summary>
|
||||
/// ExeFS offset, in media units
|
||||
/// </summary>
|
||||
public uint ExeFSOffsetInMediaUnits;
|
||||
|
||||
/// <summary>
|
||||
/// ExeFS size, in media units
|
||||
/// </summary>
|
||||
public uint ExeFSSizeInMediaUnits;
|
||||
|
||||
/// <summary>
|
||||
/// ExeFS hash region size, in media units
|
||||
/// </summary>
|
||||
public uint ExeFSHashRegionSizeInMediaUnits;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved3;
|
||||
|
||||
/// <summary>
|
||||
/// RomFS offset, in media units
|
||||
/// </summary>
|
||||
public uint RomFSOffsetInMediaUnits;
|
||||
|
||||
/// <summary>
|
||||
/// RomFS size, in media units
|
||||
/// </summary>
|
||||
public uint RomFSSizeInMediaUnits;
|
||||
|
||||
/// <summary>
|
||||
/// RomFS hash region size, in media units
|
||||
/// </summary>
|
||||
public uint RomFSHashRegionSizeInMediaUnits;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved4;
|
||||
|
||||
/// <summary>
|
||||
/// ExeFS superblock SHA-256 hash - (SHA-256 hash, starting at 0x0 of the ExeFS over the number of
|
||||
/// media units specified in the ExeFS hash region size)
|
||||
/// </summary>
|
||||
public byte[] ExeFSSuperblockHash;
|
||||
|
||||
/// <summary>
|
||||
/// RomFS superblock SHA-256 hash - (SHA-256 hash, starting at 0x0 of the RomFS over the number
|
||||
/// of media units specified in the RomFS hash region size)
|
||||
/// </summary>
|
||||
public byte[] RomFSSuperblockHash;
|
||||
}
|
||||
}
|
||||
49
BurnOutSharp.Models/N3DS/NCCHHeaderFlags.cs
Normal file
49
BurnOutSharp.Models/N3DS/NCCHHeaderFlags.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
namespace BurnOutSharp.Models.N3DS
|
||||
{
|
||||
/// <see href="https://www.3dbrew.org/wiki/NCCH#NCCH_Flags"/>
|
||||
public sealed class NCCHHeaderFlags
|
||||
{
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte Reserved0;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte Reserved1;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte Reserved2;
|
||||
|
||||
/// <summary>
|
||||
/// Crypto Method: When this is non-zero, a NCCH crypto method using two keyslots is used.
|
||||
/// </summary>
|
||||
public CryptoMethod CryptoMethod;
|
||||
|
||||
/// <summary>
|
||||
/// Content Platform: 1 = CTR, 2 = snake (New 3DS).
|
||||
/// </summary>
|
||||
public ContentPlatform ContentPlatform;
|
||||
|
||||
/// <summary>
|
||||
/// Content Type Bit-masks: Data = 0x1, Executable = 0x2, SystemUpdate = 0x4, Manual = 0x8,
|
||||
/// Child = (0x4|0x8), Trial = 0x10. When 'Data' is set, but not 'Executable', NCCH is a CFA.
|
||||
/// Otherwise when 'Executable' is set, NCCH is a CXI.
|
||||
/// </summary>
|
||||
public ContentType MediaPlatformIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Content Unit Size i.e. u32 ContentUnitSize = 0x200*2^flags[6];
|
||||
/// </summary>
|
||||
public byte ContentUnitSize;
|
||||
|
||||
/// <summary>
|
||||
/// Bit-masks: FixedCryptoKey = 0x1, NoMountRomFs = 0x2, NoCrypto = 0x4, using a new keyY
|
||||
/// generator = 0x20(starting with FIRM 9.6.0-X).
|
||||
/// </summary>
|
||||
public BitMasks BitMasks;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user