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

644 lines
22 KiB
C#

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<Executable>
{
#region Descriptive Properties
/// <inheritdoc/>
public override string DescriptionString => "New Executable (NE)";
#endregion
#region Extension Properties
/// <inheritdoc cref="Executable.Header"/>
public ExecutableHeader Header => Model.Header;
/// <inheritdoc cref="Executable.ImportedNameTable"/>
public Dictionary<ushort, ImportedNameTableEntry> ImportedNameTable => Model.ImportedNameTable;
/// <inheritdoc cref="Executable.NonResidentNameTable"/>
public NonResidentNameTableEntry[] NonResidentNameTable => Model.NonResidentNameTable;
/// <summary>
/// Address of the overlay, if it exists
/// </summary>
/// <see href="https://codeberg.org/CYBERDEV/REWise/src/branch/master/src/exefile.c"/>
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;
/// <summary>
/// Overlay data, if it exists
/// </summary>
/// <remarks>Caches up to 0x10000 bytes</remarks>
/// <see href="https://codeberg.org/CYBERDEV/REWise/src/branch/master/src/exefile.c"/>
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;
/// <summary>
/// Size of the overlay data, if it exists
/// </summary>
/// <see href="https://codeberg.org/CYBERDEV/REWise/src/branch/master/src/exefile.c"/>
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;
/// <summary>
/// Overlay strings, if they exist
/// </summary>
public List<string> 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;
/// <inheritdoc cref="Executable.ResidentNameTable"/>
public ResidentNameTableEntry[] ResidentNameTable => Model.ResidentNameTable;
/// <inheritdoc cref="Executable.ResourceTable"/>
public ResourceTable ResourceTable => Model.ResourceTable;
/// <inheritdoc cref="Executable.SegmentTable"/>
public SegmentTableEntry[] SegmentTable => Model.SegmentTable;
/// <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="OverlayAddress"/>
/// </summary>
private readonly object _overlayAddressLock = new();
/// <summary>
/// Lock object for <see cref="OverlayData"/>
/// </summary>
private readonly object _overlayDataLock = new();
/// <summary>
/// Lock object for <see cref="OverlaySize"/>
/// </summary>
private readonly object _overlaySizeLock = new();
/// <summary>
/// Lock object for <see cref="OverlayStrings"/>
/// </summary>
private readonly object _overlayStringsLock = new();
/// <summary>
/// Lock object for <see cref="StubExecutableData"/>
/// </summary>
private readonly object _stubExecutableDataLock = new();
#endregion
#region Constructors
/// <inheritdoc/>
public NewExecutable(Executable model, byte[] data) : base(model, data) { }
/// <inheritdoc/>
public NewExecutable(Executable model, byte[] data, int offset) : base(model, data, offset) { }
/// <inheritdoc/>
public NewExecutable(Executable model, byte[] data, int offset, int length) : base(model, data, offset, length) { }
/// <inheritdoc/>
public NewExecutable(Executable model, Stream data) : base(model, data) { }
/// <inheritdoc/>
public NewExecutable(Executable model, Stream data, long offset) : base(model, data, offset) { }
/// <inheritdoc/>
public NewExecutable(Executable model, Stream data, long offset, long length) : base(model, data, offset, length) { }
#endregion
#region Static Constructors
/// <summary>
/// Create an NE 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 NE executable wrapper on success, null on failure</returns>
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);
}
/// <summary>
/// Create an NE executable from a Stream
/// </summary>
/// <param name="data">Stream representing the executable</param>
/// <returns>An NE executable wrapper on success, null on failure</returns>
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
/// <summary>
/// Find the location of a Wise overlay header, if it exists
/// </summary>
/// <param name="includeDebug">True to include debug data, false otherwise</param>
/// <returns>Offset to the overlay header on success, -1 otherwise</returns>
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;
}
}
/// <summary>
/// Get a single resource entry
/// </summary>
/// <param name="id">Resource ID to retrieve</param>
/// <returns>Resource on success, null otherwise</returns>
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;
}
/// <summary>
/// Get the data for a single resource entry
/// </summary>
/// <param name="id">Resource ID to retrieve</param>
/// <returns>Resource data on success, null otherwise</returns>
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);
}
/// <summary>
/// Get the data length for a single resource entry
/// </summary>
/// <param name="id">Resource ID to retrieve</param>
/// <returns>Resource length on success, -1 otherwise</returns>
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;
}
/// <summary>
/// Get the data offset for a single resource entry
/// </summary>
/// <param name="id">Resource ID to retrieve</param>
/// <returns>Resource offset on success, -1 otherwise</returns>
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
/// <summary>
/// Get a single segment
/// </summary>
/// <param name="index">Segment index to retrieve</param>
/// <returns>Segment on success, null otherwise</returns>
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];
}
/// <summary>
/// Get the data for a single segment
/// </summary>
/// <param name="index">Segment index to retrieve</param>
/// <returns>Segment data on success, null otherwise</returns>
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);
}
/// <summary>
/// Get the data length for a single segment
/// </summary>
/// <param name="index">Segment index to retrieve</param>
/// <returns>Segment length on success, -1 otherwise</returns>
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;
}
/// <summary>
/// Get the data offset for a single segment
/// </summary>
/// <param name="index">Segment index to retrieve</param>
/// <returns>Segment offset on success, -1 otherwise</returns>
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
/// <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
}
}