Files
Matt Nadareski 8f49e190d8 Fix everything
2026-03-24 19:17:25 -04:00

366 lines
12 KiB
C#

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<Executable>
{
#region Descriptive Properties
/// <inheritdoc/>
public override string DescriptionString => "Linear Executable (LE/LX)";
#endregion
#region Extension Properties
/// <inheritdoc cref="Executable.ObjectPageMap"/>
public InformationBlock InformationBlock => Model.InformationBlock;
/// <inheritdoc cref="Executable.ObjectPageMap"/>
public ObjectPageMapEntry[] ObjectPageMap => Model.ObjectPageMap;
/// <inheritdoc cref="Executable.ResourceTable"/>
public ResourceTableEntry[] ResourceTable => Model.ResourceTable;
/// <inheritdoc cref="Executable.Stub"/>
public Data.Models.MSDOS.Executable Stub => Model.Stub;
/// <summary>
/// Stub executable data, if it exists
/// </summary>
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
/// <summary>
/// Lock object for <see cref="StubExecutableData"/>
/// </summary>
private readonly object _stubExecutableDataLock = new();
#endregion
#region Constructors
/// <inheritdoc/>
public LinearExecutable(Executable model, byte[] data) : base(model, data) { }
/// <inheritdoc/>
public LinearExecutable(Executable model, byte[] data, int offset) : base(model, data, offset) { }
/// <inheritdoc/>
public LinearExecutable(Executable model, byte[] data, int offset, int length) : base(model, data, offset, length) { }
/// <inheritdoc/>
public LinearExecutable(Executable model, Stream data) : base(model, data) { }
/// <inheritdoc/>
public LinearExecutable(Executable model, Stream data, long offset) : base(model, data, offset) { }
/// <inheritdoc/>
public LinearExecutable(Executable model, Stream data, long offset, long length) : base(model, data, offset, length) { }
#endregion
#region Static Constructors
/// <summary>
/// Create an LE/LX executable from a byte array and offset
/// </summary>
/// <param name="data">Byte array representing the executable</param>
/// <param name="offset">Offset within the array to parse</param>
/// <returns>An LE/LX executable wrapper on success, null on failure</returns>
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);
}
/// <summary>
/// Create an LE/LX executable from a Stream
/// </summary>
/// <param name="data">Stream representing the executable</param>
/// <returns>An LE/LX executable wrapper on success, null on failure</returns>
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
/// <summary>
/// Get a single object page map entry
/// </summary>
/// <param name="index">Object page map index to retrieve</param>
/// <returns>Entry on success, null otherwise</returns>
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];
}
/// <summary>
/// Get the data for a single object page map entry
/// </summary>
/// <param name="index">Object page map index to retrieve</param>
/// <returns>Entry data on success, null otherwise</returns>
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);
}
/// <summary>
/// Get the data length for a object page map entry
/// </summary>
/// <param name="index">Object page map index to retrieve</param>
/// <returns>Entry length on success, -1 otherwise</returns>
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;
}
/// <summary>
/// Get the data offset for a single object page map entry
/// </summary>
/// <param name="index">Object page map index to retrieve</param>
/// <returns>Entry offset on success, -1 otherwise</returns>
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
/// <summary>
/// Get a single resource table entry
/// </summary>
/// <param name="index">Resource table index to retrieve</param>
/// <returns>Entry on success, null otherwise</returns>
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];
}
/// <summary>
/// Get the data for a single resource table entry
/// </summary>
/// <param name="index">Resource table index to retrieve</param>
/// <returns>Entry data on success, null otherwise</returns>
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);
}
/// <summary>
/// Get the data length for a resource table entry
/// </summary>
/// <param name="index">Resource table index to retrieve</param>
/// <returns>Entry length on success, -1 otherwise</returns>
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;
}
/// <summary>
/// Get the data offset for a single resource table entry
/// </summary>
/// <param name="index">Resource table index to retrieve</param>
/// <returns>Entry offset on success, -1 otherwise</returns>
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
/// <summary>
/// Read an arbitrary range from the source
/// </summary>
/// <param name="rangeStart">The start of where to read data from, -1 means start of source</param>
/// <param name="length">How many bytes to read, -1 means read until end</param>
/// <returns>Byte array representing the range, null on error</returns>
[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
}
}