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);