using System; using System.Collections.Generic; using System.IO; using SabreTools.Data.Models.NewExecutable; using SabreTools.IO.Extensions; using SabreTools.Text.Extensions; #pragma warning disable IDE0330 // Use 'System.Threading.Lock' namespace SabreTools.Wrappers { public partial class NewExecutable : WrapperBase { #region Descriptive Properties /// public override string DescriptionString => "New Executable (NE)"; #endregion #region Extension Properties /// public ExecutableHeader Header => Model.Header; /// public Dictionary ImportedNameTable => Model.ImportedNameTable; /// public NonResidentNameTableEntry[] NonResidentNameTable => Model.NonResidentNameTable; /// /// Address of the overlay, if it exists /// /// public long OverlayAddress { get { lock (_overlayAddressLock) { // Use the cached data if possible if (field >= 0) return field; // Get the available source length, if possible long dataLength = Length; if (dataLength == -1) { field = -1; return field; } // Search through the segments table to find the furthest long endOfSectionData = -1; foreach (var entry in SegmentTable) { // Get end of segment data long offset = (entry.Offset * (1 << Header.SegmentAlignmentShiftCount)) + entry.Length; // Read and find the end of the relocation data #if NET20 || NET35 if ((entry.FlagWord & SegmentTableEntryFlag.RELOCINFO) != 0) #else if (entry.FlagWord.HasFlag(SegmentTableEntryFlag.RELOCINFO)) #endif { lock (_dataSourceLock) { _dataSource.SeekIfPossible(offset, SeekOrigin.Begin); var relocationData = Serialization.Readers.NewExecutable.ParsePerSegmentData(_dataSource); offset = _dataSource.Position; } } 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.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 field = endOfSectionData; return field; } } } = -1; /// /// Overlay data, if it exists /// /// Caches up to 0x10000 bytes /// public byte[] OverlayData { get { lock (_overlayDataLock) { // Use the cached data if possible if (field is not null) return field; // Get the available source length, if possible long dataLength = Length; if (dataLength == -1) { field = []; return field; } // Get the overlay address and size if possible long endOfSectionData = OverlayAddress; long overlaySize = OverlaySize; // If we didn't find the address or size if (endOfSectionData <= 0 || overlaySize <= 0) { field = []; return field; } // If we're at the end of the file, cache an empty byte array if (endOfSectionData >= dataLength) { field = []; return field; } // Otherwise, cache and return the data overlaySize = Math.Min(overlaySize, 0x10000); field = ReadRangeFromSource((int)endOfSectionData, (int)overlaySize); return field; } } } = null; /// /// Size of the overlay data, if it exists /// /// public long OverlaySize { get { lock (_overlaySizeLock) { // Use the cached data if possible if (field >= 0) return field; // Get the available source length, if possible long dataLength = Length; if (dataLength == -1) { field = 0; return field; } // Get the overlay address if possible long endOfSectionData = OverlayAddress; // If we didn't find the end of section data if (endOfSectionData <= 0) { field = 0; return field; } // If we're at the end of the file, cache an empty byte array if (endOfSectionData >= dataLength) { field = 0; return field; } // Otherwise, cache and return the data field = dataLength - endOfSectionData; return field; } } } = -1; /// /// Overlay strings, if they exist /// public List OverlayStrings { get { lock (_overlayStringsLock) { // Use the cached data if possible if (field is not null) return field; // Get the available source length, if possible long dataLength = Length; if (dataLength == -1) { field = []; return field; } // Get the overlay data, if possible var overlayData = OverlayData; if (overlayData.Length == 0) { field = []; return field; } // Otherwise, cache and return the strings field = overlayData.ReadStringsFrom(charLimit: 3) ?? []; return field; } } } = null; /// public ResidentNameTableEntry[] ResidentNameTable => Model.ResidentNameTable; /// public ResourceTable ResourceTable => Model.ResourceTable; /// public SegmentTableEntry[] SegmentTable => Model.SegmentTable; /// public Data.Models.MSDOS.Executable Stub => Model.Stub; /// /// Stub executable data, if it exists /// public byte[] StubExecutableData { get { lock (_stubExecutableDataLock) { // If we already have cached data, just use that immediately if (field is not null) return field; // Populate the raw stub executable data based on the source int endOfStubHeader = 0x40; int lengthOfStubExecutableData = (int)Stub.Header.NewExeHeaderAddr - endOfStubHeader; field = ReadRangeFromSource(endOfStubHeader, lengthOfStubExecutableData); // Cache and return the stub executable data, even if null return field; } } } = null; #endregion #region Instance Variables /// /// Lock object for /// private readonly object _overlayAddressLock = new(); /// /// Lock object for /// private readonly object _overlayDataLock = new(); /// /// Lock object for /// private readonly object _overlaySizeLock = new(); /// /// Lock object for /// private readonly object _overlayStringsLock = new(); /// /// Lock object for /// private readonly object _stubExecutableDataLock = new(); #endregion #region Constructors /// public NewExecutable(Executable model, byte[] data) : base(model, data) { } /// public NewExecutable(Executable model, byte[] data, int offset) : base(model, data, offset) { } /// public NewExecutable(Executable model, byte[] data, int offset, int length) : base(model, data, offset, length) { } /// public NewExecutable(Executable model, Stream data) : base(model, data) { } /// public NewExecutable(Executable model, Stream data, long offset) : base(model, data, offset) { } /// public NewExecutable(Executable model, Stream data, long offset, long length) : base(model, data, offset, length) { } #endregion #region Static Constructors /// /// Create an NE executable from a byte array and offset /// /// Byte array representing the executable /// Offset within the array to parse /// An NE executable wrapper on success, null on failure public static NewExecutable? Create(byte[]? data, int offset) { // If the data is invalid if (data is null || data.Length == 0) return null; // If the offset is out of bounds if (offset < 0 || offset >= data.Length) return null; // Create a memory stream and use that var dataStream = new MemoryStream(data, offset, data.Length - offset); return Create(dataStream); } /// /// Create an NE executable from a Stream /// /// Stream representing the executable /// An NE executable wrapper on success, null on failure public static NewExecutable? Create(Stream? data) { // If the data is invalid if (data is null || !data.CanRead) return null; try { // Cache the current offset long currentOffset = data.Position; var model = new Serialization.Readers.NewExecutable().Deserialize(data); if (model is null) return null; return new NewExecutable(model, data, currentOffset); } catch { return null; } } #endregion #region Resources /// /// Find the location of a Wise overlay header, if it exists /// /// True to include debug data, false otherwise /// Offset to the overlay header on success, -1 otherwise public long FindWiseOverlayHeader() { // Get the overlay offset long overlayOffset = OverlayAddress; if (overlayOffset < 0 || overlayOffset >= Length) return -1; lock (_dataSourceLock) { // Attempt to get the overlay header _dataSource.SeekIfPossible(overlayOffset, SeekOrigin.Begin); var header = WiseOverlayHeader.Create(_dataSource); if (header is not null) return overlayOffset; // Align and loop to see if it can be found _dataSource.SeekIfPossible(overlayOffset, SeekOrigin.Begin); _dataSource.AlignToBoundary(0x10); overlayOffset = _dataSource.Position; while (_dataSource.Position < Length) { _dataSource.SeekIfPossible(overlayOffset, SeekOrigin.Begin); header = WiseOverlayHeader.Create(_dataSource); if (header is not null) return overlayOffset; overlayOffset += 0x10; } header = null; return -1; } } /// /// Get a single resource entry /// /// Resource ID to retrieve /// Resource on success, null otherwise public ResourceTypeResourceEntry? GetResource(int id) { // Get the available source length, if possible long dataLength = Length; if (dataLength == -1) return null; // If the resource table is invalid if (ResourceTable.ResourceTypes.Length == 0) return null; // Loop through the resources to find a matching ID foreach (var resourceType in ResourceTable.ResourceTypes) { // Skip invalid resource types if (resourceType.ResourceCount == 0 || resourceType.Resources.Length == 0) continue; // Loop through the entries to find a matching ID foreach (var resource in resourceType.Resources) { // Skip non-matching entries if (resource.ResourceID != id) continue; // Return the resource return resource; } } // No entry could be found return null; } /// /// Get the data for a single resource entry /// /// Resource ID to retrieve /// Resource data on success, null otherwise public byte[]? GetResourceData(int id) { // Get the resource offset int offset = GetResourceOffset(id); if (offset < 0) return null; // Get the resource length int length = GetResourceLength(id); if (length < 0) return null; else if (length == 0) return []; // Read the resource data and return return ReadRangeFromSource(offset, length); } /// /// Get the data length for a single resource entry /// /// Resource ID to retrieve /// Resource length on success, -1 otherwise public int GetResourceLength(int id) { // Get the matching resource var resource = GetSegment(id); if (resource is null) return -1; // Return the reported length return resource.Length; } /// /// Get the data offset for a single resource entry /// /// Resource ID to retrieve /// Resource offset on success, -1 otherwise public int GetResourceOffset(int id) { // Get the available source length, if possible long dataLength = Length; if (dataLength == -1) return -1; // Get the matching resource var resource = GetSegment(id); if (resource is null) return -1; // Verify the resource offset int offset = resource.Offset << ResourceTable.AlignmentShiftCount; if (offset < 0 || offset + resource.Length >= dataLength) return -1; // Return the verified offset return offset; } #endregion #region Segments /// /// Get a single segment /// /// Segment index to retrieve /// Segment on success, null otherwise public SegmentTableEntry? GetSegment(int index) { // If the segment table is invalid if (SegmentTable.Length == 0) return null; // If the index is invalid if (index < 0 || index >= SegmentTable.Length) return null; // Return the segment return SegmentTable[index]; } /// /// Get the data for a single segment /// /// Segment index to retrieve /// Segment data on success, null otherwise public byte[]? GetSegmentData(int index) { // Get the segment offset int offset = GetSegmentOffset(index); if (offset < 0) return null; // Get the segment length int length = GetSegmentLength(index); if (length < 0) return null; else if (length == 0) return []; // Read the segment data and return return ReadRangeFromSource(offset, length); } /// /// Get the data length for a single segment /// /// Segment index to retrieve /// Segment length on success, -1 otherwise public int GetSegmentLength(int index) { // Get the matching segment var segment = GetSegment(index); if (segment is null) return -1; // Return the reported length return segment.Length; } /// /// Get the data offset for a single segment /// /// Segment index to retrieve /// Segment offset on success, -1 otherwise public int GetSegmentOffset(int index) { // Get the available source length, if possible long dataLength = Length; if (dataLength == -1) return -1; // Get the matching segment var segment = GetSegment(index); if (segment is null) return -1; // Verify the segment offset int offset = segment.Offset << Header.SegmentAlignmentShiftCount; if (offset < 0 || offset + segment.Length >= dataLength) return -1; // Return the verified offset return offset; } #endregion #region REMOVE -- DO NOT USE /// /// Read an arbitrary range from the source /// /// The start of where to read data from, -1 means start of source /// How many bytes to read, -1 means read until end /// Byte array representing the range, null on error [Obsolete("Passthrough method that should not be used")] public byte[]? ReadArbitraryRange(int rangeStart = -1, long length = -1) { // If we have an unset range start, read from the start of the source if (rangeStart == -1) rangeStart = 0; // If we have an unset length, read the whole source if (length == -1) length = Length; return ReadRangeFromSource(rangeStart, (int)length); } #endregion } }