mirror of
https://github.com/SabreTools/SabreTools.Serialization.git
synced 2026-04-05 22:01:33 +00:00
366 lines
12 KiB
C#
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
|
|
}
|
|
}
|