From 8b9952d0c701a28d987bee60fc3a17fc0289276f Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Fri, 8 Sep 2023 22:41:49 -0400 Subject: [PATCH] Add and convert LE/LX --- Bytes/LinearExecutable.Deserializer.cs | 28 + Streams/LinearExecutable.Deserializer.cs | 914 +++++++++++++++++++++++ Streams/LinearExecutable.Serializer.cs | 16 + 3 files changed, 958 insertions(+) create mode 100644 Bytes/LinearExecutable.Deserializer.cs create mode 100644 Streams/LinearExecutable.Deserializer.cs create mode 100644 Streams/LinearExecutable.Serializer.cs diff --git a/Bytes/LinearExecutable.Deserializer.cs b/Bytes/LinearExecutable.Deserializer.cs new file mode 100644 index 00000000..257e3089 --- /dev/null +++ b/Bytes/LinearExecutable.Deserializer.cs @@ -0,0 +1,28 @@ +using System.IO; +using SabreTools.Models.LinearExecutable; + +namespace SabreTools.Serialization.Bytes +{ + public partial class LinearExecutable : IByteSerializer + { + /// +#if NET48 + public Executable Deserialize(byte[] data, int offset) +#else + public Executable? Deserialize(byte[]? data, int offset) +#endif + { + // 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 new Streams.LinearExecutable().Deserialize(dataStream); + } + } +} \ No newline at end of file diff --git a/Streams/LinearExecutable.Deserializer.cs b/Streams/LinearExecutable.Deserializer.cs new file mode 100644 index 00000000..1020ff1e --- /dev/null +++ b/Streams/LinearExecutable.Deserializer.cs @@ -0,0 +1,914 @@ +using System.Collections.Generic; +using System.IO; +using System.Text; +using SabreTools.IO; +using SabreTools.Models.LinearExecutable; +using static SabreTools.Models.LinearExecutable.Constants; + +namespace SabreTools.Serialization.Streams +{ + public partial class LinearExecutable : IStreamSerializer + { + /// +#if NET48 + public Executable Deserialize(Stream data) +#else + public Executable? Deserialize(Stream? data) +#endif + { + // 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 executable to fill + var executable = new Executable(); + + #region MS-DOS Stub + + // Parse the MS-DOS stub + var stub = new MSDOS().Deserialize(data); + if (stub?.Header == null || stub.Header.NewExeHeaderAddr == 0) + return null; + + // Set the MS-DOS stub + executable.Stub = stub; + + #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(); + + // 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(); + + // 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(); + + // 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(); + + // 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; + } + + /// + /// Parse a Stream into an information block + /// + /// Stream to parse + /// Filled information block on success, null on error + private static InformationBlock ParseInformationBlock(Stream data) + { + // 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; + } + + /// + /// Parse a Stream into an object table entry + /// + /// Stream to parse + /// Filled object table entry on success, null on error + 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; + } + + /// + /// Parse a Stream into an object page map entry + /// + /// Stream to parse + /// Filled object page map entry on success, null on error + 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; + } + + /// + /// Parse a Stream into a resource table entry + /// + /// Stream to parse + /// Filled resource table entry on success, null on error + 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; + } + + /// + /// Parse a Stream into a resident names table entry + /// + /// Stream to parse + /// Filled resident names table entry on success, null on error + 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; + } + + /// + /// Parse a Stream into an entry table bundle + /// + /// Stream to parse + /// Filled entry table bundle on success, null on error + 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; + } + + /// + /// Parse a Stream into a module format directives table entry + /// + /// Stream to parse + /// Filled module format directives table entry on success, null on error + 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; + } + + /// + /// Parse a Stream into a verify record directive table entry + /// + /// Stream to parse + /// Filled verify record directive table entry on success, null on error + 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; + } + + /// + /// Parse a Stream into a fix-up page table entry + /// + /// Stream to parse + /// Filled fix-up page table entry on success, null on error + private static FixupPageTableEntry ParseFixupPageTableEntry(Stream data) + { + // TODO: Use marshalling here instead of building + var entry = new FixupPageTableEntry(); + + entry.Offset = data.ReadUInt32(); + + return entry; + } + + /// + /// Parse a Stream into a fix-up record table entry + /// + /// Stream to parse + /// Filled fix-up record table entry on success, null on error + 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; + } + + /// + /// Parse a Stream into a import module name table entry + /// + /// Stream to parse + /// Filled import module name table entry on success, null on error + 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; + } + + /// + /// Parse a Stream into a import module name table entry + /// + /// Stream to parse + /// Filled import module name table entry on success, null on error + 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; + } + + /// + /// Parse a Stream into a per-page checksum table entry + /// + /// Stream to parse + /// Filled per-page checksum table entry on success, null on error + private static PerPageChecksumTableEntry ParsePerPageChecksumTableEntry(Stream data) + { + // TODO: Use marshalling here instead of building + var entry = new PerPageChecksumTableEntry(); + + entry.Checksum = data.ReadUInt32(); + + return entry; + } + + /// + /// Parse a Stream into a non-resident names table entry + /// + /// Stream to parse + /// Filled non-resident names table entry on success, null on error + 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; + } + + /// + /// Parse a Stream into a debug information + /// + /// Stream to parse + /// Total size of the debug information + /// Filled debug information on success, null on error + 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; + } + } +} \ No newline at end of file diff --git a/Streams/LinearExecutable.Serializer.cs b/Streams/LinearExecutable.Serializer.cs new file mode 100644 index 00000000..3dcf5c79 --- /dev/null +++ b/Streams/LinearExecutable.Serializer.cs @@ -0,0 +1,16 @@ +using System; +using System.IO; +using SabreTools.Models.LinearExecutable; + +namespace SabreTools.Serialization.Streams +{ + public partial class LinearExecutable : IStreamSerializer + { + /// +#if NET48 + public Stream Serialize(Executable obj) => throw new NotImplementedException(); +#else + public Stream? Serialize(Executable? obj) => throw new NotImplementedException(); +#endif + } +} \ No newline at end of file