mirror of
https://github.com/SabreTools/SabreTools.Serialization.git
synced 2026-07-09 02:16:55 +00:00
Handle some big TODOs
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using SabreTools.IO.Encryption;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.Models.MoPaQ;
|
||||
using static SabreTools.Models.MoPaQ.Constants;
|
||||
|
||||
@@ -10,7 +10,6 @@ namespace SabreTools.Serialization.Deserializers
|
||||
public class NewExecutable : BaseBinaryDeserializer<Executable>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
/// TODO: Relocation data needs to be hooked up when Models updated
|
||||
public override Executable? Deserialize(Stream? data)
|
||||
{
|
||||
// If the data is invalid
|
||||
@@ -64,7 +63,7 @@ namespace SabreTools.Serialization.Deserializers
|
||||
executable.SegmentTable = new SegmentTableEntry[header.FileSegmentCount];
|
||||
for (int i = 0; i < header.FileSegmentCount; i++)
|
||||
{
|
||||
executable.SegmentTable[i] = ParseSegmentTableEntry(data);
|
||||
executable.SegmentTable[i] = ParseSegmentTableEntry(data, initialOffset);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -624,8 +623,9 @@ namespace SabreTools.Serialization.Deserializers
|
||||
/// Parse a Stream into an SegmentTableEntry
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <param name="initialOffset">Initial offset to use in address comparisons</param>
|
||||
/// <returns>Filled SegmentTableEntry on success, null on error</returns>
|
||||
public static SegmentTableEntry ParseSegmentTableEntry(Stream data)
|
||||
public static SegmentTableEntry ParseSegmentTableEntry(Stream data, long initialOffset)
|
||||
{
|
||||
var obj = new SegmentTableEntry();
|
||||
|
||||
@@ -634,6 +634,30 @@ namespace SabreTools.Serialization.Deserializers
|
||||
obj.FlagWord = (SegmentTableEntryFlag)data.ReadUInt16LittleEndian();
|
||||
obj.MinimumAllocationSize = data.ReadUInt16LittleEndian();
|
||||
|
||||
// If the data offset is invalid
|
||||
if (obj.Offset < 0 || obj.Offset + initialOffset >= data.Length)
|
||||
return obj;
|
||||
|
||||
// Cache the current offset
|
||||
long currentOffset = data.Position;
|
||||
|
||||
// Seek to the data offset and read
|
||||
data.Seek(obj.Offset + initialOffset, SeekOrigin.Begin);
|
||||
obj.Data = data.ReadBytes(obj.Length);
|
||||
|
||||
|
||||
#if NET20 || NET35
|
||||
if ((obj.FlagWord & SegmentTableEntryFlag.RELOCINFO) != 0)
|
||||
#else
|
||||
if (obj.FlagWord.HasFlag(flag: SegmentTableEntryFlag.RELOCINFO))
|
||||
#endif
|
||||
{
|
||||
obj.PerSegmentData = ParsePerSegmentData(data);
|
||||
}
|
||||
|
||||
// Seek back to the end of the entry
|
||||
data.Seek(currentOffset, SeekOrigin.Begin);
|
||||
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,8 +97,7 @@ namespace SabreTools.Serialization.Deserializers
|
||||
blocks[i] = block;
|
||||
}
|
||||
|
||||
// TODO: Make this a direct assignment when Models is updated
|
||||
obj.Blocks = [.. blocks];
|
||||
obj.Blocks = blocks;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -79,15 +79,6 @@ namespace SabreTools.Serialization.Deserializers
|
||||
header.Version = data.ReadBytes(versionOffset);
|
||||
wisOffset = offset;
|
||||
}
|
||||
bool earlyReturn = false;
|
||||
|
||||
// If the header is invalid
|
||||
if (header.Version == null)
|
||||
earlyReturn = true;
|
||||
if (wisOffset < 0)
|
||||
earlyReturn = true;
|
||||
if (headerLength < 0)
|
||||
earlyReturn = true;
|
||||
|
||||
//Seek back to the beginning of the section
|
||||
data.Seek(initialOffset, 0);
|
||||
@@ -101,10 +92,13 @@ namespace SabreTools.Serialization.Deserializers
|
||||
header.FirstExecutableFileEntryLength = data.ReadUInt32LittleEndian();
|
||||
header.MsiFileEntryLength = data.ReadUInt32LittleEndian();
|
||||
|
||||
if (earlyReturn)
|
||||
{
|
||||
// If the reported header information is invalid
|
||||
if (header.Version == null)
|
||||
return header;
|
||||
if (wisOffset < 0)
|
||||
return header;
|
||||
if (headerLength < 0)
|
||||
return header;
|
||||
}
|
||||
|
||||
if (headerLength > 6)
|
||||
{
|
||||
@@ -155,7 +149,6 @@ namespace SabreTools.Serialization.Deserializers
|
||||
header.Strings = stringArrays;
|
||||
|
||||
// Not sure what this data is. Might be a wisescript?
|
||||
// TODO: Should really be done in the wrapper, but almost everything there is static so there's no good place\
|
||||
if (header.UnknownDataSize != 0)
|
||||
data.Seek(header.UnknownDataSize, SeekOrigin.Current);
|
||||
|
||||
@@ -216,9 +209,8 @@ namespace SabreTools.Serialization.Deserializers
|
||||
/// <summary>
|
||||
/// Parse the string table, if possible
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <param name="header"></param>
|
||||
/// <param name="preStringBytesSize"></param>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <param name="preStringValues">Pre-string byte array containing string lengths</param>
|
||||
/// <returns>The filled string table on success, false otherwise</returns>
|
||||
private static byte[][]? ParseStringTable(Stream data, byte[] preStringValues)
|
||||
{
|
||||
|
||||
@@ -1186,51 +1186,9 @@ namespace SabreTools.Serialization
|
||||
while (offset < entry.Data.Length && (offset % 4) != 0)
|
||||
versionInfo.Padding2 = entry.Data.ReadUInt16LittleEndian(ref offset);
|
||||
|
||||
// TODO: Make the following block a private helper method
|
||||
|
||||
// Determine if we have a StringFileInfo or VarFileInfo next
|
||||
if (offset < versionInfo.Length)
|
||||
{
|
||||
// Cache the current offset for reading
|
||||
int currentOffset = offset;
|
||||
|
||||
offset += 6;
|
||||
string? nextKey = entry.Data.ReadNullTerminatedUnicodeString(ref offset);
|
||||
offset = currentOffset;
|
||||
|
||||
if (nextKey == "StringFileInfo")
|
||||
{
|
||||
var stringFileInfo = AsStringFileInfo(entry.Data, ref offset);
|
||||
versionInfo.StringFileInfo = stringFileInfo;
|
||||
}
|
||||
else if (nextKey == "VarFileInfo")
|
||||
{
|
||||
var varFileInfo = AsVarFileInfo(entry.Data, ref offset);
|
||||
versionInfo.VarFileInfo = varFileInfo;
|
||||
}
|
||||
}
|
||||
|
||||
// And again
|
||||
if (offset < versionInfo.Length)
|
||||
{
|
||||
// Cache the current offset for reading
|
||||
int currentOffset = offset;
|
||||
|
||||
offset += 6;
|
||||
string? nextKey = entry.Data.ReadNullTerminatedUnicodeString(ref offset);
|
||||
offset = currentOffset;
|
||||
|
||||
if (nextKey == "StringFileInfo")
|
||||
{
|
||||
var stringFileInfo = AsStringFileInfo(entry.Data, ref offset);
|
||||
versionInfo.StringFileInfo = stringFileInfo;
|
||||
}
|
||||
else if (nextKey == "VarFileInfo")
|
||||
{
|
||||
var varFileInfo = AsVarFileInfo(entry.Data, ref offset);
|
||||
versionInfo.VarFileInfo = varFileInfo;
|
||||
}
|
||||
}
|
||||
// Determine if we have a StringFileInfo or VarFileInfo twice
|
||||
ReadInfoSection(entry.Data, ref offset, versionInfo);
|
||||
ReadInfoSection(entry.Data, ref offset, versionInfo);
|
||||
|
||||
return versionInfo;
|
||||
}
|
||||
@@ -1408,6 +1366,38 @@ namespace SabreTools.Serialization
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read either a `StringFileInfo` or `VarFileInfo` based on the key
|
||||
/// </summary>
|
||||
/// <param name="entry"></param>
|
||||
/// <param name="offset"></param>
|
||||
/// <param name="versionInfo"></param>
|
||||
/// <returns></returns>
|
||||
private static void ReadInfoSection(byte[] data, ref int offset, VersionInfo versionInfo)
|
||||
{
|
||||
// If the offset is invalid, don't move the pointer
|
||||
if (offset < 0 || offset >= versionInfo.Length)
|
||||
return;
|
||||
|
||||
// Cache the current offset for reading
|
||||
int currentOffset = offset;
|
||||
|
||||
offset += 6;
|
||||
string? nextKey = data.ReadNullTerminatedUnicodeString(ref offset);
|
||||
offset = currentOffset;
|
||||
|
||||
if (nextKey == "StringFileInfo")
|
||||
{
|
||||
var stringFileInfo = AsStringFileInfo(data, ref offset);
|
||||
versionInfo.StringFileInfo = stringFileInfo;
|
||||
}
|
||||
else if (nextKey == "VarFileInfo")
|
||||
{
|
||||
var varFileInfo = AsVarFileInfo(data, ref offset);
|
||||
versionInfo.VarFileInfo = varFileInfo;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helpers
|
||||
|
||||
@@ -1,173 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using SabreTools.Hashing;
|
||||
using SabreTools.Matching;
|
||||
using static SabreTools.Models.MoPaQ.Constants;
|
||||
|
||||
namespace SabreTools.Serialization
|
||||
{
|
||||
/// <summary>
|
||||
/// Handler for decrypting MoPaQ block and table data
|
||||
/// </summary>
|
||||
/// TODO: Should this live in IO? New `Encryption` namespace?
|
||||
public class MoPaQDecrypter
|
||||
{
|
||||
#region Private Instance Variables
|
||||
|
||||
/// <summary>
|
||||
/// Buffer for encryption and decryption
|
||||
/// </summary>
|
||||
private readonly uint[] _stormBuffer = new uint[STORM_BUFFER_SIZE];
|
||||
|
||||
#endregion
|
||||
|
||||
public MoPaQDecrypter()
|
||||
{
|
||||
PrepareCryptTable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prepare the encryption table
|
||||
/// </summary>
|
||||
private void PrepareCryptTable()
|
||||
{
|
||||
uint seed = 0x00100001;
|
||||
for (uint index1 = 0; index1 < 0x100; index1++)
|
||||
{
|
||||
for (uint index2 = index1, i = 0; i < 5; i++, index2 += 0x100)
|
||||
{
|
||||
seed = (seed * 125 + 3) % 0x2AAAAB;
|
||||
uint temp1 = (seed & 0xFFFF) << 0x10;
|
||||
|
||||
seed = (seed * 125 + 3) % 0x2AAAAB;
|
||||
uint temp2 = (seed & 0xFFFF);
|
||||
|
||||
_stormBuffer[index2] = (temp1 | temp2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load a table block by optionally decompressing and
|
||||
/// decrypting before returning the data.
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <param name="offset">Data offset to parse</param>
|
||||
/// <param name="expectedHash">Optional MD5 hash for validation</param>
|
||||
/// <param name="compressedSize">Size of the table in the file</param>
|
||||
/// <param name="tableSize">Expected size of the table</param>
|
||||
/// <param name="key">Encryption key to use</param>
|
||||
/// <param name="realTableSize">Output represening the real table size</param>
|
||||
/// <returns>Byte array representing the processed table</returns>
|
||||
public byte[]? LoadTable(Stream data,
|
||||
long offset,
|
||||
byte[]? expectedHash,
|
||||
uint compressedSize,
|
||||
uint tableSize,
|
||||
uint key,
|
||||
out long realTableSize)
|
||||
{
|
||||
byte[]? tableData;
|
||||
byte[]? readBytes;
|
||||
long bytesToRead = tableSize;
|
||||
|
||||
// Allocate the MPQ table
|
||||
tableData = readBytes = new byte[tableSize];
|
||||
|
||||
// Check if the MPQ table is compressed
|
||||
if (compressedSize != 0 && compressedSize < tableSize)
|
||||
{
|
||||
// Allocate temporary buffer for holding compressed data
|
||||
readBytes = new byte[compressedSize];
|
||||
bytesToRead = compressedSize;
|
||||
}
|
||||
|
||||
// Get the file offset from which we will read the table
|
||||
// Note: According to Storm.dll from Warcraft III (version 2002),
|
||||
// if the hash table position is 0xFFFFFFFF, no SetFilePointer call is done
|
||||
// and the table is loaded from the current file offset
|
||||
if (offset == 0xFFFFFFFF)
|
||||
offset = data.Position;
|
||||
|
||||
// Is the sector table within the file?
|
||||
if (offset >= data.Length)
|
||||
{
|
||||
realTableSize = 0;
|
||||
return null;
|
||||
}
|
||||
|
||||
// The hash table and block table can go beyond EOF.
|
||||
// Storm.dll reads as much as possible, then fills the missing part with zeros.
|
||||
// Abused by Spazzler map protector which sets hash table size to 0x00100000
|
||||
// Abused by NP_Protect in MPQs v4 as well
|
||||
if ((offset + bytesToRead) > data.Length)
|
||||
bytesToRead = (uint)(data.Length - offset);
|
||||
|
||||
// Give the caller information that the table was cut
|
||||
realTableSize = bytesToRead;
|
||||
|
||||
// If everything succeeded, read the raw table from the MPQ
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
_ = data.Read(readBytes, 0, (int)bytesToRead);
|
||||
|
||||
// Verify the MD5 of the table, if present
|
||||
byte[]? actualHash = HashTool.GetByteArrayHashArray(readBytes, HashType.MD5);
|
||||
if (expectedHash != null && actualHash != null && !actualHash.EqualsExactly(expectedHash))
|
||||
{
|
||||
Console.WriteLine("Table is corrupt!");
|
||||
return null;
|
||||
}
|
||||
|
||||
// First of all, decrypt the table
|
||||
if (key != 0)
|
||||
tableData = DecryptBlock(readBytes, bytesToRead, key);
|
||||
|
||||
// If the table is compressed, decompress it
|
||||
if (compressedSize != 0 && compressedSize < tableSize)
|
||||
{
|
||||
Console.WriteLine("Table is compressed, it will not read properly!");
|
||||
return null;
|
||||
|
||||
// TODO: Handle decompression
|
||||
// int cbOutBuffer = (int)tableSize;
|
||||
// int cbInBuffer = (int)compressedSize;
|
||||
|
||||
// if (!SCompDecompress2(readBytes, &cbOutBuffer, tableData, cbInBuffer))
|
||||
// errorCode = SErrGetLastError();
|
||||
|
||||
// tableData = readBytes;
|
||||
}
|
||||
|
||||
// Return the MPQ table
|
||||
return tableData;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrypt a single block of data
|
||||
/// </summary>
|
||||
public unsafe byte[] DecryptBlock(byte[] block, long length, uint key)
|
||||
{
|
||||
uint seed = 0xEEEEEEEE;
|
||||
|
||||
uint[] castBlock = new uint[length >> 2];
|
||||
Buffer.BlockCopy(block, 0, castBlock, 0, (int)length);
|
||||
int castBlockPtr = 0;
|
||||
|
||||
// Round to uints
|
||||
length >>= 2;
|
||||
|
||||
while (length-- > 0)
|
||||
{
|
||||
seed += _stormBuffer[MPQ_HASH_KEY2_MIX + (key & 0xFF)];
|
||||
uint ch = castBlock[castBlockPtr] ^ (key + seed);
|
||||
|
||||
key = ((~key << 0x15) + 0x11111111) | (key >> 0x0B);
|
||||
seed = ch + seed + (seed << 5) + 3;
|
||||
castBlock[castBlockPtr++] = ch;
|
||||
}
|
||||
|
||||
Buffer.BlockCopy(castBlock, 0, block, 0, block.Length >> 2);
|
||||
return block;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Text;
|
||||
using SabreTools.Models.BSP;
|
||||
using SabreTools.Models.TAR;
|
||||
using SabreTools.Serialization.Interfaces;
|
||||
|
||||
namespace SabreTools.Serialization.Printers
|
||||
@@ -144,10 +145,21 @@ namespace SabreTools.Serialization.Printers
|
||||
|
||||
for (int i = 0; i < lump.Entities.Length; i++)
|
||||
{
|
||||
// TODO: Implement entity printing
|
||||
var entity = lump.Entities[i];
|
||||
|
||||
builder.AppendLine($" Entity {i}: Not printed yet");
|
||||
builder.AppendLine($" Entity {i}:");
|
||||
if (entity.Attributes == null || entity.Attributes.Count == 0)
|
||||
{
|
||||
builder.AppendLine(" No attributes");
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int j = 0; j < entity.Attributes.Count; j++)
|
||||
{
|
||||
var kvp = entity.Attributes[j];
|
||||
|
||||
builder.AppendLine($" Attribute {j}: {kvp.Key}={kvp.Value}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ namespace SabreTools.Serialization.Printers
|
||||
builder.AppendLine();
|
||||
|
||||
Print(builder, file.Header);
|
||||
// TODO: Capture or print the compressed data
|
||||
Print(builder, file.Trailer);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using SabreTools.IO.Compression.Deflate;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.Models.BFPK;
|
||||
using SabreTools.Serialization.Interfaces;
|
||||
|
||||
@@ -166,7 +167,7 @@ namespace SabreTools.Serialization.Wrappers
|
||||
using FileStream fs = File.OpenWrite(filename);
|
||||
|
||||
// Read the data block
|
||||
var data = ReadFromDataSource(offset, compressedSize);
|
||||
var data = _dataSource.ReadFrom(offset, compressedSize, retainPosition: true);
|
||||
if (data == null)
|
||||
return false;
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.Models.BSP;
|
||||
using SabreTools.Serialization.Interfaces;
|
||||
|
||||
@@ -127,7 +128,7 @@ namespace SabreTools.Serialization.Wrappers
|
||||
|
||||
// Read the data
|
||||
var lump = Lumps[index];
|
||||
var data = ReadFromDataSource(lump.Offset, lump.Length);
|
||||
var data = _dataSource.ReadFrom(lump.Offset, lump.Length, retainPosition: true);
|
||||
if (data == null)
|
||||
return false;
|
||||
|
||||
|
||||
@@ -322,7 +322,7 @@ namespace SabreTools.Serialization.Wrappers
|
||||
return null;
|
||||
|
||||
// Try to read the sector data
|
||||
var sectorData = ReadFromDataSource(sectorDataOffset, (int)SectorSize);
|
||||
var sectorData = _dataSource.ReadFrom(sectorDataOffset, (int)SectorSize, retainPosition: true);
|
||||
if (sectorData == null)
|
||||
return null;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.Serialization.Interfaces;
|
||||
|
||||
namespace SabreTools.Serialization.Wrappers
|
||||
@@ -320,7 +321,7 @@ namespace SabreTools.Serialization.Wrappers
|
||||
for (int i = 0; i < dataBlockOffsets.Count; i++)
|
||||
{
|
||||
int readSize = (int)Math.Min(BlockSize, fileSize);
|
||||
var data = ReadFromDataSource((int)dataBlockOffsets[i], readSize);
|
||||
var data = _dataSource.ReadFrom((int)dataBlockOffsets[i], readSize, retainPosition: true);
|
||||
if (data == null)
|
||||
return false;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using SabreTools.IO.Compression.Blast;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.Models.InstallShieldArchiveV3;
|
||||
using SabreTools.Serialization.Interfaces;
|
||||
|
||||
@@ -239,7 +240,7 @@ namespace SabreTools.Serialization.Wrappers
|
||||
long outputFileSize = file.UncompressedSize;
|
||||
|
||||
// Read the compressed data directly
|
||||
var compressedData = ReadFromDataSource((int)fileOffset, (int)fileSize);
|
||||
var compressedData = _dataSource.ReadFrom((int)fileOffset, (int)fileSize, retainPosition: true);
|
||||
if (compressedData == null)
|
||||
return false;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using SabreTools.IO.Compression.SZDD;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.Models.LZ;
|
||||
using SabreTools.Serialization.Interfaces;
|
||||
|
||||
@@ -110,7 +111,7 @@ namespace SabreTools.Serialization.Wrappers
|
||||
return false;
|
||||
|
||||
// Read in the data as an array
|
||||
byte[]? contents = ReadFromDataSource(DataOffset, (int)compressedSize);
|
||||
byte[]? contents = _dataSource.ReadFrom(DataOffset, (int)compressedSize, retainPosition: true);
|
||||
if (contents == null)
|
||||
return false;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using SabreTools.IO.Compression.SZDD;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.Models.LZ;
|
||||
using SabreTools.Serialization.Interfaces;
|
||||
|
||||
@@ -94,7 +95,7 @@ namespace SabreTools.Serialization.Wrappers
|
||||
return false;
|
||||
|
||||
// Read in the data as an array
|
||||
byte[]? contents = ReadFromDataSource(12, (int)compressedSize);
|
||||
byte[]? contents = _dataSource.ReadFrom(12, (int)compressedSize, retainPosition: true);
|
||||
if (contents == null)
|
||||
return false;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using SabreTools.IO.Compression.SZDD;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.Models.LZ;
|
||||
using SabreTools.Serialization.Interfaces;
|
||||
|
||||
@@ -110,7 +111,7 @@ namespace SabreTools.Serialization.Wrappers
|
||||
return false;
|
||||
|
||||
// Read in the data as an array
|
||||
byte[]? contents = ReadFromDataSource(14, (int)compressedSize);
|
||||
byte[]? contents = _dataSource.ReadFrom(14, (int)compressedSize, retainPosition: true);
|
||||
if (contents == null)
|
||||
return false;
|
||||
|
||||
|
||||
@@ -139,7 +139,7 @@ namespace SabreTools.Serialization.Wrappers
|
||||
return [];
|
||||
|
||||
// Read the entry data and return
|
||||
return ReadFromDataSource(offset, length);
|
||||
return _dataSource.ReadFrom(offset, length, retainPosition: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -315,7 +315,7 @@ namespace SabreTools.Serialization.Wrappers
|
||||
if (length == -1)
|
||||
length = Length;
|
||||
|
||||
return ReadFromDataSource(rangeStart, (int)length);
|
||||
return _dataSource.ReadFrom(rangeStart, (int)length, retainPosition: true);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -176,7 +176,7 @@ namespace SabreTools.Serialization.Wrappers
|
||||
|
||||
// Otherwise, cache and return the data
|
||||
long overlayLength = dataLength - endOfSectionData;
|
||||
_overlayData = ReadFromDataSource((int)endOfSectionData, (int)overlayLength);
|
||||
_overlayData = _dataSource.ReadFrom((int)endOfSectionData, (int)overlayLength, retainPosition: true);
|
||||
return _overlayData;
|
||||
}
|
||||
}
|
||||
@@ -248,7 +248,7 @@ namespace SabreTools.Serialization.Wrappers
|
||||
long overlayLength = Math.Min(dataLength - endOfSectionData, 16 * 1024 * 1024);
|
||||
|
||||
// Otherwise, cache and return the strings
|
||||
_overlayStrings = ReadStringsFromDataSource(endOfSectionData, (int)overlayLength, charLimit: 3);
|
||||
_overlayStrings = _dataSource.ReadStringsFrom(endOfSectionData, (int)overlayLength, charLimit: 3);
|
||||
return _overlayStrings;
|
||||
}
|
||||
}
|
||||
@@ -285,7 +285,7 @@ namespace SabreTools.Serialization.Wrappers
|
||||
// Populate the raw stub executable data based on the source
|
||||
int endOfStubHeader = 0x40;
|
||||
int lengthOfStubExecutableData = (int)Stub.Header.NewExeHeaderAddr - endOfStubHeader;
|
||||
_stubExecutableData = ReadFromDataSource(endOfStubHeader, lengthOfStubExecutableData);
|
||||
_stubExecutableData = _dataSource.ReadFrom(endOfStubHeader, lengthOfStubExecutableData, retainPosition: true);
|
||||
|
||||
// Cache and return the stub executable data, even if null
|
||||
return _stubExecutableData;
|
||||
@@ -398,13 +398,14 @@ namespace SabreTools.Serialization.Wrappers
|
||||
/// <remarks>
|
||||
/// This extracts the following data:
|
||||
/// - Archives and executables in the overlay
|
||||
/// - Wise installers
|
||||
/// </remarks>
|
||||
public bool Extract(string outputDirectory, bool includeDebug)
|
||||
{
|
||||
bool overlay = ExtractFromOverlay(outputDirectory, includeDebug);
|
||||
// TODO: Add Wise installer handling here
|
||||
bool wise = ExtractWise(outputDirectory, includeDebug);
|
||||
|
||||
return overlay;
|
||||
return overlay | wise;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -512,6 +513,71 @@ namespace SabreTools.Serialization.Wrappers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extract data from a Wise installer
|
||||
/// </summary>
|
||||
/// <param name="outputDirectory">Output directory to write to</param>
|
||||
/// <param name="includeDebug">True to include debug data, false otherwise</param>
|
||||
/// <returns>True if extraction succeeded, false otherwise</returns>
|
||||
public bool ExtractWise(string outputDirectory, bool includeDebug)
|
||||
{
|
||||
// Get the source data for reading
|
||||
Stream source = _dataSource;
|
||||
if (Filename != null)
|
||||
{
|
||||
// Try to open a multipart file
|
||||
if (WiseOverlayHeader.OpenFile(Filename, includeDebug, out var temp) && temp != null)
|
||||
source = temp;
|
||||
}
|
||||
|
||||
// Try to find the overlay header
|
||||
long offset = FindWiseOverlayHeader(includeDebug);
|
||||
if (offset < 0)
|
||||
{
|
||||
if (includeDebug) Console.Error.WriteLine("Could not find the overlay header");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Seek to the overlay and parse
|
||||
source.Seek(offset, SeekOrigin.Begin);
|
||||
var header = WiseOverlayHeader.Create(source);
|
||||
if (header == null)
|
||||
{
|
||||
if (includeDebug) Console.Error.WriteLine("Could not parse the overlay header");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extract the header-defined files
|
||||
bool extracted = header.ExtractHeaderDefinedFiles(outputDirectory, includeDebug, out long dataStart);
|
||||
if (!extracted)
|
||||
{
|
||||
if (includeDebug) Console.Error.WriteLine("Could not extract header-defined files");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Open the script file from the output directory
|
||||
var scriptStream = File.OpenRead(Path.Combine(outputDirectory, "WiseScript.bin"));
|
||||
var script = WiseScript.Create(scriptStream);
|
||||
if (script == null)
|
||||
{
|
||||
if (includeDebug) Console.Error.WriteLine("Could not parse WiseScript.bin");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the source directory
|
||||
string? sourceDirectory = null;
|
||||
if (Filename != null)
|
||||
sourceDirectory = Path.GetDirectoryName(Path.GetFullPath(Filename));
|
||||
|
||||
// Process the state machine
|
||||
return script.ProcessStateMachine(_dataSource,
|
||||
sourceDirectory,
|
||||
dataStart,
|
||||
outputDirectory,
|
||||
header.IsPKZIP,
|
||||
includeDebug);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Resources
|
||||
@@ -579,7 +645,7 @@ namespace SabreTools.Serialization.Wrappers
|
||||
return [];
|
||||
|
||||
// Read the resource data and return
|
||||
return ReadFromDataSource(offset, length);
|
||||
return _dataSource.ReadFrom(offset, length, retainPosition: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -628,6 +694,45 @@ namespace SabreTools.Serialization.Wrappers
|
||||
return offset;
|
||||
}
|
||||
|
||||
/// <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(bool includeDebug)
|
||||
{
|
||||
// Get the overlay offset
|
||||
long overlayOffset = OverlayAddress;
|
||||
if (overlayOffset < 0 || overlayOffset >= Length)
|
||||
{
|
||||
if (includeDebug) Console.Error.WriteLine("Could not parse the overlay header");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Attempt to get the overlay header
|
||||
_dataSource.Seek(overlayOffset, SeekOrigin.Begin);
|
||||
var header = Create(_dataSource);
|
||||
if (header != null)
|
||||
return overlayOffset;
|
||||
|
||||
// Align and loop to see if it can be found
|
||||
_dataSource.Seek(overlayOffset, SeekOrigin.Begin);
|
||||
_dataSource.AlignToBoundary(0x10);
|
||||
overlayOffset = _dataSource.Position;
|
||||
while (_dataSource.Position < Length)
|
||||
{
|
||||
_dataSource.Seek(overlayOffset, SeekOrigin.Begin);
|
||||
header = Create(_dataSource);
|
||||
if (header != null)
|
||||
return overlayOffset;
|
||||
|
||||
overlayOffset += 0x10;
|
||||
}
|
||||
|
||||
header = null;
|
||||
return -1;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Segments
|
||||
@@ -671,7 +776,7 @@ namespace SabreTools.Serialization.Wrappers
|
||||
return [];
|
||||
|
||||
// Read the segment data and return
|
||||
return ReadFromDataSource(offset, length);
|
||||
return _dataSource.ReadFrom(offset, length, retainPosition: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -741,7 +846,7 @@ namespace SabreTools.Serialization.Wrappers
|
||||
if (length == -1)
|
||||
length = Length;
|
||||
|
||||
return ReadFromDataSource(rangeStart, (int)length);
|
||||
return _dataSource.ReadFrom(rangeStart, (int)length, retainPosition: true);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.Models.PAK;
|
||||
using SabreTools.Serialization.Interfaces;
|
||||
|
||||
@@ -127,7 +128,7 @@ namespace SabreTools.Serialization.Wrappers
|
||||
|
||||
// Read the item data
|
||||
var directoryItem = DirectoryItems[index];
|
||||
var data = ReadFromDataSource((int)directoryItem.ItemOffset, (int)directoryItem.ItemLength);
|
||||
var data = _dataSource.ReadFrom((int)directoryItem.ItemOffset, (int)directoryItem.ItemLength, retainPosition: true);
|
||||
if (data == null)
|
||||
return false;
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.Models.PFF;
|
||||
using SabreTools.Serialization.Interfaces;
|
||||
|
||||
@@ -158,7 +159,7 @@ namespace SabreTools.Serialization.Wrappers
|
||||
using FileStream fs = File.OpenWrite(filename);
|
||||
|
||||
// Read the data block
|
||||
var data = ReadFromDataSource(offset, size);
|
||||
var data = _dataSource.ReadFrom(offset, size, retainPosition: true);
|
||||
if (data == null)
|
||||
return false;
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ namespace SabreTools.Serialization.Wrappers
|
||||
return _entryPointData;
|
||||
|
||||
// Read the first 128 bytes of the entry point
|
||||
_entryPointData = ReadFromDataSource(entryPointAddress, length: 128);
|
||||
_entryPointData = _dataSource.ReadFrom(entryPointAddress, length: 128, retainPosition: true);
|
||||
|
||||
// Cache and return the entry point padding data, even if null
|
||||
return _entryPointData;
|
||||
@@ -135,7 +135,7 @@ namespace SabreTools.Serialization.Wrappers
|
||||
if (headerLength <= 0)
|
||||
_headerPaddingData = [];
|
||||
else
|
||||
_headerPaddingData = ReadFromDataSource((int)headerStartAddress, headerLength);
|
||||
_headerPaddingData = _dataSource.ReadFrom((int)headerStartAddress, headerLength, retainPosition: true);
|
||||
|
||||
// Cache and return the header padding data, even if null
|
||||
return _headerPaddingData;
|
||||
@@ -183,7 +183,7 @@ namespace SabreTools.Serialization.Wrappers
|
||||
if (headerLength <= 0)
|
||||
_headerPaddingStrings = [];
|
||||
else
|
||||
_headerPaddingStrings = ReadStringsFromDataSource((int)headerStartAddress, headerLength, charLimit: 3);
|
||||
_headerPaddingStrings = _dataSource.ReadStringsFrom((int)headerStartAddress, headerLength, charLimit: 3);
|
||||
|
||||
// Cache and return the header padding data, even if null
|
||||
return _headerPaddingStrings;
|
||||
@@ -333,7 +333,7 @@ namespace SabreTools.Serialization.Wrappers
|
||||
|
||||
// Otherwise, cache and return the data
|
||||
long overlayLength = dataLength - endOfSectionData;
|
||||
_overlayData = ReadFromDataSource(endOfSectionData, (int)overlayLength);
|
||||
_overlayData = _dataSource.ReadFrom(endOfSectionData, (int)overlayLength, retainPosition: true);
|
||||
return _overlayData;
|
||||
}
|
||||
}
|
||||
@@ -414,7 +414,7 @@ namespace SabreTools.Serialization.Wrappers
|
||||
long overlayLength = Math.Min(dataLength - endOfSectionData, 16 * 1024 * 1024);
|
||||
|
||||
// Otherwise, cache and return the strings
|
||||
_overlayStrings = ReadStringsFromDataSource(endOfSectionData, (int)overlayLength, charLimit: 3);
|
||||
_overlayStrings = _dataSource.ReadStringsFrom(endOfSectionData, (int)overlayLength, charLimit: 3);
|
||||
return _overlayStrings;
|
||||
}
|
||||
}
|
||||
@@ -487,7 +487,7 @@ namespace SabreTools.Serialization.Wrappers
|
||||
// Populate the raw stub executable data based on the source
|
||||
int endOfStubHeader = 0x40;
|
||||
int lengthOfStubExecutableData = (int)Stub.Header.NewExeHeaderAddr - endOfStubHeader;
|
||||
_stubExecutableData = ReadFromDataSource(endOfStubHeader, lengthOfStubExecutableData);
|
||||
_stubExecutableData = _dataSource.ReadFrom(endOfStubHeader, lengthOfStubExecutableData, retainPosition: true);
|
||||
|
||||
// Cache and return the stub executable data, even if null
|
||||
return _stubExecutableData;
|
||||
@@ -1029,7 +1029,7 @@ namespace SabreTools.Serialization.Wrappers
|
||||
byte[]? entryData;
|
||||
try
|
||||
{
|
||||
entryData = ReadFromDataSource((int)address, (int)size);
|
||||
entryData = _dataSource.ReadFrom((int)address, (int)size, retainPosition: true);
|
||||
if (entryData == null || entryData.Length < 4)
|
||||
continue;
|
||||
}
|
||||
@@ -1088,15 +1088,16 @@ namespace SabreTools.Serialization.Wrappers
|
||||
/// - Archives and executables in resource data
|
||||
/// - CExe-compressed resource data
|
||||
/// - SFX archives (7z, PKZIP, RAR)
|
||||
/// - Wise installers
|
||||
/// </remarks>
|
||||
public bool Extract(string outputDirectory, bool includeDebug)
|
||||
{
|
||||
bool cexe = ExtractCExe(outputDirectory, includeDebug);
|
||||
bool overlay = ExtractFromOverlay(outputDirectory, includeDebug);
|
||||
bool resources = ExtractFromResources(outputDirectory, includeDebug);
|
||||
// TODO: Add Wise installer handling here
|
||||
bool wise = ExtractWise(outputDirectory, includeDebug);
|
||||
|
||||
return cexe || overlay || resources;
|
||||
return cexe || overlay || resources | wise;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1366,6 +1367,37 @@ namespace SabreTools.Serialization.Wrappers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extract data from a Wise installer
|
||||
/// </summary>
|
||||
/// <param name="outputDirectory">Output directory to write to</param>
|
||||
/// <param name="includeDebug">True to include debug data, false otherwise</param>
|
||||
/// <returns>True if extraction succeeded, false otherwise</returns>
|
||||
public bool ExtractWise(string outputDirectory, bool includeDebug)
|
||||
{
|
||||
// Get the source data for reading
|
||||
Stream source = _dataSource;
|
||||
if (Filename != null)
|
||||
{
|
||||
// Try to open a multipart file
|
||||
if (WiseOverlayHeader.OpenFile(Filename, includeDebug, out var temp) && temp != null)
|
||||
source = temp;
|
||||
}
|
||||
|
||||
// Try to find the overlay header
|
||||
long offset = FindWiseOverlayHeader(includeDebug);
|
||||
if (offset > 0 && offset < Length)
|
||||
return ExtractWiseOverlay(outputDirectory, includeDebug, source, offset);
|
||||
|
||||
// Try to find the section header
|
||||
offset = FindWiseSectionHeader(includeDebug);
|
||||
if (offset > 0 && offset < Length)
|
||||
return ExtractWiseSection(outputDirectory, includeDebug, source, offset);
|
||||
|
||||
// Everything else could not extract
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decompress CExe data compressed with LZ
|
||||
/// </summary>
|
||||
@@ -1435,6 +1467,81 @@ namespace SabreTools.Serialization.Wrappers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extract using Wise overlay
|
||||
/// </summary>
|
||||
/// <param name="outputDirectory">Output directory to write to</param>
|
||||
/// <param name="includeDebug">True to include debug data, false otherwise</param>
|
||||
/// <param name="source">Potentially multi-part stream to read</param>
|
||||
/// <param name="offset">Offset to the start of the overlay header</param>
|
||||
/// <returns>True if extraction succeeded, false otherwise</returns>
|
||||
private bool ExtractWiseOverlay(string outputDirectory, bool includeDebug, Stream source, long offset)
|
||||
{
|
||||
// Seek to the overlay and parse
|
||||
source.Seek(offset, SeekOrigin.Begin);
|
||||
var header = WiseOverlayHeader.Create(source);
|
||||
if (header == null)
|
||||
{
|
||||
if (includeDebug) Console.Error.WriteLine("Could not parse the overlay header");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extract the header-defined files
|
||||
bool extracted = header.ExtractHeaderDefinedFiles(outputDirectory, includeDebug, out long dataStart);
|
||||
if (!extracted)
|
||||
{
|
||||
if (includeDebug) Console.Error.WriteLine("Could not extract header-defined files");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Open the script file from the output directory
|
||||
var scriptStream = File.OpenRead(Path.Combine(outputDirectory, "WiseScript.bin"));
|
||||
var script = WiseScript.Create(scriptStream);
|
||||
if (script == null)
|
||||
{
|
||||
if (includeDebug) Console.Error.WriteLine("Could not parse WiseScript.bin");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the source directory
|
||||
string? sourceDirectory = null;
|
||||
if (Filename != null)
|
||||
sourceDirectory = Path.GetDirectoryName(Path.GetFullPath(Filename));
|
||||
|
||||
// Process the state machine
|
||||
return script.ProcessStateMachine(_dataSource,
|
||||
sourceDirectory,
|
||||
dataStart,
|
||||
outputDirectory,
|
||||
header.IsPKZIP,
|
||||
includeDebug);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extract using Wise section
|
||||
/// </summary>
|
||||
/// <param name="outputDirectory">Output directory to write to</param>
|
||||
/// <param name="includeDebug">True to include debug data, false otherwise</param>
|
||||
/// <param name="source">Potentially multi-part stream to read</param>
|
||||
/// <param name="offset">Offset to the start of the section header</param>
|
||||
/// <returns>True if extraction succeeded, false otherwise</returns>
|
||||
private bool ExtractWiseSection(string outputDirectory, bool includeDebug, Stream source, long offset)
|
||||
{
|
||||
// Get the size of the section and seek to the start
|
||||
source.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Write section data to new stream
|
||||
var header = WiseSectionHeader.Create(source);
|
||||
if (header == null)
|
||||
{
|
||||
if (includeDebug) Console.Error.WriteLine("Could not parse the section header");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Attempt to extract section
|
||||
return header.Extract(outputDirectory, includeDebug);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Resource Data
|
||||
@@ -1620,6 +1727,149 @@ namespace SabreTools.Serialization.Wrappers
|
||||
return 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(bool includeDebug)
|
||||
{
|
||||
// Get the overlay offset
|
||||
long overlayOffset = OverlayAddress;
|
||||
|
||||
// Attempt to get the overlay header
|
||||
if (overlayOffset >= 0 && overlayOffset < Length)
|
||||
{
|
||||
_dataSource.Seek(overlayOffset, SeekOrigin.Begin);
|
||||
var header = Create(_dataSource);
|
||||
if (header != null)
|
||||
return overlayOffset;
|
||||
}
|
||||
|
||||
// Check section data
|
||||
foreach (var section in SectionTable ?? [])
|
||||
{
|
||||
string sectionName = Encoding.ASCII.GetString(section.Name ?? []).TrimEnd('\0');
|
||||
long sectionOffset = section.VirtualAddress.ConvertVirtualAddress(SectionTable);
|
||||
_dataSource.Seek(sectionOffset, SeekOrigin.Begin);
|
||||
|
||||
var header = Create(_dataSource);
|
||||
if (header != null)
|
||||
return sectionOffset;
|
||||
|
||||
// Check after the resource table
|
||||
if (sectionName == ".rsrc")
|
||||
{
|
||||
// Data immediately following
|
||||
long afterResourceOffset = sectionOffset + section.SizeOfRawData;
|
||||
_dataSource.Seek(afterResourceOffset, SeekOrigin.Begin);
|
||||
|
||||
header = Create(_dataSource);
|
||||
if (header != null)
|
||||
return afterResourceOffset;
|
||||
|
||||
// Data following padding data
|
||||
_dataSource.Seek(afterResourceOffset, SeekOrigin.Begin);
|
||||
_ = _dataSource.ReadNullTerminatedAnsiString();
|
||||
|
||||
afterResourceOffset = _dataSource.Position;
|
||||
header = Create(_dataSource);
|
||||
if (header != null)
|
||||
return afterResourceOffset;
|
||||
}
|
||||
}
|
||||
|
||||
// If there are no resources
|
||||
if (OptionalHeader?.ResourceTable == null || ResourceData == null)
|
||||
return -1;
|
||||
|
||||
// Get the resources that have an executable signature
|
||||
bool exeResources = false;
|
||||
foreach (var kvp in ResourceData)
|
||||
{
|
||||
if (kvp.Value == null || kvp.Value is not byte[] ba)
|
||||
continue;
|
||||
if (!ba.StartsWith(Models.MSDOS.Constants.SignatureBytes))
|
||||
continue;
|
||||
|
||||
exeResources = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// If there are no executable resources
|
||||
if (!exeResources)
|
||||
{
|
||||
if (includeDebug) Console.Error.WriteLine("Could not find the overlay header");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Get the raw resource table offset
|
||||
long resourceTableOffset = OptionalHeader.ResourceTable.VirtualAddress.ConvertVirtualAddress(SectionTable);
|
||||
if (resourceTableOffset <= 0)
|
||||
{
|
||||
if (includeDebug) Console.Error.WriteLine("Could not find the overlay header");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Search the resource table data for the offset
|
||||
long resourceOffset = -1;
|
||||
_dataSource.Seek(resourceTableOffset, SeekOrigin.Begin);
|
||||
while (_dataSource.Position < resourceTableOffset + OptionalHeader.ResourceTable.Size && _dataSource.Position < _dataSource.Length)
|
||||
{
|
||||
ushort possibleSignature = _dataSource.ReadUInt16();
|
||||
if (possibleSignature == Models.MSDOS.Constants.SignatureUInt16)
|
||||
{
|
||||
resourceOffset = _dataSource.Position - 2;
|
||||
break;
|
||||
}
|
||||
|
||||
_dataSource.Seek(-1, SeekOrigin.Current);
|
||||
}
|
||||
|
||||
// If there was no valid offset, somehow
|
||||
if (resourceOffset == -1)
|
||||
{
|
||||
if (includeDebug) Console.Error.WriteLine("Could not find the overlay header");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Parse the executable and recurse
|
||||
_dataSource.Seek(resourceOffset, SeekOrigin.Begin);
|
||||
var resourceExe = WrapperFactory.CreateExecutableWrapper(_dataSource);
|
||||
if (resourceExe is not PortableExecutable resourcePex)
|
||||
{
|
||||
if (includeDebug) Console.Error.WriteLine("Could not find the overlay header");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return resourcePex.FindWiseOverlayHeader(includeDebug);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find the location of a Wise section header, if it exists
|
||||
/// </summary>
|
||||
/// <param name="includeDebug">True to include debug data, false otherwise</param>
|
||||
/// <returns>Offset to the section header on success, -1 otherwise</returns>
|
||||
public long FindWiseSectionHeader(bool includeDebug)
|
||||
{
|
||||
// If the section table is invalid
|
||||
if (SectionTable == null)
|
||||
return -1;
|
||||
|
||||
// Find the .WISE section
|
||||
foreach (var section in SectionTable)
|
||||
{
|
||||
string sectionName = Encoding.ASCII.GetString(section.Name ?? []).TrimEnd('\0');
|
||||
if (sectionName != ".WISE")
|
||||
continue;
|
||||
|
||||
return section.VirtualAddress.ConvertVirtualAddress(SectionTable);
|
||||
}
|
||||
|
||||
// Otherwise, it could not be found
|
||||
return -1;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Resource Parsing
|
||||
@@ -1966,7 +2216,7 @@ namespace SabreTools.Serialization.Wrappers
|
||||
return _sectionData[index];
|
||||
|
||||
// Populate the raw section data based on the source
|
||||
byte[]? sectionData = ReadFromDataSource((int)address, (int)size);
|
||||
byte[]? sectionData = _dataSource.ReadFrom((int)address, (int)size, retainPosition: true);
|
||||
|
||||
// Cache and return the section data, even if null
|
||||
_sectionData[index] = sectionData ?? [];
|
||||
@@ -2052,7 +2302,7 @@ namespace SabreTools.Serialization.Wrappers
|
||||
return _sectionStringData[index];
|
||||
|
||||
// Populate the section string data based on the source
|
||||
List<string>? sectionStringData = ReadStringsFromDataSource((int)address, (int)size);
|
||||
List<string>? sectionStringData = _dataSource.ReadStringsFrom((int)address, (int)size);
|
||||
|
||||
// Cache and return the section string data, even if null
|
||||
_sectionStringData[index] = sectionStringData ?? [];
|
||||
@@ -2136,7 +2386,7 @@ namespace SabreTools.Serialization.Wrappers
|
||||
return _tableData[index];
|
||||
|
||||
// Populate the raw table data based on the source
|
||||
byte[]? tableData = ReadFromDataSource((int)address, (int)size);
|
||||
byte[]? tableData = _dataSource.ReadFrom((int)address, (int)size, retainPosition: true);
|
||||
|
||||
// Cache and return the table data, even if null
|
||||
_tableData[index] = tableData ?? [];
|
||||
@@ -2181,7 +2431,7 @@ namespace SabreTools.Serialization.Wrappers
|
||||
return _tableStringData[index];
|
||||
|
||||
// Populate the table string data based on the source
|
||||
List<string>? tableStringData = ReadStringsFromDataSource((int)address, (int)size);
|
||||
List<string>? tableStringData = _dataSource.ReadStringsFrom((int)address, (int)size);
|
||||
|
||||
// Cache and return the table string data, even if null
|
||||
_tableStringData[index] = tableStringData ?? [];
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.Models.Quantum;
|
||||
using SabreTools.Serialization.Interfaces;
|
||||
|
||||
@@ -140,7 +141,7 @@ namespace SabreTools.Serialization.Wrappers
|
||||
// Read the entire compressed data
|
||||
int compressedDataOffset = (int)CompressedDataOffset;
|
||||
long compressedDataLength = Length - compressedDataOffset;
|
||||
var compressedData = ReadFromDataSource(compressedDataOffset, (int)compressedDataLength);
|
||||
var compressedData = _dataSource.ReadFrom(compressedDataOffset, (int)compressedDataLength, retainPosition: true);
|
||||
|
||||
// Print a debug reminder
|
||||
if (includeDebug) Console.WriteLine("Quantum archive extraction is unsupported");
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using SabreTools.IO.Compression.zlib;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.Models.SGA;
|
||||
using SabreTools.Serialization.Interfaces;
|
||||
|
||||
@@ -203,7 +204,7 @@ namespace SabreTools.Serialization.Wrappers
|
||||
long outputFileSize = GetUncompressedSize(index);
|
||||
|
||||
// Read the compressed data directly
|
||||
var compressedData = ReadFromDataSource((int)fileOffset, (int)fileSize);
|
||||
var compressedData = _dataSource.ReadFrom((int)fileOffset, (int)fileSize, retainPosition: true);
|
||||
if (compressedData == null)
|
||||
return false;
|
||||
|
||||
|
||||
@@ -17,8 +17,7 @@ namespace SabreTools.Serialization.Wrappers
|
||||
#region Extension Properties
|
||||
|
||||
/// <inheritdoc cref="Archive.Entries"/>
|
||||
/// TODO: Simplify when Models is updated
|
||||
public Entry[]? Entries => Model.Entries != null ? [.. Model.Entries] : null;
|
||||
public Entry[]? Entries => Model.Entries;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.Models.BSP;
|
||||
using SabreTools.Serialization.Interfaces;
|
||||
|
||||
@@ -127,7 +128,7 @@ namespace SabreTools.Serialization.Wrappers
|
||||
|
||||
// Read the data
|
||||
var lump = Lumps[index];
|
||||
var data = ReadFromDataSource(lump.Offset, lump.Length);
|
||||
var data = _dataSource.ReadFrom(lump.Offset, lump.Length, retainPosition: true);
|
||||
if (data == null)
|
||||
return false;
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.Serialization.Interfaces;
|
||||
|
||||
namespace SabreTools.Serialization.Wrappers
|
||||
@@ -126,7 +127,7 @@ namespace SabreTools.Serialization.Wrappers
|
||||
|
||||
// Read the data -- TODO: Handle uncompressed lumps (see BSP.ExtractTexture)
|
||||
var lump = DirEntries[index];
|
||||
var data = ReadFromDataSource((int)lump.Offset, (int)lump.Length);
|
||||
var data = _dataSource.ReadFrom((int)lump.Offset, (int)lump.Length, retainPosition: true);
|
||||
if (data == null)
|
||||
return false;
|
||||
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using SabreTools.IO.Compression.Deflate;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.IO.Streams;
|
||||
using SabreTools.Matching;
|
||||
using SabreTools.Models.WiseInstaller;
|
||||
|
||||
namespace SabreTools.Serialization.Wrappers
|
||||
@@ -20,6 +17,59 @@ namespace SabreTools.Serialization.Wrappers
|
||||
|
||||
#region Extension Properties
|
||||
|
||||
/// <summary>
|
||||
/// Returns the offset relative to the start of the header
|
||||
/// where the compressed data lives
|
||||
/// </summary>
|
||||
public long CompressedDataOffset
|
||||
{
|
||||
get
|
||||
{
|
||||
long offset = 0;
|
||||
if (Model.DllNameLen > 0)
|
||||
{
|
||||
offset += Model.DllNameLen;
|
||||
offset += 4; // DllSize
|
||||
}
|
||||
|
||||
offset += 4; // Flags
|
||||
offset += 12; // GraphicsData
|
||||
offset += 4; // WiseScriptExitEventOffset
|
||||
offset += 4; // WiseScriptCancelEventOffset
|
||||
offset += 4; // WiseScriptInflatedSize
|
||||
offset += 4; // WiseScriptDeflatedSize
|
||||
offset += 4; // WiseDllDeflatedSize
|
||||
offset += 4; // Ctl3d32DeflatedSize
|
||||
offset += 4; // SomeData4DeflatedSize
|
||||
offset += 4; // RegToolDeflatedSize
|
||||
offset += 4; // ProgressDllDeflatedSize
|
||||
offset += 4; // SomeData7DeflatedSize
|
||||
offset += 4; // SomeData8DeflatedSize
|
||||
offset += 4; // SomeData9DeflatedSize
|
||||
offset += 4; // SomeData10DeflatedSize
|
||||
offset += 4; // FinalFileDeflatedSize
|
||||
offset += 4; // FinalFileInflatedSize
|
||||
offset += 4; // EOF
|
||||
|
||||
if (DibDeflatedSize == 0 && Model.Endianness == 0)
|
||||
return offset;
|
||||
|
||||
offset += 4; // DibDeflatedSize
|
||||
offset += 4; // DibInflatedSize
|
||||
|
||||
if (Model.InstallScriptDeflatedSize != null)
|
||||
offset += 4; // InstallScriptDeflatedSize
|
||||
|
||||
if (Model.CharacterSet != null)
|
||||
offset += 4; // CharacterSet
|
||||
|
||||
offset += 2; // Endianness
|
||||
offset += Model.InitTextLen;
|
||||
|
||||
return offset;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="OverlayHeader.Ctl3d32DeflatedSize"/>
|
||||
public uint Ctl3d32DeflatedSize => Model.Ctl3d32DeflatedSize;
|
||||
|
||||
@@ -155,280 +205,86 @@ namespace SabreTools.Serialization.Wrappers
|
||||
#region Extraction
|
||||
|
||||
/// <summary>
|
||||
/// Extract all files from a Wise installer to an output directory
|
||||
/// Extract the predefined, static files defined in the header
|
||||
/// </summary>
|
||||
/// <param name="filename">Input filename to read from</param>
|
||||
/// <param name="outputDirectory">Output directory to write to</param>
|
||||
/// <param name="includeDebug">True to include debug data, false otherwise</param>
|
||||
/// <returns>True if all files extracted, false otherwise</returns>
|
||||
public static bool ExtractAll(string? filename, string outputDirectory, bool includeDebug)
|
||||
/// <returns>True if the files extracted successfully, false otherwise</returns>
|
||||
public bool ExtractHeaderDefinedFiles(string outputDirectory, bool includeDebug, out long dataStart)
|
||||
{
|
||||
// If the filename is invalid
|
||||
if (filename == null)
|
||||
// Seek to the compressed data offset
|
||||
_dataSource.Seek(CompressedDataOffset, SeekOrigin.Begin);
|
||||
|
||||
// Determine where the remaining compressed data starts
|
||||
dataStart = _dataSource.Position;
|
||||
|
||||
// Extract WiseColors.dib, if it exists
|
||||
var expected = new DeflateInfo { InputSize = DibDeflatedSize, OutputSize = DibInflatedSize, Crc32 = 0 };
|
||||
if (InflateWrapper.ExtractFile(_dataSource, "WiseColors.dib", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL)
|
||||
return false;
|
||||
|
||||
// If the file could not be opened
|
||||
if (!OpenFile(filename, includeDebug, out var stream))
|
||||
// Extract WiseScript.bin
|
||||
expected = new DeflateInfo { InputSize = WiseScriptDeflatedSize, OutputSize = WiseScriptInflatedSize, Crc32 = 0 };
|
||||
if (InflateWrapper.ExtractFile(_dataSource, "WiseScript.bin", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL)
|
||||
return false;
|
||||
|
||||
// Get the source directory
|
||||
string? sourceDirectory = Path.GetDirectoryName(Path.GetFullPath(filename));
|
||||
|
||||
return ExtractAll(stream, sourceDirectory, outputDirectory, includeDebug);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extract all files from a Wise installer to an output directory
|
||||
/// </summary>
|
||||
/// <param name="data">Stream representing the Wise installer</param>
|
||||
/// <param name="outputDirectory">Output directory to write to</param>
|
||||
/// <param name="includeDebug">True to include debug data, false otherwise</param>
|
||||
/// <returns>True if all files extracted, false otherwise</returns>
|
||||
public static bool ExtractAll(Stream? data, string outputDirectory, bool includeDebug)
|
||||
=> ExtractAll(data, sourceDirectory: null, outputDirectory, includeDebug);
|
||||
|
||||
/// <summary>
|
||||
/// Extract all files from a Wise installer to an output directory
|
||||
/// </summary>
|
||||
/// <param name="data">Stream representing the Wise installer</param>
|
||||
/// <param name="sourceDirectory">Directory where installer files live, if possible</param>
|
||||
/// <param name="outputDirectory">Output directory to write to</param>
|
||||
/// <param name="includeDebug">True to include debug data, false otherwise</param>
|
||||
/// <returns>True if all files extracted, false otherwise</returns>
|
||||
public static bool ExtractAll(Stream? data, string? sourceDirectory, string outputDirectory, bool includeDebug)
|
||||
{
|
||||
// If the data is invalid
|
||||
if (data == null || !data.CanRead)
|
||||
// Extract WISE0001.DLL, if it exists
|
||||
expected = new DeflateInfo { InputSize = WiseDllDeflatedSize, OutputSize = -1, Crc32 = 0 };
|
||||
if (InflateWrapper.ExtractFile(_dataSource, "WISE0001.DLL", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL)
|
||||
return false;
|
||||
|
||||
// Attempt to get the overlay header
|
||||
if (!FindOverlayHeader(data, includeDebug, out var header) || header == null)
|
||||
{
|
||||
if (includeDebug) Console.Error.WriteLine("Could not parse the overlay header");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extract the header-defined files
|
||||
bool extracted = header.ExtractHeaderDefinedFiles(data, outputDirectory, includeDebug, out long dataStart);
|
||||
if (!extracted)
|
||||
{
|
||||
if (includeDebug) Console.Error.WriteLine("Could not extract header-defined files");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Open the script file from the output directory
|
||||
var scriptStream = File.OpenRead(Path.Combine(outputDirectory, "WiseScript.bin"));
|
||||
var script = WiseScript.Create(scriptStream);
|
||||
if (script == null)
|
||||
{
|
||||
if (includeDebug) Console.Error.WriteLine("Could not parse WiseScript.bin");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Process the state machine
|
||||
return script.ProcessStateMachine(data,
|
||||
sourceDirectory,
|
||||
dataStart,
|
||||
outputDirectory,
|
||||
header.IsPKZIP,
|
||||
includeDebug);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find the overlay header from the Wise installer, if possible
|
||||
/// </summary>
|
||||
/// <param name="data">Stream representing the Wise installer</param>
|
||||
/// <param name="includeDebug">True to include debug data, false otherwise</param>
|
||||
/// <param name="header">The found overlay header on success, null otherwise</param>
|
||||
/// <returns>True if the header was found and valid, false otherwise</returns>
|
||||
public static bool FindOverlayHeader(Stream data, bool includeDebug, out WiseOverlayHeader? header)
|
||||
{
|
||||
// Set the default header value
|
||||
header = null;
|
||||
|
||||
// Attempt to deserialize the file as either NE or PE
|
||||
var wrapper = WrapperFactory.CreateExecutableWrapper(data);
|
||||
if (wrapper is NewExecutable ne)
|
||||
{
|
||||
return FindOverlayHeader(data, ne, includeDebug, out header);
|
||||
}
|
||||
else if (wrapper is PortableExecutable pe)
|
||||
{
|
||||
return FindOverlayHeader(data, pe, includeDebug, out header);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (includeDebug) Console.Error.WriteLine("Only NE and PE executables are supported");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find the overlay header from a NE Wise installer, if possible
|
||||
/// </summary>
|
||||
/// <param name="data">Stream representing the Wise installer</param>
|
||||
/// <param name="nex">Wrapper representing the NE</param>
|
||||
/// <param name="includeDebug">True to include debug data, false otherwise</param>
|
||||
/// <param name="header">The found overlay header on success, null otherwise</param>
|
||||
/// <returns>True if the header was found and valid, false otherwise</returns>
|
||||
public static bool FindOverlayHeader(Stream data, NewExecutable nex, bool includeDebug, out WiseOverlayHeader? header)
|
||||
{
|
||||
// Set the default header value
|
||||
header = null;
|
||||
|
||||
// Get the overlay offset
|
||||
long overlayOffset = nex.OverlayAddress;
|
||||
if (overlayOffset < 0 || overlayOffset >= data.Length)
|
||||
{
|
||||
if (includeDebug) Console.Error.WriteLine("Could not parse the overlay header");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Attempt to get the overlay header
|
||||
data.Seek(overlayOffset, SeekOrigin.Begin);
|
||||
header = Create(data);
|
||||
if (header != null)
|
||||
return true;
|
||||
|
||||
// Align and loop to see if it can be found
|
||||
data.Seek(overlayOffset, SeekOrigin.Begin);
|
||||
data.AlignToBoundary(0x10);
|
||||
overlayOffset = data.Position;
|
||||
while (data.Position < data.Length)
|
||||
{
|
||||
data.Seek(overlayOffset, SeekOrigin.Begin);
|
||||
header = Create(data);
|
||||
if (header != null)
|
||||
return true;
|
||||
|
||||
overlayOffset += 0x10;
|
||||
}
|
||||
|
||||
header = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find the overlay header from a PE Wise installer, if possible
|
||||
/// </summary>
|
||||
/// <param name="data">Stream representing the Wise installer</param>
|
||||
/// <param name="pex">Wrapper representing the PE</param>
|
||||
/// <param name="includeDebug">True to include debug data, false otherwise</param>
|
||||
/// <param name="header">The found overlay header on success, null otherwise</param>
|
||||
/// <returns>True if the header was found and valid, false otherwise</returns>
|
||||
public static bool FindOverlayHeader(Stream data, PortableExecutable pex, bool includeDebug, out WiseOverlayHeader? header)
|
||||
{
|
||||
// Set the default header value
|
||||
header = null;
|
||||
|
||||
// Get the overlay offset
|
||||
long overlayOffset = pex.OverlayAddress;
|
||||
|
||||
// Attempt to get the overlay header
|
||||
if (overlayOffset >= 0 && overlayOffset < pex.Length)
|
||||
{
|
||||
data.Seek(overlayOffset, SeekOrigin.Begin);
|
||||
header = Create(data);
|
||||
if (header != null)
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check section data
|
||||
foreach (var section in pex.Model.SectionTable ?? [])
|
||||
{
|
||||
string sectionName = Encoding.ASCII.GetString(section.Name ?? []).TrimEnd('\0');
|
||||
long sectionOffset = section.VirtualAddress.ConvertVirtualAddress(pex.Model.SectionTable);
|
||||
data.Seek(sectionOffset, SeekOrigin.Begin);
|
||||
|
||||
header = Create(data);
|
||||
if (header != null)
|
||||
return true;
|
||||
|
||||
// Check after the resource table
|
||||
if (sectionName == ".rsrc")
|
||||
{
|
||||
// Data immediately following
|
||||
long afterResourceOffset = sectionOffset + section.SizeOfRawData;
|
||||
data.Seek(afterResourceOffset, SeekOrigin.Begin);
|
||||
|
||||
header = Create(data);
|
||||
if (header != null)
|
||||
return true;
|
||||
|
||||
// Data following padding data
|
||||
data.Seek(afterResourceOffset, SeekOrigin.Begin);
|
||||
_ = data.ReadNullTerminatedAnsiString();
|
||||
|
||||
header = Create(data);
|
||||
if (header != null)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// If there are no resources
|
||||
if (pex.Model.OptionalHeader?.ResourceTable == null || pex.ResourceData == null)
|
||||
// Extract CTL3D32.DLL, if it exists
|
||||
expected = new DeflateInfo { InputSize = Ctl3d32DeflatedSize, OutputSize = -1, Crc32 = 0 };
|
||||
if (InflateWrapper.ExtractFile(_dataSource, "CTL3D32.DLL", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL)
|
||||
return false;
|
||||
|
||||
// Get the resources that have an executable signature
|
||||
bool exeResources = false;
|
||||
foreach (var kvp in pex.ResourceData)
|
||||
{
|
||||
if (kvp.Value == null || kvp.Value is not byte[] ba)
|
||||
continue;
|
||||
if (!ba.StartsWith(Models.MSDOS.Constants.SignatureBytes))
|
||||
continue;
|
||||
|
||||
exeResources = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// If there are no executable resources
|
||||
if (!exeResources)
|
||||
{
|
||||
if (includeDebug) Console.Error.WriteLine("Could not find the overlay header");
|
||||
// Extract FILE0004, if it exists
|
||||
expected = new DeflateInfo { InputSize = SomeData4DeflatedSize, OutputSize = -1, Crc32 = 0 };
|
||||
if (InflateWrapper.ExtractFile(_dataSource, "FILE0004", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the raw resource table offset
|
||||
long resourceTableOffset = pex.Model.OptionalHeader.ResourceTable.VirtualAddress.ConvertVirtualAddress(pex.Model.SectionTable);
|
||||
if (resourceTableOffset <= 0)
|
||||
{
|
||||
if (includeDebug) Console.Error.WriteLine("Could not find the overlay header");
|
||||
// Extract Ocxreg32.EXE, if it exists
|
||||
expected = new DeflateInfo { InputSize = RegToolDeflatedSize, OutputSize = -1, Crc32 = 0 };
|
||||
if (InflateWrapper.ExtractFile(_dataSource, "Ocxreg32.EXE", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Search the resource table data for the offset
|
||||
long resourceOffset = -1;
|
||||
data.Seek(resourceTableOffset, SeekOrigin.Begin);
|
||||
while (data.Position < resourceTableOffset + pex.Model.OptionalHeader.ResourceTable.Size && data.Position < data.Length)
|
||||
{
|
||||
ushort possibleSignature = data.ReadUInt16();
|
||||
if (possibleSignature == Models.MSDOS.Constants.SignatureUInt16)
|
||||
{
|
||||
resourceOffset = data.Position - 2;
|
||||
break;
|
||||
}
|
||||
|
||||
data.Seek(-1, SeekOrigin.Current);
|
||||
}
|
||||
|
||||
// If there was no valid offset, somehow
|
||||
if (resourceOffset == -1)
|
||||
{
|
||||
if (includeDebug) Console.Error.WriteLine("Could not find the overlay header");
|
||||
// Extract PROGRESS.DLL, if it exists
|
||||
expected = new DeflateInfo { InputSize = ProgressDllDeflatedSize, OutputSize = -1, Crc32 = 0 };
|
||||
if (InflateWrapper.ExtractFile(_dataSource, "PROGRESS.DLL", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parse the executable and recurse
|
||||
data.Seek(resourceOffset, SeekOrigin.Begin);
|
||||
var resourceExe = WrapperFactory.CreateExecutableWrapper(data);
|
||||
if (resourceExe is not PortableExecutable resourcePex)
|
||||
{
|
||||
if (includeDebug) Console.Error.WriteLine("Could not find the overlay header");
|
||||
// Extract FILE0007, if it exists
|
||||
expected = new DeflateInfo { InputSize = SomeData7DeflatedSize, OutputSize = -1, Crc32 = 0 };
|
||||
if (InflateWrapper.ExtractFile(_dataSource, "FILE0007", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL)
|
||||
return false;
|
||||
}
|
||||
|
||||
return FindOverlayHeader(data, resourcePex, includeDebug, out header);
|
||||
// Extract FILE0008, if it exists
|
||||
expected = new DeflateInfo { InputSize = SomeData8DeflatedSize, OutputSize = -1, Crc32 = 0 };
|
||||
if (InflateWrapper.ExtractFile(_dataSource, "FILE0008", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL)
|
||||
return false;
|
||||
|
||||
// Extract FILE0009, if it exists
|
||||
expected = new DeflateInfo { InputSize = SomeData9DeflatedSize, OutputSize = -1, Crc32 = 0 };
|
||||
if (InflateWrapper.ExtractFile(_dataSource, "FILE0009", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL)
|
||||
return false;
|
||||
|
||||
// Extract FILE000A, if it exists
|
||||
expected = new DeflateInfo { InputSize = SomeData10DeflatedSize, OutputSize = -1, Crc32 = 0 };
|
||||
if (InflateWrapper.ExtractFile(_dataSource, "FILE000A", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL)
|
||||
return false;
|
||||
|
||||
// Extract install script, if it exists
|
||||
expected = new DeflateInfo { InputSize = InstallScriptDeflatedSize, OutputSize = -1, Crc32 = 0 };
|
||||
if (InflateWrapper.ExtractFile(_dataSource, "INSTALL_SCRIPT", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL)
|
||||
return false;
|
||||
|
||||
// Extract FILE000{n}.DAT, if it exists
|
||||
expected = new DeflateInfo { InputSize = FinalFileDeflatedSize, OutputSize = FinalFileInflatedSize, Crc32 = 0 };
|
||||
if (InflateWrapper.ExtractFile(_dataSource, IsPKZIP ? null : "FILE00XX.DAT", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL)
|
||||
return false;
|
||||
|
||||
dataStart = _dataSource.Position;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -437,7 +293,7 @@ namespace SabreTools.Serialization.Wrappers
|
||||
/// <param name="filename">Input filename or base name to read from</param>
|
||||
/// <param name="includeDebug">True to include debug data, false otherwise</param>
|
||||
/// <returns>True if the file could be opened, false otherwise</returns>
|
||||
private static bool OpenFile(string filename, bool includeDebug, out ReadOnlyCompositeStream? stream)
|
||||
public static bool OpenFile(string filename, bool includeDebug, out ReadOnlyCompositeStream? stream)
|
||||
{
|
||||
// If the file exists as-is
|
||||
if (File.Exists(filename))
|
||||
@@ -535,89 +391,6 @@ namespace SabreTools.Serialization.Wrappers
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extract the predefined, static files defined in the header
|
||||
/// </summary>
|
||||
/// <param name="data">Stream representing the Wise installer</param>
|
||||
/// <param name="outputDirectory">Output directory to write to</param>
|
||||
/// <param name="includeDebug">True to include debug data, false otherwise</param>
|
||||
/// <returns>True if the files extracted successfully, false otherwise</returns>
|
||||
private bool ExtractHeaderDefinedFiles(Stream data, string outputDirectory, bool includeDebug, out long dataStart)
|
||||
{
|
||||
// TODO: This needs to seek to the end of the header before extracting
|
||||
|
||||
// Determine where the remaining compressed data starts
|
||||
dataStart = data.Position;
|
||||
|
||||
// Extract WiseColors.dib, if it exists
|
||||
var expected = new DeflateInfo { InputSize = DibDeflatedSize, OutputSize = DibInflatedSize, Crc32 = 0 };
|
||||
if (InflateWrapper.ExtractFile(data, "WiseColors.dib", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL)
|
||||
return false;
|
||||
|
||||
// Extract WiseScript.bin
|
||||
expected = new DeflateInfo { InputSize = WiseScriptDeflatedSize, OutputSize = WiseScriptInflatedSize, Crc32 = 0 };
|
||||
if (InflateWrapper.ExtractFile(data, "WiseScript.bin", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL)
|
||||
return false;
|
||||
|
||||
// Extract WISE0001.DLL, if it exists
|
||||
expected = new DeflateInfo { InputSize = WiseDllDeflatedSize, OutputSize = -1, Crc32 = 0 };
|
||||
if (InflateWrapper.ExtractFile(data, "WISE0001.DLL", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL)
|
||||
return false;
|
||||
|
||||
// Extract CTL3D32.DLL, if it exists
|
||||
expected = new DeflateInfo { InputSize = Ctl3d32DeflatedSize, OutputSize = -1, Crc32 = 0 };
|
||||
if (InflateWrapper.ExtractFile(data, "CTL3D32.DLL", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL)
|
||||
return false;
|
||||
|
||||
// Extract FILE0004, if it exists
|
||||
expected = new DeflateInfo { InputSize = SomeData4DeflatedSize, OutputSize = -1, Crc32 = 0 };
|
||||
if (InflateWrapper.ExtractFile(data, "FILE0004", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL)
|
||||
return false;
|
||||
|
||||
// Extract Ocxreg32.EXE, if it exists
|
||||
expected = new DeflateInfo { InputSize = RegToolDeflatedSize, OutputSize = -1, Crc32 = 0 };
|
||||
if (InflateWrapper.ExtractFile(data, "Ocxreg32.EXE", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL)
|
||||
return false;
|
||||
|
||||
// Extract PROGRESS.DLL, if it exists
|
||||
expected = new DeflateInfo { InputSize = ProgressDllDeflatedSize, OutputSize = -1, Crc32 = 0 };
|
||||
if (InflateWrapper.ExtractFile(data, "PROGRESS.DLL", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL)
|
||||
return false;
|
||||
|
||||
// Extract FILE0007, if it exists
|
||||
expected = new DeflateInfo { InputSize = SomeData7DeflatedSize, OutputSize = -1, Crc32 = 0 };
|
||||
if (InflateWrapper.ExtractFile(data, "FILE0007", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL)
|
||||
return false;
|
||||
|
||||
// Extract FILE0008, if it exists
|
||||
expected = new DeflateInfo { InputSize = SomeData8DeflatedSize, OutputSize = -1, Crc32 = 0 };
|
||||
if (InflateWrapper.ExtractFile(data, "FILE0008", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL)
|
||||
return false;
|
||||
|
||||
// Extract FILE0009, if it exists
|
||||
expected = new DeflateInfo { InputSize = SomeData9DeflatedSize, OutputSize = -1, Crc32 = 0 };
|
||||
if (InflateWrapper.ExtractFile(data, "FILE0009", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL)
|
||||
return false;
|
||||
|
||||
// Extract FILE000A, if it exists
|
||||
expected = new DeflateInfo { InputSize = SomeData10DeflatedSize, OutputSize = -1, Crc32 = 0 };
|
||||
if (InflateWrapper.ExtractFile(data, "FILE000A", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL)
|
||||
return false;
|
||||
|
||||
// Extract install script, if it exists
|
||||
expected = new DeflateInfo { InputSize = InstallScriptDeflatedSize, OutputSize = -1, Crc32 = 0 };
|
||||
if (InflateWrapper.ExtractFile(data, "INSTALL_SCRIPT", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL)
|
||||
return false;
|
||||
|
||||
// Extract FILE000{n}.DAT, if it exists
|
||||
expected = new DeflateInfo { InputSize = FinalFileDeflatedSize, OutputSize = FinalFileInflatedSize, Crc32 = 0 };
|
||||
if (InflateWrapper.ExtractFile(data, IsPKZIP ? null : "FILE00XX.DAT", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL)
|
||||
return false;
|
||||
|
||||
dataStart = data.Position;
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using SabreTools.IO.Compression.Deflate;
|
||||
#if NETFRAMEWORK || NETSTANDARD
|
||||
using SabreTools.IO.Extensions;
|
||||
#endif
|
||||
using SabreTools.Models.WiseInstaller;
|
||||
using SabreTools.Models.WiseInstaller.Actions;
|
||||
|
||||
@@ -472,12 +474,10 @@ namespace SabreTools.Serialization.Wrappers
|
||||
if (directoryName != null && !Directory.Exists(directoryName))
|
||||
Directory.CreateDirectory(directoryName);
|
||||
|
||||
using (var iniFile = File.Open(iniFilePath, FileMode.Append, FileAccess.Write, FileShare.ReadWrite))
|
||||
{
|
||||
iniFile.Write(Encoding.ASCII.GetBytes($"[{obj.Section}]\n"));
|
||||
iniFile.Write(Encoding.ASCII.GetBytes($"{obj.Values ?? string.Empty}\n"));
|
||||
iniFile.Flush();
|
||||
}
|
||||
using var iniFile = File.Open(iniFilePath, FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
|
||||
iniFile.Write(Encoding.ASCII.GetBytes($"[{obj.Section}]\n"));
|
||||
iniFile.Write(Encoding.ASCII.GetBytes($"{obj.Values ?? string.Empty}\n"));
|
||||
iniFile.Flush();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -19,6 +19,54 @@ namespace SabreTools.Serialization.Wrappers
|
||||
|
||||
#region Extension Properties
|
||||
|
||||
/// <summary>
|
||||
/// Returns the offset relative to the start of the header
|
||||
/// where the compressed data lives
|
||||
/// </summary>
|
||||
public long CompressedDataOffset
|
||||
{
|
||||
get
|
||||
{
|
||||
long offset = 0;
|
||||
|
||||
offset += 4; // UnknownDataSize
|
||||
offset += 4; // SecondExecutableFileEntryLength
|
||||
offset += 4; // UnknownValue2
|
||||
offset += 4; // UnknownValue3
|
||||
offset += 4; // UnknownValue4
|
||||
offset += 4; // FirstExecutableFileEntryLength
|
||||
offset += 4; // MsiFileEntryLength
|
||||
offset += 4; // UnknownValue7
|
||||
offset += 4; // UnknownValue8
|
||||
offset += 4; // ThirdExecutableFileEntryLength
|
||||
offset += 4; // UnknownValue10
|
||||
offset += 4; // UnknownValue11
|
||||
offset += 4; // UnknownValue12
|
||||
offset += 4; // UnknownValue13
|
||||
offset += 4; // UnknownValue14
|
||||
offset += 4; // UnknownValue15
|
||||
offset += 4; // UnknownValue16
|
||||
offset += 4; // UnknownValue17
|
||||
offset += 4; // UnknownValue18
|
||||
offset += Version?.Length ?? 0;
|
||||
offset += Model.TmpString == null ? 0 : Model.TmpString.Length + 1;
|
||||
offset += Model.GuidString == null ? 0 : Model.GuidString.Length + 1;
|
||||
offset += Model.NonWiseVersion == null ? 0 : Model.NonWiseVersion.Length + 1;
|
||||
offset += Model.PreFontValue == null ? 0 : Model.PreFontValue.Length;
|
||||
offset += 4; // FontSize
|
||||
offset += Model.PreStringValues == null ? 0 : Model.PreStringValues.Length;
|
||||
if (Model.Strings != null)
|
||||
{
|
||||
foreach (var str in Model.Strings)
|
||||
{
|
||||
offset += str.Length;
|
||||
}
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="SectionHeader.UnknownDataSize"/>
|
||||
public uint UnknownDataSize => Model.UnknownDataSize;
|
||||
|
||||
@@ -178,9 +226,8 @@ namespace SabreTools.Serialization.Wrappers
|
||||
/// <returns>True if the files extracted successfully, false otherwise</returns>
|
||||
private bool ExtractHeaderDefinedFiles(string outputDirectory, bool includeDebug)
|
||||
{
|
||||
// TODO: All reads need to be sequential. At the moment, the data position is at the start of the header;
|
||||
// TODO: it needs to be wherever the header parser was when it finished. This applies to all logic in the
|
||||
// TODO: WiseSectionHeader deserializer and wrapper code.
|
||||
// Seek to the compressed data offset
|
||||
_dataSource.Seek(CompressedDataOffset, SeekOrigin.Begin);
|
||||
|
||||
// Extract first executable, if it exists
|
||||
if (ExtractFile("FirstExecutable.exe", outputDirectory, FirstExecutableFileEntryLength, includeDebug) != ExtractionStatus.GOOD)
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.IO.Streams;
|
||||
using SabreTools.Serialization.Interfaces;
|
||||
|
||||
@@ -92,141 +89,6 @@ namespace SabreTools.Serialization.Wrappers
|
||||
|
||||
#endregion
|
||||
|
||||
// TODO: This entire section will be replaced when IO updates
|
||||
#region Data
|
||||
|
||||
/// <summary>
|
||||
/// Read data from the source
|
||||
/// </summary>
|
||||
/// <param name="position">Position in the source to read from</param>
|
||||
/// <param name="length">Length of the requested data</param>
|
||||
/// <returns>Byte array containing the requested data, null on error</returns>
|
||||
/// TODO: This will be replaced with the ReadFrom extension method in IO
|
||||
public byte[]? ReadFromDataSource(int position, int length)
|
||||
{
|
||||
// Validate the requested segment
|
||||
if (!_dataSource.SegmentValid(position, length))
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
long currentLocation = _dataSource.Position;
|
||||
|
||||
_dataSource.Seek(position, SeekOrigin.Begin);
|
||||
byte[] sectionData = _dataSource.ReadBytes(length);
|
||||
_dataSource.Seek(currentLocation, SeekOrigin.Begin);
|
||||
|
||||
return sectionData;
|
||||
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Absorb the error
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read string data from the source
|
||||
/// </summary>
|
||||
/// <param name="position">Position in the source to read from</param>
|
||||
/// <param name="length">Length of the requested data</param>
|
||||
/// <param name="charLimit">Number of characters needed to be a valid string</param>
|
||||
/// <returns>String list containing the requested data, null on error</returns>
|
||||
/// TODO: Remove when IO updated
|
||||
public List<string>? ReadStringsFromDataSource(int position, int length, int charLimit = 5)
|
||||
{
|
||||
// Read the data as a byte array first
|
||||
byte[]? sourceData = ReadFromDataSource(position, length);
|
||||
if (sourceData == null)
|
||||
return null;
|
||||
|
||||
// Check for ASCII strings
|
||||
var asciiStrings = ReadStringsWithEncoding(sourceData, charLimit, Encoding.ASCII);
|
||||
|
||||
// Check for UTF-8 strings
|
||||
// We are limiting the check for Unicode characters with a second byte of 0x00 for now
|
||||
var utf8Strings = ReadStringsWithEncoding(sourceData, charLimit, Encoding.UTF8);
|
||||
|
||||
// Check for Unicode strings
|
||||
// We are limiting the check for Unicode characters with a second byte of 0x00 for now
|
||||
var unicodeStrings = ReadStringsWithEncoding(sourceData, charLimit, Encoding.Unicode);
|
||||
|
||||
// Ignore duplicate strings across encodings
|
||||
List<string> sourceStrings = [.. asciiStrings, .. utf8Strings, .. unicodeStrings];
|
||||
|
||||
// Sort the strings and return
|
||||
sourceStrings.Sort();
|
||||
return sourceStrings;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read string data from the source with an encoding
|
||||
/// </summary>
|
||||
/// <param name="sourceData">Byte array representing the source data</param>
|
||||
/// <param name="charLimit">Number of characters needed to be a valid string</param>
|
||||
/// <param name="encoding">Character encoding to use for checking</param>
|
||||
/// <returns>String list containing the requested data, empty on error</returns>
|
||||
/// TODO: Remove when IO updated
|
||||
#if NET20
|
||||
private static List<string> ReadStringsWithEncoding(byte[] sourceData, int charLimit, Encoding encoding)
|
||||
#else
|
||||
private static HashSet<string> ReadStringsWithEncoding(byte[] sourceData, int charLimit, Encoding encoding)
|
||||
#endif
|
||||
{
|
||||
// Constant from IO
|
||||
const int MaximumCharactersInString = 64;
|
||||
|
||||
if (sourceData == null || sourceData.Length == 0)
|
||||
return [];
|
||||
if (charLimit <= 0 || charLimit > sourceData.Length)
|
||||
return [];
|
||||
|
||||
// Create the string set to return
|
||||
#if NET20
|
||||
var strings = new List<string>();
|
||||
#else
|
||||
var strings = new HashSet<string>();
|
||||
#endif
|
||||
|
||||
// Check for strings
|
||||
int index = 0;
|
||||
while (index < sourceData.Length)
|
||||
{
|
||||
// Get the maximum number of characters
|
||||
int maxChars = encoding.GetMaxCharCount(sourceData.Length - index);
|
||||
int maxBytes = encoding.GetMaxByteCount(Math.Min(MaximumCharactersInString, maxChars));
|
||||
|
||||
// Read the longest string allowed
|
||||
int maxRead = Math.Min(maxBytes, sourceData.Length - index);
|
||||
string temp = encoding.GetString(sourceData, index, maxRead);
|
||||
char[] tempArr = temp.ToCharArray();
|
||||
|
||||
// Ignore empty strings
|
||||
if (temp.Length == 0)
|
||||
{
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find the first instance of a control character
|
||||
int endOfString = Array.FindIndex(tempArr, c => char.IsControl(c) || (c & 0xFF00) != 0);
|
||||
if (endOfString > -1)
|
||||
temp = temp.Substring(0, endOfString);
|
||||
|
||||
// Otherwise, just add the string if long enough
|
||||
if (temp.Length >= charLimit)
|
||||
strings.Add(temp);
|
||||
|
||||
// Increment and continue
|
||||
index += Math.Max(encoding.GetByteCount(temp), 1);
|
||||
}
|
||||
|
||||
return strings;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region JSON Export
|
||||
|
||||
#if NETCOREAPP
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.Serialization.Interfaces;
|
||||
|
||||
namespace SabreTools.Serialization.Wrappers
|
||||
@@ -138,7 +139,7 @@ namespace SabreTools.Serialization.Wrappers
|
||||
return false;
|
||||
|
||||
// Load the item data
|
||||
var data = ReadFromDataSource((int)directoryEntry.EntryOffset, (int)directoryEntry.EntryLength);
|
||||
var data = _dataSource.ReadFrom((int)directoryEntry.EntryOffset, (int)directoryEntry.EntryLength, retainPosition: true);
|
||||
if (data == null)
|
||||
return false;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user