using System.IO; using System.Linq; using BurnOutSharp.Models.NewExecutable; namespace BurnOutSharp.Builder { // TODO: Make Stream Data rely on Byte Data public static class NewExecutable { #region Byte Data /// /// Parse a byte array into a New Executable /// /// Byte array to parse /// Offset into the byte array /// Filled executable on success, null on error public static Executable ParseExecutable(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; // Cache the current offset int initialOffset = offset; // Create a new executable to fill var executable = new Executable(); #region MS-DOS Stub // Parse the MS-DOS stub var stub = MSDOS.ParseExecutable(data, offset); 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 var executableHeader = ParseExecutableHeader(data, offset); 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 var segmentTable = ParseSegmentTable(data, tableAddress, 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 var resourceTable = ParseResourceTable(data, tableAddress, executableHeader.FileSegmentCount); if (resourceTable == null) return null; // Set the resource table executable.ResourceTable = resourceTable; #endregion // TODO: Complete NE parsing return executable; } /// /// Parse a byte array into a New Executable header /// /// Byte array to parse /// Offset into the byte array /// Filled executable header on success, null on error private static ExecutableHeader ParseExecutableHeader(byte[] data, int offset) { // TODO: Use marshalling here instead of building var header = new ExecutableHeader(); header.Magic = new char[2]; for (int i = 0; i < header.Magic.Length; i++) { header.Magic[i] = data.ReadChar(ref offset); } if (header.Magic[0] != 'N' || header.Magic[1] != 'E') return null; header.LinkerVersion = data.ReadByte(ref offset); header.LinkerRevision = data.ReadByte(ref offset); header.EntryTableOffset = data.ReadUInt16(ref offset); header.EntryTableSize = data.ReadUInt16(ref offset); header.CrcChecksum = data.ReadUInt32(ref offset); header.FlagWord = (HeaderFlag)data.ReadUInt16(ref offset); header.AutomaticDataSegmentNumber = data.ReadUInt16(ref offset); header.InitialHeapAlloc = data.ReadUInt16(ref offset); header.InitialStackAlloc = data.ReadUInt16(ref offset); header.InitialCSIPSetting = data.ReadUInt32(ref offset); header.InitialSSSPSetting = data.ReadUInt32(ref offset); header.FileSegmentCount = data.ReadUInt16(ref offset); header.ModuleReferenceTableSize = data.ReadUInt16(ref offset); header.NonResidentNameTableSize = data.ReadUInt16(ref offset); header.SegmentTableOffset = data.ReadUInt16(ref offset); header.ResourceTableOffset = data.ReadUInt16(ref offset); header.ResidentNameTableOffset = data.ReadUInt16(ref offset); header.ModuleReferenceTableOffset = data.ReadUInt16(ref offset); header.ImportedNamesTableOffset = data.ReadUInt16(ref offset); header.NonResidentNamesTableOffset = data.ReadUInt32(ref offset); header.MovableEntriesCount = data.ReadUInt16(ref offset); header.SegmentAlignmentShiftCount = data.ReadUInt16(ref offset); header.ResourceEntriesCount = data.ReadUInt16(ref offset); header.TargetOperatingSystem = (OperatingSystem)data.ReadByte(ref offset); header.AdditionalFlags = (OS2Flag)data.ReadByte(ref offset); header.ReturnThunkOffset = data.ReadUInt16(ref offset); header.SegmentReferenceThunkOffset = data.ReadUInt16(ref offset); header.MinCodeSwapAreaSize = data.ReadUInt16(ref offset); header.WindowsSDKRevision = data.ReadByte(ref offset); header.WindowsSDKVersion = data.ReadByte(ref offset); return header; } /// /// Parse a byte array into a segment table /// /// Byte array to parse /// Offset into the byte array /// Number of segment table entries to read /// Filled segment table on success, null on error private static SegmentTableEntry[] ParseSegmentTable(byte[] data, int offset, 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(ref offset); entry.Length = data.ReadUInt16(ref offset); entry.FlagWord = (SegmentTableEntryFlag)data.ReadUInt16(ref offset); entry.MinimumAllocationSize = data.ReadUInt16(ref offset); segmentTable[i] = entry; } return segmentTable; } /// /// Parse a byte array into a resource table /// /// Byte array to parse /// Offset into the byte array /// Number of resource table entries to read /// Filled resource table on success, null on error private static ResourceTable ParseResourceTable(byte[] data, int offset, int count) { int initialOffset = offset; // TODO: Use marshalling here instead of building var resourceTable = new ResourceTable(); resourceTable.AlignmentShiftCount = data.ReadUInt16(ref offset); resourceTable.ResourceTypes = new ResourceTypeInformationEntry[count]; for (int i = 0; i < resourceTable.ResourceTypes.Length; i++) { var entry = new ResourceTypeInformationEntry(); entry.TypeID = data.ReadUInt16(ref offset); entry.ResourceCount = data.ReadUInt16(ref offset); entry.Reserved = data.ReadUInt32(ref offset); 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(ref offset); resource.Length = data.ReadUInt16(ref offset); resource.FlagWord = (ResourceTypeResourceFlag)data.ReadUInt16(ref offset); resource.ResourceID = data.ReadUInt16(ref offset); resource.Reserved = data.ReadUInt32(ref offset); 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 for (int i = 0; i < stringOffsets.Count; i++) { int stringOffset = stringOffsets[i] + initialOffset; var str = new ResourceTypeAndNameString(); str.Length = data.ReadByte(ref stringOffset); str.Text = data.ReadBytes(ref stringOffset, str.Length); resourceTable.TypeAndNameStrings[stringOffsets[i]] = str; } return resourceTable; } #endregion #region Stream Data /// /// Parse a Stream into a New Executable /// /// Stream to parse /// Filled executable on success, null on error public static Executable ParseExecutable(Stream data) { // If the data is invalid if (data == null) 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 = MSDOS.ParseExecutable(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 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.FileSegmentCount); if (resourceTable == null) return null; // Set the resource table executable.ResourceTable = resourceTable; #endregion // TODO: Complete NE parsing 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(); header.Magic = new char[2]; for (int i = 0; i < header.Magic.Length; i++) { header.Magic[i] = data.ReadChar(); } if (header.Magic[0] != 'N' || header.Magic[1] != 'E') 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 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; } #endregion } }