Files

644 lines
22 KiB
C#
Raw Permalink Normal View History

using System;
2025-07-31 14:00:55 -04:00
using System.Collections.Generic;
using System.IO;
2025-09-26 13:06:18 -04:00
using SabreTools.Data.Models.NewExecutable;
2025-08-28 23:52:09 -04:00
using SabreTools.IO.Extensions;
2026-03-24 19:17:25 -04:00
using SabreTools.Text.Extensions;
2026-01-27 12:03:01 -05:00
#pragma warning disable IDE0330 // Use 'System.Threading.Lock'
2026-03-18 16:37:59 -04:00
namespace SabreTools.Wrappers
{
2025-09-12 09:02:03 -04:00
public partial class NewExecutable : WrapperBase<Executable>
{
#region Descriptive Properties
/// <inheritdoc/>
public override string DescriptionString => "New Executable (NE)";
#endregion
2025-07-31 14:00:55 -04:00
#region Extension Properties
2025-08-01 08:43:01 -04:00
/// <inheritdoc cref="Executable.Header"/>
2025-10-30 22:47:17 -04:00
public ExecutableHeader Header => Model.Header;
2025-07-31 14:00:55 -04:00
2025-08-01 08:43:01 -04:00
/// <inheritdoc cref="Executable.ImportedNameTable"/>
2025-10-30 22:47:17 -04:00
public Dictionary<ushort, ImportedNameTableEntry> ImportedNameTable => Model.ImportedNameTable;
2025-07-31 14:00:55 -04:00
2025-08-01 08:43:01 -04:00
/// <inheritdoc cref="Executable.NonResidentNameTable"/>
2025-10-30 22:47:17 -04:00
public NonResidentNameTableEntry[] NonResidentNameTable => Model.NonResidentNameTable;
2025-07-31 14:00:55 -04:00
2025-08-01 07:58:54 -04:00
/// <summary>
/// Address of the overlay, if it exists
/// </summary>
/// <see href="https://codeberg.org/CYBERDEV/REWise/src/branch/master/src/exefile.c"/>
2025-08-11 11:11:51 -04:00
public long OverlayAddress
2025-08-01 07:58:54 -04:00
{
get
{
2025-09-02 23:51:02 -04:00
lock (_overlayAddressLock)
2025-08-01 07:58:54 -04:00
{
// Use the cached data if possible
2025-11-14 09:48:00 -05:00
if (field >= 0)
return field;
2025-08-01 07:58:54 -04:00
2025-08-20 07:54:52 -04:00
// Get the available source length, if possible
long dataLength = Length;
if (dataLength == -1)
{
2025-11-14 09:48:00 -05:00
field = -1;
return field;
}
2025-08-01 07:58:54 -04:00
// Search through the segments table to find the furthest
long endOfSectionData = -1;
foreach (var entry in SegmentTable)
2025-08-01 07:58:54 -04:00
{
// Get end of segment data
long offset = (entry.Offset * (1 << Header.SegmentAlignmentShiftCount)) + entry.Length;
2025-08-11 11:11:51 -04:00
// Read and find the end of the relocation data
2025-08-28 23:52:09 -04:00
#if NET20 || NET35
2025-08-11 11:11:51 -04:00
if ((entry.FlagWord & SegmentTableEntryFlag.RELOCINFO) != 0)
2025-08-28 23:52:09 -04:00
#else
if (entry.FlagWord.HasFlag(SegmentTableEntryFlag.RELOCINFO))
2025-08-28 23:52:09 -04:00
#endif
{
lock (_dataSourceLock)
{
2025-10-27 22:43:56 -04:00
_dataSource.SeekIfPossible(offset, SeekOrigin.Begin);
2026-03-18 16:37:59 -04:00
var relocationData = Serialization.Readers.NewExecutable.ParsePerSegmentData(_dataSource);
2025-08-01 07:58:54 -04:00
offset = _dataSource.Position;
}
2025-08-01 07:58:54 -04:00
}
if (offset > endOfSectionData)
endOfSectionData = offset;
}
// Search through the resources table to find the furthest
foreach (var entry in ResourceTable.ResourceTypes)
{
// Skip invalid entries
2025-10-30 22:47:17 -04:00
if (entry.ResourceCount == 0 || entry.Resources.Length == 0)
continue;
foreach (var resource in entry.Resources)
2025-09-02 23:51:02 -04:00
{
int offset = (resource.Offset << ResourceTable.AlignmentShiftCount) + resource.Length;
if (offset > endOfSectionData)
endOfSectionData = offset;
2025-09-02 23:51:02 -04:00
}
}
2025-09-02 23:51:02 -04:00
// If we didn't find the end of section data
if (endOfSectionData <= 0)
endOfSectionData = -1;
2025-08-01 07:58:54 -04:00
// Adjust the position of the data by 705 bytes
// TODO: Investigate what the byte data is
endOfSectionData += 705;
2025-09-02 23:51:02 -04:00
// Cache and return the position
2025-11-14 09:48:00 -05:00
field = endOfSectionData;
return field;
2025-08-01 07:58:54 -04:00
}
}
2025-11-14 09:48:00 -05:00
} = -1;
2025-08-01 07:58:54 -04:00
/// <summary>
/// Overlay data, if it exists
/// </summary>
2025-10-27 22:43:56 -04:00
/// <remarks>Caches up to 0x10000 bytes</remarks>
2025-08-01 07:58:54 -04:00
/// <see href="https://codeberg.org/CYBERDEV/REWise/src/branch/master/src/exefile.c"/>
public byte[] OverlayData
2025-08-01 07:58:54 -04:00
{
get
{
2025-09-02 23:51:02 -04:00
lock (_overlayDataLock)
2025-08-01 07:58:54 -04:00
{
// Use the cached data if possible
2026-01-25 14:32:49 -05:00
if (field is not null)
2025-11-14 09:48:00 -05:00
return field;
2025-08-01 07:58:54 -04:00
2025-08-20 07:54:52 -04:00
// Get the available source length, if possible
long dataLength = Length;
if (dataLength == -1)
{
2025-11-14 09:48:00 -05:00
field = [];
return field;
}
2025-08-01 07:58:54 -04:00
2025-09-25 13:50:51 -04:00
// 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)
{
2025-11-14 09:48:00 -05:00
field = [];
return field;
}
2025-08-01 07:58:54 -04:00
2025-09-25 13:50:51 -04:00
// If we're at the end of the file, cache an empty byte array
if (endOfSectionData >= dataLength)
{
2025-11-14 09:48:00 -05:00
field = [];
return field;
2025-09-25 13:50:51 -04:00
}
// Otherwise, cache and return the data
2025-09-25 20:14:41 -04:00
overlaySize = Math.Min(overlaySize, 0x10000);
2025-09-25 13:50:51 -04:00
2025-11-14 09:48:00 -05:00
field = ReadRangeFromSource((int)endOfSectionData, (int)overlaySize);
return field;
2025-09-25 13:50:51 -04:00
}
}
2025-11-14 09:48:00 -05:00
} = null;
2025-09-25 13:50:51 -04:00
/// <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
2025-11-14 09:48:00 -05:00
if (field >= 0)
return field;
2025-09-25 13:50:51 -04:00
// Get the available source length, if possible
long dataLength = Length;
if (dataLength == -1)
{
2025-11-14 09:48:00 -05:00
field = 0;
return field;
2025-09-25 13:50:51 -04:00
}
2025-09-02 23:51:02 -04:00
// Get the overlay address if possible
long endOfSectionData = OverlayAddress;
2025-08-01 07:58:54 -04:00
// If we didn't find the end of section data
if (endOfSectionData <= 0)
{
2025-11-14 09:48:00 -05:00
field = 0;
return field;
}
2025-08-01 07:58:54 -04:00
// If we're at the end of the file, cache an empty byte array
2025-08-20 07:54:52 -04:00
if (endOfSectionData >= dataLength)
2025-08-01 07:58:54 -04:00
{
2025-11-14 09:48:00 -05:00
field = 0;
return field;
2025-08-01 07:58:54 -04:00
}
// Otherwise, cache and return the data
2025-11-14 09:48:00 -05:00
field = dataLength - endOfSectionData;
return field;
2025-08-01 07:58:54 -04:00
}
}
2025-11-14 09:48:00 -05:00
} = -1;
2025-08-01 07:58:54 -04:00
/// <summary>
/// Overlay strings, if they exist
/// </summary>
public List<string> OverlayStrings
2025-08-01 07:58:54 -04:00
{
get
{
2025-09-02 23:51:02 -04:00
lock (_overlayStringsLock)
2025-08-01 07:58:54 -04:00
{
// Use the cached data if possible
2026-01-25 14:32:49 -05:00
if (field is not null)
2025-11-14 09:48:00 -05:00
return field;
2025-08-01 07:58:54 -04:00
2025-08-20 07:54:52 -04:00
// Get the available source length, if possible
long dataLength = Length;
if (dataLength == -1)
{
2025-11-14 09:48:00 -05:00
field = [];
return field;
}
2025-08-01 07:58:54 -04:00
// Get the overlay data, if possible
var overlayData = OverlayData;
if (overlayData.Length == 0)
2025-08-01 07:58:54 -04:00
{
2025-11-14 09:48:00 -05:00
field = [];
return field;
2025-08-01 07:58:54 -04:00
}
// Otherwise, cache and return the strings
2025-11-14 09:48:00 -05:00
field = overlayData.ReadStringsFrom(charLimit: 3) ?? [];
return field;
2025-08-01 07:58:54 -04:00
}
}
2025-11-14 09:48:00 -05:00
} = null;
2025-08-01 07:58:54 -04:00
2025-08-01 08:43:01 -04:00
/// <inheritdoc cref="Executable.ResidentNameTable"/>
2025-10-30 22:47:17 -04:00
public ResidentNameTableEntry[] ResidentNameTable => Model.ResidentNameTable;
2025-07-31 14:00:55 -04:00
2025-08-01 08:43:01 -04:00
/// <inheritdoc cref="Executable.ResourceTable"/>
2025-10-30 22:47:17 -04:00
public ResourceTable ResourceTable => Model.ResourceTable;
2025-08-01 07:58:54 -04:00
2025-08-01 08:43:01 -04:00
/// <inheritdoc cref="Executable.SegmentTable"/>
2025-10-30 22:47:17 -04:00
public SegmentTableEntry[] SegmentTable => Model.SegmentTable;
2025-08-01 07:58:54 -04:00
2025-08-01 08:43:01 -04:00
/// <inheritdoc cref="Executable.Stub"/>
2025-10-30 22:47:17 -04:00
public Data.Models.MSDOS.Executable Stub => Model.Stub;
2025-07-31 14:00:55 -04:00
2025-08-01 07:58:54 -04:00
/// <summary>
/// Stub executable data, if it exists
/// </summary>
public byte[] StubExecutableData
2025-08-01 07:58:54 -04:00
{
get
{
2025-09-02 23:51:02 -04:00
lock (_stubExecutableDataLock)
2025-08-01 07:58:54 -04:00
{
// If we already have cached data, just use that immediately
2026-01-25 14:32:49 -05:00
if (field is not null)
2025-11-14 09:48:00 -05:00
return field;
2025-08-01 07:58:54 -04:00
// Populate the raw stub executable data based on the source
int endOfStubHeader = 0x40;
int lengthOfStubExecutableData = (int)Stub.Header.NewExeHeaderAddr - endOfStubHeader;
2025-11-14 09:48:00 -05:00
field = ReadRangeFromSource(endOfStubHeader, lengthOfStubExecutableData);
2025-08-01 07:58:54 -04:00
// Cache and return the stub executable data, even if null
2025-11-14 09:48:00 -05:00
return field;
2025-08-01 07:58:54 -04:00
}
}
2025-11-14 09:48:00 -05:00
} = null;
2025-08-01 07:58:54 -04:00
#endregion
#region Instance Variables
/// <summary>
2025-11-14 09:48:00 -05:00
/// Lock object for <see cref="OverlayAddress"/>
2025-09-02 23:51:02 -04:00
/// </summary>
private readonly object _overlayAddressLock = new();
2025-08-01 07:58:54 -04:00
/// <summary>
2025-11-14 09:48:00 -05:00
/// Lock object for <see cref="OverlayData"/>
2025-09-02 23:51:02 -04:00
/// </summary>
private readonly object _overlayDataLock = new();
2025-09-25 13:50:51 -04:00
/// <summary>
2025-11-14 09:48:00 -05:00
/// Lock object for <see cref="OverlaySize"/>
2025-09-25 13:50:51 -04:00
/// </summary>
private readonly object _overlaySizeLock = new();
2025-08-01 07:58:54 -04:00
/// <summary>
2025-11-14 09:48:00 -05:00
/// Lock object for <see cref="OverlayStrings"/>
2025-09-02 23:51:02 -04:00
/// </summary>
private readonly object _overlayStringsLock = new();
2025-08-01 07:58:54 -04:00
/// <summary>
2025-11-14 09:48:00 -05:00
/// Lock object for <see cref="StubExecutableData"/>
2025-09-02 23:51:02 -04:00
/// </summary>
private readonly object _stubExecutableDataLock = new();
2025-07-31 14:00:55 -04:00
#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
2026-01-25 14:30:18 -05:00
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
2026-01-25 14:30:18 -05:00
if (data is null || !data.CanRead)
return null;
try
{
// Cache the current offset
long currentOffset = data.Position;
2026-03-18 16:37:59 -04:00
var model = new Serialization.Readers.NewExecutable().Deserialize(data);
2026-01-25 14:30:18 -05:00
if (model is null)
return null;
return new NewExecutable(model, data, currentOffset);
}
catch
{
return null;
}
}
#endregion
2025-08-01 08:19:41 -04:00
#region Resources
2025-09-02 08:00:51 -04:00
/// <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)
2025-09-02 08:00:51 -04:00
{
// Attempt to get the overlay header
2025-10-27 22:43:56 -04:00
_dataSource.SeekIfPossible(overlayOffset, SeekOrigin.Begin);
var header = WiseOverlayHeader.Create(_dataSource);
2026-01-25 14:32:49 -05:00
if (header is not null)
2025-09-02 08:00:51 -04:00
return overlayOffset;
// Align and loop to see if it can be found
2025-10-27 22:43:56 -04:00
_dataSource.SeekIfPossible(overlayOffset, SeekOrigin.Begin);
_dataSource.AlignToBoundary(0x10);
overlayOffset = _dataSource.Position;
while (_dataSource.Position < Length)
{
2025-10-27 22:43:56 -04:00
_dataSource.SeekIfPossible(overlayOffset, SeekOrigin.Begin);
header = WiseOverlayHeader.Create(_dataSource);
2026-01-25 14:32:49 -05:00
if (header is not null)
return overlayOffset;
2025-09-02 08:00:51 -04:00
overlayOffset += 0x10;
}
header = null;
return -1;
}
2025-09-02 08:00:51 -04:00
}
2025-08-01 08:19:41 -04:00
/// <summary>
2025-08-01 08:39:14 -04:00
/// Get a single resource entry
2025-08-01 08:19:41 -04:00
/// </summary>
/// <param name="id">Resource ID to retrieve</param>
2025-08-01 08:39:14 -04:00
/// <returns>Resource on success, null otherwise</returns>
2025-08-01 08:43:01 -04:00
public ResourceTypeResourceEntry? GetResource(int id)
2025-08-01 08:19:41 -04:00
{
2025-08-20 07:54:52 -04:00
// Get the available source length, if possible
long dataLength = Length;
if (dataLength == -1)
2025-08-01 08:19:41 -04:00
return null;
// If the resource table is invalid
2025-10-30 22:47:17 -04:00
if (ResourceTable.ResourceTypes.Length == 0)
2025-08-01 08:19:41 -04:00
return null;
// Loop through the resources to find a matching ID
foreach (var resourceType in ResourceTable.ResourceTypes)
{
// Skip invalid resource types
2025-10-30 22:47:17 -04:00
if (resourceType.ResourceCount == 0 || resourceType.Resources.Length == 0)
2025-08-01 08:19:41 -04:00
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;
2025-08-01 08:39:14 -04:00
// Return the resource
return resource;
2025-08-01 08:19:41 -04:00
}
}
// No entry could be found
return null;
}
2025-08-01 08:39:14 -04:00
/// <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)
{
2025-08-01 08:56:14 -04:00
// Get the resource offset
int offset = GetResourceOffset(id);
if (offset < 0)
2025-08-01 08:39:14 -04:00
return null;
2025-08-01 08:56:14 -04:00
// Get the resource length
int length = GetResourceLength(id);
if (length < 0)
2025-08-01 08:39:14 -04:00
return null;
2025-08-01 08:56:14 -04:00
else if (length == 0)
2025-08-01 08:39:14 -04:00
return [];
2025-08-01 08:56:14 -04:00
// Read the resource data and return
return ReadRangeFromSource(offset, length);
2025-08-01 08:39:14 -04:00
}
/// <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);
2026-01-25 14:30:18 -05:00
if (resource is null)
2025-08-01 08:39:14 -04:00
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)
{
2025-08-20 07:54:52 -04:00
// Get the available source length, if possible
long dataLength = Length;
if (dataLength == -1)
2025-08-01 08:39:14 -04:00
return -1;
// Get the matching resource
var resource = GetSegment(id);
2026-01-25 14:30:18 -05:00
if (resource is null)
2025-08-01 08:39:14 -04:00
return -1;
// Verify the resource offset
int offset = resource.Offset << ResourceTable.AlignmentShiftCount;
2025-08-20 07:54:52 -04:00
if (offset < 0 || offset + resource.Length >= dataLength)
2025-08-01 08:39:14 -04:00
return -1;
// Return the verified offset
return offset;
}
2025-08-01 08:19:41 -04:00
#endregion
#region Segments
2025-08-01 08:39:14 -04:00
/// <summary>
/// Get a single segment
/// </summary>
/// <param name="index">Segment index to retrieve</param>
/// <returns>Segment on success, null otherwise</returns>
2025-08-01 08:43:01 -04:00
public SegmentTableEntry? GetSegment(int index)
2025-08-01 08:39:14 -04:00
{
// If the segment table is invalid
2025-10-30 22:47:17 -04:00
if (SegmentTable.Length == 0)
2025-08-01 08:39:14 -04:00
return null;
// If the index is invalid
if (index < 0 || index >= SegmentTable.Length)
return null;
// Return the segment
return SegmentTable[index];
}
2025-08-01 08:19:41 -04:00
/// <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)
{
2025-08-01 08:56:14 -04:00
// Get the segment offset
int offset = GetSegmentOffset(index);
if (offset < 0)
2025-08-01 08:19:41 -04:00
return null;
2025-08-01 08:56:14 -04:00
// Get the segment length
int length = GetSegmentLength(index);
if (length < 0)
2025-08-01 08:19:41 -04:00
return null;
2025-08-01 08:56:14 -04:00
else if (length == 0)
2025-08-01 08:19:41 -04:00
return [];
// Read the segment data and return
return ReadRangeFromSource(offset, length);
2025-08-01 08:19:41 -04:00
}
2025-08-01 08:39:14 -04:00
/// <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);
2026-01-25 14:30:18 -05:00
if (segment is null)
2025-08-01 08:39:14 -04:00
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)
{
2025-08-20 07:54:52 -04:00
// Get the available source length, if possible
long dataLength = Length;
if (dataLength == -1)
2025-08-01 08:39:14 -04:00
return -1;
// Get the matching segment
var segment = GetSegment(index);
2026-01-25 14:30:18 -05:00
if (segment is null)
2025-08-01 08:39:14 -04:00
return -1;
// Verify the segment offset
int offset = segment.Offset << Header.SegmentAlignmentShiftCount;
2025-08-20 07:54:52 -04:00
if (offset < 0 || offset + segment.Length >= dataLength)
2025-08-01 08:39:14 -04:00
return -1;
// Return the verified offset
return offset;
}
2025-08-01 08:19:41 -04:00
#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>
2025-11-14 09:06:59 -05:00
[Obsolete("Passthrough method that should not be used")]
2025-08-19 07:41:29 -04:00
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)
2025-08-20 07:54:52 -04:00
length = Length;
return ReadRangeFromSource(rangeStart, (int)length);
}
#endregion
}
}