From 249c51933b3ea9b25f965709ec0336265b653047 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Thu, 14 May 2026 09:25:26 -0400 Subject: [PATCH] Update packages --- ExtractionTool/ExtractionTool.csproj | 4 +- InfoPrint/InfoPrint.csproj | 2 +- .../SabreTools.Data.Extensions.Test.csproj | 6 +- .../SabreTools.Data.Extensions.csproj | 2 +- .../SabreTools.Data.Models.csproj | 2 +- .../SabreTools.Metadata.DatFiles.Test.csproj | 6 +- .../SabreTools.Metadata.DatFiles.csproj | 6 +- .../SabreTools.Metadata.DatItems.Test.csproj | 6 +- SabreTools.Metadata.DatItems/Formats/Rom.cs | 93 +---- .../SabreTools.Metadata.DatItems.csproj | 2 +- .../SabreTools.Metadata.Filter.Test.csproj | 6 +- .../SabreTools.Metadata.Filter.csproj | 2 +- .../SabreTools.Metadata.Reports.csproj | 2 +- .../SabreTools.ObjectIdentifier.Test.csproj | 6 +- ...Tools.Serialization.CrossModel.Test.csproj | 6 +- ...breTools.Serialization.Readers.Test.csproj | 6 +- .../SabreTools.Serialization.Readers.csproj | 2 +- ...breTools.Serialization.Writers.Test.csproj | 6 +- .../SabreTools.Serialization.Writers.csproj | 2 +- .../SabreTools.Serialization.csproj | 8 +- .../SabreTools.Wrappers.Test.csproj | 6 +- SabreTools.Wrappers.Test/WIATests.cs | 9 +- SabreTools.Wrappers/Helpers/AesCbc.cs | 77 ---- .../Helpers/FileSystemTableReader.cs | 172 -------- .../Helpers/LaggedFibonacciGenerator.cs | 377 ------------------ .../Helpers/PurgeCompressor.cs | 142 ------- .../Helpers/PurgeDecompressor.cs | 104 ----- .../Helpers/RvzPackDecompressor.cs | 143 ------- SabreTools.Wrappers/Helpers/RvzPackEncoder.cs | 307 -------------- SabreTools.Wrappers/Helpers/WiiDecrypter.cs | 157 -------- .../NintendoDisc.Extraction.cs | 1 + SabreTools.Wrappers/RAR.Extraction.cs | 32 +- .../SabreTools.Wrappers.csproj | 8 +- SabreTools.Wrappers/SevenZip.Extraction.cs | 47 +-- SabreTools.Wrappers/WIA.Writing.cs | 25 +- SabreTools.Wrappers/WIA.cs | 11 +- 36 files changed, 110 insertions(+), 1683 deletions(-) delete mode 100644 SabreTools.Wrappers/Helpers/AesCbc.cs delete mode 100644 SabreTools.Wrappers/Helpers/FileSystemTableReader.cs delete mode 100644 SabreTools.Wrappers/Helpers/LaggedFibonacciGenerator.cs delete mode 100644 SabreTools.Wrappers/Helpers/PurgeCompressor.cs delete mode 100644 SabreTools.Wrappers/Helpers/PurgeDecompressor.cs delete mode 100644 SabreTools.Wrappers/Helpers/RvzPackDecompressor.cs delete mode 100644 SabreTools.Wrappers/Helpers/RvzPackEncoder.cs delete mode 100644 SabreTools.Wrappers/Helpers/WiiDecrypter.cs diff --git a/ExtractionTool/ExtractionTool.csproj b/ExtractionTool/ExtractionTool.csproj index e10a8edd..2066bef3 100644 --- a/ExtractionTool/ExtractionTool.csproj +++ b/ExtractionTool/ExtractionTool.csproj @@ -49,8 +49,8 @@ - - + + \ No newline at end of file diff --git a/InfoPrint/InfoPrint.csproj b/InfoPrint/InfoPrint.csproj index e28c017b..a1226edb 100644 --- a/InfoPrint/InfoPrint.csproj +++ b/InfoPrint/InfoPrint.csproj @@ -34,7 +34,7 @@ - + \ No newline at end of file diff --git a/SabreTools.Data.Extensions.Test/SabreTools.Data.Extensions.Test.csproj b/SabreTools.Data.Extensions.Test/SabreTools.Data.Extensions.Test.csproj index d18dcf28..50bd4146 100644 --- a/SabreTools.Data.Extensions.Test/SabreTools.Data.Extensions.Test.csproj +++ b/SabreTools.Data.Extensions.Test/SabreTools.Data.Extensions.Test.csproj @@ -1,4 +1,4 @@ - + net8.0;net9.0;net10.0 @@ -8,8 +8,8 @@ - - + + diff --git a/SabreTools.Data.Extensions/SabreTools.Data.Extensions.csproj b/SabreTools.Data.Extensions/SabreTools.Data.Extensions.csproj index 29536947..b80e9bb4 100644 --- a/SabreTools.Data.Extensions/SabreTools.Data.Extensions.csproj +++ b/SabreTools.Data.Extensions/SabreTools.Data.Extensions.csproj @@ -32,7 +32,7 @@ - + diff --git a/SabreTools.Data.Models/SabreTools.Data.Models.csproj b/SabreTools.Data.Models/SabreTools.Data.Models.csproj index 4fbbb860..0f372c26 100644 --- a/SabreTools.Data.Models/SabreTools.Data.Models.csproj +++ b/SabreTools.Data.Models/SabreTools.Data.Models.csproj @@ -32,7 +32,7 @@ - + \ No newline at end of file diff --git a/SabreTools.Metadata.DatFiles.Test/SabreTools.Metadata.DatFiles.Test.csproj b/SabreTools.Metadata.DatFiles.Test/SabreTools.Metadata.DatFiles.Test.csproj index 1699dd34..24ff0b73 100644 --- a/SabreTools.Metadata.DatFiles.Test/SabreTools.Metadata.DatFiles.Test.csproj +++ b/SabreTools.Metadata.DatFiles.Test/SabreTools.Metadata.DatFiles.Test.csproj @@ -1,4 +1,4 @@ - + net8.0;net9.0;net10.0 @@ -8,8 +8,8 @@ - - + + diff --git a/SabreTools.Metadata.DatFiles/SabreTools.Metadata.DatFiles.csproj b/SabreTools.Metadata.DatFiles/SabreTools.Metadata.DatFiles.csproj index 6e3aa439..ae64f34a 100644 --- a/SabreTools.Metadata.DatFiles/SabreTools.Metadata.DatFiles.csproj +++ b/SabreTools.Metadata.DatFiles/SabreTools.Metadata.DatFiles.csproj @@ -1,4 +1,4 @@ - + @@ -47,9 +47,9 @@ - + - + diff --git a/SabreTools.Metadata.DatItems.Test/SabreTools.Metadata.DatItems.Test.csproj b/SabreTools.Metadata.DatItems.Test/SabreTools.Metadata.DatItems.Test.csproj index e029bb79..34844bd4 100644 --- a/SabreTools.Metadata.DatItems.Test/SabreTools.Metadata.DatItems.Test.csproj +++ b/SabreTools.Metadata.DatItems.Test/SabreTools.Metadata.DatItems.Test.csproj @@ -1,4 +1,4 @@ - + net8.0;net9.0;net10.0 @@ -8,8 +8,8 @@ - - + + diff --git a/SabreTools.Metadata.DatItems/Formats/Rom.cs b/SabreTools.Metadata.DatItems/Formats/Rom.cs index 0c77c9c0..8f6d38fa 100644 --- a/SabreTools.Metadata.DatItems/Formats/Rom.cs +++ b/SabreTools.Metadata.DatItems/Formats/Rom.cs @@ -670,19 +670,17 @@ namespace SabreTools.Metadata.DatItems.Formats CopyMachineInformation(machine); // Process hash values - // TODO: This should be normalized to CRC-16 string? crc16 = CRC16; if (crc16 is not null) - CRC16 = NormalizeHashData(crc16, 4); + CRC16 = TextHelper.NormalizeCRC16(crc16); string? crc32 = CRC32; if (crc32 is not null) CRC32 = TextHelper.NormalizeCRC32(crc32); - // TODO: This should be normalized to CRC-64 string? crc64 = CRC64; if (crc64 is not null) - CRC64 = NormalizeHashData(crc64, 16); + CRC64 = TextHelper.NormalizeCRC64(crc64); string? md2 = MD2; if (md2 is not null) @@ -726,19 +724,17 @@ namespace SabreTools.Metadata.DatItems.Formats DupeType = 0x00; // Process hash values - // TODO: This should be normalized to CRC-16 string? crc16 = CRC16; if (crc16 is not null) - CRC16 = NormalizeHashData(crc16, 4); + CRC16 = TextHelper.NormalizeCRC16(crc16); string? crc32 = CRC32; if (crc32 is not null) CRC32 = TextHelper.NormalizeCRC32(crc32); - // TODO: This should be normalized to CRC-64 string? crc64 = CRC64; if (crc64 is not null) - CRC64 = NormalizeHashData(crc64, 16); + CRC64 = TextHelper.NormalizeCRC64(crc64); string? md2 = MD2; if (md2 is not null) @@ -789,87 +785,6 @@ namespace SabreTools.Metadata.DatItems.Formats MachineIndex = machineIndex; } - /// - /// Normalize a hash string and pad to the correct size - /// - /// TODO: Remove when IO is updated - private static string? NormalizeHashData(string? hash, int expectedLength) - { - // If we have a known blank hash, return blank - if (hash is null) - return null; - else if (hash == string.Empty || hash == "-" || hash == "_") - return string.Empty; - - // Check to see if it's a "hex" hash - hash = hash!.Trim().Replace("0x", string.Empty); - - // If we have a blank hash now, return blank - if (string.IsNullOrEmpty(hash)) - return string.Empty; - - // If the hash shorter than the required length, pad it - if (hash.Length < expectedLength) - hash = hash.PadLeft(expectedLength, '0'); - - // If the hash is longer than the required length, it's invalid - else if (hash.Length > expectedLength) - return string.Empty; - - // Now normalize the hash - hash = hash.ToLowerInvariant(); - - // Otherwise, make sure that every character is a proper match - for (int i = 0; i < hash.Length; i++) - { - char c = hash[i]; -#if NET7_0_OR_GREATER - if (!char.IsAsciiHexDigit(c)) -#else - if (!IsAsciiHexDigit(c)) -#endif - { - hash = string.Empty; - break; - } - } - - return hash; - } - -#if NETFRAMEWORK || NETCOREAPP3_1 || NET5_0 || NET6_0 || NETSTANDARD2_0_OR_GREATER - /// - /// Indicates whether a character is categorized as an ASCII hexademical digit. - /// - /// The character to evaluate. - /// true if c is a hexademical digit; otherwise, false. - /// This method determines whether the character is in the range '0' through '9', inclusive, 'A' through 'F', inclusive, or 'a' through 'f', inclusive. - /// TODO: Remove when IO is updated - internal static bool IsAsciiHexDigit(char c) - { - return char.ToLowerInvariant(c) switch - { - '0' => true, - '1' => true, - '2' => true, - '3' => true, - '4' => true, - '5' => true, - '6' => true, - '7' => true, - '8' => true, - '9' => true, - 'a' => true, - 'b' => true, - 'c' => true, - 'd' => true, - 'e' => true, - 'f' => true, - _ => false, - }; - } -#endif - #endregion #region Accessors diff --git a/SabreTools.Metadata.DatItems/SabreTools.Metadata.DatItems.csproj b/SabreTools.Metadata.DatItems/SabreTools.Metadata.DatItems.csproj index b9f908bf..67e32c1c 100644 --- a/SabreTools.Metadata.DatItems/SabreTools.Metadata.DatItems.csproj +++ b/SabreTools.Metadata.DatItems/SabreTools.Metadata.DatItems.csproj @@ -33,7 +33,7 @@ - + diff --git a/SabreTools.Metadata.Filter.Test/SabreTools.Metadata.Filter.Test.csproj b/SabreTools.Metadata.Filter.Test/SabreTools.Metadata.Filter.Test.csproj index 6f45b24e..b4229be3 100644 --- a/SabreTools.Metadata.Filter.Test/SabreTools.Metadata.Filter.Test.csproj +++ b/SabreTools.Metadata.Filter.Test/SabreTools.Metadata.Filter.Test.csproj @@ -1,4 +1,4 @@ - + net8.0;net9.0;net10.0 @@ -18,8 +18,8 @@ - - + + diff --git a/SabreTools.Metadata.Filter/SabreTools.Metadata.Filter.csproj b/SabreTools.Metadata.Filter/SabreTools.Metadata.Filter.csproj index f2f804c6..3cd3fefe 100644 --- a/SabreTools.Metadata.Filter/SabreTools.Metadata.Filter.csproj +++ b/SabreTools.Metadata.Filter/SabreTools.Metadata.Filter.csproj @@ -36,7 +36,7 @@ - + \ No newline at end of file diff --git a/SabreTools.Metadata.Reports/SabreTools.Metadata.Reports.csproj b/SabreTools.Metadata.Reports/SabreTools.Metadata.Reports.csproj index a80bbd43..f6cdf0a0 100644 --- a/SabreTools.Metadata.Reports/SabreTools.Metadata.Reports.csproj +++ b/SabreTools.Metadata.Reports/SabreTools.Metadata.Reports.csproj @@ -38,7 +38,7 @@ - + \ No newline at end of file diff --git a/SabreTools.ObjectIdentifier.Test/SabreTools.ObjectIdentifier.Test.csproj b/SabreTools.ObjectIdentifier.Test/SabreTools.ObjectIdentifier.Test.csproj index 4d204995..d70b26f9 100644 --- a/SabreTools.ObjectIdentifier.Test/SabreTools.ObjectIdentifier.Test.csproj +++ b/SabreTools.ObjectIdentifier.Test/SabreTools.ObjectIdentifier.Test.csproj @@ -1,4 +1,4 @@ - + net8.0;net9.0;net10.0 @@ -8,8 +8,8 @@ - - + + diff --git a/SabreTools.Serialization.CrossModel.Test/SabreTools.Serialization.CrossModel.Test.csproj b/SabreTools.Serialization.CrossModel.Test/SabreTools.Serialization.CrossModel.Test.csproj index 4b272ff6..84b627e2 100644 --- a/SabreTools.Serialization.CrossModel.Test/SabreTools.Serialization.CrossModel.Test.csproj +++ b/SabreTools.Serialization.CrossModel.Test/SabreTools.Serialization.CrossModel.Test.csproj @@ -1,4 +1,4 @@ - + net8.0;net9.0;net10.0 @@ -8,8 +8,8 @@ - - + + diff --git a/SabreTools.Serialization.Readers.Test/SabreTools.Serialization.Readers.Test.csproj b/SabreTools.Serialization.Readers.Test/SabreTools.Serialization.Readers.Test.csproj index 3ec125e1..388f6e97 100644 --- a/SabreTools.Serialization.Readers.Test/SabreTools.Serialization.Readers.Test.csproj +++ b/SabreTools.Serialization.Readers.Test/SabreTools.Serialization.Readers.Test.csproj @@ -1,4 +1,4 @@ - + net8.0;net9.0;net10.0 @@ -8,8 +8,8 @@ - - + + diff --git a/SabreTools.Serialization.Readers/SabreTools.Serialization.Readers.csproj b/SabreTools.Serialization.Readers/SabreTools.Serialization.Readers.csproj index 9e623f66..78121042 100644 --- a/SabreTools.Serialization.Readers/SabreTools.Serialization.Readers.csproj +++ b/SabreTools.Serialization.Readers/SabreTools.Serialization.Readers.csproj @@ -37,7 +37,7 @@ - + diff --git a/SabreTools.Serialization.Writers.Test/SabreTools.Serialization.Writers.Test.csproj b/SabreTools.Serialization.Writers.Test/SabreTools.Serialization.Writers.Test.csproj index 7823078c..899cdd3f 100644 --- a/SabreTools.Serialization.Writers.Test/SabreTools.Serialization.Writers.Test.csproj +++ b/SabreTools.Serialization.Writers.Test/SabreTools.Serialization.Writers.Test.csproj @@ -1,4 +1,4 @@ - + net8.0;net9.0;net10.0 @@ -8,8 +8,8 @@ - - + + diff --git a/SabreTools.Serialization.Writers/SabreTools.Serialization.Writers.csproj b/SabreTools.Serialization.Writers/SabreTools.Serialization.Writers.csproj index 355919c3..13246cb8 100644 --- a/SabreTools.Serialization.Writers/SabreTools.Serialization.Writers.csproj +++ b/SabreTools.Serialization.Writers/SabreTools.Serialization.Writers.csproj @@ -33,7 +33,7 @@ - + diff --git a/SabreTools.Serialization/SabreTools.Serialization.csproj b/SabreTools.Serialization/SabreTools.Serialization.csproj index d2e241db..29791c4a 100644 --- a/SabreTools.Serialization/SabreTools.Serialization.csproj +++ b/SabreTools.Serialization/SabreTools.Serialization.csproj @@ -58,16 +58,16 @@ - - + + - + - + \ No newline at end of file diff --git a/SabreTools.Wrappers.Test/SabreTools.Wrappers.Test.csproj b/SabreTools.Wrappers.Test/SabreTools.Wrappers.Test.csproj index 53c776f9..555f8911 100644 --- a/SabreTools.Wrappers.Test/SabreTools.Wrappers.Test.csproj +++ b/SabreTools.Wrappers.Test/SabreTools.Wrappers.Test.csproj @@ -1,4 +1,4 @@ - + net8.0;net9.0;net10.0 @@ -18,8 +18,8 @@ - - + + diff --git a/SabreTools.Wrappers.Test/WIATests.cs b/SabreTools.Wrappers.Test/WIATests.cs index 5a597021..4b433428 100644 --- a/SabreTools.Wrappers.Test/WIATests.cs +++ b/SabreTools.Wrappers.Test/WIATests.cs @@ -2,6 +2,7 @@ using System; using System.IO; using System.Linq; using SabreTools.Numerics.Extensions; +using SabreTools.Security.Cryptography; using Xunit; using static SabreTools.Data.Models.NintendoDisc.Constants; @@ -120,7 +121,7 @@ namespace SabreTools.Wrappers.Test /// Anti-bias: the final decryption uses — a single-block /// AES-CBC call that is completely independent of EncryptWiiGroup — so a symmetric bug /// (broken encrypt paired with broken decrypt) would still fail the plaintext comparison. - /// The title key is encrypted via (BouncyCastle), while the + /// The title key is encrypted via (BouncyCastle), while the /// verification uses — a different code path. /// [Fact(Skip = "Common keys are validated so this cannot pass")] @@ -375,14 +376,14 @@ namespace SabreTools.Wrappers.Test /// /// Encrypts a Wii title key with the given using - /// . + /// . /// private static byte[] EncryptTitleKeyIndependent(byte[] titleKey, byte[] titleId, byte[] commonKey) { byte[] iv = new byte[16]; Array.Copy(titleId, 0, iv, 0, 8); - return AesCbc.Encrypt(titleKey, commonKey, iv) - ?? throw new InvalidOperationException("AesCbc.Encrypt returned null"); + return AESCBC.Encrypt(titleKey, commonKey, iv) + ?? throw new InvalidOperationException("AESCBC.Encrypt returned null"); } #endregion diff --git a/SabreTools.Wrappers/Helpers/AesCbc.cs b/SabreTools.Wrappers/Helpers/AesCbc.cs deleted file mode 100644 index a0eb3015..00000000 --- a/SabreTools.Wrappers/Helpers/AesCbc.cs +++ /dev/null @@ -1,77 +0,0 @@ -using Org.BouncyCastle.Crypto; -using Org.BouncyCastle.Crypto.Parameters; -using Org.BouncyCastle.Security; - -// TODO: Remove when IO is updated -namespace SabreTools.Wrappers -{ - /// - /// AES-128-CBC encrypt/decrypt helpers used by NintendoDisc and WIA/RVZ. - /// - /// - /// Implemented directly via BouncyCastle because SabreTools.Security.Cryptography - /// currently only exposes AES-CTR. When an AESCBC wrapper is added to that - /// library, replace the bodies of and with - /// the equivalent AESCBC.Decrypt / AESCBC.Encrypt calls and remove the - /// BouncyCastle using directives from this file. - /// - public static class AesCbc - { - /// - /// Decrypts with AES-128-CBC (no padding). - /// Returns null if any argument is invalid or decryption fails. - /// - /// Ciphertext to decrypt. - /// 16-byte AES key. - /// 16-byte initialisation vector. - public static byte[]? Decrypt(byte[] data, byte[] key, byte[] iv) - { - // TODO: replace with AESCBC.Decrypt(data, key, iv) once - // SabreTools.Security.Cryptography adds an AES-CBC wrapper. - try - { - var cipher = CreateCipher(forEncryption: false, key, iv); - return cipher.DoFinal(data); - } - catch - { - return null; - } - } - - /// - /// Encrypts with AES-128-CBC (no padding). - /// Returns null if any argument is invalid or encryption fails. - /// - /// Plaintext to encrypt. - /// 16-byte AES key. - /// 16-byte initialisation vector. - public static byte[]? Encrypt(byte[] data, byte[] key, byte[] iv) - { - // TODO: replace with AESCBC.Encrypt(data, key, iv) once - // SabreTools.Security.Cryptography adds an AES-CBC wrapper. - try - { - var cipher = CreateCipher(forEncryption: true, key, iv); - return cipher.DoFinal(data); - } - catch - { - return null; - } - } - - /// - /// Create an AES/CBC cipher with a given key and initial value - /// - private static IBufferedCipher CreateCipher(bool forEncryption, byte[] key, byte[] iv) - { - var keyParam = new KeyParameter(key); - var cipher = CipherUtilities.GetCipher("AES/CBC/NoPadding"); - - cipher.Init(forEncryption, new ParametersWithIV(keyParam, iv)); - - return cipher; - } - } -} diff --git a/SabreTools.Wrappers/Helpers/FileSystemTableReader.cs b/SabreTools.Wrappers/Helpers/FileSystemTableReader.cs deleted file mode 100644 index 14806ef4..00000000 --- a/SabreTools.Wrappers/Helpers/FileSystemTableReader.cs +++ /dev/null @@ -1,172 +0,0 @@ -using System.Collections.Generic; - -// TODO: Remove when IO is updated -namespace SabreTools.Wrappers -{ - /// - /// Lightweight GameCube / Wii File-System Table (FST) reader used by - /// to distinguish real-file regions from junk. - /// - /// Mirrors Dolphin's FileSystemGCWii offset-to-file-info cache - /// (m_offset_file_info_cache). - /// - public sealed class FileSystemTableReader - { - /// - /// File entry with start and end byte offsets on disc - /// - public struct FileEntry - { - public long FileStart; - public long FileEnd; - } - - /// - /// Sorted ascending by FileEnd for O(log n) upper_bound queries. - /// - private readonly List _files; - - private FileSystemTableReader(List files) - { - _files = files; - } - - /// - /// Parses a raw FST binary blob and returns a , - /// or null if the data is too short or structurally invalid. - /// - /// - /// Raw FST bytes exactly as stored on disc (GameCube) or in decrypted - /// Wii partition data. - /// - /// - /// Bit-shift to convert raw file-offset fields to byte addresses. - /// 0 for GameCube (direct bytes); 2 for Wii (offset × 4). - /// - public static FileSystemTableReader? TryParse(byte[] fstData, int offsetShift) - { - // Read the file system table - var table = Serialization.Readers.NintendoDisc.ParseFileSystemTable(fstData); - if (table is null) - return null; - - // Filter out the entries to non-empty files only - var filtered = new List(); - foreach (var entry in table.Entries) - { - // Directory entry - if ((entry.NameOffset & 0xFF000000) != 0) - continue; - - // Empty file - if (entry.FileSize == 0) - continue; - - long fileStart = entry.FileOffset << offsetShift; - long fileEnd = fileStart + entry.FileSize; - - var fileEntry = new FileEntry - { - FileStart = fileStart, - FileEnd = fileEnd, - }; - filtered.Add(fileEntry); - } - - // Sort ascending by FileEnd so binary-search upper_bound works correctly. - filtered.Sort(delegate (FileEntry a, FileEntry b) - { - return a.FileEnd.CompareTo(b.FileEnd); - }); - - return new FileSystemTableReader(filtered); - } - - /// - /// Returns the file entry whose byte range contains , - /// or null if no file does. - /// - /// TODO: Determine how to use List.BinarySearch here - public FileEntry? FindFileInfo(long discOffset) - { - if (_files.Count == 0) - return null; - - // Binary search: first index where _files[i].FileEnd > discOffset - int lo = 0, hi = _files.Count; - while (lo < hi) - { - int mid = (lo + hi) >> 1; - if (_files[mid].FileEnd <= discOffset) - lo = mid + 1; - else - hi = mid; - } - - if (lo >= _files.Count) - return null; - - var e = _files[lo]; - if (e.FileStart <= discOffset) - return e; - - return null; - } - - /// - /// Returns the smallest FileEnd value strictly greater than - /// , or null if there is none. - /// - public long? FindNextFileEnd(long discOffset) - { - if (_files.Count == 0) - return null; - - int lo = 0, hi = _files.Count; - while (lo < hi) - { - int mid = (lo + hi) >> 1; - if (_files[mid].FileEnd <= discOffset) - lo = mid + 1; - else - hi = mid; - } - - return lo < _files.Count ? _files[lo].FileEnd : null; - } - - /// - /// Returns the smallest FileStart value strictly greater than - /// , or null if there is none. - /// - public long? FindNextFileStart(long discOffset) - { - if (_files.Count == 0) - return null; - - // Sort is by FileEnd; scan all entries whose FileEnd > discOffset - int lo = 0, hi = _files.Count; - while (lo < hi) - { - int mid = (lo + hi) >> 1; - if (_files[mid].FileEnd <= discOffset) - lo = mid + 1; - else - hi = mid; - } - - long? best = null; - for (int i = lo; i < _files.Count; i++) - { - long start = _files[i].FileStart; - if (start <= discOffset) - continue; - - if (best == null || start < best.Value) - best = start; - } - - return best; - } - } -} diff --git a/SabreTools.Wrappers/Helpers/LaggedFibonacciGenerator.cs b/SabreTools.Wrappers/Helpers/LaggedFibonacciGenerator.cs deleted file mode 100644 index ebfe858d..00000000 --- a/SabreTools.Wrappers/Helpers/LaggedFibonacciGenerator.cs +++ /dev/null @@ -1,377 +0,0 @@ -using System; -using SabreTools.Numerics.Extensions; - -// TODO: Remove when IO is updated -namespace SabreTools.Wrappers -{ - /// - /// Lagged Fibonacci Generator matching Dolphin's LaggedFibonacciGenerator exactly. - /// Used to regenerate Nintendo's deterministic "junk" padding data in disc images. - /// RVZ format identifies junk regions and stores only a 68-byte seed (17 uint words) - /// instead of the full data, enabling significant compression of padding areas. - /// - public class LaggedFibonacciGenerator - { - /// - /// - /// - private const int LFG_K = 521; - - /// - /// - /// - private const int LFG_J = 32; - - /// - /// Size of the LFG output buffer in bytes (LFG_K * 4 = 2084) - /// - public const int BUFFER_BYTES = LFG_K * 4; - - /// - /// Size of the seed in 32-bit words (68 bytes total) - /// - public const int SEED_SIZE = 17; - - /// - /// - /// - private readonly uint[] _buffer = new uint[LFG_K]; - - /// - /// - /// - private int _position = 0; - - /// - /// Initializes the generator from a 17-element uint seed array. - /// Each seed word is treated as a raw LE uint from the file (Dolphin: reinterpret_cast then swap32). - /// - public void SetSeed(uint[] seed) - { - if (seed == null || seed.Length < SEED_SIZE) - throw new ArgumentException($"Seed must contain at least {SEED_SIZE} uint values.", nameof(seed)); - - // Reinterpret LE bytes as BE (Dolphin swap32) - _position = 0; - for (int i = 0; i < SEED_SIZE; i++) - { - _buffer[i] = SwapU32(seed[i]); - } - - Initialize(false); - } - - /// - /// Initializes the generator from a 68-byte seed (17 BE uint values as in the RVZ file). - /// - /// Matches Dolphin: m_buffer[i] = Common::swap32(seed + i * 4). - public void SetSeed(byte[] seedBytes) - { - if (seedBytes == null || seedBytes.Length < SEED_SIZE * 4) - throw new ArgumentException($"Seed must be {SEED_SIZE * 4} bytes.", nameof(seedBytes)); - - _position = 0; - int offset = 0; - for (int i = 0; i < SEED_SIZE; i++) - { - _buffer[i] = seedBytes.ReadUInt32BigEndian(ref offset); - } - - Initialize(false); - } - - /// - /// Skips forward by bytes in the output stream. - /// - /// Matches Dolphin: LaggedFibonacciGenerator::Forward(size_t count). - public void Forward(int count) - { - _position += count; - while (_position >= BUFFER_BYTES) - { - ForwardStep(); - _position -= BUFFER_BYTES; - } - } - - /// - /// Generates junk bytes and returns them. - /// - public byte[] GetBytes(int count) - { - byte[] output = new byte[count]; - GetBytes(count, output, 0); - return output; - } - - /// - /// Generates junk bytes into starting at . - /// Matches Dolphin: LaggedFibonacciGenerator::GetBytes using memcpy pattern. - /// - /// - public void GetBytes(int count, byte[] output, int outputOffset) - { - while (count > 0) - { - int length = Math.Min(count, BUFFER_BYTES - _position); - Buffer.BlockCopy(_buffer, _position, output, outputOffset, length); - _position += length; - count -= length; - outputOffset += length; - - if (_position == BUFFER_BYTES) - { - ForwardStep(); - _position = 0; - } - } - } - - /// - /// Returns a single junk byte at the current position, advancing by one byte. - /// Matches Dolphin: LaggedFibonacciGenerator::GetByte. - /// - /// - internal byte GetByte() - { - int wordIdx = _position / 4; - int byteInWord = _position % 4; - byte result = (byte)(_buffer[wordIdx] >> (byteInWord * 8)); // LE byte order - - _position++; - if (_position == BUFFER_BYTES) - { - ForwardStep(); - _position = 0; - } - - return result; - } - - #region Private forward/backward state steps - - /// - /// Full buffer state step forward - /// - /// - /// Dolphin: Forward() (no args). - /// for i in [0,J): buf[i] ^= buf[i + K - J] (= buf[i + 489]) - /// for i in [J,K): buf[i] ^= buf[i - J] (= buf[i - 32]) - /// - private void ForwardStep() - { - for (int i = 0; i < LFG_J; i++) - { - _buffer[i] ^= _buffer[i + LFG_K - LFG_J]; - } - - for (int i = LFG_J; i < LFG_K; i++) - { - _buffer[i] ^= _buffer[i - LFG_J]; - } - } - - /// - /// Partial or full buffer state step backward — undoes ForwardStep. - /// - /// Dolphin: Backward(size_t start_word, size_t end_word). - private void Backward(int startWord = 0, int endWord = LFG_K) - { - int loopEnd = Math.Max(LFG_J, startWord); - - // Undo second loop of ForwardStep (reversed) - for (int i = Math.Min(endWord, LFG_K); i > loopEnd; i--) - { - _buffer[i - 1] ^= _buffer[i - 1 - LFG_J]; - } - - // Undo first loop of ForwardStep (reversed) - for (int i = Math.Min(endWord, LFG_J); i > startWord; i--) - { - _buffer[i - 1] ^= _buffer[i - 1 + LFG_K - LFG_J]; - } - } - - /// - /// Recovers the original 17-word seed from the current buffer state and outputs it - /// as LE uint values into . - /// - /// Dolphin: Reinitialize(uint seed_out[]). - private bool Reinitialize(uint[] seedOut) - { - for (int i = 0; i < 4; i++) - { - Backward(); - } - - // Swap all words back to big-endian representation - for (int i = 0; i < LFG_K; i++) - { - _buffer[i] = SwapU32(_buffer[i]); - } - - // Reconstruct bits 16-17 for the first SEED_SIZE words - for (int i = 0; i < SEED_SIZE; i++) - { - _buffer[i] = (_buffer[i] & 0xFF00FFFF) - | ((_buffer[i] << 2) & 0x00FC0000) - | (((_buffer[i + 16] ^ _buffer[i + 15]) << 9) & 0x00030000); - } - - // Output seed as LE uint values (swap32 converts BE->LE) - for (int i = 0; i < SEED_SIZE; i++) - { - seedOut[i] = SwapU32(_buffer[i]); - } - - return Initialize(true); - } - - /// - /// Fills m_buffer[SEED_SIZE..K-1] from the first SEED_SIZE words, applies the output - /// transform, and runs 4× ForwardStep. When is true, - /// verifies the data in m_buffer[SEED_SIZE..] matches the recurrence. - /// - /// Dolphin: Initialize(bool check_existing_data). - private bool Initialize(bool checkExisting) - { - for (int i = SEED_SIZE; i < LFG_K; i++) - { - uint calculated = (_buffer[i - 17] << 23) - ^ (_buffer[i - 16] >> 9) - ^ _buffer[i - 1]; - - if (checkExisting) - { - uint actual = (_buffer[i] & 0xFF00FFFF) | ((_buffer[i] << 2) & 0x00FC0000); - if ((calculated & 0xFFFCFFFF) != actual) - return false; - } - - _buffer[i] = calculated; - } - - // Output transform: each word -> swap32((x & 0xFF00FFFF) | ((x >> 2) & 0x00FF0000)) - for (int i = 0; i < LFG_K; i++) - { - _buffer[i] = SwapU32((_buffer[i] & 0xFF00FFFF) | ((_buffer[i] >> 2) & 0x00FF0000)); - } - - for (int i = 0; i < 4; i++) - { - ForwardStep(); - } - - return true; - } - - #endregion - - #region Static seed-recovery API (used by RvzPackDecompressor) - - /// - /// Attempts to recover a 17-word seed from disc data starting at - /// within . - /// is the number of bytes to match (up to the next 32 KiB boundary). - /// is discOffset % 0x8000 — the offset within - /// the current LFG cycle. - /// - /// the number of bytes that were successfully reconstructed (0 = not junk data). - /// Matches Dolphin: LaggedFibonacciGenerator::GetSeed(u8*, size_t, size_t, uint[]). - public static int GetSeed(byte[] data, int dataStart, int size, int dataOffsetMod, uint[] seedOut) - { - if (size <= 0 || dataStart < 0 || dataStart + size > data.Length) - return 0; - - // Skip any bytes before the next uint-aligned boundary - int bytesToSkip = (4 - (dataOffsetMod % 4)) % 4; - if (bytesToSkip >= size) - return 0; - - int uintDataStart = dataStart + bytesToSkip; - int uintSize = (size - bytesToSkip) / 4; - int uintDataOffset = (dataOffsetMod + bytesToSkip) / 4; - - if (uintSize < LFG_K) - return 0; - - // Read disc bytes as little-endian uint values (Dolphin: reinterpret_cast) - uint[] uintData = new uint[uintSize]; - for (int i = 0; i < uintSize; i++) - { - uintData[i] = data.ReadUInt32LittleEndian(ref uintDataStart); - } - - var lfg = new LaggedFibonacciGenerator(); - if (!GetSeed(uintData, uintSize, uintDataOffset, lfg, seedOut)) - return 0; - - // Set position to data_offset % BUFFER_BYTES and count matching bytes from data[dataStart] - lfg._position = dataOffsetMod % BUFFER_BYTES; - - int reconstructed = 0; - for (int i = 0; i < size && lfg.GetByte() == data[dataStart + i]; i++) - { - reconstructed++; - } - - return reconstructed; - } - - /// - /// Inner UInt32-level seed recovery. - /// - /// Dolphin: GetSeed(const uint* data, size_t size, size_t data_offset, LFG*, uint[]). - private static bool GetSeed(uint[] data, int size, int dataOffset, LaggedFibonacciGenerator lfg, uint[] seedOut) - { - if (size < LFG_K) - return false; - - // Quick sanity check: bits 22-23 of swap32(x) must equal bits 20-21 - // (a property of the LFG output transform). - for (int i = 0; i < LFG_K; i++) - { - uint x = SwapU32(data[i]); - if ((x & 0x00C00000) != ((x >> 2) & 0x00C00000)) - return false; - } - - int dataOffsetModK = dataOffset % LFG_K; - int dataOffsetDivK = dataOffset / LFG_K; - - // Rotate data into buffer so buffer[dataOffsetModK] = data[0] - Array.Copy(data, 0, lfg._buffer, dataOffsetModK, LFG_K - dataOffsetModK); - if (dataOffsetModK > 0) - Array.Copy(data, LFG_K - dataOffsetModK, lfg._buffer, 0, dataOffsetModK); - - lfg.Backward(0, dataOffsetModK); - - for (int i = 0; i < dataOffsetDivK; i++) - { - lfg.Backward(); - } - - if (!lfg.Reinitialize(seedOut)) - return false; - - for (int i = 0; i < dataOffsetDivK; i++) - { - lfg.ForwardStep(); - } - - return true; - } - - #endregion - - #region Helpers - - /// - /// Swap endinaness of a UInt32 - /// - internal static uint SwapU32(uint value) - => (value << 24) | ((value << 8) & 0x00FF0000) | ((value >> 8) & 0x0000FF00) | (value >> 24); - - #endregion - } -} diff --git a/SabreTools.Wrappers/Helpers/PurgeCompressor.cs b/SabreTools.Wrappers/Helpers/PurgeCompressor.cs deleted file mode 100644 index 4497121c..00000000 --- a/SabreTools.Wrappers/Helpers/PurgeCompressor.cs +++ /dev/null @@ -1,142 +0,0 @@ -using System; -using System.IO; -using SabreTools.Hashing; -using SabreTools.Numerics.Extensions; - -// TODO: Remove when IO is updated -namespace SabreTools.Wrappers -{ - /// - /// Compresses data using the WIA PURGE format. - /// - /// PURGE layout produced: - /// [ { u32 offset BE, u32 size BE, data[size] } ] ... (zero or more segments) - /// [ SHA-1 (20 bytes) ] - /// - /// Only non-zero byte runs are emitted as segments; consecutive non-zero regions - /// separated by a gap of 8 or fewer zero bytes are merged into a single segment. - /// The SHA-1 covers: (e.g. exception-list prefix) + - /// all segment headers and data bytes. - /// - /// This is the exact inverse of . - /// - internal static class PurgeCompressor - { - /// - /// Zero-byte runs of this length or fewer are bridged - /// - private const int MaxGap = 8; - - /// - /// Compress [ .. - /// +) into PURGE format. - /// - /// Source buffer. - /// Start of data within . - /// Number of bytes to compress. - /// - /// Optional bytes that precede this payload in the WIA group - /// (e.g. the serialised exception list). Included in the SHA-1 but not emitted. - /// Pass null or empty if there are none. - /// - /// PURGE-compressed byte array (segments + 20-byte SHA-1). - public static byte[] Compress(byte[] data, int offset, int count, byte[]? precedingBytes = null) - { - var output = new MemoryStream((count / 2) + 32); - - int end = offset + count; - int pos = offset; - - while (pos < end) - { - // Skip leading zeros - while (pos < end && data[pos] == 0) - { - pos++; - } - - if (pos >= end) - break; - - // pos is now the start of a non-zero run (segment start) - int segStart = pos; - int segEnd = pos; - - // Extend the segment, bridging zero-gaps of <= MaxGap bytes - while (segEnd < end) - { - // advance through non-zero bytes - while (segEnd < end && data[segEnd] != 0) - { - segEnd++; - } - - // peek ahead: count zero bytes - int zeroRun = 0; - while (segEnd + zeroRun < end && data[segEnd + zeroRun] == 0) - { - zeroRun++; - } - - // If the gap is small enough (and there is more non-zero data after it), - // bridge the gap by including it in the segment. - if (zeroRun > 0 && zeroRun <= MaxGap && segEnd + zeroRun < end) - segEnd += zeroRun; // include zeros in segment, keep scanning - else - break; // end of segment - } - - // Trim trailing zeros from segment end - while (segEnd > segStart && data[segEnd - 1] == 0) - { - segEnd--; - } - - if (segEnd <= segStart) - { - pos = segEnd + 1; - continue; - } - - uint segOffset = (uint)(segStart - offset); - uint segSize = (uint)(segEnd - segStart); - - output.WriteBigEndian(segOffset); - output.WriteBigEndian(segSize); - output.Write(data, segStart, (int)segSize); - - pos = segEnd; - } - - byte[] segments = output.ToArray(); - - // SHA-1 over: precedingBytes + segments - byte[] hash = ComputeSha1(precedingBytes, segments); - - // Final result: segments + hash - byte[] result = new byte[segments.Length + 20]; - Array.Copy(segments, 0, result, 0, segments.Length); - Array.Copy(hash, 0, result, segments.Length, 20); - return result; - } - - /// - /// Get the SHA-1 hash of preceeding bytes and segment data - /// - /// Optional preceeding bytes - /// Segment data - /// SHA-1 hash of the data, all 0x00 on error - private static byte[] ComputeSha1(byte[]? precedingBytes, byte[] segments) - { - using var sha1 = new HashWrapper(HashType.SHA1); - - if (precedingBytes is not null && precedingBytes.Length > 0) - sha1.Process(precedingBytes, 0, precedingBytes.Length); - - sha1.Process(segments, 0, segments.Length); - sha1.Terminate(); - - return sha1.CurrentHashBytes ?? new byte[20]; - } - } -} diff --git a/SabreTools.Wrappers/Helpers/PurgeDecompressor.cs b/SabreTools.Wrappers/Helpers/PurgeDecompressor.cs deleted file mode 100644 index 0191766e..00000000 --- a/SabreTools.Wrappers/Helpers/PurgeDecompressor.cs +++ /dev/null @@ -1,104 +0,0 @@ -using System; -using SabreTools.Hashing; -using SabreTools.Numerics.Extensions; - -// TODO: Remove when IO is updated -namespace SabreTools.Wrappers -{ - /// - /// Decompresses WIA PURGE-compressed group data. - /// - /// PURGE layout (uncompressed exception-list prefix already stripped by caller): - /// [ { u32 offset BE, u32 size BE } { data[size] } ] ... (zero or more segments) - /// [ SHA-1 (20 bytes) ] - /// - /// The SHA-1 covers: (exception-list bytes, if any) + - /// all segment headers and their data bytes. - /// Bytes in the output not covered by any segment are implicitly 0x00. - /// - /// References: Dolphin WIACompression.cpp — PurgeDecompressor / PurgeCompressor - /// docs/WiaAndRvz.md — wia_segment_t section - /// - internal static class PurgeDecompressor - { - private const int SHA1_SIZE = 20; - - private const int SEGMENT_HEADER_SIZE = 8; // u32 offset + u32 size - - /// - /// Decompresses a PURGE-compressed block. - /// - /// Buffer containing the compressed data. - /// Byte offset within where compressed data starts. - /// Number of bytes of compressed data (segments + trailing SHA-1). - /// Expected decompressed output size in bytes. - /// - /// Bytes that precede the compressed data in the SHA-1 computation — the uncompressed - /// exception-list section for Wii partition groups. Pass null for non-Wii groups. - /// - /// - /// The decompressed byte array, or null if the data is malformed or the - /// trailing SHA-1 does not match. - /// - public static byte[]? Decompress( - byte[] input, - int inputOffset, - int inputLength, - int decompressedSize, - byte[]? precedingBytes = null) - { - if (inputLength < SHA1_SIZE) - return null; - - byte[] output = new byte[decompressedSize]; - int pos = inputOffset; - int dataEnd = inputOffset + inputLength - SHA1_SIZE; - - using (var sha1 = new HashWrapper(HashType.SHA1)) - { - if (precedingBytes is not null && precedingBytes.Length > 0) - sha1.Process(precedingBytes, 0, precedingBytes.Length); - - while (pos < dataEnd) - { - if (pos + SEGMENT_HEADER_SIZE > dataEnd) - return null; - - int headerPos = pos; - uint segOffset = input.ReadUInt32BigEndian(ref headerPos); - uint segSize = input.ReadUInt32BigEndian(ref headerPos); - - sha1.Process(input, pos, SEGMENT_HEADER_SIZE); - pos += SEGMENT_HEADER_SIZE; - - if (segSize == 0) - continue; - - if (pos + (int)segSize > dataEnd) - return null; - - if (segOffset + segSize > (uint)decompressedSize) - return null; - - Array.Copy(input, pos, output, (int)segOffset, (int)segSize); - sha1.Process(input, pos, (int)segSize); - pos += (int)segSize; - } - - sha1.Terminate(); - - byte[]? computed = sha1.CurrentHashBytes; - if (computed is null) - return null; - - for (int i = 0; i < SHA1_SIZE; i++) - { - if (computed[i] != input[dataEnd + i]) - return null; - } - } - - return output; - } - } -} diff --git a/SabreTools.Wrappers/Helpers/RvzPackDecompressor.cs b/SabreTools.Wrappers/Helpers/RvzPackDecompressor.cs deleted file mode 100644 index b71ed7d6..00000000 --- a/SabreTools.Wrappers/Helpers/RvzPackDecompressor.cs +++ /dev/null @@ -1,143 +0,0 @@ -using System; -using SabreTools.Numerics.Extensions; - -// TODO: Remove when IO is updated -namespace SabreTools.Wrappers -{ - /// - /// Decompressor for RVZ packed format. - /// RVZ uses run-length encoding to store real data and junk data efficiently: - /// - Real data: size (4 bytes) + data bytes - /// - Junk data: size with high bit set (4 bytes) + 68-byte seed -> regenerate using LFG - /// - internal class RvzPackDecompressor - { - /// - /// - /// - private readonly byte[] _packedData; - - /// - /// - /// - private readonly uint _rvzPackedSize; - - /// - /// - /// - private long _dataOffset; - - /// - /// - /// - private readonly LaggedFibonacciGenerator _lfg; - - /// - /// - /// - private int _inPosition = 0; - - /// - /// - /// - private uint _currentSize = 0; - - /// - /// - /// - private bool _currentIsJunk = false; - - /// - /// Creates a new RVZ pack decompressor. - /// - /// The packed RVZ data - /// Expected size of packed data (for validation) - /// Offset in the virtual disc (for LFG alignment) - public RvzPackDecompressor(byte[] packedData, uint rvzPackedSize, long dataOffset) - { - _packedData = packedData; - _rvzPackedSize = rvzPackedSize; - _dataOffset = dataOffset; - _lfg = new LaggedFibonacciGenerator(); - } - - /// - /// Decompresses the packed data into the output buffer. - /// - /// Destination buffer - /// Offset in destination buffer - /// Number of bytes to decompress - /// Number of bytes actually decompressed - public int Decompress(byte[] output, int outputOffset, int count) - { - int totalWritten = 0; - - while (totalWritten < count && !IsDone()) - { - if (_currentSize == 0) - { - if (!ReadNextSegment()) - break; - } - - int bytesToWrite = Math.Min((int)_currentSize, count - totalWritten); - if (_currentIsJunk) - { - _lfg.GetBytes(bytesToWrite, output, outputOffset + totalWritten); - } - else - { - Array.Copy(_packedData, _inPosition, output, outputOffset + totalWritten, bytesToWrite); - _inPosition += bytesToWrite; - } - - _currentSize -= (uint)bytesToWrite; - totalWritten += bytesToWrite; - _dataOffset += bytesToWrite; - } - - return totalWritten; - } - - /// - /// Checks if decompression is complete. - /// - public bool IsDone() => _currentSize == 0 && _inPosition >= _rvzPackedSize; - - /// - /// Read the next segment of packed data - /// - /// True if the segment was read and cached, false otherwise - private bool ReadNextSegment() - { - if (_inPosition + 4 > _packedData.Length) - return false; - - // Size field is big-endian u32; high bit signals junk data - uint sizeField = _packedData.ReadUInt32BigEndian(ref _inPosition); - - _currentIsJunk = (sizeField & 0x80000000) != 0; - _currentSize = sizeField & 0x7FFFFFFF; - - if (_currentIsJunk) - { - if (_inPosition + (LaggedFibonacciGenerator.SEED_SIZE * 4) > _packedData.Length) - return false; - - byte[] seed = new byte[LaggedFibonacciGenerator.SEED_SIZE * 4]; - Array.Copy(_packedData, _inPosition, seed, 0, seed.Length); - _inPosition += seed.Length; - - _lfg.SetSeed(seed); - - // Advance LFG to the correct position within the buffer. - // Dolphin: lfg.m_position_bytes = data_offset % (LFG_K * sizeof(u32)) - int offsetInBuffer = (int)(_dataOffset % LaggedFibonacciGenerator.BUFFER_BYTES); - if (offsetInBuffer > 0) - _lfg.Forward(offsetInBuffer); - } - - return true; - } - } -} diff --git a/SabreTools.Wrappers/Helpers/RvzPackEncoder.cs b/SabreTools.Wrappers/Helpers/RvzPackEncoder.cs deleted file mode 100644 index fcc2d5b4..00000000 --- a/SabreTools.Wrappers/Helpers/RvzPackEncoder.cs +++ /dev/null @@ -1,307 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using SabreTools.Numerics.Extensions; - -// TODO: Remove when IO is updated -namespace SabreTools.Wrappers -{ - /// - /// Encodes disc data into RVZ-Pack format by replacing predictable LFG - /// (Lagged Fibonacci Generator) junk regions with compact seed descriptors. - /// - /// This is the exact inverse of and mirrors - /// Dolphin's RVZPack() in WIABlob.cpp. - /// - /// Two-phase algorithm: - /// - /// Phase 1 (): walk the buffer, identify LFG - /// junk regions, build a map keyed by end-offset. - /// Phase 2 (): for each chunk, use the map to - /// emit alternating real-data and junk-seed segments. - /// - /// - internal static class RvzPackEncoder - { - /// - /// 17 u32s × 4 bytes = 68 bytes — minimum size to record a seed - /// - private const int SeedSizeBytes = LaggedFibonacciGenerator.SEED_SIZE * 4; - - /// - /// - /// - private sealed class JunkRegion - { - public long StartOffset; - - public uint[]? Seed; - } - - /// - /// Result of packing a single chunk: compressed payload and its logical size. - /// - internal struct ChunkResult - { - /// - /// Packed payload, or null if the chunk contains no junk. - /// - public byte[]? Packed; - - /// - /// Number of bytes the decompressor needs to consume from . - /// - public uint RvzPackedSize; - } - - /// - /// RVZ-pack a single chunk. - /// Returns null if the chunk contains no junk (write raw instead). - /// is the number of bytes actually needed - /// by the decompressor (may be < packed.Length due to alignment). - /// - /// Source buffer - /// Start of data within - public static byte[]? Pack(byte[] data, - int dataOffset, - int size, - long discDataOffset, - out uint rvzPackedSize, - FileSystemTableReader? fst = null) - { - rvzPackedSize = 0; - if (size <= 0) - return null; - - var junkInfo = ScanForJunk(data, dataOffset, size, discDataOffset, fst); - if (junkInfo.Count == 0) - return null; - - ChunkResult result = EmitChunk(data, dataOffset, 0L, size, junkInfo); - rvzPackedSize = result.RvzPackedSize; - return result.Packed; - } - - /// - /// RVZ-pack a multi-chunk buffer (e.g. a full 2 MiB Wii group). - /// Performs one Phase-1 scan over the entire buffer, then calls - /// per chunk. - /// - /// Source buffer - /// Start of data within - /// Total number of bytes to process - /// Size of each individual chunk - /// Number of chunks - /// Disc-partition byte offset of the first byte - /// Optional FST for file-boundary optimisation - /// - /// One per chunk; - /// Packed == null means the chunk has no junk and should be written raw. - /// - public static ChunkResult[] PackGroup(byte[] data, - int dataOffset, - int totalSize, - int bytesPerChunk, - int numChunks, - long discDataOffset, - FileSystemTableReader? fst = null) - { - var junkInfo = ScanForJunk(data, dataOffset, totalSize, discDataOffset, fst); - - var result = new ChunkResult[numChunks]; - for (int i = 0; i < numChunks; i++) - { - long chunkStart = (long)i * bytesPerChunk; - long chunkEnd = Math.Min(chunkStart + bytesPerChunk, totalSize); - - result[i] = EmitChunk(data, dataOffset, chunkStart, chunkEnd, junkInfo); - } - - return result; - } - - /// - /// Scan buffer for junk regions - /// - /// Source buffer - /// Start of data within - /// Total number of bytes to process - /// Disc-partition byte offset of the first byte - /// Optional FST for file-boundary optimisation - /// - private static SortedDictionary ScanForJunk( - byte[] data, - int dataOffset, - int totalSize, - long discDataOffset, - FileSystemTableReader? fst) - { - var junkInfo = new SortedDictionary(); - - long position = 0; - long dataOff = discDataOffset; - - while (position < totalSize) - { - // Step 1: count and advance past leading zeros - long zeroes = 0; - while ((position + zeroes) < totalSize && data[dataOffset + position + zeroes] == 0) - { - zeroes++; - } - - if (zeroes > SeedSizeBytes) - { - junkInfo[position + zeroes] = new JunkRegion - { - StartOffset = position, - Seed = new uint[LaggedFibonacciGenerator.SEED_SIZE] - }; - } - - position += zeroes; - dataOff += zeroes; - - if (position >= totalSize) - break; - - // Step 2: compute aligned read window (next 0x8000 boundary) - long nextBoundary = AlignUp(dataOff + 1, 0x8000); - long bytesToRead = Math.Min(nextBoundary - dataOff, totalSize - position); - int dataOffMod = (int)(dataOff % 0x8000); - - // Step 3: ALWAYS call GetSeed unconditionally — no FST pre-check - var seed = new uint[LaggedFibonacciGenerator.SEED_SIZE]; - int reconstructed = LaggedFibonacciGenerator.GetSeed(data, (int)(dataOffset + position), (int)bytesToRead, dataOffMod, seed); - - if (reconstructed > 0) - { - junkInfo[position + reconstructed] = new JunkRegion - { - StartOffset = position, - Seed = seed - }; - } - - // Step 4: FST skip AFTER GetSeed - if (fst is not null) - { - long queryOff = dataOff + reconstructed; - FileSystemTableReader.FileEntry? fileInfo = fst.FindFileInfo(queryOff); - if (fileInfo is not null) - { - long fileEnd = fileInfo.Value.FileEnd; - if (fileEnd < (dataOff + bytesToRead)) - { - position += fileEnd - dataOff; - dataOff = fileEnd; - continue; - } - } - } - - // Step 5: normal advance by block window - position += bytesToRead; - dataOff += bytesToRead; - } - - return junkInfo; - } - - /// - /// Emit packed segments for a single chunk - /// - /// Source buffer - /// Start of data within - /// - /// - /// - /// - private static ChunkResult EmitChunk( - byte[] data, - int dataOffset, - long chunkStart, - long chunkEnd, - SortedDictionary junkInfo) - { - long currentOffset = chunkStart; - bool firstIteration = true; - - var output = new MemoryStream((int)(chunkEnd - chunkStart)); - uint packedSize = 0; - - while (currentOffset < chunkEnd) - { - long remaining = chunkEnd - currentOffset; - long nextJunkStart = chunkEnd; - long nextJunkEnd = chunkEnd; - uint[]? junkSeed = null; - - if (remaining > SeedSizeBytes) - { - foreach (var kvp in junkInfo) - { - // Dolphin Phase-2 condition: - // key > currentOffset + SEED_SIZE_BYTES AND - // startOffset + SEED_SIZE_BYTES < chunkEnd - if ((kvp.Key > (currentOffset + SeedSizeBytes)) && ((kvp.Value.StartOffset + SeedSizeBytes) < chunkEnd)) - { - nextJunkStart = Math.Max(currentOffset, kvp.Value.StartOffset); - nextJunkEnd = Math.Min(chunkEnd, kvp.Key); - junkSeed = kvp.Value.Seed; - break; - } - } - } - - // On the first iteration, bail out if there is no junk in this chunk - if (firstIteration) - { - if (nextJunkStart == chunkEnd) - return new ChunkResult { Packed = null, RvzPackedSize = 0 }; - - firstIteration = false; - } - - // Emit real-data segment before the junk region - long nonJunkBytes = nextJunkStart - currentOffset; - if (nonJunkBytes > 0) - { - output.WriteBigEndian((uint)nonJunkBytes); - output.Write(data, (int)(dataOffset + currentOffset), (int)nonJunkBytes); - packedSize += 4 + (uint)nonJunkBytes; - currentOffset += nonJunkBytes; - } - - // Emit junk-seed segment - long junkBytes = nextJunkEnd - currentOffset; - if (junkBytes > 0 && junkSeed is not null) - { - output.WriteBigEndian(0x80000000u | (uint)junkBytes); - - byte[] seedBytes = new byte[SeedSizeBytes]; - Buffer.BlockCopy(junkSeed, 0, seedBytes, 0, SeedSizeBytes); - output.Write(seedBytes, 0, SeedSizeBytes); - - packedSize += 4 + (uint)SeedSizeBytes; - currentOffset += junkBytes; - } - - if (junkSeed == null) - break; - } - - return new ChunkResult { Packed = output.ToArray(), RvzPackedSize = packedSize }; - } - - #region Helpers - - /// - /// Align a value to a boundary - /// - /// TODO: Figure out how to use buffer alignment helpers here - private static long AlignUp(long value, long alignment) => (value + alignment - 1) & ~(alignment - 1); - - #endregion - } -} diff --git a/SabreTools.Wrappers/Helpers/WiiDecrypter.cs b/SabreTools.Wrappers/Helpers/WiiDecrypter.cs deleted file mode 100644 index 1b9f501e..00000000 --- a/SabreTools.Wrappers/Helpers/WiiDecrypter.cs +++ /dev/null @@ -1,157 +0,0 @@ -using System; -using SabreTools.Hashing; - -// TODO: Remove when IO is updated -namespace SabreTools.Wrappers -{ - public class WiiDecrypter - { - /// - /// Retail common key (index 0) - /// - public byte[] RetailCommonKey - { - get; - set - { - if (ValidateRetailCommonKey(value)) - field = value; - } - } = []; - - /// - /// Korean common key (index 1) - /// - public byte[] KoreanCommonKey - { - get; - set - { - if (ValidateKoreanCommonKey(value)) - field = value; - } - } = []; - - #region Internal Test Values - - /// - /// Korean common key SHA-256 hash - /// - private const string KoreanCommonKeySHA256 = "b9f42ca27a1e178f0f14ebf1a05d486fa8db8d08875336c4e6e8dfae29f2901c"; - - /// - /// Retail common key SHA-256 hash - /// - private const string RetailCommonKeySHA256 = "de38aeab4fe0c36d828a47e6fd315100e7ce234d3b00aa25e6ad6f5ff2824af8"; - - #endregion - - #region Decryption - - /// - /// Decrypt a Wii partition title key from the ticket data. - /// - /// 16-byte encrypted title key from ticket offset 0x1BF - /// 8-byte title ID from ticket offset 0x1DC (big-endian) - /// Common key index to use for decryption - /// Decrypted 16-byte title key, or null if no key is available for the given index - public byte[]? DecryptTitleKey(byte[]? encryptedTitleKey, byte[]? titleId, int commonKeyIndex) - { - if (encryptedTitleKey is null || encryptedTitleKey.Length != 16) - return null; - if (titleId is null || titleId.Length != 8) - return null; - - byte[] commonKey; - if (commonKeyIndex == 0) - commonKey = RetailCommonKey; - else if (commonKeyIndex == 1) - commonKey = KoreanCommonKey; - else - return null; - - return DecryptTitleKey(encryptedTitleKey, titleId, commonKey); - } - - /// - /// Decrypt a Wii partition title key from the ticket data. - /// - /// 16-byte encrypted title key from ticket offset 0x1BF - /// 8-byte title ID from ticket offset 0x1DC (big-endian) - /// Common key to use for decryption - /// Decrypted 16-byte title key, or null if no key is available for the given index - public static byte[]? DecryptTitleKey(byte[]? encryptedTitleKey, byte[]? titleId, byte[] commonKey) - { - if (encryptedTitleKey is null || encryptedTitleKey.Length != 16) - return null; - if (titleId is null || titleId.Length != 8) - return null; - - if (commonKey.Length != 16) - return null; - - // IV is the 8-byte title ID padded with zeros to 16 bytes - byte[] iv = new byte[16]; - Array.Copy(titleId, 0, iv, 0, 8); - - return AesCbc.Decrypt(encryptedTitleKey, commonKey, iv); - } - - /// - /// Decrypt one Wii block of data (0x7C00 bytes) using AES-128-CBC. - /// - /// 0x7C00 bytes of encrypted block data - /// 16-byte partition title key - /// 16-byte initialization vector (last 16 bytes of the preceding hash block) - /// Decrypted 0x7C00-byte block data, or null on error - public static byte[]? DecryptBlock(byte[] encryptedData, byte[] titleKey, byte[] iv) - { - if (encryptedData is null || encryptedData.Length != 0x7C00) - return null; - if (titleKey is null || titleKey.Length != 16) - return null; - if (iv is null || iv.Length != 16) - return null; - - return AesCbc.Decrypt(encryptedData, titleKey, iv); - } - - #endregion - - #region Validation Methods - - /// - /// Validate the Korean common key based on hash and length - /// - /// Korean common key to validate - /// True if the key was valid, false otherwise - public static bool ValidateKoreanCommonKey(byte[]? commonKey) - { - // Ignore invalid values - if (commonKey is null || commonKey.Length != 16) - return false; - - // Hash the key and compare - string? actualHash = HashTool.GetByteArrayHash(commonKey, HashType.SHA256); - return string.Equals(actualHash, KoreanCommonKeySHA256, StringComparison.OrdinalIgnoreCase); - } - - /// - /// Validate the retail common key based on hash and length - /// - /// Retail common key to validate - /// True if the key was valid, false otherwise - public static bool ValidateRetailCommonKey(byte[]? commonKey) - { - // Ignore invalid values - if (commonKey is null || commonKey.Length != 16) - return false; - - // Hash the key and compare - string? actualHash = HashTool.GetByteArrayHash(commonKey, HashType.SHA256); - return string.Equals(actualHash, RetailCommonKeySHA256, StringComparison.OrdinalIgnoreCase); - } - - #endregion - } -} diff --git a/SabreTools.Wrappers/NintendoDisc.Extraction.cs b/SabreTools.Wrappers/NintendoDisc.Extraction.cs index 504d3552..29a3afb4 100644 --- a/SabreTools.Wrappers/NintendoDisc.Extraction.cs +++ b/SabreTools.Wrappers/NintendoDisc.Extraction.cs @@ -4,6 +4,7 @@ using System.IO; using System.Text; using SabreTools.Data.Models.NintendoDisc; using SabreTools.Numerics.Extensions; +using SabreTools.Security.Cryptography; using SabreTools.Text.Extensions; namespace SabreTools.Wrappers diff --git a/SabreTools.Wrappers/RAR.Extraction.cs b/SabreTools.Wrappers/RAR.Extraction.cs index 507b3233..05ff9d62 100644 --- a/SabreTools.Wrappers/RAR.Extraction.cs +++ b/SabreTools.Wrappers/RAR.Extraction.cs @@ -5,6 +5,7 @@ using System.Text.RegularExpressions; #if NET462_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER using SharpCompress.Archives; using SharpCompress.Archives.Rar; +using SharpCompress.Common; using SharpCompress.Readers; #endif @@ -31,12 +32,7 @@ namespace SabreTools.Wrappers #if NET462_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER try { - var readerOptions = new ReaderOptions() - { - LookForHeader = lookForHeader, - ExtractFullPath = true, - Overwrite = true - }; + var readerOptions = new ReaderOptions() { LookForHeader = lookForHeader }; var rarFile = (RarArchive)RarArchive.OpenArchive(_dataSource, readerOptions); // If the file exists @@ -216,7 +212,7 @@ namespace SabreTools.Wrappers /// Extraction method for non-solid archives. This iterates over each entry in the archive to extract every /// file individually, in order to extract all valid files from the archive. /// - private static bool ExtractNonSolid(RarArchive rarFile, string outDir, bool includeDebug) + private static bool ExtractNonSolid(RarArchive rarFile, string outputDirectory, bool includeDebug) { foreach (var entry in rarFile.Entries) { @@ -250,12 +246,17 @@ namespace SabreTools.Wrappers filename = filename.Replace('\\', '/'); // Ensure the full output directory exists - filename = Path.Combine(outDir, filename); + filename = Path.Combine(outputDirectory, filename); var directoryName = Path.GetDirectoryName(filename); if (directoryName is not null && !Directory.Exists(directoryName)) Directory.CreateDirectory(directoryName); - entry.WriteToFile(filename); + var options = new ExtractionOptions + { + ExtractFullPath = true, + Overwrite = true, + }; + entry.WriteToFile(filename, options); } catch (Exception ex) { @@ -270,14 +271,19 @@ namespace SabreTools.Wrappers /// Extraction method for solid archives. Uses ExtractAllEntries because extraction for solid archives must be /// done sequentially, and files beyond a corrupted point in a solid archive will be unreadable anyways. /// - private static bool ExtractSolid(RarArchive rarFile, string outDir, bool includeDebug) + private static bool ExtractSolid(RarArchive rarFile, string outputDirectory, bool includeDebug) { try { - if (!Directory.Exists(outDir)) - Directory.CreateDirectory(outDir); + if (!Directory.Exists(outputDirectory)) + Directory.CreateDirectory(outputDirectory); - rarFile.WriteToDirectory(outDir); + var options = new ExtractionOptions + { + ExtractFullPath = true, + Overwrite = true, + }; + rarFile.WriteToDirectory(outputDirectory, options); } catch (Exception ex) diff --git a/SabreTools.Wrappers/SabreTools.Wrappers.csproj b/SabreTools.Wrappers/SabreTools.Wrappers.csproj index 1e69368d..faf65f83 100644 --- a/SabreTools.Wrappers/SabreTools.Wrappers.csproj +++ b/SabreTools.Wrappers/SabreTools.Wrappers.csproj @@ -1,4 +1,4 @@ - + @@ -45,11 +45,11 @@ - - + + - + diff --git a/SabreTools.Wrappers/SevenZip.Extraction.cs b/SabreTools.Wrappers/SevenZip.Extraction.cs index 4ee39c30..8e7544ed 100644 --- a/SabreTools.Wrappers/SevenZip.Extraction.cs +++ b/SabreTools.Wrappers/SevenZip.Extraction.cs @@ -5,6 +5,7 @@ using System.Text.RegularExpressions; #if NET462_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER using SharpCompress.Archives; using SharpCompress.Archives.SevenZip; +using SharpCompress.Common; using SharpCompress.Readers; #endif @@ -31,12 +32,7 @@ namespace SabreTools.Wrappers #if NET462_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER try { - var readerOptions = new ReaderOptions() - { - LookForHeader = lookForHeader, - ExtractFullPath = true, - Overwrite = true - }; + var readerOptions = new ReaderOptions() { LookForHeader = lookForHeader }; var sevenZip = (SevenZipArchive)SevenZipArchive.OpenArchive(_dataSource, readerOptions); if (sevenZip is null) return false; @@ -226,7 +222,12 @@ namespace SabreTools.Wrappers if (directoryName is not null && !Directory.Exists(directoryName)) Directory.CreateDirectory(directoryName); - entry.WriteToFile(filename); + var options = new ExtractionOptions + { + ExtractFullPath = true, + Overwrite = true, + }; + entry.WriteToFile(filename, options); } catch (Exception ex) { @@ -248,35 +249,13 @@ namespace SabreTools.Wrappers if (!Directory.Exists(outputDirectory)) Directory.CreateDirectory(outputDirectory); - int index = 0; - var entries = sevenZip.ExtractAllEntries(); - while (entries.MoveToNextEntry()) + var options = new ExtractionOptions { - var entry = entries.Entry; - if (entry.IsDirectory) - continue; + ExtractFullPath = true, + Overwrite = true, + }; + sevenZip.WriteToDirectory(outputDirectory, options); - // Ensure directory separators are consistent - string filename = entry.Key ?? $"extracted_file_{index}"; - if (Path.DirectorySeparatorChar == '\\') - filename = filename.Replace('/', '\\'); - else if (Path.DirectorySeparatorChar == '/') - filename = filename.Replace('\\', '/'); - - // Ensure the full output directory exists - filename = Path.Combine(outputDirectory, filename); - var directoryName = Path.GetDirectoryName(filename); - if (directoryName is not null && !Directory.Exists(directoryName)) - Directory.CreateDirectory(directoryName); - - // Write to file - using var fs = File.Open(filename, FileMode.Create, FileAccess.Write, FileShare.None); - entries.WriteEntryTo(fs); - fs.Flush(); - - // Increment the index - index++; - } } catch (Exception ex) { diff --git a/SabreTools.Wrappers/WIA.Writing.cs b/SabreTools.Wrappers/WIA.Writing.cs index 4a879db7..d5925146 100644 --- a/SabreTools.Wrappers/WIA.Writing.cs +++ b/SabreTools.Wrappers/WIA.Writing.cs @@ -9,6 +9,7 @@ using SabreTools.Data.Models.NintendoDisc; using SabreTools.Data.Models.WIA; using SabreTools.Hashing; using SabreTools.Numerics.Extensions; +using SabreTools.Security.Cryptography; using static SabreTools.Data.Models.NintendoDisc.Constants; using static SabreTools.Data.Models.WIA.Constants; @@ -188,7 +189,7 @@ namespace SabreTools.Wrappers var groupEntries = new RvzGroupEntry[numGroups]; var rawDedupMap = new Dictionary(); - FileSystemTableReader? gcFst = isRvz ? BuildFileSystemTableReader(source) : null; + IO.Compression.RVZPack.FileSystemTableReader? gcFst = isRvz ? BuildFileSystemTableReader(source) : null; GetCompressorData(compressionType, compressionLevel, out byte[] propData, out byte propSize); @@ -240,7 +241,7 @@ namespace SabreTools.Wrappers if (isRvz) { - byte[]? packed = RvzPackEncoder.Pack(raw, 0, toRead, srcOff - toRead, out gi.RvzPackedSize, gcFst); + byte[]? packed = IO.Compression.RVZPack.Compressor.Pack(raw, 0, toRead, srcOff - toRead, out gi.RvzPackedSize, gcFst); gi.MainData = packed ?? raw; if (packed is null) gi.RvzPackedSize = 0; @@ -673,7 +674,7 @@ namespace SabreTools.Wrappers if (isRvz) { long baseDecOff = regionDecOff + ((long)c * (blocksPerChunk * WiiBlockDataSize)); - byte[]? packed = RvzPackEncoder.Pack(procData, 0, procData.Length, baseDecOff, out rvzPackedSize); + byte[]? packed = IO.Compression.RVZPack.Compressor.Pack(procData, 0, procData.Length, baseDecOff, out rvzPackedSize); mainData = packed ?? procData; if (packed is null) rvzPackedSize = 0; @@ -722,7 +723,7 @@ namespace SabreTools.Wrappers } else if (compressionType == WiaRvzCompressionType.Purge) { - pw.CompressedData = PurgeCompressor.Compress(pw.MainDataBytes, 0, pw.MainDataBytes.Length, pw.ExceptionListBytes); + pw.CompressedData = IO.Compression.PURGE.Compressor.Compress(pw.MainDataBytes, 0, pw.MainDataBytes.Length, pw.ExceptionListBytes); } }); } @@ -930,7 +931,7 @@ namespace SabreTools.Wrappers uint rvzPackedSize = 0; if (isRvz) { - byte[]? packed = RvzPackEncoder.Pack(data, 0, toRead, srcOff, out rvzPackedSize); + byte[]? packed = IO.Compression.RVZPack.Compressor.Pack(data, 0, toRead, srcOff, out rvzPackedSize); mainData = packed ?? data; if (packed is null) rvzPackedSize = 0; @@ -949,7 +950,7 @@ namespace SabreTools.Wrappers } else if (compressionType == WiaRvzCompressionType.Purge) { - compressed = PurgeCompressor.Compress(mainData, 0, mainData.Length); + compressed = IO.Compression.PURGE.Compressor.Compress(mainData, 0, mainData.Length); } uint groupOff = (uint)(bytesWritten >> 2); @@ -1061,11 +1062,11 @@ namespace SabreTools.Wrappers byte[] encHashBlock = new byte[WiiBlockHeaderSize]; Array.Copy(encGroup, blockOff, encHashBlock, 0, WiiBlockHeaderSize); - byte[] origHash = AesCbc.Decrypt(encHashBlock, titleKey, new byte[16]) ?? new byte[WiiBlockHeaderSize]; + byte[] origHash = AESCBC.Decrypt(encHashBlock, titleKey, new byte[16]) ?? new byte[WiiBlockHeaderSize]; byte[] reEncHashBlock = new byte[WiiBlockHeaderSize]; Array.Copy(reEncGroup, blockOff, reEncHashBlock, 0, WiiBlockHeaderSize); - byte[] recompHash = AesCbc.Decrypt(reEncHashBlock, titleKey, new byte[16]) ?? new byte[WiiBlockHeaderSize]; + byte[] recompHash = AESCBC.Decrypt(reEncHashBlock, titleKey, new byte[16]) ?? new byte[WiiBlockHeaderSize]; for (int off = 0; off < WiiBlockHeaderSize; off += 20) { @@ -1243,7 +1244,7 @@ namespace SabreTools.Wrappers /// /// /// - private static FileSystemTableReader? BuildFileSystemTableReader(NintendoDisc source) + private static IO.Compression.RVZPack.FileSystemTableReader? BuildFileSystemTableReader(NintendoDisc source) { byte[]? hdr = source.ReadData(0x420, 12); if (hdr is null) @@ -1259,7 +1260,7 @@ namespace SabreTools.Wrappers if (fstData is null) return null; - return FileSystemTableReader.TryParse(fstData, offsetShift: 0); + return IO.Compression.RVZPack.FileSystemTableReader.TryParse(fstData, offsetShift: 0); } #region Serialization - TODO: MOVE TO WRITER @@ -1411,7 +1412,7 @@ namespace SabreTools.Wrappers byte propSize) { if (compressionType == WiaRvzCompressionType.Purge) - return PurgeCompressor.Compress(data, 0, data.Length); + return IO.Compression.PURGE.Compressor.Compress(data, 0, data.Length); if (compressionType > WiaRvzCompressionType.Purge) return Compress(compressionType, data, 0, data.Length, compressionLevel, propData, propSize); @@ -1593,7 +1594,7 @@ namespace SabreTools.Wrappers if (compressionType == WiaRvzCompressionType.Purge && gi.MainData is not null) { - byte[] comp = PurgeCompressor.Compress(gi.MainData, 0, gi.MainData.Length); + byte[] comp = IO.Compression.PURGE.Compressor.Compress(gi.MainData, 0, gi.MainData.Length); dest.Write(comp, 0, comp.Length); bytesWritten += comp.Length; return (uint)comp.Length; diff --git a/SabreTools.Wrappers/WIA.cs b/SabreTools.Wrappers/WIA.cs index 1977d8c7..3a69ee23 100644 --- a/SabreTools.Wrappers/WIA.cs +++ b/SabreTools.Wrappers/WIA.cs @@ -5,6 +5,9 @@ using SabreTools.Data.Models.WIA; using SabreTools.Hashing; #if NET462_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER using SabreTools.IO.Extensions; +#endif +using SabreTools.Security.Cryptography; +#if NET462_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER using SharpCompress.Compressors; using SharpCompress.Compressors.BZip2; using SharpCompress.Compressors.LZMA; @@ -1143,7 +1146,7 @@ namespace SabreTools.Wrappers Array.Copy(fileData, offset, exceptionBytes, 0, exceptionLen); int purgeLen = length - exceptionLen; - return PurgeDecompressor.Decompress(fileData, purgeStart, purgeLen, expectedSize, exceptionBytes); + return IO.Compression.PURGE.Decompressor.Decompress(fileData, purgeStart, purgeLen, expectedSize, exceptionBytes); } else { @@ -1183,7 +1186,7 @@ namespace SabreTools.Wrappers byte[] rvzPayload = new byte[rvzDataLen]; Array.Copy(workingData, rvzDataStart, rvzPayload, 0, rvzDataLen); - var rvzDecomp = new RvzPackDecompressor(rvzPayload, rvzPackedSize, dataOffsetForLfg); + var rvzDecomp = new IO.Compression.RVZPack.Decompressor(rvzPayload, rvzPackedSize, dataOffsetForLfg); byte[] unpacked = new byte[expectedSize]; int bytesRead = rvzDecomp.Decompress(unpacked, 0, expectedSize); if (bytesRead < expectedSize) @@ -1358,7 +1361,7 @@ namespace SabreTools.Wrappers // Note: off is now 0x3D4; IV will sit at 0x3D0 after encryption // Encrypt hash block with IV = zero - byte[] encHashBlock = AesCbc.Encrypt(hashBlock, key, new byte[16]) ?? new byte[WiiBlockHashSize]; + byte[] encHashBlock = AESCBC.Encrypt(hashBlock, key, new byte[16]) ?? new byte[WiiBlockHashSize]; // Extract IV for data block from offset 0x3D0 of the encrypted hash block byte[] iv = new byte[16]; @@ -1371,7 +1374,7 @@ namespace SabreTools.Wrappers if (dataLen > 0) Array.Copy(decryptedData, dataSrc, dataBlock, 0, dataLen); - byte[] encDataBlock = AesCbc.Encrypt(dataBlock, key, iv) ?? new byte[WiiBlockDataSize]; + byte[] encDataBlock = AESCBC.Encrypt(dataBlock, key, iv) ?? new byte[WiiBlockDataSize]; int dest = b * WiiBlockSize; Array.Copy(encHashBlock, 0, result, dest, WiiBlockHashSize);