mirror of
https://github.com/SabreTools/SabreTools.Serialization.git
synced 2026-02-17 05:45:38 +00:00
697 lines
27 KiB
C#
697 lines
27 KiB
C#
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Text;
|
|
using SabreTools.Data.Extensions;
|
|
using SabreTools.Data.Models.NewExecutable;
|
|
using SabreTools.IO.Extensions;
|
|
using static SabreTools.Data.Models.NewExecutable.Constants;
|
|
|
|
#pragma warning disable IDE0017 // Simplify object initialization
|
|
namespace SabreTools.Serialization.Readers
|
|
{
|
|
public class NewExecutable : BaseBinaryReader<Executable>
|
|
{
|
|
/// <inheritdoc/>
|
|
public override Executable? Deserialize(Stream? data)
|
|
{
|
|
// If the data is invalid
|
|
if (data is null || !data.CanRead)
|
|
return null;
|
|
|
|
try
|
|
{
|
|
// Cache the current offset
|
|
long initialOffset = data.Position;
|
|
|
|
// Create a new executable to fill
|
|
var nex = new Executable();
|
|
|
|
#region MS-DOS Stub
|
|
|
|
// Parse the MS-DOS stub
|
|
var stub = new MSDOS().Deserialize(data);
|
|
if (stub?.Header is null || stub.Header.NewExeHeaderAddr == 0)
|
|
return null;
|
|
|
|
// Set the MS-DOS stub
|
|
nex.Stub = stub;
|
|
|
|
#endregion
|
|
|
|
#region Executable Header
|
|
|
|
// Get the new executable offset
|
|
long newExeOffset = initialOffset + stub.Header.NewExeHeaderAddr;
|
|
if (newExeOffset < initialOffset || newExeOffset > data.Length)
|
|
return null;
|
|
|
|
// Try to parse the executable header
|
|
data.SeekIfPossible(newExeOffset, SeekOrigin.Begin);
|
|
var header = ParseExecutableHeader(data);
|
|
if (header.Magic != SignatureString)
|
|
return null;
|
|
|
|
// Set the executable header
|
|
nex.Header = header;
|
|
|
|
#endregion
|
|
|
|
#region Segment Table
|
|
|
|
// If the offset for the segment table doesn't exist
|
|
long tableAddress = initialOffset + stub.Header.NewExeHeaderAddr + header.SegmentTableOffset;
|
|
if (tableAddress >= data.Length)
|
|
return nex;
|
|
|
|
// Seek to the segment table
|
|
data.SeekIfPossible(tableAddress, SeekOrigin.Begin);
|
|
|
|
// Set the segment table
|
|
nex.SegmentTable = new SegmentTableEntry[header.FileSegmentCount];
|
|
for (int i = 0; i < header.FileSegmentCount; i++)
|
|
{
|
|
nex.SegmentTable[i] = ParseSegmentTableEntry(data, initialOffset);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Resource Table
|
|
|
|
// If the offset for the segment table doesn't exist
|
|
tableAddress = initialOffset + stub.Header.NewExeHeaderAddr + header.ResourceTableOffset;
|
|
if (tableAddress >= data.Length)
|
|
return nex;
|
|
|
|
// Seek to the resource table
|
|
data.SeekIfPossible(tableAddress, SeekOrigin.Begin);
|
|
|
|
// Set the resource table
|
|
nex.ResourceTable = ParseResourceTable(data, header.ResourceEntriesCount);
|
|
|
|
#endregion
|
|
|
|
#region Resident-Name Table
|
|
|
|
// If the offset for the resident-name table doesn't exist
|
|
tableAddress = initialOffset + stub.Header.NewExeHeaderAddr + header.ResidentNameTableOffset;
|
|
long endOffset = initialOffset + stub.Header.NewExeHeaderAddr + header.ModuleReferenceTableOffset;
|
|
if (tableAddress >= data.Length)
|
|
return nex;
|
|
|
|
// Seek to the resident-name table
|
|
data.SeekIfPossible(tableAddress, SeekOrigin.Begin);
|
|
|
|
// Set the resident-name table
|
|
nex.ResidentNameTable = ParseResidentNameTable(data, endOffset);
|
|
|
|
#endregion
|
|
|
|
#region Module-Reference Table
|
|
|
|
// If the offset for the module-reference table doesn't exist
|
|
tableAddress = initialOffset + stub.Header.NewExeHeaderAddr + header.ModuleReferenceTableOffset;
|
|
if (tableAddress >= data.Length)
|
|
return nex;
|
|
|
|
// Seek to the module-reference table
|
|
data.SeekIfPossible(tableAddress, SeekOrigin.Begin);
|
|
|
|
// Set the module-reference table
|
|
nex.ModuleReferenceTable = new ModuleReferenceTableEntry[header.ModuleReferenceTableSize];
|
|
for (int i = 0; i < header.ModuleReferenceTableSize; i++)
|
|
{
|
|
nex.ModuleReferenceTable[i] = ParseModuleReferenceTableEntry(data);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Imported-Name Table
|
|
|
|
// If the offset for the imported-name table doesn't exist
|
|
tableAddress = initialOffset + stub.Header.NewExeHeaderAddr + header.ImportedNamesTableOffset;
|
|
endOffset = initialOffset + stub.Header.NewExeHeaderAddr + header.EntryTableOffset;
|
|
if (tableAddress >= data.Length)
|
|
return nex;
|
|
|
|
// Seek to the imported-name table
|
|
data.SeekIfPossible(tableAddress, SeekOrigin.Begin);
|
|
|
|
// Set the imported-name table
|
|
nex.ImportedNameTable = ParseImportedNameTable(data, endOffset);
|
|
|
|
#endregion
|
|
|
|
#region Entry Table
|
|
|
|
// If the offset for the imported-name table doesn't exist
|
|
tableAddress = initialOffset + stub.Header.NewExeHeaderAddr + header.EntryTableOffset;
|
|
endOffset = initialOffset + stub.Header.NewExeHeaderAddr + header.EntryTableOffset + header.EntryTableSize;
|
|
if (tableAddress >= data.Length)
|
|
return nex;
|
|
|
|
// Seek to the imported-name table
|
|
data.SeekIfPossible(tableAddress, SeekOrigin.Begin);
|
|
|
|
// Set the entry table
|
|
nex.EntryTable = ParseEntryTable(data, endOffset);
|
|
|
|
#endregion
|
|
|
|
#region Nonresident-Name Table
|
|
|
|
// If the offset for the nonresident-name table doesn't exist
|
|
tableAddress = initialOffset + header.NonResidentNamesTableOffset;
|
|
endOffset = initialOffset + header.NonResidentNamesTableOffset + header.NonResidentNameTableSize;
|
|
if (tableAddress >= data.Length)
|
|
return nex;
|
|
|
|
// Seek to the nonresident-name table
|
|
data.SeekIfPossible(tableAddress, SeekOrigin.Begin);
|
|
|
|
// Set the nonresident-name table
|
|
nex.NonResidentNameTable = ParseNonResidentNameTable(data, endOffset);
|
|
|
|
#endregion
|
|
|
|
return nex;
|
|
}
|
|
catch
|
|
{
|
|
// Ignore the actual error
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into an entry table
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <param name="endOffset">First address not part of the entry table</param>
|
|
/// <returns>Filled entry table on success, null on error</returns>
|
|
public static EntryTableBundle[] ParseEntryTable(Stream data, long endOffset)
|
|
{
|
|
var entryTable = new List<EntryTableBundle>();
|
|
|
|
while (data.Position < endOffset && data.Position < data.Length)
|
|
{
|
|
var entry = new EntryTableBundle();
|
|
entry.EntryCount = data.ReadByteValue();
|
|
entry.SegmentIndicator = data.ReadByteValue();
|
|
switch (entry.GetEntryType())
|
|
{
|
|
case SegmentEntryType.Unused:
|
|
break;
|
|
|
|
case SegmentEntryType.FixedSegment:
|
|
entry.FixedFlagWord = (FixedSegmentEntryFlag)data.ReadByteValue();
|
|
entry.FixedOffset = data.ReadUInt16LittleEndian();
|
|
break;
|
|
|
|
case SegmentEntryType.MoveableSegment:
|
|
entry.MoveableFlagWord = (MoveableSegmentEntryFlag)data.ReadByteValue();
|
|
entry.MoveableReserved = data.ReadUInt16LittleEndian();
|
|
entry.MoveableSegmentNumber = data.ReadByteValue();
|
|
entry.MoveableOffset = data.ReadUInt16LittleEndian();
|
|
break;
|
|
|
|
default:
|
|
// TODO: Log invalid values
|
|
break;
|
|
}
|
|
|
|
entryTable.Add(entry);
|
|
}
|
|
|
|
return [.. entryTable];
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into an ExecutableHeader
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <returns>Filled ExecutableHeader on success, null on error</returns>
|
|
public static ExecutableHeader ParseExecutableHeader(Stream data)
|
|
{
|
|
var obj = new ExecutableHeader();
|
|
|
|
byte[] magic = data.ReadBytes(2);
|
|
obj.Magic = Encoding.ASCII.GetString(magic);
|
|
obj.LinkerVersion = data.ReadByteValue();
|
|
obj.LinkerRevision = data.ReadByteValue();
|
|
obj.EntryTableOffset = data.ReadUInt16LittleEndian();
|
|
obj.EntryTableSize = data.ReadUInt16LittleEndian();
|
|
obj.CrcChecksum = data.ReadUInt32LittleEndian();
|
|
obj.FlagWord = (HeaderFlag)data.ReadUInt16LittleEndian();
|
|
obj.AutomaticDataSegmentNumber = data.ReadUInt16LittleEndian();
|
|
obj.InitialHeapAlloc = data.ReadUInt16LittleEndian();
|
|
obj.InitialStackAlloc = data.ReadUInt16LittleEndian();
|
|
obj.InitialCSIPSetting = data.ReadUInt32LittleEndian();
|
|
obj.InitialSSSPSetting = data.ReadUInt32LittleEndian();
|
|
obj.FileSegmentCount = data.ReadUInt16LittleEndian();
|
|
obj.ModuleReferenceTableSize = data.ReadUInt16LittleEndian();
|
|
obj.NonResidentNameTableSize = data.ReadUInt16LittleEndian();
|
|
obj.SegmentTableOffset = data.ReadUInt16LittleEndian();
|
|
obj.ResourceTableOffset = data.ReadUInt16LittleEndian();
|
|
obj.ResidentNameTableOffset = data.ReadUInt16LittleEndian();
|
|
obj.ModuleReferenceTableOffset = data.ReadUInt16LittleEndian();
|
|
obj.ImportedNamesTableOffset = data.ReadUInt16LittleEndian();
|
|
obj.NonResidentNamesTableOffset = data.ReadUInt32LittleEndian();
|
|
obj.MovableEntriesCount = data.ReadUInt16LittleEndian();
|
|
obj.SegmentAlignmentShiftCount = data.ReadUInt16LittleEndian();
|
|
obj.ResourceEntriesCount = data.ReadUInt16LittleEndian();
|
|
obj.TargetOperatingSystem = (OperatingSystem)data.ReadByteValue();
|
|
obj.AdditionalFlags = (OS2Flag)data.ReadByteValue();
|
|
obj.ReturnThunkOffset = data.ReadUInt16LittleEndian();
|
|
obj.SegmentReferenceThunkOffset = data.ReadUInt16LittleEndian();
|
|
obj.MinCodeSwapAreaSize = data.ReadUInt16LittleEndian();
|
|
obj.WindowsSDKRevision = data.ReadByteValue();
|
|
obj.WindowsSDKVersion = data.ReadByteValue();
|
|
|
|
return obj;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into an imported-name table
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <param name="endOffset">First address not part of the imported-name table</param>
|
|
/// <returns>Filled imported-name table on success, null on error</returns>
|
|
public static Dictionary<ushort, ImportedNameTableEntry> ParseImportedNameTable(Stream data, long endOffset)
|
|
{
|
|
var obj = new Dictionary<ushort, ImportedNameTableEntry>();
|
|
|
|
while (data.Position < endOffset && data.Position < data.Length)
|
|
{
|
|
ushort currentOffset = (ushort)data.Position;
|
|
obj[currentOffset] = ParseImportedNameTableEntry(data);
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into an ImportedNameTableEntry
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <returns>Filled ImportedNameTableEntry on success, null on error</returns>
|
|
public static ImportedNameTableEntry ParseImportedNameTableEntry(Stream data)
|
|
{
|
|
var obj = new ImportedNameTableEntry();
|
|
|
|
obj.Length = data.ReadByteValue();
|
|
obj.NameString = data.ReadBytes(obj.Length);
|
|
|
|
return obj;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into an ImportOrdinalRelocationRecord
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <returns>Filled ImportOrdinalRelocationRecord on success, null on error</returns>
|
|
public static ImportOrdinalRelocationRecord ParseImportOrdinalRelocationRecord(Stream data)
|
|
{
|
|
var obj = new ImportOrdinalRelocationRecord();
|
|
|
|
obj.Index = data.ReadUInt16LittleEndian();
|
|
obj.Ordinal = data.ReadUInt16LittleEndian();
|
|
|
|
return obj;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into an InternalRefRelocationRecord
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <returns>Filled InternalRefRelocationRecord on success, null on error</returns>
|
|
public static InternalRefRelocationRecord ParseInternalRefRelocationRecord(Stream data)
|
|
{
|
|
var obj = new InternalRefRelocationRecord();
|
|
|
|
obj.SegmentNumber = data.ReadByteValue();
|
|
obj.Reserved = data.ReadByteValue();
|
|
obj.Offset = data.ReadUInt16LittleEndian();
|
|
|
|
return obj;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into an ModuleReferenceTableEntry
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <returns>Filled ModuleReferenceTableEntry on success, null on error</returns>
|
|
public static ModuleReferenceTableEntry ParseModuleReferenceTableEntry(Stream data)
|
|
{
|
|
var obj = new ModuleReferenceTableEntry();
|
|
|
|
obj.Offset = data.ReadUInt16LittleEndian();
|
|
|
|
return obj;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into an ImportNameRelocationRecord
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <returns>Filled ImportNameRelocationRecord on success, null on error</returns>
|
|
public static ImportNameRelocationRecord ParseImportNameRelocationRecord(Stream data)
|
|
{
|
|
var obj = new ImportNameRelocationRecord();
|
|
|
|
obj.Index = data.ReadUInt16LittleEndian();
|
|
obj.Offset = data.ReadUInt16LittleEndian();
|
|
|
|
return obj;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a nonresident-name table
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <param name="endOffset">First address not part of the nonresident-name table</param>
|
|
/// <returns>Filled nonresident-name table on success, null on error</returns>
|
|
public static NonResidentNameTableEntry[] ParseNonResidentNameTable(Stream data, long endOffset)
|
|
{
|
|
var obj = new List<NonResidentNameTableEntry>();
|
|
|
|
while (data.Position < endOffset && data.Position < data.Length)
|
|
{
|
|
var entry = ParseNonResidentNameTableEntry(data);
|
|
obj.Add(entry);
|
|
}
|
|
|
|
return [.. obj];
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a NonResidentNameTableEntry
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <returns>Filled NonResidentNameTableEntry on success, null on error</returns>
|
|
public static NonResidentNameTableEntry ParseNonResidentNameTableEntry(Stream data)
|
|
{
|
|
var obj = new NonResidentNameTableEntry();
|
|
|
|
obj.Length = data.ReadByteValue();
|
|
obj.NameString = data.ReadBytes(obj.Length);
|
|
obj.OrdinalNumber = data.ReadUInt16LittleEndian();
|
|
|
|
return obj;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into an OSFixupRelocationRecord
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <returns>Filled OSFixupRelocationRecord on success, null on error</returns>
|
|
public static OSFixupRelocationRecord ParseOSFixupRelocationRecord(Stream data)
|
|
{
|
|
var obj = new OSFixupRelocationRecord();
|
|
|
|
obj.FixupType = (OSFixupType)data.ReadUInt16LittleEndian();
|
|
obj.Reserved = data.ReadUInt16LittleEndian();
|
|
|
|
return obj;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into an PerSegmentData
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <returns>Filled PerSegmentData on success, null on error</returns>
|
|
public static PerSegmentData ParsePerSegmentData(Stream data)
|
|
{
|
|
var obj = new PerSegmentData();
|
|
|
|
obj.RelocationRecordCount = data.ReadUInt16LittleEndian();
|
|
obj.RelocationRecords = new RelocationRecord[obj.RelocationRecordCount];
|
|
for (int i = 0; i < obj.RelocationRecords.Length; i++)
|
|
{
|
|
if (data.Position >= data.Length)
|
|
break;
|
|
|
|
var record = ParseRelocationRecord(data);
|
|
if (record is null)
|
|
break;
|
|
|
|
obj.RelocationRecords[i] = record;
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into an RelocationRecord
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <returns>Filled RelocationRecord on success, null on error</returns>
|
|
public static RelocationRecord? ParseRelocationRecord(Stream data)
|
|
{
|
|
// Handle partial relocation sections
|
|
if (data.Position > data.Length - 4)
|
|
return null;
|
|
|
|
var obj = new RelocationRecord();
|
|
|
|
obj.SourceType = (RelocationRecordSourceType)data.ReadByteValue();
|
|
obj.Flags = (RelocationRecordFlag)data.ReadByteValue();
|
|
obj.Offset = data.ReadUInt16LittleEndian();
|
|
|
|
// Handle incomplete entries
|
|
if (data.Position > data.Length - 4)
|
|
return obj;
|
|
|
|
switch (obj.Flags & RelocationRecordFlag.TARGET_MASK)
|
|
{
|
|
case RelocationRecordFlag.INTERNALREF:
|
|
obj.InternalRefRelocationRecord = ParseInternalRefRelocationRecord(data);
|
|
break;
|
|
case RelocationRecordFlag.IMPORTORDINAL:
|
|
obj.ImportOrdinalRelocationRecord = ParseImportOrdinalRelocationRecord(data);
|
|
break;
|
|
case RelocationRecordFlag.IMPORTNAME:
|
|
obj.ImportNameRelocationRecord = ParseImportNameRelocationRecord(data);
|
|
break;
|
|
case RelocationRecordFlag.OSFIXUP:
|
|
obj.OSFixupRelocationRecord = ParseOSFixupRelocationRecord(data);
|
|
break;
|
|
case RelocationRecordFlag.ADDITIVE:
|
|
// TODO: Figure out the record for this, if possible
|
|
break;
|
|
default:
|
|
// TODO: Log invalid values
|
|
break;
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a resident-name table
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <param name="endOffset">First address not part of the resident-name table</param>
|
|
/// <returns>Filled resident-name table on success, null on error</returns>
|
|
public static ResidentNameTableEntry[] ParseResidentNameTable(Stream data, long endOffset)
|
|
{
|
|
var obj = new List<ResidentNameTableEntry>();
|
|
|
|
while (data.Position < endOffset && data.Position < data.Length)
|
|
{
|
|
var entry = ParseResidentNameTableEntry(data);
|
|
obj.Add(entry);
|
|
}
|
|
|
|
return [.. obj];
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a ResidentNameTableEntry
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <returns>Filled ResidentNameTableEntry on success, null on error</returns>
|
|
public static ResidentNameTableEntry ParseResidentNameTableEntry(Stream data)
|
|
{
|
|
var obj = new ResidentNameTableEntry();
|
|
|
|
obj.Length = data.ReadByteValue();
|
|
obj.NameString = data.ReadBytes(obj.Length);
|
|
obj.OrdinalNumber = data.ReadUInt16LittleEndian();
|
|
|
|
return obj;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a ResourceTable
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <param name="count">Number of resource table entries to read</param>
|
|
/// <returns>Filled ResourceTable on success, null on error</returns>
|
|
public static ResourceTable ParseResourceTable(Stream data, ushort count)
|
|
{
|
|
long initialOffset = data.Position;
|
|
|
|
var resourceTable = new ResourceTable();
|
|
|
|
resourceTable.AlignmentShiftCount = data.ReadUInt16LittleEndian();
|
|
var resourceTypes = new List<ResourceTypeInformationEntry>();
|
|
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
var entry = ParseResourceTypeInformationEntry(data);
|
|
resourceTypes.Add(entry);
|
|
|
|
// A zero type ID marks the end of the resource type information blocks.
|
|
if (entry.TypeID == 0)
|
|
break;
|
|
}
|
|
|
|
resourceTable.ResourceTypes = [.. resourceTypes];
|
|
|
|
// Get the full list of unique string offsets
|
|
var stringOffsets = new List<ushort>();
|
|
foreach (var rtie in resourceTable.ResourceTypes)
|
|
{
|
|
// Skip invalid entries
|
|
if (rtie is null || rtie.TypeID == 0)
|
|
continue;
|
|
|
|
// Handle offset types
|
|
if (!rtie.IsIntegerType() && !stringOffsets.Contains(rtie.TypeID))
|
|
stringOffsets.Add(rtie.TypeID);
|
|
|
|
// Handle types with resources
|
|
foreach (var rtre in rtie.Resources)
|
|
{
|
|
// Skip invalid entries
|
|
if (rtre is null || rtre.IsIntegerType() || rtre.ResourceID == 0)
|
|
continue;
|
|
|
|
// Skip already added entries
|
|
if (stringOffsets.Contains(rtre.ResourceID))
|
|
continue;
|
|
|
|
stringOffsets.Add(rtre.ResourceID);
|
|
}
|
|
}
|
|
|
|
// Order the offsets list
|
|
stringOffsets.Sort();
|
|
|
|
// Populate the type and name string dictionary
|
|
resourceTable.TypeAndNameStrings = [];
|
|
for (int i = 0; i < stringOffsets.Count; i++)
|
|
{
|
|
int stringOffset = (int)(stringOffsets[i] + initialOffset);
|
|
data.SeekIfPossible(stringOffset, SeekOrigin.Begin);
|
|
|
|
var str = ParseResourceTypeAndNameString(data);
|
|
resourceTable.TypeAndNameStrings[stringOffsets[i]] = str;
|
|
}
|
|
|
|
return resourceTable;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into an ResourceTypeInformationEntry
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <returns>Filled ResourceTypeInformationEntry on success, null on error</returns>
|
|
public static ResourceTypeInformationEntry ParseResourceTypeInformationEntry(Stream data)
|
|
{
|
|
var obj = new ResourceTypeInformationEntry();
|
|
|
|
obj.TypeID = data.ReadUInt16LittleEndian();
|
|
obj.ResourceCount = data.ReadUInt16LittleEndian();
|
|
obj.Reserved = data.ReadUInt32LittleEndian();
|
|
|
|
// A zero type ID marks the end of the resource type information blocks.
|
|
if (obj.TypeID == 0)
|
|
return obj;
|
|
|
|
obj.Resources = new ResourceTypeResourceEntry[obj.ResourceCount];
|
|
for (int i = 0; i < obj.ResourceCount; i++)
|
|
{
|
|
// TODO: Should we read and store the resource data?
|
|
obj.Resources[i] = ParseResourceTypeResourceEntry(data);
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a ResourceTypeAndNameString
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <returns>Filled rResourceTypeAndNameString on success, null on error</returns>
|
|
public static ResourceTypeAndNameString ParseResourceTypeAndNameString(Stream data)
|
|
{
|
|
var obj = new ResourceTypeAndNameString();
|
|
|
|
obj.Length = data.ReadByteValue();
|
|
obj.Text = data.ReadBytes(obj.Length);
|
|
|
|
return obj;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into an ResourceTypeResourceEntry
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <returns>Filled ResourceTypeResourceEntry on success, null on error</returns>
|
|
public static ResourceTypeResourceEntry ParseResourceTypeResourceEntry(Stream data)
|
|
{
|
|
var obj = new ResourceTypeResourceEntry();
|
|
|
|
obj.Offset = data.ReadUInt16LittleEndian();
|
|
obj.Length = data.ReadUInt16LittleEndian();
|
|
obj.FlagWord = (ResourceTypeResourceFlag)data.ReadUInt16LittleEndian();
|
|
obj.ResourceID = data.ReadUInt16LittleEndian();
|
|
obj.Reserved = data.ReadUInt32LittleEndian();
|
|
|
|
return obj;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into an SegmentTableEntry
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <param name="initialOffset">Initial offset to use in address comparisons</param>
|
|
/// <returns>Filled SegmentTableEntry on success, null on error</returns>
|
|
public static SegmentTableEntry ParseSegmentTableEntry(Stream data, long initialOffset)
|
|
{
|
|
var obj = new SegmentTableEntry();
|
|
|
|
obj.Offset = data.ReadUInt16LittleEndian();
|
|
obj.Length = data.ReadUInt16LittleEndian();
|
|
obj.FlagWord = (SegmentTableEntryFlag)data.ReadUInt16LittleEndian();
|
|
obj.MinimumAllocationSize = data.ReadUInt16LittleEndian();
|
|
|
|
// If the data offset is invalid
|
|
if (obj.Offset < 0 || obj.Offset + initialOffset >= data.Length)
|
|
return obj;
|
|
|
|
// Cache the current offset
|
|
long currentOffset = data.Position;
|
|
|
|
// Seek to the data offset and read
|
|
data.SeekIfPossible(obj.Offset + initialOffset, SeekOrigin.Begin);
|
|
obj.Data = data.ReadBytes(obj.Length);
|
|
|
|
#if NET20 || NET35
|
|
if ((obj.FlagWord & SegmentTableEntryFlag.RELOCINFO) != 0)
|
|
#else
|
|
if (obj.FlagWord.HasFlag(flag: SegmentTableEntryFlag.RELOCINFO))
|
|
#endif
|
|
{
|
|
obj.PerSegmentData = ParsePerSegmentData(data);
|
|
}
|
|
|
|
// Seek back to the end of the entry
|
|
data.SeekIfPossible(currentOffset, SeekOrigin.Begin);
|
|
|
|
return obj;
|
|
}
|
|
}
|
|
}
|