using System; using System.IO; using SabreTools.Data.Models.LinearExecutable; using SabreTools.IO.Extensions; using SabreTools.Numerics.Extensions; #pragma warning disable IDE0330 // Use 'System.Threading.Lock' namespace SabreTools.Wrappers { public partial class LinearExecutable : WrapperBase { #region Descriptive Properties /// public override string DescriptionString => "Linear Executable (LE/LX)"; #endregion #region Extension Properties /// public InformationBlock InformationBlock => Model.InformationBlock; /// public ObjectPageMapEntry[] ObjectPageMap => Model.ObjectPageMap; /// public ResourceTableEntry[] ResourceTable => Model.ResourceTable; /// 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 _stubExecutableDataLock = new(); #endregion #region Constructors /// public LinearExecutable(Executable model, byte[] data) : base(model, data) { } /// public LinearExecutable(Executable model, byte[] data, int offset) : base(model, data, offset) { } /// public LinearExecutable(Executable model, byte[] data, int offset, int length) : base(model, data, offset, length) { } /// public LinearExecutable(Executable model, Stream data) : base(model, data) { } /// public LinearExecutable(Executable model, Stream data, long offset) : base(model, data, offset) { } /// public LinearExecutable(Executable model, Stream data, long offset, long length) : base(model, data, offset, length) { } #endregion #region Static Constructors /// /// Create an LE/LX executable from a byte array and offset /// /// Byte array representing the executable /// Offset within the array to parse /// An LE/LX executable wrapper on success, null on failure public static LinearExecutable? 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 LE/LX executable from a Stream /// /// Stream representing the executable /// An LE/LX executable wrapper on success, null on failure public static LinearExecutable? 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.LinearExecutable().Deserialize(data); if (model is null) return null; return new LinearExecutable(model, data, currentOffset); } catch { return null; } } #endregion #region Object Page Map /// /// Get a single object page map entry /// /// Object page map index to retrieve /// Entry on success, null otherwise public ObjectPageMapEntry? GetObjectPageMapEntry(int index) { // If the object page map table is invalid if (ObjectPageMap is null || ObjectPageMap.Length == 0) return null; // If the index is invalid if (index < 0 || index >= ObjectPageMap.Length) return null; // Return the entry return ObjectPageMap[index]; } /// /// Get the data for a single object page map entry /// /// Object page map index to retrieve /// Entry data on success, null otherwise public byte[]? GetObjectPageMapEntryData(int index) { // Get the entry offset int offset = GetObjectPageMapEntryOffset(index); if (offset < 0) return null; // Get the entry length int length = GetObjectPageMapEntryLength(index); if (length < 0) return null; else if (length == 0) return []; // Read the entry data and return return ReadRangeFromSource(offset, length); } /// /// Get the data length for a object page map entry /// /// Object page map index to retrieve /// Entry length on success, -1 otherwise public int GetObjectPageMapEntryLength(int index) { // Get the matching entry var entry = GetObjectPageMapEntry(index); if (entry is null) return -1; // Return the reported length return entry.DataSize; } /// /// Get the data offset for a single object page map entry /// /// Object page map index to retrieve /// Entry offset on success, -1 otherwise public int GetObjectPageMapEntryOffset(int index) { // If the information block is invalid if (InformationBlock is null) return -1; // Get the available source length, if possible long dataLength = Length; if (dataLength == -1) return -1; // Get the matching entry var entry = GetObjectPageMapEntry(index); if (entry is null) return -1; // Verify the entry offset int offset = (int)(entry.PageDataOffset << (int)InformationBlock.BytesOnLastPage); if (offset < 0 || offset + entry.DataSize >= dataLength) return -1; // Return the verified offset return offset; } #endregion #region Resource Table /// /// Get a single resource table entry /// /// Resource table index to retrieve /// Entry on success, null otherwise public ResourceTableEntry? GetResourceTableEntry(int index) { // If the resource table table is invalid if (ResourceTable is null || ResourceTable.Length == 0) return null; // If the index is invalid if (index < 0 || index >= ResourceTable.Length) return null; // Return the entry return ResourceTable[index]; } /// /// Get the data for a single resource table entry /// /// Resource table index to retrieve /// Entry data on success, null otherwise public byte[]? GetResourceTableEntryData(int index) { // Get the entry var entry = GetResourceTableEntry(index); if (entry is null) return null; // Get the entry offset int offset = GetResourceTableEntryOffset(index); if (offset < 0) return null; // Get the entry length int length = GetResourceTableEntryLength(index); if (length < 0) return null; else if (length == 0) return []; // Get the matching object data var objectData = GetObjectPageMapEntryData(entry.ObjectNumber); if (objectData is null) return null; // Read the entry data and return return objectData.ReadBytes(ref offset, length); } /// /// Get the data length for a resource table entry /// /// Resource table index to retrieve /// Entry length on success, -1 otherwise public int GetResourceTableEntryLength(int index) { // Get the matching entry var entry = GetResourceTableEntry(index); if (entry is null) return -1; // Return the reported length return (int)entry.ResourceSize; } /// /// Get the data offset for a single resource table entry /// /// Resource table index to retrieve /// Entry offset on success, -1 otherwise public int GetResourceTableEntryOffset(int index) { // If the information block is invalid if (InformationBlock is null) return -1; // Get the available source length, if possible long dataLength = Length; if (dataLength == -1) return -1; // Get the matching entry var entry = GetResourceTableEntry(index); if (entry is null) return -1; // Get the matching object length int objectLength = GetObjectPageMapEntryLength(entry.ObjectNumber); if (objectLength == -1) return -1; // Verify the entry offset if (entry.Offset < 0 || entry.Offset + entry.ResourceSize >= objectLength) return -1; // Return the verified offset return (int)entry.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 } }