From ae9dc9f9ac7864ef11dc20ee5d17583566857ff2 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Fri, 8 Sep 2023 22:45:21 -0400 Subject: [PATCH] Add and convert NE --- Bytes/NewExecutable.Deserializer.cs | 28 ++ Streams/NewExecutable.Deserializer.cs | 479 ++++++++++++++++++++++++++ Streams/NewExecutable.Serializer.cs | 16 + 3 files changed, 523 insertions(+) create mode 100644 Bytes/NewExecutable.Deserializer.cs create mode 100644 Streams/NewExecutable.Deserializer.cs create mode 100644 Streams/NewExecutable.Serializer.cs diff --git a/Bytes/NewExecutable.Deserializer.cs b/Bytes/NewExecutable.Deserializer.cs new file mode 100644 index 00000000..cff73a73 --- /dev/null +++ b/Bytes/NewExecutable.Deserializer.cs @@ -0,0 +1,28 @@ +using System.IO; +using SabreTools.Models.NewExecutable; + +namespace SabreTools.Serialization.Bytes +{ + public partial class NewExecutable : 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.NewExecutable().Deserialize(dataStream); + } + } +} \ No newline at end of file diff --git a/Streams/NewExecutable.Deserializer.cs b/Streams/NewExecutable.Deserializer.cs new file mode 100644 index 00000000..50444311 --- /dev/null +++ b/Streams/NewExecutable.Deserializer.cs @@ -0,0 +1,479 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using SabreTools.IO; +using SabreTools.Models.NewExecutable; +using static SabreTools.Models.NewExecutable.Constants; + +namespace SabreTools.Serialization.Streams +{ + public partial class NewExecutable : 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 Executable Header + + // Try to parse the executable header + data.Seek(initialOffset + stub.Header.NewExeHeaderAddr, SeekOrigin.Begin); + var executableHeader = ParseExecutableHeader(data); + if (executableHeader == null) + return null; + + // Set the executable header + executable.Header = executableHeader; + + #endregion + + #region Segment Table + + // If the offset for the segment table doesn't exist + int tableAddress = initialOffset + + (int)stub.Header.NewExeHeaderAddr + + executableHeader.SegmentTableOffset; + if (tableAddress >= data.Length) + return executable; + + // Try to parse the segment table + data.Seek(tableAddress, SeekOrigin.Begin); + var segmentTable = ParseSegmentTable(data, executableHeader.FileSegmentCount); + if (segmentTable == null) + return null; + + // Set the segment table + executable.SegmentTable = segmentTable; + + #endregion + + #region Resource Table + + // If the offset for the segment table doesn't exist + tableAddress = initialOffset + + (int)stub.Header.NewExeHeaderAddr + + executableHeader.SegmentTableOffset; + if (tableAddress >= data.Length) + return executable; + + // Try to parse the resource table + data.Seek(tableAddress, SeekOrigin.Begin); + var resourceTable = ParseResourceTable(data, executableHeader.ResourceEntriesCount); + if (resourceTable == null) + return null; + + // Set the resource table + executable.ResourceTable = resourceTable; + + #endregion + + #region Resident-Name Table + + // If the offset for the resident-name table doesn't exist + tableAddress = initialOffset + + (int)stub.Header.NewExeHeaderAddr + + executableHeader.ResidentNameTableOffset; + int endOffset = initialOffset + + (int)stub.Header.NewExeHeaderAddr + + executableHeader.ModuleReferenceTableOffset; + if (tableAddress >= data.Length) + return executable; + + // Try to parse the resident-name table + data.Seek(tableAddress, SeekOrigin.Begin); + var residentNameTable = ParseResidentNameTable(data, endOffset); + if (residentNameTable == null) + return null; + + // Set the resident-name table + executable.ResidentNameTable = residentNameTable; + + #endregion + + #region Module-Reference Table + + // If the offset for the module-reference table doesn't exist + tableAddress = initialOffset + + (int)stub.Header.NewExeHeaderAddr + + executableHeader.ModuleReferenceTableOffset; + if (tableAddress >= data.Length) + return executable; + + // Try to parse the module-reference table + data.Seek(tableAddress, SeekOrigin.Begin); + var moduleReferenceTable = ParseModuleReferenceTable(data, executableHeader.ModuleReferenceTableSize); + if (moduleReferenceTable == null) + return null; + + // Set the module-reference table + executable.ModuleReferenceTable = moduleReferenceTable; + + #endregion + + #region Imported-Name Table + + // If the offset for the imported-name table doesn't exist + tableAddress = initialOffset + + (int)stub.Header.NewExeHeaderAddr + + executableHeader.ImportedNamesTableOffset; + endOffset = initialOffset + + (int)stub.Header.NewExeHeaderAddr + + executableHeader.EntryTableOffset; + if (tableAddress >= data.Length) + return executable; + + // Try to parse the imported-name table + data.Seek(tableAddress, SeekOrigin.Begin); + var importedNameTable = ParseImportedNameTable(data, endOffset); + if (importedNameTable == null) + return null; + + // Set the imported-name table + executable.ImportedNameTable = importedNameTable; + + #endregion + + #region Entry Table + + // If the offset for the imported-name table doesn't exist + tableAddress = initialOffset + + (int)stub.Header.NewExeHeaderAddr + + executableHeader.EntryTableOffset; + endOffset = initialOffset + + (int)stub.Header.NewExeHeaderAddr + + executableHeader.EntryTableOffset + + executableHeader.EntryTableSize; + if (tableAddress >= data.Length) + return executable; + + // Try to parse the imported-name table + data.Seek(tableAddress, SeekOrigin.Begin); + var entryTable = ParseEntryTable(data, endOffset); + if (entryTable == null) + return null; + + // Set the entry table + executable.EntryTable = entryTable; + + #endregion + + #region Nonresident-Name Table + + // If the offset for the nonresident-name table doesn't exist + tableAddress = initialOffset + + (int)executableHeader.NonResidentNamesTableOffset; + endOffset = initialOffset + + (int)executableHeader.NonResidentNamesTableOffset + + executableHeader.NonResidentNameTableSize; + if (tableAddress >= data.Length) + return executable; + + // Try to parse the nonresident-name table + data.Seek(tableAddress, SeekOrigin.Begin); + var nonResidentNameTable = ParseNonResidentNameTable(data, endOffset); + if (nonResidentNameTable == null) + return null; + + // Set the nonresident-name table + executable.NonResidentNameTable = nonResidentNameTable; + + #endregion + + return executable; + } + + /// + /// Parse a Stream into a New Executable header + /// + /// Stream to parse + /// Filled executable header on success, null on error + private static ExecutableHeader ParseExecutableHeader(Stream data) + { + // TODO: Use marshalling here instead of building + var header = new ExecutableHeader(); + + byte[] magic = data.ReadBytes(2); + header.Magic = Encoding.ASCII.GetString(magic); + if (header.Magic != SignatureString) + return null; + + header.LinkerVersion = data.ReadByteValue(); + header.LinkerRevision = data.ReadByteValue(); + header.EntryTableOffset = data.ReadUInt16(); + header.EntryTableSize = data.ReadUInt16(); + header.CrcChecksum = data.ReadUInt32(); + header.FlagWord = (HeaderFlag)data.ReadUInt16(); + header.AutomaticDataSegmentNumber = data.ReadUInt16(); + header.InitialHeapAlloc = data.ReadUInt16(); + header.InitialStackAlloc = data.ReadUInt16(); + header.InitialCSIPSetting = data.ReadUInt32(); + header.InitialSSSPSetting = data.ReadUInt32(); + header.FileSegmentCount = data.ReadUInt16(); + header.ModuleReferenceTableSize = data.ReadUInt16(); + header.NonResidentNameTableSize = data.ReadUInt16(); + header.SegmentTableOffset = data.ReadUInt16(); + header.ResourceTableOffset = data.ReadUInt16(); + header.ResidentNameTableOffset = data.ReadUInt16(); + header.ModuleReferenceTableOffset = data.ReadUInt16(); + header.ImportedNamesTableOffset = data.ReadUInt16(); + header.NonResidentNamesTableOffset = data.ReadUInt32(); + header.MovableEntriesCount = data.ReadUInt16(); + header.SegmentAlignmentShiftCount = data.ReadUInt16(); + header.ResourceEntriesCount = data.ReadUInt16(); + header.TargetOperatingSystem = (OperatingSystem)data.ReadByteValue(); + header.AdditionalFlags = (OS2Flag)data.ReadByteValue(); + header.ReturnThunkOffset = data.ReadUInt16(); + header.SegmentReferenceThunkOffset = data.ReadUInt16(); + header.MinCodeSwapAreaSize = data.ReadUInt16(); + header.WindowsSDKRevision = data.ReadByteValue(); + header.WindowsSDKVersion = data.ReadByteValue(); + + return header; + } + + /// + /// Parse a Stream into a segment table + /// + /// Stream to parse + /// Number of segment table entries to read + /// Filled segment table on success, null on error + private static SegmentTableEntry[] ParseSegmentTable(Stream data, int count) + { + // TODO: Use marshalling here instead of building + var segmentTable = new SegmentTableEntry[count]; + + for (int i = 0; i < count; i++) + { + var entry = new SegmentTableEntry(); + entry.Offset = data.ReadUInt16(); + entry.Length = data.ReadUInt16(); + entry.FlagWord = (SegmentTableEntryFlag)data.ReadUInt16(); + entry.MinimumAllocationSize = data.ReadUInt16(); + segmentTable[i] = entry; + } + + return segmentTable; + } + + /// + /// Parse a Stream into a resource table + /// + /// Stream to parse + /// Number of resource table entries to read + /// Filled resource table on success, null on error + private static ResourceTable ParseResourceTable(Stream data, int count) + { + long initialOffset = data.Position; + + // TODO: Use marshalling here instead of building + var resourceTable = new ResourceTable(); + + resourceTable.AlignmentShiftCount = data.ReadUInt16(); + resourceTable.ResourceTypes = new ResourceTypeInformationEntry[count]; + for (int i = 0; i < resourceTable.ResourceTypes.Length; i++) + { + var entry = new ResourceTypeInformationEntry(); + entry.TypeID = data.ReadUInt16(); + entry.ResourceCount = data.ReadUInt16(); + entry.Reserved = data.ReadUInt32(); + entry.Resources = new ResourceTypeResourceEntry[entry.ResourceCount]; + for (int j = 0; j < entry.ResourceCount; j++) + { + // TODO: Should we read and store the resource data? + var resource = new ResourceTypeResourceEntry(); + resource.Offset = data.ReadUInt16(); + resource.Length = data.ReadUInt16(); + resource.FlagWord = (ResourceTypeResourceFlag)data.ReadUInt16(); + resource.ResourceID = data.ReadUInt16(); + resource.Reserved = data.ReadUInt32(); + entry.Resources[j] = resource; + } + resourceTable.ResourceTypes[i] = entry; + } + + // Get the full list of unique string offsets + var stringOffsets = resourceTable.ResourceTypes + .Where(rt => rt.IsIntegerType() == false) + .Select(rt => rt.TypeID) + .Union(resourceTable.ResourceTypes + .SelectMany(rt => rt.Resources) + .Where(r => r.IsIntegerType() == false) + .Select(r => r.ResourceID)) + .Distinct() + .OrderBy(o => o) + .ToList(); + + // Populate the type and name string dictionary + resourceTable.TypeAndNameStrings = new Dictionary(); + for (int i = 0; i < stringOffsets.Count; i++) + { + int stringOffset = (int)(stringOffsets[i] + initialOffset); + data.Seek(stringOffset, SeekOrigin.Begin); + var str = new ResourceTypeAndNameString(); + str.Length = data.ReadByteValue(); + str.Text = data.ReadBytes(str.Length); + resourceTable.TypeAndNameStrings[stringOffsets[i]] = str; + } + + return resourceTable; + } + + /// + /// Parse a Stream into a resident-name table + /// + /// Stream to parse + /// First address not part of the resident-name table + /// Filled resident-name table on success, null on error + private static ResidentNameTableEntry[] ParseResidentNameTable(Stream data, int endOffset) + { + // TODO: Use marshalling here instead of building + var residentNameTable = new List(); + + while (data.Position < endOffset) + { + var entry = new ResidentNameTableEntry(); + entry.Length = data.ReadByteValue(); + entry.NameString = data.ReadBytes(entry.Length); + entry.OrdinalNumber = data.ReadUInt16(); + residentNameTable.Add(entry); + } + + return residentNameTable.ToArray(); + } + + /// + /// Parse a Stream into a module-reference table + /// + /// Stream to parse + /// Number of module-reference table entries to read + /// Filled module-reference table on success, null on error + private static ModuleReferenceTableEntry[] ParseModuleReferenceTable(Stream data, int count) + { + // TODO: Use marshalling here instead of building + var moduleReferenceTable = new ModuleReferenceTableEntry[count]; + + for (int i = 0; i < count; i++) + { + var entry = new ModuleReferenceTableEntry(); + entry.Offset = data.ReadUInt16(); + moduleReferenceTable[i] = entry; + } + + return moduleReferenceTable; + } + + /// + /// Parse a Stream into an imported-name table + /// + /// Stream to parse + /// First address not part of the imported-name table + /// Filled imported-name table on success, null on error + private static Dictionary ParseImportedNameTable(Stream data, int endOffset) + { + // TODO: Use marshalling here instead of building + var importedNameTable = new Dictionary(); + + while (data.Position < endOffset) + { + ushort currentOffset = (ushort)data.Position; + var entry = new ImportedNameTableEntry(); + entry.Length = data.ReadByteValue(); + entry.NameString = data.ReadBytes(entry.Length); + importedNameTable[currentOffset] = entry; + } + + return importedNameTable; + } + + /// + /// Parse a Stream into an entry table + /// + /// Stream to parse + /// First address not part of the entry table + /// Filled entry table on success, null on error + private static EntryTableBundle[] ParseEntryTable(Stream data, int endOffset) + { + // TODO: Use marshalling here instead of building + var entryTable = new List(); + + while (data.Position < endOffset) + { + 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.ReadUInt16(); + break; + + case SegmentEntryType.MoveableSegment: + entry.MoveableFlagWord = (MoveableSegmentEntryFlag)data.ReadByteValue(); + entry.MoveableReserved = data.ReadUInt16(); + entry.MoveableSegmentNumber = data.ReadByteValue(); + entry.MoveableOffset = data.ReadUInt16(); + break; + } + entryTable.Add(entry); + } + + return entryTable.ToArray(); + } + + /// + /// Parse a Stream into a nonresident-name table + /// + /// Stream to parse + /// First address not part of the nonresident-name table + /// Filled nonresident-name table on success, null on error + private static NonResidentNameTableEntry[] ParseNonResidentNameTable(Stream data, int endOffset) + { + // TODO: Use marshalling here instead of building + var residentNameTable = new List(); + + while (data.Position < endOffset) + { + var entry = new NonResidentNameTableEntry(); + entry.Length = data.ReadByteValue(); + entry.NameString = data.ReadBytes(entry.Length); + entry.OrdinalNumber = data.ReadUInt16(); + residentNameTable.Add(entry); + } + + return residentNameTable.ToArray(); + } + } +} \ No newline at end of file diff --git a/Streams/NewExecutable.Serializer.cs b/Streams/NewExecutable.Serializer.cs new file mode 100644 index 00000000..1004076c --- /dev/null +++ b/Streams/NewExecutable.Serializer.cs @@ -0,0 +1,16 @@ +using System; +using System.IO; +using SabreTools.Models.NewExecutable; + +namespace SabreTools.Serialization.Streams +{ + public partial class NewExecutable : 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