diff --git a/SabreTools.Serialization/Wrappers/NewExecutable.cs b/SabreTools.Serialization/Wrappers/NewExecutable.cs index f7ec8f7a..57985b7f 100644 --- a/SabreTools.Serialization/Wrappers/NewExecutable.cs +++ b/SabreTools.Serialization/Wrappers/NewExecutable.cs @@ -24,12 +24,278 @@ namespace SabreTools.Serialization.Wrappers /// public Models.NewExecutable.NonResidentNameTableEntry[]? NonResidentNameTable => Model.NonResidentNameTable; + /// + /// Address of the overlay, if it exists + /// + /// + public int OverlayAddress + { + get + { + lock (_sourceDataLock) + { + // Use the cached data if possible + if (_overlayAddress != null) + return _overlayAddress.Value; + + // Get the end of the file, if possible + int endOfFile = GetEndOfFile(); + if (endOfFile == -1) + return -1; + + // If a required property is missing + if (Header == null || SegmentTable == null || ResourceTable?.ResourceTypes == null) + return -1; + + // Search through the segments table to find the furthest + int endOfSectionData = -1; + foreach (var entry in SegmentTable) + { + int offset = (entry.Offset << Header.SegmentAlignmentShiftCount) + entry.Length; + if (offset > endOfSectionData) + endOfSectionData = offset; + } + + // Search through the resources table to find the furthest + foreach (var entry in ResourceTable.ResourceTypes) + { + // Skip invalid entries + if (entry.ResourceCount == 0 || entry.Resources == null || entry.Resources.Length == 0) + continue; + + foreach (var resource in entry.Resources) + { + int offset = (resource.Offset << ResourceTable.AlignmentShiftCount) + resource.Length; + if (offset > endOfSectionData) + endOfSectionData = offset; + } + } + + // If we didn't find the end of section data + if (endOfSectionData <= 0) + endOfSectionData = -1; + + // Adjust the position of the data by 705 bytes + // TODO: Investigate what the byte data is + endOfSectionData += 705; + + // Cache and return the position + _overlayAddress = endOfSectionData; + return _overlayAddress.Value; + } + } + } + + /// + /// Overlay data, if it exists + /// + /// + public byte[]? OverlayData + { + get + { + lock (_sourceDataLock) + { + // Use the cached data if possible + if (_overlayData != null) + return _overlayData; + + // Get the end of the file, if possible + int endOfFile = GetEndOfFile(); + if (endOfFile == -1) + return null; + + // If a required property is missing + if (Header == null || SegmentTable == null || ResourceTable?.ResourceTypes == null) + return null; + + // Search through the segments table to find the furthest + int endOfSectionData = -1; + foreach (var entry in SegmentTable) + { + int offset = (entry.Offset << Header.SegmentAlignmentShiftCount) + entry.Length; + if (offset > endOfSectionData) + endOfSectionData = offset; + } + + // Search through the resources table to find the furthest + foreach (var entry in ResourceTable.ResourceTypes) + { + // Skip invalid entries + if (entry.ResourceCount == 0 || entry.Resources == null || entry.Resources.Length == 0) + continue; + + foreach (var resource in entry.Resources) + { + int offset = (resource.Offset << ResourceTable.AlignmentShiftCount) + resource.Length; + if (offset > endOfSectionData) + endOfSectionData = offset; + } + } + + // If we didn't find the end of section data + if (endOfSectionData <= 0) + return null; + + // Adjust the position of the data by 705 bytes + // TODO: Investigate what the byte data is + endOfSectionData += 705; + + // If we're at the end of the file, cache an empty byte array + if (endOfSectionData >= endOfFile) + { + _overlayData = []; + return _overlayData; + } + + // Otherwise, cache and return the data + int overlayLength = endOfFile - endOfSectionData; + _overlayData = ReadFromDataSource(endOfSectionData, overlayLength); + return _overlayData; + } + } + } + + /// + /// Overlay strings, if they exist + /// + public List? OverlayStrings + { + get + { + lock (_sourceDataLock) + { + // Use the cached data if possible + if (_overlayStrings != null) + return _overlayStrings; + + // Get the end of the file, if possible + int endOfFile = GetEndOfFile(); + if (endOfFile == -1) + return null; + + // If a required property is missing + if (Header == null || SegmentTable == null || ResourceTable?.ResourceTypes == null) + return null; + + // Search through the segments table to find the furthest + int endOfSectionData = -1; + foreach (var entry in SegmentTable) + { + int offset = (entry.Offset << Header.SegmentAlignmentShiftCount) + entry.Length; + if (offset > endOfSectionData) + endOfSectionData = offset; + } + + // Search through the resources table to find the furthest + foreach (var entry in ResourceTable.ResourceTypes) + { + // Skip invalid entries + if (entry.ResourceCount == 0 || entry.Resources == null || entry.Resources.Length == 0) + continue; + + foreach (var resource in entry.Resources) + { + int offset = (resource.Offset << ResourceTable.AlignmentShiftCount) + resource.Length; + if (offset > endOfSectionData) + endOfSectionData = offset; + } + } + + // If we didn't find the end of section data + if (endOfSectionData <= 0) + return null; + + // Adjust the position of the data by 705 bytes + // TODO: Investigate what the byte data is + endOfSectionData += 705; + + // If we're at the end of the file, cache an empty list + if (endOfSectionData >= endOfFile) + { + _overlayStrings = []; + return _overlayStrings; + } + + // TODO: Revisit the 16 MiB limit + // Cap the check for overlay strings to 16 MiB (arbitrary) + int overlayLength = Math.Min(endOfFile - endOfSectionData, 16 * 1024 * 1024); + + // Otherwise, cache and return the strings + _overlayStrings = ReadStringsFromDataSource(endOfSectionData, overlayLength, charLimit: 3); + return _overlayStrings; + } + } + } + /// public Models.NewExecutable.ResidentNameTableEntry[]? ResidentNameTable => Model.ResidentNameTable; + /// + public Models.NewExecutable.ResourceTable? ResourceTable => Model.ResourceTable; + + /// + public Models.NewExecutable.SegmentTableEntry[]? SegmentTable => Model.SegmentTable; + /// public Models.MSDOS.Executable? Stub => Model.Stub; + /// + /// Stub executable data, if it exists + /// + public byte[]? StubExecutableData + { + get + { + lock (_sourceDataLock) + { + // If we already have cached data, just use that immediately + if (_stubExecutableData != null) + return _stubExecutableData; + + if (Stub?.Header?.NewExeHeaderAddr == null) + return null; + + // Populate the raw stub executable data based on the source + int endOfStubHeader = 0x40; + int lengthOfStubExecutableData = (int)Stub.Header.NewExeHeaderAddr - endOfStubHeader; + _stubExecutableData = ReadFromDataSource(endOfStubHeader, lengthOfStubExecutableData); + + // Cache and return the stub executable data, even if null + return _stubExecutableData; + } + } + } + + #endregion + + #region Instance Variables + + /// + /// Address of the overlay, if it exists + /// + private int? _overlayAddress = null; + + /// + /// Overlay data, if it exists + /// + private byte[]? _overlayData = null; + + /// + /// Overlay strings, if they exist + /// + private List? _overlayStrings = null; + + /// + /// Stub executable data, if it exists + /// + private byte[]? _stubExecutableData = null; + + /// + /// Lock object for reading from the source + /// + private readonly object _sourceDataLock = new(); + #endregion #region Constructors diff --git a/SabreTools.Serialization/Wrappers/PortableExecutable.cs b/SabreTools.Serialization/Wrappers/PortableExecutable.cs index 00355a20..3bc400cc 100644 --- a/SabreTools.Serialization/Wrappers/PortableExecutable.cs +++ b/SabreTools.Serialization/Wrappers/PortableExecutable.cs @@ -58,21 +58,21 @@ namespace SabreTools.Serialization.Wrappers lock (_sourceDataLock) { // If the section table is missing - if (Model.SectionTable == null) + if (SectionTable == null) return null; // If the address is missing - if (Model.OptionalHeader?.AddressOfEntryPoint == null) + if (OptionalHeader?.AddressOfEntryPoint == null) return null; // If we have no entry point - int entryPointAddress = (int)Model.OptionalHeader.AddressOfEntryPoint.ConvertVirtualAddress(Model.SectionTable); + int entryPointAddress = (int)OptionalHeader.AddressOfEntryPoint.ConvertVirtualAddress(SectionTable); if (entryPointAddress == 0) return null; // If the entry point matches with the start of a section, use that int entryPointSection = FindEntryPointSectionIndex(); - if (entryPointSection >= 0 && Model.OptionalHeader.AddressOfEntryPoint == Model.SectionTable[entryPointSection]?.VirtualAddress) + if (entryPointSection >= 0 && OptionalHeader.AddressOfEntryPoint == SectionTable[entryPointSection]?.VirtualAddress) return GetSectionData(entryPointSection); // If we already have cached data, just use that immediately @@ -213,27 +213,27 @@ namespace SabreTools.Serialization.Wrappers return -1; // If the section table is missing - if (Model.SectionTable == null) + if (SectionTable == null) return -1; // If we have certificate data, use that as the end - if (Model.OptionalHeader?.CertificateTable != null) + if (OptionalHeader?.CertificateTable != null) { - int certificateTableAddress = (int)Model.OptionalHeader.CertificateTable.VirtualAddress.ConvertVirtualAddress(Model.SectionTable); + int certificateTableAddress = (int)OptionalHeader.CertificateTable.VirtualAddress.ConvertVirtualAddress(SectionTable); if (certificateTableAddress != 0 && certificateTableAddress < endOfFile) endOfFile = certificateTableAddress; } // Search through all sections and find the furthest a section goes int endOfSectionData = -1; - foreach (var section in Model.SectionTable) + foreach (var section in SectionTable) { // If we have an invalid section if (section == null) continue; // If we have an invalid section address - int sectionAddress = (int)section.VirtualAddress.ConvertVirtualAddress(Model.SectionTable); + int sectionAddress = (int)section.VirtualAddress.ConvertVirtualAddress(SectionTable); if (sectionAddress == 0) continue; @@ -284,27 +284,27 @@ namespace SabreTools.Serialization.Wrappers return null; // If the section table is missing - if (Model.SectionTable == null) + if (SectionTable == null) return null; // If we have certificate data, use that as the end - if (Model.OptionalHeader?.CertificateTable != null) + if (OptionalHeader?.CertificateTable != null) { - int certificateTableAddress = (int)Model.OptionalHeader.CertificateTable.VirtualAddress.ConvertVirtualAddress(Model.SectionTable); + int certificateTableAddress = (int)OptionalHeader.CertificateTable.VirtualAddress.ConvertVirtualAddress(SectionTable); if (certificateTableAddress != 0 && certificateTableAddress < endOfFile) endOfFile = certificateTableAddress; } // Search through all sections and find the furthest a section goes int endOfSectionData = -1; - foreach (var section in Model.SectionTable) + foreach (var section in SectionTable) { // If we have an invalid section if (section == null) continue; // If we have an invalid section address - int sectionAddress = (int)section.VirtualAddress.ConvertVirtualAddress(Model.SectionTable); + int sectionAddress = (int)section.VirtualAddress.ConvertVirtualAddress(SectionTable); if (sectionAddress == 0) continue; @@ -362,27 +362,27 @@ namespace SabreTools.Serialization.Wrappers return null; // If the section table is missing - if (Model.SectionTable == null) + if (SectionTable == null) return null; // If we have certificate data, use that as the end - if (Model.OptionalHeader?.CertificateTable != null) + if (OptionalHeader?.CertificateTable != null) { - int certificateTableAddress = (int)Model.OptionalHeader.CertificateTable.VirtualAddress.ConvertVirtualAddress(Model.SectionTable); + int certificateTableAddress = (int)OptionalHeader.CertificateTable.VirtualAddress.ConvertVirtualAddress(SectionTable); if (certificateTableAddress != 0 && certificateTableAddress < endOfFile) endOfFile = certificateTableAddress; } // Search through all sections and find the furthest a section goes int endOfSectionData = -1; - foreach (var section in Model.SectionTable) + foreach (var section in SectionTable) { // If we have an invalid section if (section == null) continue; // If we have an invalid section address - int sectionAddress = (int)section.VirtualAddress.ConvertVirtualAddress(Model.SectionTable); + int sectionAddress = (int)section.VirtualAddress.ConvertVirtualAddress(SectionTable); if (sectionAddress == 0) continue; @@ -424,6 +424,9 @@ namespace SabreTools.Serialization.Wrappers } } + /// + public Models.PortableExecutable.ResourceDirectoryTable? ResourceDirectoryTable => Model.ResourceDirectoryTable; + /// /// Sanitized section names /// @@ -438,14 +441,14 @@ namespace SabreTools.Serialization.Wrappers return _sectionNames; // If there are no sections - if (Model.SectionTable == null) + if (SectionTable == null) return null; // Otherwise, build and return the cached array - _sectionNames = new string[Model.SectionTable.Length]; + _sectionNames = new string[SectionTable.Length]; for (int i = 0; i < _sectionNames.Length; i++) { - var section = Model.SectionTable[i]; + var section = SectionTable[i]; if (section == null) continue; @@ -510,13 +513,13 @@ namespace SabreTools.Serialization.Wrappers return _resourceData; // If we have no resource table, just return - if (Model.OptionalHeader?.ResourceTable == null - || Model.OptionalHeader.ResourceTable.VirtualAddress == 0 - || Model.ResourceDirectoryTable == null) + if (OptionalHeader?.ResourceTable == null + || OptionalHeader.ResourceTable.VirtualAddress == 0 + || ResourceDirectoryTable == null) return null; // Otherwise, build and return the cached dictionary - ParseResourceDirectoryTable(Model.ResourceDirectoryTable, types: []); + ParseResourceDirectoryTable(ResourceDirectoryTable, types: []); return _resourceData; } }