From 8eb5898ef6ff695f0480d20b39e49e9cc35a7718 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Mon, 1 Sep 2025 18:38:43 -0400 Subject: [PATCH] Handle some big TODOs --- .../Deserializers/MoPaQ.cs | 1 + .../Deserializers/NewExecutable.cs | 30 +- .../Deserializers/TapeArchive.cs | 3 +- .../Deserializers/WiseSectionHeader.cs | 24 +- .../Extensions.PortableExecutable.cs | 80 ++- SabreTools.Serialization/MoPaQDecrypter.cs | 173 ------- SabreTools.Serialization/Printers/BSP.cs | 16 +- SabreTools.Serialization/Printers/GZip.cs | 1 - SabreTools.Serialization/Wrappers/BFPK.cs | 3 +- SabreTools.Serialization/Wrappers/BSP.cs | 3 +- SabreTools.Serialization/Wrappers/CFB.cs | 2 +- SabreTools.Serialization/Wrappers/GCF.cs | 3 +- .../Wrappers/InstallShieldArchiveV3.cs | 3 +- SabreTools.Serialization/Wrappers/LZKWAJ.cs | 3 +- SabreTools.Serialization/Wrappers/LZQBasic.cs | 3 +- SabreTools.Serialization/Wrappers/LZSZDD.cs | 3 +- .../Wrappers/LinearExecutable.cs | 4 +- .../Wrappers/NewExecutable.cs | 121 ++++- SabreTools.Serialization/Wrappers/PAK.cs | 3 +- SabreTools.Serialization/Wrappers/PFF.cs | 3 +- .../Wrappers/PortableExecutable.cs | 276 ++++++++++- SabreTools.Serialization/Wrappers/Quantum.cs | 3 +- SabreTools.Serialization/Wrappers/SGA.cs | 3 +- .../Wrappers/TapeArchive.cs | 3 +- SabreTools.Serialization/Wrappers/VBSP.cs | 3 +- SabreTools.Serialization/Wrappers/WAD3.cs | 3 +- .../Wrappers/WiseOverlayHeader.cs | 455 +++++------------- .../Wrappers/WiseScript.cs | 12 +- .../Wrappers/WiseSectionHeader.cs | 53 +- .../Wrappers/WrapperBase.cs | 138 ------ SabreTools.Serialization/Wrappers/XZP.cs | 3 +- 31 files changed, 664 insertions(+), 770 deletions(-) delete mode 100644 SabreTools.Serialization/MoPaQDecrypter.cs diff --git a/SabreTools.Serialization/Deserializers/MoPaQ.cs b/SabreTools.Serialization/Deserializers/MoPaQ.cs index 0c042217..1ebfb3b6 100644 --- a/SabreTools.Serialization/Deserializers/MoPaQ.cs +++ b/SabreTools.Serialization/Deserializers/MoPaQ.cs @@ -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; diff --git a/SabreTools.Serialization/Deserializers/NewExecutable.cs b/SabreTools.Serialization/Deserializers/NewExecutable.cs index ec9d5582..e045296e 100644 --- a/SabreTools.Serialization/Deserializers/NewExecutable.cs +++ b/SabreTools.Serialization/Deserializers/NewExecutable.cs @@ -10,7 +10,6 @@ namespace SabreTools.Serialization.Deserializers public class NewExecutable : BaseBinaryDeserializer { /// - /// 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 /// /// Stream to parse + /// Initial offset to use in address comparisons /// Filled SegmentTableEntry on success, null on error - 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; } } diff --git a/SabreTools.Serialization/Deserializers/TapeArchive.cs b/SabreTools.Serialization/Deserializers/TapeArchive.cs index accecf26..f9f014f0 100644 --- a/SabreTools.Serialization/Deserializers/TapeArchive.cs +++ b/SabreTools.Serialization/Deserializers/TapeArchive.cs @@ -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 diff --git a/SabreTools.Serialization/Deserializers/WiseSectionHeader.cs b/SabreTools.Serialization/Deserializers/WiseSectionHeader.cs index bf94755b..26d8bb9b 100644 --- a/SabreTools.Serialization/Deserializers/WiseSectionHeader.cs +++ b/SabreTools.Serialization/Deserializers/WiseSectionHeader.cs @@ -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 /// /// Parse the string table, if possible /// - /// - /// - /// + /// Stream to parse + /// Pre-string byte array containing string lengths /// The filled string table on success, false otherwise private static byte[][]? ParseStringTable(Stream data, byte[] preStringValues) { diff --git a/SabreTools.Serialization/Extensions.PortableExecutable.cs b/SabreTools.Serialization/Extensions.PortableExecutable.cs index e37abf0b..e32464fc 100644 --- a/SabreTools.Serialization/Extensions.PortableExecutable.cs +++ b/SabreTools.Serialization/Extensions.PortableExecutable.cs @@ -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; } + /// + /// Read either a `StringFileInfo` or `VarFileInfo` based on the key + /// + /// + /// + /// + /// + 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 diff --git a/SabreTools.Serialization/MoPaQDecrypter.cs b/SabreTools.Serialization/MoPaQDecrypter.cs deleted file mode 100644 index 7b8141d7..00000000 --- a/SabreTools.Serialization/MoPaQDecrypter.cs +++ /dev/null @@ -1,173 +0,0 @@ -using System; -using System.IO; -using SabreTools.Hashing; -using SabreTools.Matching; -using static SabreTools.Models.MoPaQ.Constants; - -namespace SabreTools.Serialization -{ - /// - /// Handler for decrypting MoPaQ block and table data - /// - /// TODO: Should this live in IO? New `Encryption` namespace? - public class MoPaQDecrypter - { - #region Private Instance Variables - - /// - /// Buffer for encryption and decryption - /// - private readonly uint[] _stormBuffer = new uint[STORM_BUFFER_SIZE]; - - #endregion - - public MoPaQDecrypter() - { - PrepareCryptTable(); - } - - /// - /// Prepare the encryption table - /// - 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); - } - } - } - - /// - /// Load a table block by optionally decompressing and - /// decrypting before returning the data. - /// - /// Stream to parse - /// Data offset to parse - /// Optional MD5 hash for validation - /// Size of the table in the file - /// Expected size of the table - /// Encryption key to use - /// Output represening the real table size - /// Byte array representing the processed table - 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; - } - - /// - /// Decrypt a single block of data - /// - 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; - } - } -} \ No newline at end of file diff --git a/SabreTools.Serialization/Printers/BSP.cs b/SabreTools.Serialization/Printers/BSP.cs index 6ecacb7b..dcb83ecd 100644 --- a/SabreTools.Serialization/Printers/BSP.cs +++ b/SabreTools.Serialization/Printers/BSP.cs @@ -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}"); + } } } diff --git a/SabreTools.Serialization/Printers/GZip.cs b/SabreTools.Serialization/Printers/GZip.cs index 7d297ad5..1737e3a0 100644 --- a/SabreTools.Serialization/Printers/GZip.cs +++ b/SabreTools.Serialization/Printers/GZip.cs @@ -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); } diff --git a/SabreTools.Serialization/Wrappers/BFPK.cs b/SabreTools.Serialization/Wrappers/BFPK.cs index fe186e04..2cb051ef 100644 --- a/SabreTools.Serialization/Wrappers/BFPK.cs +++ b/SabreTools.Serialization/Wrappers/BFPK.cs @@ -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; diff --git a/SabreTools.Serialization/Wrappers/BSP.cs b/SabreTools.Serialization/Wrappers/BSP.cs index de04a6eb..c7d125e3 100644 --- a/SabreTools.Serialization/Wrappers/BSP.cs +++ b/SabreTools.Serialization/Wrappers/BSP.cs @@ -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; diff --git a/SabreTools.Serialization/Wrappers/CFB.cs b/SabreTools.Serialization/Wrappers/CFB.cs index 6aa35d35..a988b59c 100644 --- a/SabreTools.Serialization/Wrappers/CFB.cs +++ b/SabreTools.Serialization/Wrappers/CFB.cs @@ -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; diff --git a/SabreTools.Serialization/Wrappers/GCF.cs b/SabreTools.Serialization/Wrappers/GCF.cs index a2d1da64..47dc5f3c 100644 --- a/SabreTools.Serialization/Wrappers/GCF.cs +++ b/SabreTools.Serialization/Wrappers/GCF.cs @@ -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; diff --git a/SabreTools.Serialization/Wrappers/InstallShieldArchiveV3.cs b/SabreTools.Serialization/Wrappers/InstallShieldArchiveV3.cs index 738f1784..6354fa56 100644 --- a/SabreTools.Serialization/Wrappers/InstallShieldArchiveV3.cs +++ b/SabreTools.Serialization/Wrappers/InstallShieldArchiveV3.cs @@ -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; diff --git a/SabreTools.Serialization/Wrappers/LZKWAJ.cs b/SabreTools.Serialization/Wrappers/LZKWAJ.cs index 97aa4d8a..a6f1230a 100644 --- a/SabreTools.Serialization/Wrappers/LZKWAJ.cs +++ b/SabreTools.Serialization/Wrappers/LZKWAJ.cs @@ -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; diff --git a/SabreTools.Serialization/Wrappers/LZQBasic.cs b/SabreTools.Serialization/Wrappers/LZQBasic.cs index 219e22aa..dd0416a6 100644 --- a/SabreTools.Serialization/Wrappers/LZQBasic.cs +++ b/SabreTools.Serialization/Wrappers/LZQBasic.cs @@ -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; diff --git a/SabreTools.Serialization/Wrappers/LZSZDD.cs b/SabreTools.Serialization/Wrappers/LZSZDD.cs index fb175af1..28749867 100644 --- a/SabreTools.Serialization/Wrappers/LZSZDD.cs +++ b/SabreTools.Serialization/Wrappers/LZSZDD.cs @@ -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; diff --git a/SabreTools.Serialization/Wrappers/LinearExecutable.cs b/SabreTools.Serialization/Wrappers/LinearExecutable.cs index 575d2e7a..a894d134 100644 --- a/SabreTools.Serialization/Wrappers/LinearExecutable.cs +++ b/SabreTools.Serialization/Wrappers/LinearExecutable.cs @@ -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); } /// @@ -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 diff --git a/SabreTools.Serialization/Wrappers/NewExecutable.cs b/SabreTools.Serialization/Wrappers/NewExecutable.cs index e9478c29..6fafa64a 100644 --- a/SabreTools.Serialization/Wrappers/NewExecutable.cs +++ b/SabreTools.Serialization/Wrappers/NewExecutable.cs @@ -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 /// /// This extracts the following data: /// - Archives and executables in the overlay + /// - Wise installers /// 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; } /// @@ -512,6 +513,71 @@ namespace SabreTools.Serialization.Wrappers } } + /// + /// Extract data from a Wise installer + /// + /// Output directory to write to + /// True to include debug data, false otherwise + /// True if extraction succeeded, false otherwise + 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); } /// @@ -628,6 +694,45 @@ namespace SabreTools.Serialization.Wrappers return offset; } + /// + /// Find the location of a Wise overlay header, if it exists + /// + /// True to include debug data, false otherwise + /// Offset to the overlay header on success, -1 otherwise + 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); } /// @@ -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 diff --git a/SabreTools.Serialization/Wrappers/PAK.cs b/SabreTools.Serialization/Wrappers/PAK.cs index de5ceeb1..d2244028 100644 --- a/SabreTools.Serialization/Wrappers/PAK.cs +++ b/SabreTools.Serialization/Wrappers/PAK.cs @@ -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; diff --git a/SabreTools.Serialization/Wrappers/PFF.cs b/SabreTools.Serialization/Wrappers/PFF.cs index 6a74f8ca..1ba60192 100644 --- a/SabreTools.Serialization/Wrappers/PFF.cs +++ b/SabreTools.Serialization/Wrappers/PFF.cs @@ -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; diff --git a/SabreTools.Serialization/Wrappers/PortableExecutable.cs b/SabreTools.Serialization/Wrappers/PortableExecutable.cs index 60ea9814..0aa1a6d2 100644 --- a/SabreTools.Serialization/Wrappers/PortableExecutable.cs +++ b/SabreTools.Serialization/Wrappers/PortableExecutable.cs @@ -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 /// 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; } /// @@ -1366,6 +1367,37 @@ namespace SabreTools.Serialization.Wrappers } } + /// + /// Extract data from a Wise installer + /// + /// Output directory to write to + /// True to include debug data, false otherwise + /// True if extraction succeeded, false otherwise + 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; + } + /// /// Decompress CExe data compressed with LZ /// @@ -1435,6 +1467,81 @@ namespace SabreTools.Serialization.Wrappers } } + /// + /// Extract using Wise overlay + /// + /// Output directory to write to + /// True to include debug data, false otherwise + /// Potentially multi-part stream to read + /// Offset to the start of the overlay header + /// True if extraction succeeded, false otherwise + 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); + } + + /// + /// Extract using Wise section + /// + /// Output directory to write to + /// True to include debug data, false otherwise + /// Potentially multi-part stream to read + /// Offset to the start of the section header + /// True if extraction succeeded, false otherwise + 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; } + /// + /// Find the location of a Wise overlay header, if it exists + /// + /// True to include debug data, false otherwise + /// Offset to the overlay header on success, -1 otherwise + 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); + } + + /// + /// Find the location of a Wise section header, if it exists + /// + /// True to include debug data, false otherwise + /// Offset to the section header on success, -1 otherwise + 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? sectionStringData = ReadStringsFromDataSource((int)address, (int)size); + List? 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? tableStringData = ReadStringsFromDataSource((int)address, (int)size); + List? tableStringData = _dataSource.ReadStringsFrom((int)address, (int)size); // Cache and return the table string data, even if null _tableStringData[index] = tableStringData ?? []; diff --git a/SabreTools.Serialization/Wrappers/Quantum.cs b/SabreTools.Serialization/Wrappers/Quantum.cs index 73457b48..b78f9777 100644 --- a/SabreTools.Serialization/Wrappers/Quantum.cs +++ b/SabreTools.Serialization/Wrappers/Quantum.cs @@ -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"); diff --git a/SabreTools.Serialization/Wrappers/SGA.cs b/SabreTools.Serialization/Wrappers/SGA.cs index 2f673b10..de59af2e 100644 --- a/SabreTools.Serialization/Wrappers/SGA.cs +++ b/SabreTools.Serialization/Wrappers/SGA.cs @@ -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; diff --git a/SabreTools.Serialization/Wrappers/TapeArchive.cs b/SabreTools.Serialization/Wrappers/TapeArchive.cs index 559dc398..5c5d97c5 100644 --- a/SabreTools.Serialization/Wrappers/TapeArchive.cs +++ b/SabreTools.Serialization/Wrappers/TapeArchive.cs @@ -17,8 +17,7 @@ namespace SabreTools.Serialization.Wrappers #region Extension Properties /// - /// TODO: Simplify when Models is updated - public Entry[]? Entries => Model.Entries != null ? [.. Model.Entries] : null; + public Entry[]? Entries => Model.Entries; #endregion diff --git a/SabreTools.Serialization/Wrappers/VBSP.cs b/SabreTools.Serialization/Wrappers/VBSP.cs index f8839cac..8fc16853 100644 --- a/SabreTools.Serialization/Wrappers/VBSP.cs +++ b/SabreTools.Serialization/Wrappers/VBSP.cs @@ -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; diff --git a/SabreTools.Serialization/Wrappers/WAD3.cs b/SabreTools.Serialization/Wrappers/WAD3.cs index 35450b9a..ac562c36 100644 --- a/SabreTools.Serialization/Wrappers/WAD3.cs +++ b/SabreTools.Serialization/Wrappers/WAD3.cs @@ -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; diff --git a/SabreTools.Serialization/Wrappers/WiseOverlayHeader.cs b/SabreTools.Serialization/Wrappers/WiseOverlayHeader.cs index f035ef64..bfd5facf 100644 --- a/SabreTools.Serialization/Wrappers/WiseOverlayHeader.cs +++ b/SabreTools.Serialization/Wrappers/WiseOverlayHeader.cs @@ -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 + /// + /// Returns the offset relative to the start of the header + /// where the compressed data lives + /// + 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; + } + } + /// public uint Ctl3d32DeflatedSize => Model.Ctl3d32DeflatedSize; @@ -155,280 +205,86 @@ namespace SabreTools.Serialization.Wrappers #region Extraction /// - /// Extract all files from a Wise installer to an output directory + /// Extract the predefined, static files defined in the header /// - /// Input filename to read from /// Output directory to write to /// True to include debug data, false otherwise - /// True if all files extracted, false otherwise - public static bool ExtractAll(string? filename, string outputDirectory, bool includeDebug) + /// True if the files extracted successfully, false otherwise + 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); - } - - /// - /// Extract all files from a Wise installer to an output directory - /// - /// Stream representing the Wise installer - /// Output directory to write to - /// True to include debug data, false otherwise - /// True if all files extracted, false otherwise - public static bool ExtractAll(Stream? data, string outputDirectory, bool includeDebug) - => ExtractAll(data, sourceDirectory: null, outputDirectory, includeDebug); - - /// - /// Extract all files from a Wise installer to an output directory - /// - /// Stream representing the Wise installer - /// Directory where installer files live, if possible - /// Output directory to write to - /// True to include debug data, false otherwise - /// True if all files extracted, false otherwise - 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); - } - - /// - /// Find the overlay header from the Wise installer, if possible - /// - /// Stream representing the Wise installer - /// True to include debug data, false otherwise - /// The found overlay header on success, null otherwise - /// True if the header was found and valid, false otherwise - 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; - } - } - - /// - /// Find the overlay header from a NE Wise installer, if possible - /// - /// Stream representing the Wise installer - /// Wrapper representing the NE - /// True to include debug data, false otherwise - /// The found overlay header on success, null otherwise - /// True if the header was found and valid, false otherwise - 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; - } - - /// - /// Find the overlay header from a PE Wise installer, if possible - /// - /// Stream representing the Wise installer - /// Wrapper representing the PE - /// True to include debug data, false otherwise - /// The found overlay header on success, null otherwise - /// True if the header was found and valid, false otherwise - 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; } /// @@ -437,7 +293,7 @@ namespace SabreTools.Serialization.Wrappers /// Input filename or base name to read from /// True to include debug data, false otherwise /// True if the file could be opened, false otherwise - 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; } - /// - /// Extract the predefined, static files defined in the header - /// - /// Stream representing the Wise installer - /// Output directory to write to - /// True to include debug data, false otherwise - /// True if the files extracted successfully, false otherwise - 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 } } diff --git a/SabreTools.Serialization/Wrappers/WiseScript.cs b/SabreTools.Serialization/Wrappers/WiseScript.cs index 3f2e06a4..cb39c4c2 100644 --- a/SabreTools.Serialization/Wrappers/WiseScript.cs +++ b/SabreTools.Serialization/Wrappers/WiseScript.cs @@ -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 diff --git a/SabreTools.Serialization/Wrappers/WiseSectionHeader.cs b/SabreTools.Serialization/Wrappers/WiseSectionHeader.cs index 946e3d54..97d1f7fe 100644 --- a/SabreTools.Serialization/Wrappers/WiseSectionHeader.cs +++ b/SabreTools.Serialization/Wrappers/WiseSectionHeader.cs @@ -19,6 +19,54 @@ namespace SabreTools.Serialization.Wrappers #region Extension Properties + /// + /// Returns the offset relative to the start of the header + /// where the compressed data lives + /// + 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; + } + } + /// public uint UnknownDataSize => Model.UnknownDataSize; @@ -178,9 +226,8 @@ namespace SabreTools.Serialization.Wrappers /// True if the files extracted successfully, false otherwise 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) diff --git a/SabreTools.Serialization/Wrappers/WrapperBase.cs b/SabreTools.Serialization/Wrappers/WrapperBase.cs index 3ffec9da..ea9746de 100644 --- a/SabreTools.Serialization/Wrappers/WrapperBase.cs +++ b/SabreTools.Serialization/Wrappers/WrapperBase.cs @@ -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 - - /// - /// Read data from the source - /// - /// Position in the source to read from - /// Length of the requested data - /// Byte array containing the requested data, null on error - /// 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; - } - } - - /// - /// Read string data from the source - /// - /// Position in the source to read from - /// Length of the requested data - /// Number of characters needed to be a valid string - /// String list containing the requested data, null on error - /// TODO: Remove when IO updated - public List? 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 sourceStrings = [.. asciiStrings, .. utf8Strings, .. unicodeStrings]; - - // Sort the strings and return - sourceStrings.Sort(); - return sourceStrings; - } - - /// - /// Read string data from the source with an encoding - /// - /// Byte array representing the source data - /// Number of characters needed to be a valid string - /// Character encoding to use for checking - /// String list containing the requested data, empty on error - /// TODO: Remove when IO updated -#if NET20 - private static List ReadStringsWithEncoding(byte[] sourceData, int charLimit, Encoding encoding) -#else - private static HashSet 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(); -#else - var strings = new HashSet(); -#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 diff --git a/SabreTools.Serialization/Wrappers/XZP.cs b/SabreTools.Serialization/Wrappers/XZP.cs index f3f7af88..4f1b9805 100644 --- a/SabreTools.Serialization/Wrappers/XZP.cs +++ b/SabreTools.Serialization/Wrappers/XZP.cs @@ -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;