Files

1229 lines
44 KiB
C#
Raw Permalink Normal View History

2024-04-24 18:53:10 -04:00
using System;
using System.IO;
2026-05-14 20:08:32 -04:00
using SabreTools.Data.Extensions;
using SabreTools.IO.Extensions;
2026-03-24 19:17:25 -04:00
using SabreTools.Matching;
using SabreTools.Numerics.Extensions;
2024-04-24 18:53:10 -04:00
namespace SabreTools.Wrappers
2024-04-24 18:53:10 -04:00
{
public static class WrapperFactory
2024-04-24 18:53:10 -04:00
{
/// <summary>
/// Create an instance of a wrapper based on file type
/// </summary>
public static IWrapper? CreateWrapper(WrapperType fileType, Stream? data)
{
2024-11-13 02:42:14 -05:00
return fileType switch
{
2024-11-13 02:42:14 -05:00
WrapperType.AACSMediaKeyBlock => AACSMediaKeyBlock.Create(data),
2026-03-07 21:27:11 -05:00
WrapperType.Atari7800Cart => Atari7800Cart.Create(data),
2026-03-10 14:28:09 -04:00
WrapperType.AtariLynxCart => AtariLynxCart.Create(data),
2024-11-13 02:42:14 -05:00
WrapperType.BDPlusSVM => BDPlusSVM.Create(data),
WrapperType.BFPK => BFPK.Create(data),
WrapperType.BSP => BSP.Create(data),
WrapperType.BZip2 => BZip2.Create(data),
WrapperType.CDROM => CreateCDROMImageWrapper(data),
2024-11-13 02:42:14 -05:00
WrapperType.CFB => CFB.Create(data),
WrapperType.CHD => CHD.Create(data),
WrapperType.CIA => CIA.Create(data),
WrapperType.Executable => CreateExecutableWrapper(data),
2026-03-10 17:27:58 -04:00
WrapperType.FDS => FDS.Create(data),
2024-11-13 02:42:14 -05:00
WrapperType.GCF => GCF.Create(data),
Dolphin lib (#85) * Add GCZ, WIA/RVZ, and NintendoDisc (GameCube/Wii) format support Port of DolphinIsoLib into SabreTools.Serialization architecture: - Data.Models: GCZ/, WIA/, NintendoDisc/ model subdirectories (15 files) - Serialization.Readers: GCZ, WIA, NintendoDisc readers - Serialization.Writers: GCZ, WIA writers (structural metadata; full round-trip TODO) - Wrappers: NintendoDisc, GCZ, WIA wrappers with Encryption partial class - Wrappers: WiaRvzCompressionHelper (BZip2/LZMA/LZMA2/Zstd, net462+ guarded) - WrapperType + WrapperFactory: GCZ, WIA, NintendoDisc entries added GetInnerWrapper() decompression and NintendoDisc.Extraction FST extraction are stubbed with TODO comments pending full implementation. * Implement GetInnerWrapper for GCZ and WIA, full NintendoDisc extraction * Add GCZ/WIA/RVZ write pipeline and Nintendo disc compression helpers * Add WIA/RVZ table decompression, NintendoDisc/GCZ printing, NintendoDisc detection for .iso files * Fix NintendoDisc header layout, GC magic, and add embedded disc header to WIA/GCZ printing - Fix GCMagicWord: 0xC23D3C1F -> 0xC2339F3D (confirmed from Dolphin DiscUtils.h) - Fix disc header field layout to match Dolphin's confirmed offsets: MakerCode is bytes 4-5 of the 6-char GameId (no separate field at 0x006), DiscNumber at 0x006, DiscVersion at 0x007, unused region is 14 bytes (0x00A-0x017) - Update NintendoDisc reader: derive MakerCode from GameId[4..5], fix skip count - Add ParseDiscHeaderOnly() to reader for partial (short) stream parsing - Guard DisableHash/DisableEnc reads at the 0x080 boundary for 128-byte embedded headers - Guard DOL/FST skip for streams shorter than full 0x440 boot block - Fix WrapperFactory: NintendoDisc magic detection now precedes .iso -> ISO9660 fallback - Add GameId-prefix heuristic in WrapperFactory for GC discs lacking magic word - Add GameId-prefix platform fallback in reader for GC discs without GCMagicWord - Add DiscHeader property to WIA wrapper (parsed from Header2.DiscHeader bytes) - Add DiscHeader property to GCZ wrapper (decompresses first block only) - Add ReadDiscHeader() helper to GCZ for lightweight first-block decompression - Print embedded disc header (Game ID, Maker, Disc/Rev, Title) in WIA.Printing.cs and GCZ.Printing.cs * Fix Wii partition extraction: correct IV, FST size shift, partition naming - Block decryption: IV is at raw block offset 0x3D0 (still-encrypted), matching Dolphin/DolphinIsoLib WiiPartitionDecryptor.DecryptBlock exactly. - FST size field at boot.bin 0x428 is also stored >>2 on Wii; apply <<2 to get true byte size. - Partition folder naming now matches DolphinIsoLib WiiDiscExtractor exactly: type 0->GM+n, 1->UP+n, 2->CH+n, printable ASCII unknown->raw 4-char string, non-printable->P{index}. SSBB VC channels extract as HA8E, HA9E, etc. - ExtractionTool peek buffer increased from 16 to 32 bytes. Verified: SSBB GM0 extracts 5524 files, boot.bin/fst.bin byte-identical to Dolphin reference extraction. * Fix FST extraction: create zero-byte files instead of skipping them Files with fileSize=0 in the FST were silently skipped. Now they are created as empty files, matching Dolphin/DolphinIsoLib behavior. Verified: SSBB now extracts 5958 files with 0 missing, 0 extra, 0 size mismatches, and 0 hash mismatches vs DolphinIsoLib reference. * Add GCZ/WIA/RVZ virtual stream extraction via NintendoDisc wrapper * Address PR #85 review comments (Copilot + mnadareski) * Address PR #85 review comments * Replace custom endian helpers and SHA1 with SabreTools.IO equivalents * Update GCZ.Printing.cs * Update NintendoDisc.Printing.cs * Update WIA.Printing.cs * Add WIA/RVZ Wii partition crypto round-trip support - Add AesCbc internal helper (BouncyCastle AES-CBC encrypt/decrypt) - Add NintendoDisc.CommonKeyProvider hook for injectable test keys - Fix sha1.Terminate() missing in all three ComputeSha1 helpers in WIA.cs - Fix Wii partition dataOff alignment to 0x8000 boundary - Add WIA.EncryptWiiGroup (internal) for re-encrypting plaintext groups - Add WIA.DumpIso to WIA.Writing.cs (WIA/RVZ -> flat ISO conversion) - Add WiaVirtualStream on-demand group decompression - Add _preDecryptedReader bypass on NintendoDisc for WIA extraction path - Add WIATests.cs with Wii crypto round-trip test using synthetic data - Move DumpIso from WIA.Extraction.cs to WIA.Writing.cs - Bump DumpIso read buffer from 1 MiB to 2 MiB (aligns to WIA chunk size) - Add InternalsVisibleTo SabreTools.Wrappers.Test in csproj * Remove hardcoded Wii common keys from NintendoDisc.Encryption - Delete the embedded WiiCommonKeyRetail and WiiCommonKeyKorean byte arrays from NintendoDisc.Encryption.cs. - Make CommonKeyProvider public so any caller (not just tests) can inject keys; DecryptTitleKey now returns null when no key is available for the requested index rather than falling back to hardcoded values. - Add NintendoDiscEncryptionTests.cs: - Argument guard and no-provider tests for DecryptTitleKey. - Fake-key round-trip test (encrypt then decrypt with injected key). - Integration test that reads TestData/NintendoDisc/keys.json, verifies each key against hardcoded SHA256 constants, and skips silently if the file is absent or the keys do not match. - LoadKeyProvider helper (named JSON format, index-keyed). - Add [Collection(NintendoDisc)] to both NintendoDiscEncryptionTests and WIATests to prevent parallel access to the static CommonKeyProvider from racing between test classes. - Add TestData/NintendoDisc/keys.json.example documenting the expected key file format. - Add Newtonsoft.Json reference to SabreTools.Wrappers.Test.csproj. * Didn't actually commit the changes. My bad. Fixed. * Edited a comment * Added in XUnit outputs that show up in Test Viewer in VS --------- Co-authored-by: Matt Nadareski <mnadareski@outlook.com>
2026-05-12 09:41:32 -05:00
WrapperType.GCZ => GCZ.Create(data),
WrapperType.GZip => GZip.Create(data),
2024-12-11 13:56:01 -05:00
WrapperType.InstallShieldArchiveV3 => InstallShieldArchiveV3.Create(data),
2024-11-13 02:42:14 -05:00
WrapperType.InstallShieldCAB => InstallShieldCabinet.Create(data),
2025-10-29 08:57:01 -04:00
WrapperType.IRD => IRD.Create(data),
WrapperType.ISO9660 => CreateDiscImageWrapper(data),
2025-09-22 20:07:18 -04:00
WrapperType.LDSCRYPT => LDSCRYPT.Create(data),
2024-12-11 13:31:54 -05:00
WrapperType.LZKWAJ => LZKWAJ.Create(data),
WrapperType.LZQBasic => LZQBasic.Create(data),
WrapperType.LZSZDD => LZSZDD.Create(data),
2024-11-13 02:42:14 -05:00
WrapperType.MicrosoftCAB => MicrosoftCabinet.Create(data),
WrapperType.MoPaQ => MoPaQ.Create(data),
WrapperType.N3DS => N3DS.Create(data),
WrapperType.NCF => NCF.Create(data),
2026-03-06 11:11:26 -05:00
WrapperType.NESCart => NESCart.Create(data),
2024-11-13 02:42:14 -05:00
WrapperType.Nitro => Nitro.Create(data),
Dolphin lib (#85) * Add GCZ, WIA/RVZ, and NintendoDisc (GameCube/Wii) format support Port of DolphinIsoLib into SabreTools.Serialization architecture: - Data.Models: GCZ/, WIA/, NintendoDisc/ model subdirectories (15 files) - Serialization.Readers: GCZ, WIA, NintendoDisc readers - Serialization.Writers: GCZ, WIA writers (structural metadata; full round-trip TODO) - Wrappers: NintendoDisc, GCZ, WIA wrappers with Encryption partial class - Wrappers: WiaRvzCompressionHelper (BZip2/LZMA/LZMA2/Zstd, net462+ guarded) - WrapperType + WrapperFactory: GCZ, WIA, NintendoDisc entries added GetInnerWrapper() decompression and NintendoDisc.Extraction FST extraction are stubbed with TODO comments pending full implementation. * Implement GetInnerWrapper for GCZ and WIA, full NintendoDisc extraction * Add GCZ/WIA/RVZ write pipeline and Nintendo disc compression helpers * Add WIA/RVZ table decompression, NintendoDisc/GCZ printing, NintendoDisc detection for .iso files * Fix NintendoDisc header layout, GC magic, and add embedded disc header to WIA/GCZ printing - Fix GCMagicWord: 0xC23D3C1F -> 0xC2339F3D (confirmed from Dolphin DiscUtils.h) - Fix disc header field layout to match Dolphin's confirmed offsets: MakerCode is bytes 4-5 of the 6-char GameId (no separate field at 0x006), DiscNumber at 0x006, DiscVersion at 0x007, unused region is 14 bytes (0x00A-0x017) - Update NintendoDisc reader: derive MakerCode from GameId[4..5], fix skip count - Add ParseDiscHeaderOnly() to reader for partial (short) stream parsing - Guard DisableHash/DisableEnc reads at the 0x080 boundary for 128-byte embedded headers - Guard DOL/FST skip for streams shorter than full 0x440 boot block - Fix WrapperFactory: NintendoDisc magic detection now precedes .iso -> ISO9660 fallback - Add GameId-prefix heuristic in WrapperFactory for GC discs lacking magic word - Add GameId-prefix platform fallback in reader for GC discs without GCMagicWord - Add DiscHeader property to WIA wrapper (parsed from Header2.DiscHeader bytes) - Add DiscHeader property to GCZ wrapper (decompresses first block only) - Add ReadDiscHeader() helper to GCZ for lightweight first-block decompression - Print embedded disc header (Game ID, Maker, Disc/Rev, Title) in WIA.Printing.cs and GCZ.Printing.cs * Fix Wii partition extraction: correct IV, FST size shift, partition naming - Block decryption: IV is at raw block offset 0x3D0 (still-encrypted), matching Dolphin/DolphinIsoLib WiiPartitionDecryptor.DecryptBlock exactly. - FST size field at boot.bin 0x428 is also stored >>2 on Wii; apply <<2 to get true byte size. - Partition folder naming now matches DolphinIsoLib WiiDiscExtractor exactly: type 0->GM+n, 1->UP+n, 2->CH+n, printable ASCII unknown->raw 4-char string, non-printable->P{index}. SSBB VC channels extract as HA8E, HA9E, etc. - ExtractionTool peek buffer increased from 16 to 32 bytes. Verified: SSBB GM0 extracts 5524 files, boot.bin/fst.bin byte-identical to Dolphin reference extraction. * Fix FST extraction: create zero-byte files instead of skipping them Files with fileSize=0 in the FST were silently skipped. Now they are created as empty files, matching Dolphin/DolphinIsoLib behavior. Verified: SSBB now extracts 5958 files with 0 missing, 0 extra, 0 size mismatches, and 0 hash mismatches vs DolphinIsoLib reference. * Add GCZ/WIA/RVZ virtual stream extraction via NintendoDisc wrapper * Address PR #85 review comments (Copilot + mnadareski) * Address PR #85 review comments * Replace custom endian helpers and SHA1 with SabreTools.IO equivalents * Update GCZ.Printing.cs * Update NintendoDisc.Printing.cs * Update WIA.Printing.cs * Add WIA/RVZ Wii partition crypto round-trip support - Add AesCbc internal helper (BouncyCastle AES-CBC encrypt/decrypt) - Add NintendoDisc.CommonKeyProvider hook for injectable test keys - Fix sha1.Terminate() missing in all three ComputeSha1 helpers in WIA.cs - Fix Wii partition dataOff alignment to 0x8000 boundary - Add WIA.EncryptWiiGroup (internal) for re-encrypting plaintext groups - Add WIA.DumpIso to WIA.Writing.cs (WIA/RVZ -> flat ISO conversion) - Add WiaVirtualStream on-demand group decompression - Add _preDecryptedReader bypass on NintendoDisc for WIA extraction path - Add WIATests.cs with Wii crypto round-trip test using synthetic data - Move DumpIso from WIA.Extraction.cs to WIA.Writing.cs - Bump DumpIso read buffer from 1 MiB to 2 MiB (aligns to WIA chunk size) - Add InternalsVisibleTo SabreTools.Wrappers.Test in csproj * Remove hardcoded Wii common keys from NintendoDisc.Encryption - Delete the embedded WiiCommonKeyRetail and WiiCommonKeyKorean byte arrays from NintendoDisc.Encryption.cs. - Make CommonKeyProvider public so any caller (not just tests) can inject keys; DecryptTitleKey now returns null when no key is available for the requested index rather than falling back to hardcoded values. - Add NintendoDiscEncryptionTests.cs: - Argument guard and no-provider tests for DecryptTitleKey. - Fake-key round-trip test (encrypt then decrypt with injected key). - Integration test that reads TestData/NintendoDisc/keys.json, verifies each key against hardcoded SHA256 constants, and skips silently if the file is absent or the keys do not match. - LoadKeyProvider helper (named JSON format, index-keyed). - Add [Collection(NintendoDisc)] to both NintendoDiscEncryptionTests and WIATests to prevent parallel access to the static CommonKeyProvider from racing between test classes. - Add TestData/NintendoDisc/keys.json.example documenting the expected key file format. - Add Newtonsoft.Json reference to SabreTools.Wrappers.Test.csproj. * Didn't actually commit the changes. My bad. Fixed. * Edited a comment * Added in XUnit outputs that show up in Test Viewer in VS --------- Co-authored-by: Matt Nadareski <mnadareski@outlook.com>
2026-05-12 09:41:32 -05:00
WrapperType.NintendoDisc => NintendoDisc.Create(data),
2024-11-13 02:42:14 -05:00
WrapperType.PAK => PAK.Create(data),
WrapperType.PFF => PFF.Create(data),
WrapperType.PIC => PIC.Create(data),
WrapperType.PKZIP => PKZIP.Create(data),
WrapperType.PlayJAudioFile => PlayJAudioFile.Create(data),
WrapperType.PlayJPlaylist => PlayJPlaylist.Create(data),
2026-03-10 18:11:42 -04:00
WrapperType.QD => QD.Create(data),
2024-11-13 02:42:14 -05:00
WrapperType.Quantum => Quantum.Create(data),
WrapperType.RAR => RAR.Create(data),
2025-09-22 20:07:18 -04:00
WrapperType.RealArcadeInstaller => RealArcadeInstaller.Create(data),
WrapperType.RealArcadeMezzanine => RealArcadeMezzanine.Create(data),
2025-07-24 10:40:31 -04:00
WrapperType.SecuROMDFA => SecuROMDFA.Create(data),
WrapperType.SevenZip => SevenZip.Create(data),
WrapperType.Skeleton => Skeleton.Create(data),
WrapperType.SkuSis => SkuSis.Create(data),
2025-09-22 09:13:27 -04:00
WrapperType.SFFS => SFFS.Create(data),
2024-11-13 02:42:14 -05:00
WrapperType.SGA => SGA.Create(data),
WrapperType.Steam2Installer => Steam2Installer.Create(data),
WrapperType.STFS => STFS.Create(data),
WrapperType.TapeArchive => TapeArchive.Create(data),
2024-11-13 02:42:14 -05:00
WrapperType.VBSP => VBSP.Create(data),
WrapperType.VPK => VPK.Create(data),
2024-11-19 01:04:50 -05:00
WrapperType.WAD => WAD3.Create(data),
2026-03-28 23:25:58 -04:00
WrapperType.WiseOverlayHeader => WiseOverlayHeader.Create(data),
WrapperType.WiseScript => WiseScript.Create(data),
Dolphin lib (#85) * Add GCZ, WIA/RVZ, and NintendoDisc (GameCube/Wii) format support Port of DolphinIsoLib into SabreTools.Serialization architecture: - Data.Models: GCZ/, WIA/, NintendoDisc/ model subdirectories (15 files) - Serialization.Readers: GCZ, WIA, NintendoDisc readers - Serialization.Writers: GCZ, WIA writers (structural metadata; full round-trip TODO) - Wrappers: NintendoDisc, GCZ, WIA wrappers with Encryption partial class - Wrappers: WiaRvzCompressionHelper (BZip2/LZMA/LZMA2/Zstd, net462+ guarded) - WrapperType + WrapperFactory: GCZ, WIA, NintendoDisc entries added GetInnerWrapper() decompression and NintendoDisc.Extraction FST extraction are stubbed with TODO comments pending full implementation. * Implement GetInnerWrapper for GCZ and WIA, full NintendoDisc extraction * Add GCZ/WIA/RVZ write pipeline and Nintendo disc compression helpers * Add WIA/RVZ table decompression, NintendoDisc/GCZ printing, NintendoDisc detection for .iso files * Fix NintendoDisc header layout, GC magic, and add embedded disc header to WIA/GCZ printing - Fix GCMagicWord: 0xC23D3C1F -> 0xC2339F3D (confirmed from Dolphin DiscUtils.h) - Fix disc header field layout to match Dolphin's confirmed offsets: MakerCode is bytes 4-5 of the 6-char GameId (no separate field at 0x006), DiscNumber at 0x006, DiscVersion at 0x007, unused region is 14 bytes (0x00A-0x017) - Update NintendoDisc reader: derive MakerCode from GameId[4..5], fix skip count - Add ParseDiscHeaderOnly() to reader for partial (short) stream parsing - Guard DisableHash/DisableEnc reads at the 0x080 boundary for 128-byte embedded headers - Guard DOL/FST skip for streams shorter than full 0x440 boot block - Fix WrapperFactory: NintendoDisc magic detection now precedes .iso -> ISO9660 fallback - Add GameId-prefix heuristic in WrapperFactory for GC discs lacking magic word - Add GameId-prefix platform fallback in reader for GC discs without GCMagicWord - Add DiscHeader property to WIA wrapper (parsed from Header2.DiscHeader bytes) - Add DiscHeader property to GCZ wrapper (decompresses first block only) - Add ReadDiscHeader() helper to GCZ for lightweight first-block decompression - Print embedded disc header (Game ID, Maker, Disc/Rev, Title) in WIA.Printing.cs and GCZ.Printing.cs * Fix Wii partition extraction: correct IV, FST size shift, partition naming - Block decryption: IV is at raw block offset 0x3D0 (still-encrypted), matching Dolphin/DolphinIsoLib WiiPartitionDecryptor.DecryptBlock exactly. - FST size field at boot.bin 0x428 is also stored >>2 on Wii; apply <<2 to get true byte size. - Partition folder naming now matches DolphinIsoLib WiiDiscExtractor exactly: type 0->GM+n, 1->UP+n, 2->CH+n, printable ASCII unknown->raw 4-char string, non-printable->P{index}. SSBB VC channels extract as HA8E, HA9E, etc. - ExtractionTool peek buffer increased from 16 to 32 bytes. Verified: SSBB GM0 extracts 5524 files, boot.bin/fst.bin byte-identical to Dolphin reference extraction. * Fix FST extraction: create zero-byte files instead of skipping them Files with fileSize=0 in the FST were silently skipped. Now they are created as empty files, matching Dolphin/DolphinIsoLib behavior. Verified: SSBB now extracts 5958 files with 0 missing, 0 extra, 0 size mismatches, and 0 hash mismatches vs DolphinIsoLib reference. * Add GCZ/WIA/RVZ virtual stream extraction via NintendoDisc wrapper * Address PR #85 review comments (Copilot + mnadareski) * Address PR #85 review comments * Replace custom endian helpers and SHA1 with SabreTools.IO equivalents * Update GCZ.Printing.cs * Update NintendoDisc.Printing.cs * Update WIA.Printing.cs * Add WIA/RVZ Wii partition crypto round-trip support - Add AesCbc internal helper (BouncyCastle AES-CBC encrypt/decrypt) - Add NintendoDisc.CommonKeyProvider hook for injectable test keys - Fix sha1.Terminate() missing in all three ComputeSha1 helpers in WIA.cs - Fix Wii partition dataOff alignment to 0x8000 boundary - Add WIA.EncryptWiiGroup (internal) for re-encrypting plaintext groups - Add WIA.DumpIso to WIA.Writing.cs (WIA/RVZ -> flat ISO conversion) - Add WiaVirtualStream on-demand group decompression - Add _preDecryptedReader bypass on NintendoDisc for WIA extraction path - Add WIATests.cs with Wii crypto round-trip test using synthetic data - Move DumpIso from WIA.Extraction.cs to WIA.Writing.cs - Bump DumpIso read buffer from 1 MiB to 2 MiB (aligns to WIA chunk size) - Add InternalsVisibleTo SabreTools.Wrappers.Test in csproj * Remove hardcoded Wii common keys from NintendoDisc.Encryption - Delete the embedded WiiCommonKeyRetail and WiiCommonKeyKorean byte arrays from NintendoDisc.Encryption.cs. - Make CommonKeyProvider public so any caller (not just tests) can inject keys; DecryptTitleKey now returns null when no key is available for the requested index rather than falling back to hardcoded values. - Add NintendoDiscEncryptionTests.cs: - Argument guard and no-provider tests for DecryptTitleKey. - Fake-key round-trip test (encrypt then decrypt with injected key). - Integration test that reads TestData/NintendoDisc/keys.json, verifies each key against hardcoded SHA256 constants, and skips silently if the file is absent or the keys do not match. - LoadKeyProvider helper (named JSON format, index-keyed). - Add [Collection(NintendoDisc)] to both NintendoDiscEncryptionTests and WIATests to prevent parallel access to the static CommonKeyProvider from racing between test classes. - Add TestData/NintendoDisc/keys.json.example documenting the expected key file format. - Add Newtonsoft.Json reference to SabreTools.Wrappers.Test.csproj. * Didn't actually commit the changes. My bad. Fixed. * Edited a comment * Added in XUnit outputs that show up in Test Viewer in VS --------- Co-authored-by: Matt Nadareski <mnadareski@outlook.com>
2026-05-12 09:41:32 -05:00
WrapperType.WIA => WIA.Create(data),
WrapperType.RVZ => WIA.Create(data),
2026-03-13 11:40:01 -04:00
WrapperType.XboxExecutable => XboxExecutable.Create(data),
WrapperType.XDVDFS => XDVDFS.Create(data),
WrapperType.XenonExecutable => XenonExecutable.Create(data),
2026-04-19 22:04:25 +09:00
WrapperType.XRD => XRD.Create(data),
WrapperType.XZ => XZ.Create(data),
2024-11-13 02:42:14 -05:00
WrapperType.XZP => XZP.Create(data),
WrapperType.ZArchive => ZArchive.Create(data),
WrapperType.ZSTD => ZSTD.Create(data),
2026-01-25 16:15:05 -05:00
// Unimplemented
WrapperType.UNKNOWN => null,
2026-03-28 23:25:58 -04:00
WrapperType.IniFile => null,// TODO: Implement wrapper
WrapperType.Textfile => null,// TODO: Implement wrapper
2024-11-13 02:42:14 -05:00
_ => null,
};
}
/// <summary>
/// Create an instance of a wrapper based on the CDROM image type
/// </summary>
/// <param name="stream">Stream data to parse</param>
/// <returns>IWrapper representing the CDROM image, null on error</returns>
public static IWrapper? CreateCDROMImageWrapper(Stream? stream)
{
// If we have no stream
if (stream is null)
return null;
// Cache the current offset
long initialOffset = stream.Position;
2026-04-28 22:24:36 +09:00
// Attempt to open standard ISO9660 CDROM wrapper
var cdromWrapper = CDROM.Create(stream);
if (cdromWrapper is not null)
return cdromWrapper;
// Reset position in stream
stream.SeekIfPossible(initialOffset, SeekOrigin.Begin);
// Try to get a 3DO / M2 disc image wrapper
var operaDiscImageWrapper = OperaDiscImage.Create(stream);
if (operaDiscImageWrapper is not null)
return operaDiscImageWrapper;
// Reset position in stream
stream.SeekIfPossible(initialOffset, SeekOrigin.Begin);
2026-04-28 22:24:36 +09:00
// Try to get a PC Engine CDROM wrapper
// This reads a lot for detection, do this step last
var pcEngineDiscImageWrapper = PCEngineDiscImage.Create(stream);
if (pcEngineDiscImageWrapper is not null)
return pcEngineDiscImageWrapper;
2026-04-28 22:24:36 +09:00
// No known filesystems found
return null;
}
/// <summary>
/// Create an instance of a wrapper based on the disc image type
/// </summary>
/// <param name="stream">Stream data to parse</param>
/// <returns>IWrapper representing the disc image, null on error</returns>
public static IWrapper? CreateDiscImageWrapper(Stream? stream)
{
// If we have no stream
if (stream is null)
return null;
// Cache the current offset
long initialOffset = stream.Position;
2026-04-28 22:24:36 +09:00
// Try to get an Xbox disc image wrapper (must be before ISO9660)
var xboxWrapper = XboxISO.Create(stream);
2026-04-13 09:43:40 -04:00
if (xboxWrapper is not null)
return xboxWrapper;
// Reset position in stream
stream.SeekIfPossible(initialOffset, SeekOrigin.Begin);
2026-04-28 22:24:36 +09:00
// Try to standard ISO9660 wrapper
var isoWrapper = ISO9660.Create(stream);
if (isoWrapper is not null)
return isoWrapper;
// Reset position in stream
stream.SeekIfPossible(initialOffset, SeekOrigin.Begin);
Add a UDF reader and wrapper (#100) * Add a UDF reader Reads the Volume Recognition Sequence, the Anchor Volume Descriptor Pointer (sector 256, then the end of the volume), the Main or Reserve Volume Descriptor Sequence, the File Set Descriptor and the directory hierarchy. Type 1 and Metadata Partitions are mapped, Virtual and Sparable partitions are not. File Entries, Allocation Extent Descriptors and directories are read in ascending sector order with wrap-around, adjacent sectors merged into one request. FindFileEntry follows one path and reads only the directories on it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Add a UDF wrapper Prints the volume and file set structures and the directory hierarchy, and extracts the files. CreateDiscImageWrapper tries UDF after ISO 9660 for a stream at offset 0, so discs with both file systems still open as ISO 9660. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Treat the stream position as the start of the UDF volume The UDF reader addresses logical sectors from the start of the stream, so the wrapper hands it a view that starts at the current position and keeps that offset, like the other wrappers. CreateDiscImageWrapper no longer skips UDF for a stream that is not at offset 0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Address review comments on the UDF code - Move the descriptor tag check to UDFExtensions, with tests - Parse Volume Structure Descriptors in their own method - Use the existing extensions for numbers, the partition type identifier and printing entity identifiers - Use Array.Exists in IsSafeName - Make Extract non-virtual and remove the printing test - Use collection expressions and string interpolation Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Parse File Identifier Descriptors in their own method Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Add a TODO to use the CRC from Hashing Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 17:18:23 +01:00
// Try to get a UDF wrapper (discs without an ISO9660 file system)
var udfWrapper = UDF.Create(stream);
if (udfWrapper is not null)
return udfWrapper;
// Reset position in stream
stream.SeekIfPossible(initialOffset, SeekOrigin.Begin);
// Try to get a 3DO / M2 disc image wrapper
var operaFSWrapper = OperaFS.Create(stream);
if (operaFSWrapper is not null)
return operaFSWrapper;
// Reset position in stream
stream.SeekIfPossible(initialOffset, SeekOrigin.Begin);
2026-04-28 22:24:36 +09:00
// Try to get a PC Engine CDROM disc image wrapper
2026-05-14 20:52:12 -04:00
// This reads a lot for detection, do this step later
2026-04-28 22:24:36 +09:00
var pcEngineCDROMWrapper = PCEngineCDROM.Create(stream);
if (pcEngineCDROMWrapper is not null)
return pcEngineCDROMWrapper;
2026-05-14 20:52:12 -04:00
// Reset position in stream
stream.SeekIfPossible(initialOffset, SeekOrigin.Begin);
// Try NintendoDisc (GameCube / Wii)
// This has to be later due to slight possibility of false positives
var nintendoWrapper = NintendoDisc.Create(stream);
if (nintendoWrapper is not null)
return nintendoWrapper;
2026-04-28 22:24:36 +09:00
// No known filesystems found
return null;
}
/// <summary>
/// Create an instance of a wrapper based on the executable type
/// </summary>
/// <param name="stream">Stream data to parse</param>
/// <returns>IWrapper representing the executable, null on error</returns>
public static IWrapper? CreateExecutableWrapper(Stream? stream)
{
// If we have no stream
2026-01-25 14:30:18 -05:00
if (stream is null)
return null;
2025-08-19 09:18:50 -04:00
// Cache the current offset
long initialOffset = stream.Position;
// Try to get an MS-DOS wrapper first
var wrapper = MSDOS.Create(stream);
2026-01-25 14:30:18 -05:00
if (wrapper is null || wrapper is not MSDOS msdos)
return null;
// Check for a valid new executable address
2025-10-30 22:15:10 -04:00
if (initialOffset + msdos.Model.Header.NewExeHeaderAddr >= stream.Length)
return wrapper;
// Try to read the executable info
2025-10-27 22:43:56 -04:00
stream.SeekIfPossible(initialOffset + msdos.Model.Header.NewExeHeaderAddr, SeekOrigin.Begin);
var magic = stream.ReadBytes(4);
// If we didn't get valid data at the offset
2026-01-25 14:30:18 -05:00
if (magic is null)
{
return wrapper;
}
// New Executable
2025-09-26 13:06:18 -04:00
else if (magic.StartsWith(Data.Models.NewExecutable.Constants.SignatureBytes))
{
2025-10-27 22:43:56 -04:00
stream.SeekIfPossible(initialOffset, SeekOrigin.Begin);
return NewExecutable.Create(stream);
}
// Linear Executable
2025-09-26 13:06:18 -04:00
else if (magic.StartsWith(Data.Models.LinearExecutable.Constants.LESignatureBytes)
|| magic.StartsWith(Data.Models.LinearExecutable.Constants.LXSignatureBytes))
{
2025-10-27 22:43:56 -04:00
stream.SeekIfPossible(initialOffset, SeekOrigin.Begin);
return LinearExecutable.Create(stream);
}
// Portable Executable
2025-09-26 13:06:18 -04:00
else if (magic.StartsWith(Data.Models.PortableExecutable.Constants.SignatureBytes))
{
2025-10-27 22:43:56 -04:00
stream.SeekIfPossible(initialOffset, SeekOrigin.Begin);
return PortableExecutable.Create(stream);
}
// Everything else fails
return null;
}
2024-04-24 18:53:10 -04:00
/// <summary>
/// Get the supported file type for a magic string and an extension
2024-04-24 18:53:10 -04:00
/// </summary>
/// <remarks>Recommend sending in 16 bytes to check</remarks>
public static WrapperType GetFileType(byte[]? magic, string? extension)
2024-04-24 18:53:10 -04:00
{
// If we have an invalid magic byte array and extension
2026-01-25 14:30:18 -05:00
if (magic is null || magic.Length == 0 || extension is null)
return WrapperType.UNKNOWN;
2024-04-24 18:53:10 -04:00
// Normalize the extension
extension = extension.TrimStart('.').Trim();
2025-07-24 10:19:20 -04:00
// TODO: Use constants from Models here
2024-04-24 18:53:10 -04:00
#region AACSMediaKeyBlock
// Block starting with verify media key record
2024-11-29 20:48:50 -05:00
if (magic.StartsWith([0x81, 0x00, 0x00, 0x14]))
return WrapperType.AACSMediaKeyBlock;
// Block starting with type and version record
2024-11-29 20:48:50 -05:00
if (magic.StartsWith([0x10, 0x00, 0x00, 0x0C]))
return WrapperType.AACSMediaKeyBlock;
2024-04-24 18:53:10 -04:00
// Shares an extension with INF setup information so it can't be used accurately
// Blu-ray
// if (extension.Equals("inf", StringComparison.OrdinalIgnoreCase))
// return WrapperType.AACSMediaKeyBlock;
2024-04-24 18:53:10 -04:00
// HD-DVD
if (extension.Equals("aacs", StringComparison.OrdinalIgnoreCase))
return WrapperType.AACSMediaKeyBlock;
2024-04-24 18:53:10 -04:00
#endregion
2026-03-07 21:27:11 -05:00
// TODO: Figure out how to use constants here
#region Atari7800Cart
// Version 1 header
if (magic.StartsWith([0x01, 0x41, 0x54, 0x41, 0x52, 0x49, 0x37, 0x38, 0x30, 0x30]))
return WrapperType.Atari7800Cart;
// Version 2 header
if (magic.StartsWith([0x02, 0x41, 0x54, 0x41, 0x52, 0x49, 0x37, 0x38, 0x30, 0x30]))
return WrapperType.Atari7800Cart;
// Version 3 header
if (magic.StartsWith([0x03, 0x41, 0x54, 0x41, 0x52, 0x49, 0x37, 0x38, 0x30, 0x30]))
return WrapperType.Atari7800Cart;
// Version 4 header
if (magic.StartsWith([0x04, 0x41, 0x54, 0x41, 0x52, 0x49, 0x37, 0x38, 0x30, 0x30]))
return WrapperType.Atari7800Cart;
if (extension.Equals("a78", StringComparison.OrdinalIgnoreCase))
return WrapperType.Atari7800Cart;
#endregion
2026-03-10 14:28:09 -04:00
#region AtariLynxCart
// LNX header
if (magic.StartsWith(Data.Models.AtariLynx.Constants.MagicBytes))
return WrapperType.AtariLynxCart;
if (extension.Equals("lnx", StringComparison.OrdinalIgnoreCase))
return WrapperType.AtariLynxCart;
#endregion
2024-04-24 18:53:10 -04:00
#region BDPlusSVM
2025-09-26 13:06:18 -04:00
if (magic.StartsWith(Data.Models.BDPlus.Constants.SignatureBytes))
return WrapperType.BDPlusSVM;
2024-04-24 18:53:10 -04:00
if (extension.Equals("svm", StringComparison.OrdinalIgnoreCase))
return WrapperType.BDPlusSVM;
2024-04-24 18:53:10 -04:00
#endregion
#region BFPK
2025-09-26 13:06:18 -04:00
if (magic.StartsWith(Data.Models.BFPK.Constants.SignatureBytes))
return WrapperType.BFPK;
2024-04-24 18:53:10 -04:00
#endregion
2025-07-24 10:19:20 -04:00
// TODO: Use constants from Models here
2024-04-24 18:53:10 -04:00
#region BSP
// Shares a first 4 bytes with some .mc files
// Shares an extension with VBSP
2024-11-29 20:48:50 -05:00
if (magic.StartsWith([0x1d, 0x00, 0x00, 0x00]) && extension.Equals("bsp", StringComparison.OrdinalIgnoreCase))
return WrapperType.BSP;
// Shares a first 4 bytes with some .mc files
// Shares an extension with VBSP
2024-11-29 20:48:50 -05:00
if (magic.StartsWith([0x1e, 0x00, 0x00, 0x00]) && extension.Equals("bsp", StringComparison.OrdinalIgnoreCase))
return WrapperType.BSP;
2024-04-24 18:53:10 -04:00
#endregion
#region BZip2
2025-09-26 13:06:18 -04:00
if (magic.StartsWith(Data.Models.BZip2.Constants.SignatureBytes))
return WrapperType.BZip2;
2024-04-24 18:53:10 -04:00
if (extension.Equals("bz2", StringComparison.OrdinalIgnoreCase))
return WrapperType.BZip2;
2024-04-24 18:53:10 -04:00
#endregion
#region CDROM
// Must come before skeleton extension check for ISO9660
// CDROM skeletons have same extension as ISO9660 skeletons
if (magic.StartsWith([0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00])
2025-11-03 10:31:30 -05:00
&& (extension.Equals("bin", StringComparison.OrdinalIgnoreCase)
|| extension.Equals("skeleton", StringComparison.OrdinalIgnoreCase)))
{
return WrapperType.CDROM;
2025-11-03 10:31:30 -05:00
}
2026-04-28 22:24:36 +09:00
// Some CD-ROM images have no sync bytes in pregap
if (magic.StartsWith([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
&& (extension.Equals("bin", StringComparison.OrdinalIgnoreCase)
|| extension.Equals("skeleton", StringComparison.OrdinalIgnoreCase)))
{
return WrapperType.CDROM;
}
#endregion
2024-04-24 18:53:10 -04:00
#region CFB
2025-09-26 13:06:18 -04:00
if (magic.StartsWith(Data.Models.CFB.Constants.SignatureBytes))
return WrapperType.CFB;
2024-04-24 18:53:10 -04:00
// Installer package
if (extension.Equals("msi", StringComparison.OrdinalIgnoreCase))
return WrapperType.CFB;
2024-04-24 18:53:10 -04:00
// Merge module
else if (extension.Equals("msm", StringComparison.OrdinalIgnoreCase))
return WrapperType.CFB;
2024-04-24 18:53:10 -04:00
// Patch Package
else if (extension.Equals("msp", StringComparison.OrdinalIgnoreCase))
return WrapperType.CFB;
2024-04-24 18:53:10 -04:00
// Transform
else if (extension.Equals("mst", StringComparison.OrdinalIgnoreCase))
return WrapperType.CFB;
2024-04-24 18:53:10 -04:00
// Patch Creation Properties
else if (extension.Equals("pcp", StringComparison.OrdinalIgnoreCase))
return WrapperType.CFB;
2024-04-24 18:53:10 -04:00
#endregion
2024-10-31 12:09:36 -04:00
#region CHD
2025-09-26 13:06:18 -04:00
if (magic.StartsWith(Data.Models.CHD.Constants.SignatureBytes))
2024-10-31 12:09:36 -04:00
return WrapperType.CHD;
#endregion
2024-04-24 18:53:10 -04:00
#region CIA
if (extension.Equals("cia", StringComparison.OrdinalIgnoreCase))
return WrapperType.CIA;
2024-04-24 18:53:10 -04:00
#endregion
#region Executable
// DOS MZ executable file format (and descendants)
2025-09-26 13:06:18 -04:00
if (magic.StartsWith(Data.Models.MSDOS.Constants.SignatureBytes))
return WrapperType.Executable;
// None of the following are supported yet
2024-11-29 20:48:50 -05:00
/*
// Executable and Linkable Format
2024-11-29 20:48:50 -05:00
if (magic.StartsWith([0x7f, 0x45, 0x4c, 0x46]))
return WrapperType.Executable;
// Mach-O binary (32-bit)
2024-11-29 20:48:50 -05:00
if (magic.StartsWith([0xfe, 0xed, 0xfa, 0xce]))
return WrapperType.Executable;
// Mach-O binary (32-bit, reverse byte ordering scheme)
2024-11-29 20:48:50 -05:00
if (magic.StartsWith([0xce, 0xfa, 0xed, 0xfe]))
return WrapperType.Executable;
// Mach-O binary (64-bit)
2024-11-29 20:48:50 -05:00
if (magic.StartsWith([0xfe, 0xed, 0xfa, 0xcf]))
return WrapperType.Executable;
// Mach-O binary (64-bit, reverse byte ordering scheme)
2024-11-29 20:48:50 -05:00
if (magic.StartsWith([0xcf, 0xfa, 0xed, 0xfe]))
return WrapperType.Executable;
// Prefrred Executable File Format
2024-11-29 20:48:50 -05:00
if (magic.StartsWith([0x4a, 0x6f, 0x79, 0x21, 0x70, 0x65, 0x66, 0x66]))
return WrapperType.Executable;
*/
2024-04-24 18:53:10 -04:00
// DOS MZ executable file format (and descendants)
if (extension.Equals("exe", StringComparison.OrdinalIgnoreCase))
return WrapperType.Executable;
2024-04-24 18:53:10 -04:00
// DOS MZ library file format (and descendants)
if (extension.Equals("dll", StringComparison.OrdinalIgnoreCase))
return WrapperType.Executable;
2024-04-24 18:53:10 -04:00
#endregion
2026-03-10 17:27:58 -04:00
#region FDS
// fwNES FDS file with header
if (magic.StartsWith(Data.Models.NES.Constants.FDSSignatureBytes))
return WrapperType.FDS;
// fwNES FDS file with header
if (extension.Equals("fds", StringComparison.OrdinalIgnoreCase))
return WrapperType.FDS;
#endregion
2025-07-24 10:19:20 -04:00
// TODO: Use constants from Models here
2024-04-24 18:53:10 -04:00
#region GCF
2024-11-29 20:48:50 -05:00
if (magic.StartsWith([0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00]))
return WrapperType.GCF;
2024-04-24 18:53:10 -04:00
if (extension.Equals("gcf", StringComparison.OrdinalIgnoreCase))
return WrapperType.GCF;
2024-04-24 18:53:10 -04:00
#endregion
Dolphin lib (#85) * Add GCZ, WIA/RVZ, and NintendoDisc (GameCube/Wii) format support Port of DolphinIsoLib into SabreTools.Serialization architecture: - Data.Models: GCZ/, WIA/, NintendoDisc/ model subdirectories (15 files) - Serialization.Readers: GCZ, WIA, NintendoDisc readers - Serialization.Writers: GCZ, WIA writers (structural metadata; full round-trip TODO) - Wrappers: NintendoDisc, GCZ, WIA wrappers with Encryption partial class - Wrappers: WiaRvzCompressionHelper (BZip2/LZMA/LZMA2/Zstd, net462+ guarded) - WrapperType + WrapperFactory: GCZ, WIA, NintendoDisc entries added GetInnerWrapper() decompression and NintendoDisc.Extraction FST extraction are stubbed with TODO comments pending full implementation. * Implement GetInnerWrapper for GCZ and WIA, full NintendoDisc extraction * Add GCZ/WIA/RVZ write pipeline and Nintendo disc compression helpers * Add WIA/RVZ table decompression, NintendoDisc/GCZ printing, NintendoDisc detection for .iso files * Fix NintendoDisc header layout, GC magic, and add embedded disc header to WIA/GCZ printing - Fix GCMagicWord: 0xC23D3C1F -> 0xC2339F3D (confirmed from Dolphin DiscUtils.h) - Fix disc header field layout to match Dolphin's confirmed offsets: MakerCode is bytes 4-5 of the 6-char GameId (no separate field at 0x006), DiscNumber at 0x006, DiscVersion at 0x007, unused region is 14 bytes (0x00A-0x017) - Update NintendoDisc reader: derive MakerCode from GameId[4..5], fix skip count - Add ParseDiscHeaderOnly() to reader for partial (short) stream parsing - Guard DisableHash/DisableEnc reads at the 0x080 boundary for 128-byte embedded headers - Guard DOL/FST skip for streams shorter than full 0x440 boot block - Fix WrapperFactory: NintendoDisc magic detection now precedes .iso -> ISO9660 fallback - Add GameId-prefix heuristic in WrapperFactory for GC discs lacking magic word - Add GameId-prefix platform fallback in reader for GC discs without GCMagicWord - Add DiscHeader property to WIA wrapper (parsed from Header2.DiscHeader bytes) - Add DiscHeader property to GCZ wrapper (decompresses first block only) - Add ReadDiscHeader() helper to GCZ for lightweight first-block decompression - Print embedded disc header (Game ID, Maker, Disc/Rev, Title) in WIA.Printing.cs and GCZ.Printing.cs * Fix Wii partition extraction: correct IV, FST size shift, partition naming - Block decryption: IV is at raw block offset 0x3D0 (still-encrypted), matching Dolphin/DolphinIsoLib WiiPartitionDecryptor.DecryptBlock exactly. - FST size field at boot.bin 0x428 is also stored >>2 on Wii; apply <<2 to get true byte size. - Partition folder naming now matches DolphinIsoLib WiiDiscExtractor exactly: type 0->GM+n, 1->UP+n, 2->CH+n, printable ASCII unknown->raw 4-char string, non-printable->P{index}. SSBB VC channels extract as HA8E, HA9E, etc. - ExtractionTool peek buffer increased from 16 to 32 bytes. Verified: SSBB GM0 extracts 5524 files, boot.bin/fst.bin byte-identical to Dolphin reference extraction. * Fix FST extraction: create zero-byte files instead of skipping them Files with fileSize=0 in the FST were silently skipped. Now they are created as empty files, matching Dolphin/DolphinIsoLib behavior. Verified: SSBB now extracts 5958 files with 0 missing, 0 extra, 0 size mismatches, and 0 hash mismatches vs DolphinIsoLib reference. * Add GCZ/WIA/RVZ virtual stream extraction via NintendoDisc wrapper * Address PR #85 review comments (Copilot + mnadareski) * Address PR #85 review comments * Replace custom endian helpers and SHA1 with SabreTools.IO equivalents * Update GCZ.Printing.cs * Update NintendoDisc.Printing.cs * Update WIA.Printing.cs * Add WIA/RVZ Wii partition crypto round-trip support - Add AesCbc internal helper (BouncyCastle AES-CBC encrypt/decrypt) - Add NintendoDisc.CommonKeyProvider hook for injectable test keys - Fix sha1.Terminate() missing in all three ComputeSha1 helpers in WIA.cs - Fix Wii partition dataOff alignment to 0x8000 boundary - Add WIA.EncryptWiiGroup (internal) for re-encrypting plaintext groups - Add WIA.DumpIso to WIA.Writing.cs (WIA/RVZ -> flat ISO conversion) - Add WiaVirtualStream on-demand group decompression - Add _preDecryptedReader bypass on NintendoDisc for WIA extraction path - Add WIATests.cs with Wii crypto round-trip test using synthetic data - Move DumpIso from WIA.Extraction.cs to WIA.Writing.cs - Bump DumpIso read buffer from 1 MiB to 2 MiB (aligns to WIA chunk size) - Add InternalsVisibleTo SabreTools.Wrappers.Test in csproj * Remove hardcoded Wii common keys from NintendoDisc.Encryption - Delete the embedded WiiCommonKeyRetail and WiiCommonKeyKorean byte arrays from NintendoDisc.Encryption.cs. - Make CommonKeyProvider public so any caller (not just tests) can inject keys; DecryptTitleKey now returns null when no key is available for the requested index rather than falling back to hardcoded values. - Add NintendoDiscEncryptionTests.cs: - Argument guard and no-provider tests for DecryptTitleKey. - Fake-key round-trip test (encrypt then decrypt with injected key). - Integration test that reads TestData/NintendoDisc/keys.json, verifies each key against hardcoded SHA256 constants, and skips silently if the file is absent or the keys do not match. - LoadKeyProvider helper (named JSON format, index-keyed). - Add [Collection(NintendoDisc)] to both NintendoDiscEncryptionTests and WIATests to prevent parallel access to the static CommonKeyProvider from racing between test classes. - Add TestData/NintendoDisc/keys.json.example documenting the expected key file format. - Add Newtonsoft.Json reference to SabreTools.Wrappers.Test.csproj. * Didn't actually commit the changes. My bad. Fixed. * Edited a comment * Added in XUnit outputs that show up in Test Viewer in VS --------- Co-authored-by: Matt Nadareski <mnadareski@outlook.com>
2026-05-12 09:41:32 -05:00
#region GCZ
// GCZ magic cookie (0xB10BC001 stored little-endian)
if (magic.StartsWith([0x01, 0xC0, 0x0B, 0xB1]))
return WrapperType.GCZ;
if (extension.Equals("gcz", StringComparison.OrdinalIgnoreCase))
return WrapperType.GCZ;
#endregion
#region GZip
2024-04-24 18:53:10 -04:00
2025-09-29 21:39:37 -04:00
if (magic.StartsWith(Data.Models.GZIP.Constants.SignatureBytes))
return WrapperType.GZip;
2024-04-24 18:53:10 -04:00
if (extension.Equals("gz", StringComparison.OrdinalIgnoreCase))
return WrapperType.GZip;
2024-04-24 18:53:10 -04:00
#endregion
#region IniFile
if (extension.Equals("ini", StringComparison.OrdinalIgnoreCase))
return WrapperType.IniFile;
2024-04-24 18:53:10 -04:00
#endregion
#region InstallShieldArchiveV3
2025-09-26 13:06:18 -04:00
if (magic.StartsWith(Data.Models.InstallShieldArchiveV3.Constants.HeaderSignatureBytes))
return WrapperType.InstallShieldArchiveV3;
2024-04-24 18:53:10 -04:00
if (extension.Equals("z", StringComparison.OrdinalIgnoreCase))
return WrapperType.InstallShieldArchiveV3;
2024-04-24 18:53:10 -04:00
#endregion
#region InstallShieldCAB
2025-09-26 13:06:18 -04:00
if (magic.StartsWith(Data.Models.InstallShieldCabinet.Constants.SignatureBytes))
return WrapperType.InstallShieldCAB;
2024-04-24 18:53:10 -04:00
// Both InstallShieldCAB and MicrosoftCAB share the same extension
#endregion
ISO 9660 support (#29) * Begin defining ISO9660 models * Fix initial models * Improve ISO9660 models * ISO9660 models * Fix fields * Nullable and enums * Fix flags attribute * Start ISO9660 Reader code * semicolon * Fix build * Use EqualsExactly * Fix build * Update ISO9660.cs * Update ISO9660.cs * Move DirectoryRecordDateTime class * ParseDirectoryRecordDateTime function * ST refactoring * fix return * return array * ISO9660 wrapper * Initial printing code * semicolon * Fix * orphan variable name * fix null ref * ISO9660 Printer * Fix DirectoryRecord parsing * fix * test * test * Fix reader * extractable ISO * partial wrapper class * namespace * extension property * path tables * Cleanup * rename directory * fix * fix * typo * fix2 * Fix * Cleanup reader * Count list property * Parse Path Tables * fix model name * fix * fix2 * fix3 * print path table records * debug errors * debug error * debug * undo debug * Fix casting * Validate path table locations * print table numbers * Printer cleanup * fix printer * printer++ * Print DecDateTime * else if * BootSystemUse * Use Linq * custom zero check * fix * Update ISO9660.cs * Update ISO9660.cs * Update ISO9660.cs * Update ISO9660.cs * fix * debug * debug2 * debug3 * debug4 * debug5 * debug6 * debug7 * debug8 * Array.TrueForAll * Update ISO9660.cs * fix * cleanup * typo * Use BothInt numerics * using SabreTools.Numerics * Test nonnull * nonnull bothint * Print invalid BothInts * Print directory record * typo * fix * null check * directory descriptors * cleanup printer * semicolon * Fix * flags never null * more non nullable * no null * AppendLineBothEndian * fix * rename to Directory * fix * namespace * full namespace * fix * cleanup * Parse directories * fix * Fix * Test parse directories * PeekByteValue * Revert CI changes * Dummy extractor * Review and fixes * Fixes * Final fix * big endian directory search * fix big endian changes * Final fix
2025-10-29 21:46:08 +09:00
#region IRD
if (magic.StartsWith([0x33, 0x49, 0x52, 0x44]))
return WrapperType.IRD;
if (extension.Equals("ird", StringComparison.OrdinalIgnoreCase))
return WrapperType.IRD;
#endregion
#region ISO9660
if (extension.Equals("iso", StringComparison.OrdinalIgnoreCase))
return WrapperType.ISO9660;
#endregion
#region LDSCRYPT
2025-09-26 13:06:18 -04:00
if (magic.StartsWith(Data.Models.LDSCRYPT.Constants.SignatureBytes))
return WrapperType.LDSCRYPT;
#endregion
2024-12-11 13:31:54 -05:00
#region LZ
2024-04-24 18:53:10 -04:00
2025-09-26 13:06:18 -04:00
if (magic.StartsWith(Data.Models.LZ.Constants.KWAJSignatureBytes))
2024-12-11 13:31:54 -05:00
return WrapperType.LZKWAJ;
2025-09-26 13:06:18 -04:00
if (magic.StartsWith(Data.Models.LZ.Constants.QBasicSignatureBytes))
2024-12-11 13:31:54 -05:00
return WrapperType.LZQBasic;
2025-09-26 13:06:18 -04:00
if (magic.StartsWith(Data.Models.LZ.Constants.SZDDSignatureBytes))
2024-12-11 13:31:54 -05:00
return WrapperType.LZSZDD;
2024-04-24 18:53:10 -04:00
#endregion
2024-12-11 13:31:54 -05:00
#region MicrosoftCAB
2025-09-26 13:06:18 -04:00
if (magic.StartsWith(Data.Models.MicrosoftCabinet.Constants.SignatureBytes))
2024-12-11 13:31:54 -05:00
return WrapperType.MicrosoftCAB;
// Both InstallShieldCAB and MicrosoftCAB share the same extension
#endregion
#region MoPaQ
2025-09-26 13:06:18 -04:00
if (magic.StartsWith(Data.Models.MoPaQ.Constants.ArchiveHeaderSignatureBytes))
return WrapperType.MoPaQ;
2025-09-26 13:06:18 -04:00
if (magic.StartsWith(Data.Models.MoPaQ.Constants.UserDataSignatureBytes))
return WrapperType.MoPaQ;
2024-04-24 18:53:10 -04:00
if (extension.Equals("mpq", StringComparison.OrdinalIgnoreCase))
return WrapperType.MoPaQ;
2024-04-24 18:53:10 -04:00
#endregion
#region N3DS
// 3DS cart image
if (extension.Equals("3ds", StringComparison.OrdinalIgnoreCase))
return WrapperType.N3DS;
2024-04-24 18:53:10 -04:00
2025-07-24 10:19:20 -04:00
else if (extension.Equals("cci", StringComparison.OrdinalIgnoreCase))
return WrapperType.N3DS;
2024-04-24 18:53:10 -04:00
// CIA package -- Not currently supported
// else if (extension.Equals("cia", StringComparison.OrdinalIgnoreCase))
// return WrapperType.N3DS;
2024-04-24 18:53:10 -04:00
#endregion
2025-07-24 10:19:20 -04:00
// TODO: Use constants from Models here
2024-04-24 18:53:10 -04:00
#region NCF
2024-11-29 20:48:50 -05:00
if (magic.StartsWith([0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00]))
return WrapperType.NCF;
2024-04-24 18:53:10 -04:00
if (extension.Equals("ncf", StringComparison.OrdinalIgnoreCase))
return WrapperType.NCF;
2024-04-24 18:53:10 -04:00
#endregion
2026-03-06 11:11:26 -05:00
#region NESCart
// NES 1.0 and 2.0 image with header
2026-03-10 17:06:01 -04:00
if (magic.StartsWith(Data.Models.NES.Constants.CartSignatureBytes))
2026-03-06 11:11:26 -05:00
return WrapperType.NESCart;
// NES 1.0 and 2.0 image
if (extension.Equals("nes", StringComparison.OrdinalIgnoreCase))
return WrapperType.NESCart;
#endregion
2026-05-14 20:08:32 -04:00
#region NintendoDisc
// Wii disc magic at offset 0x018 (0x5D1C9EA3 stored big-endian on disc)
if (magic.Length > 0x1B && magic[0x18] == 0x5D && magic[0x19] == 0x1C && magic[0x1A] == 0x9E && magic[0x1B] == 0xA3)
return WrapperType.NintendoDisc;
// GameCube disc magic at offset 0x01C (0xC2339F3D stored big-endian on disc)
if (magic.Length > 0x1F && magic[0x1C] == 0xC2 && magic[0x1D] == 0x33 && magic[0x1E] == 0x9F && magic[0x1F] == 0x3D)
return WrapperType.NintendoDisc;
// GameCube/Wii disc by GameId prefix: first byte is a known title type code,
// bytes 1-2 are ASCII letters (region + developer), bytes 3-4 are ASCII digits or letters (title code),
// byte 5 is an ASCII digit (disc number). Covers redump ISOs that lack magic words.
if (magic.Length > 5
&& magic[0].IsNintendoDiscTitleType()
&& magic[1] >= 'A' && magic[1] <= 'Z'
&& ((magic[2] >= '0' && magic[2] <= '9') || (magic[2] >= 'A' && magic[2] <= 'Z'))
&& ((magic[3] >= '0' && magic[3] <= '9') || (magic[3] >= 'A' && magic[3] <= 'Z'))
&& ((magic[4] >= '0' && magic[4] <= '9') || (magic[4] >= 'A' && magic[4] <= 'Z'))
&& magic[5] >= '0' && magic[5] <= '9'
&& (extension.Equals("iso", StringComparison.OrdinalIgnoreCase)
|| extension.Equals("gcm", StringComparison.OrdinalIgnoreCase)))
{
return WrapperType.NintendoDisc;
}
// .gcm files are always GameCube disc images
if (extension.Equals("gcm", StringComparison.OrdinalIgnoreCase))
return WrapperType.NintendoDisc;
#endregion
2024-04-24 18:53:10 -04:00
#region Nitro
// DS cart image
if (extension.Equals("nds", StringComparison.OrdinalIgnoreCase))
return WrapperType.Nitro;
2024-04-24 18:53:10 -04:00
// DS development cart image
else if (extension.Equals("srl", StringComparison.OrdinalIgnoreCase))
return WrapperType.Nitro;
2024-04-24 18:53:10 -04:00
// DSi cart image
else if (extension.Equals("dsi", StringComparison.OrdinalIgnoreCase))
return WrapperType.Nitro;
2024-04-24 18:53:10 -04:00
// iQue DS cart image
else if (extension.Equals("ids", StringComparison.OrdinalIgnoreCase))
return WrapperType.Nitro;
2024-04-24 18:53:10 -04:00
#endregion
#region PAK
2025-09-26 13:06:18 -04:00
if (magic.StartsWith(Data.Models.PAK.Constants.SignatureBytes))
return WrapperType.PAK;
2024-04-24 18:53:10 -04:00
// Both PAK and Quantum share one extension
// if (extension.Equals("pak", StringComparison.OrdinalIgnoreCase))
// return WrapperType.PAK;
2024-04-24 18:53:10 -04:00
#endregion
2025-07-24 10:19:20 -04:00
// TODO: Use constants from Models here
2024-04-24 18:53:10 -04:00
#region PFF
2025-09-22 09:35:46 -04:00
// Version 0
if (magic.StartsWith([0x14, 0x00, 0x00, 0x00, 0x50, 0x46, 0x46, 0x30]))
return WrapperType.PFF;
// Version 2
2024-11-29 20:48:50 -05:00
if (magic.StartsWith([0x14, 0x00, 0x00, 0x00, 0x50, 0x46, 0x46, 0x32]))
return WrapperType.PFF;
// Version 3
2024-11-29 20:48:50 -05:00
if (magic.StartsWith([0x14, 0x00, 0x00, 0x00, 0x50, 0x46, 0x46, 0x33]))
return WrapperType.PFF;
// Version 4
2024-11-29 20:48:50 -05:00
if (magic.StartsWith([0x14, 0x00, 0x00, 0x00, 0x50, 0x46, 0x46, 0x34]))
return WrapperType.PFF;
2024-04-24 18:53:10 -04:00
if (extension.Equals("pff", StringComparison.OrdinalIgnoreCase))
return WrapperType.PFF;
2024-04-24 18:53:10 -04:00
#endregion
2025-07-24 10:19:20 -04:00
// TODO: Use constants from Models here
2024-04-24 18:53:10 -04:00
#region PKZIP
// PKZIP (Unknown)
2024-11-29 20:48:50 -05:00
if (magic.StartsWith([0x50, 0x4b, 0x00, 0x00]))
return WrapperType.PKZIP;
// PKZIP
2025-09-26 13:06:18 -04:00
if (magic.StartsWith(Data.Models.PKZIP.Constants.LocalFileHeaderSignatureBytes))
return WrapperType.PKZIP;
// PKZIP (Empty Archive)
2025-09-26 13:06:18 -04:00
if (magic.StartsWith(Data.Models.PKZIP.Constants.EndOfCentralDirectoryRecordSignatureBytes))
2025-07-24 10:19:20 -04:00
return WrapperType.PKZIP;
2025-09-26 13:06:18 -04:00
else if (magic.StartsWith(Data.Models.PKZIP.Constants.EndOfCentralDirectoryRecord64SignatureBytes))
return WrapperType.PKZIP;
// PKZIP (Spanned Archive)
2025-09-26 13:06:18 -04:00
if (magic.StartsWith(Data.Models.PKZIP.Constants.DataDescriptorSignatureBytes))
return WrapperType.PKZIP;
2024-04-24 18:53:10 -04:00
// PKZIP
if (extension.Equals("zip", StringComparison.OrdinalIgnoreCase))
return WrapperType.PKZIP;
2024-04-24 18:53:10 -04:00
// Android package
if (extension.Equals("apk", StringComparison.OrdinalIgnoreCase))
return WrapperType.PKZIP;
2024-04-24 18:53:10 -04:00
// Java archive
if (extension.Equals("jar", StringComparison.OrdinalIgnoreCase))
return WrapperType.PKZIP;
2024-04-24 18:53:10 -04:00
// Google Earth saved working session file
if (extension.Equals("kmz", StringComparison.OrdinalIgnoreCase))
return WrapperType.PKZIP;
2024-04-24 18:53:10 -04:00
// KWord document
if (extension.Equals("kwd", StringComparison.OrdinalIgnoreCase))
return WrapperType.PKZIP;
2024-04-24 18:53:10 -04:00
// Microsoft Office Open XML Format (OOXML) Document
if (extension.Equals("docx", StringComparison.OrdinalIgnoreCase))
return WrapperType.PKZIP;
2024-04-24 18:53:10 -04:00
// Microsoft Office Open XML Format (OOXML) Presentation
if (extension.Equals("pptx", StringComparison.OrdinalIgnoreCase))
return WrapperType.PKZIP;
2024-04-24 18:53:10 -04:00
// Microsoft Office Open XML Format (OOXML) Spreadsheet
if (extension.Equals("xlsx", StringComparison.OrdinalIgnoreCase))
return WrapperType.PKZIP;
2024-04-24 18:53:10 -04:00
// OpenDocument text document
if (extension.Equals("odt", StringComparison.OrdinalIgnoreCase))
return WrapperType.PKZIP;
2024-04-24 18:53:10 -04:00
// OpenDocument presentation
if (extension.Equals("odp", StringComparison.OrdinalIgnoreCase))
return WrapperType.PKZIP;
2024-04-24 18:53:10 -04:00
// OpenDocument text document template
if (extension.Equals("ott", StringComparison.OrdinalIgnoreCase))
return WrapperType.PKZIP;
2024-04-24 18:53:10 -04:00
// Microsoft Open XML paper specification file
if (extension.Equals("oxps", StringComparison.OrdinalIgnoreCase))
return WrapperType.PKZIP;
2024-04-24 18:53:10 -04:00
// OpenOffice spreadsheet
if (extension.Equals("sxc", StringComparison.OrdinalIgnoreCase))
return WrapperType.PKZIP;
2024-04-24 18:53:10 -04:00
// OpenOffice drawing
if (extension.Equals("sxd", StringComparison.OrdinalIgnoreCase))
return WrapperType.PKZIP;
2024-04-24 18:53:10 -04:00
// OpenOffice presentation
if (extension.Equals("sxi", StringComparison.OrdinalIgnoreCase))
return WrapperType.PKZIP;
2024-04-24 18:53:10 -04:00
// OpenOffice word processing
if (extension.Equals("sxw", StringComparison.OrdinalIgnoreCase))
return WrapperType.PKZIP;
2024-04-24 18:53:10 -04:00
// StarOffice spreadsheet
if (extension.Equals("sxc", StringComparison.OrdinalIgnoreCase))
return WrapperType.PKZIP;
2024-04-24 18:53:10 -04:00
// Windows Media compressed skin file
if (extension.Equals("wmz", StringComparison.OrdinalIgnoreCase))
return WrapperType.PKZIP;
2024-04-24 18:53:10 -04:00
// Mozilla Browser Archive
if (extension.Equals("xpi", StringComparison.OrdinalIgnoreCase))
return WrapperType.PKZIP;
2024-04-24 18:53:10 -04:00
// XML paper specification file
if (extension.Equals("xps", StringComparison.OrdinalIgnoreCase))
return WrapperType.PKZIP;
2024-04-24 18:53:10 -04:00
// eXact Packager Models
if (extension.Equals("xpt", StringComparison.OrdinalIgnoreCase))
return WrapperType.PKZIP;
2024-04-24 18:53:10 -04:00
#endregion
#region PLJ
// https://www.iana.org/assignments/media-types/audio/vnd.everad.plj
2025-09-26 13:06:18 -04:00
if (magic.StartsWith(Data.Models.PlayJ.Constants.SignatureBytes))
return WrapperType.PlayJAudioFile;
2024-04-24 18:53:10 -04:00
// https://www.iana.org/assignments/media-types/audio/vnd.everad.plj
if (extension.Equals("plj", StringComparison.OrdinalIgnoreCase))
return WrapperType.PlayJAudioFile;
2024-04-24 18:53:10 -04:00
#endregion
2026-03-10 18:11:42 -04:00
#region QD
if (extension.Equals("qd", StringComparison.OrdinalIgnoreCase))
return WrapperType.QD;
#endregion
2024-04-24 18:53:10 -04:00
#region Quantum
2025-09-26 13:06:18 -04:00
if (magic.StartsWith(Data.Models.Quantum.Constants.SignatureBytes))
return WrapperType.Quantum;
2024-04-24 18:53:10 -04:00
if (extension.Equals("q", StringComparison.OrdinalIgnoreCase))
return WrapperType.Quantum;
2024-04-24 18:53:10 -04:00
// Both PAK and Quantum share one extension
// if (extension.Equals("pak", StringComparison.OrdinalIgnoreCase))
// return WrapperType.Quantum;
2024-04-24 18:53:10 -04:00
#endregion
#region RAR
// RAR archive version 1.50 onwards
2025-09-26 13:06:18 -04:00
if (magic.StartsWith(Data.Models.RAR.Constants.OldSignatureBytes))
return WrapperType.RAR;
// RAR archive version 5.0 onwards
2025-09-26 13:06:18 -04:00
if (magic.StartsWith(Data.Models.RAR.Constants.NewSignatureBytes))
return WrapperType.RAR;
2024-04-24 18:53:10 -04:00
if (extension.Equals("rar", StringComparison.OrdinalIgnoreCase))
return WrapperType.RAR;
2024-04-24 18:53:10 -04:00
#endregion
#region RealArcade
// RASGI2.0
// Found in the ".rgs files in IA item "Nova_RealArcadeCD_USA".
2025-09-26 13:06:18 -04:00
if (magic.StartsWith(Data.Models.RealArcade.Constants.RgsSignatureBytes))
return WrapperType.RealArcadeInstaller;
// XZip2.0
// Found in the ".mez" files in IA item "Nova_RealArcadeCD_USA".
2025-09-26 13:06:18 -04:00
if (magic.StartsWith(Data.Models.RealArcade.Constants.MezzanineSignatureBytes))
return WrapperType.RealArcadeMezzanine;
#endregion
2025-07-24 10:40:31 -04:00
#region SecuROM DFA
2025-09-26 13:06:18 -04:00
if (magic.StartsWith(Data.Models.SecuROM.Constants.DFAMagicBytes))
2025-07-24 10:40:31 -04:00
return WrapperType.SecuROMDFA;
#endregion
2024-04-24 18:53:10 -04:00
#region SevenZip
2025-09-26 13:06:18 -04:00
if (magic.StartsWith(Data.Models.SevenZip.Constants.SignatureBytes))
return WrapperType.SevenZip;
2024-04-24 18:53:10 -04:00
if (extension.Equals("7z", StringComparison.OrdinalIgnoreCase))
return WrapperType.SevenZip;
2024-04-24 18:53:10 -04:00
#endregion
#region SFFS
// Found in Redump entry 81756, confirmed to be "StarForce Filesystem" by PiD.
2025-09-26 13:06:18 -04:00
if (magic.StartsWith(Data.Models.StarForce.Constants.SignatureBytes))
return WrapperType.SFFS;
2025-10-27 22:43:56 -04:00
#endregion
#region SkuSis
if (magic.StartsWith(Data.Models.VDF.Constants.SteamSimSidSisSignatureBytes))
return WrapperType.SkuSis;
if (magic.StartsWith(Data.Models.VDF.Constants.SteamCsmCsdSisSignatureBytes))
return WrapperType.SkuSis;
#endregion
2024-04-24 18:53:10 -04:00
#region SGA
2025-09-26 13:06:18 -04:00
if (magic.StartsWith(Data.Models.SGA.Constants.SignatureBytes))
return WrapperType.SGA;
2024-04-24 18:53:10 -04:00
if (extension.Equals("sga", StringComparison.OrdinalIgnoreCase))
return WrapperType.SGA;
2024-04-24 18:53:10 -04:00
#endregion
#region Skeleton
if (extension.Equals("skeleton", StringComparison.OrdinalIgnoreCase))
return WrapperType.Skeleton;
#endregion
#region Steam2Installer
if (magic.StartsWith(Data.Models.Steam2Installer.Constants.SimSignatureBytes))
return WrapperType.Steam2Installer;
#endregion
#region STFS
// LIVE
if (magic.StartsWith(Data.Models.STFS.Constants.MagicBytesLIVE))
return WrapperType.STFS;
// PIRS
if (magic.StartsWith(Data.Models.STFS.Constants.MagicBytesPIRS))
return WrapperType.STFS;
// "CON "
if (magic.StartsWith(Data.Models.STFS.Constants.MagicBytesCON))
return WrapperType.STFS;
#endregion
2025-07-24 10:19:20 -04:00
// TODO: Use constants from Models here
2024-04-24 18:53:10 -04:00
#region TapeArchive
2024-11-29 20:48:50 -05:00
if (magic.StartsWith([0x75, 0x73, 0x74, 0x61, 0x72, 0x00, 0x30, 0x30]))
return WrapperType.TapeArchive;
2024-11-29 20:48:50 -05:00
if (magic.StartsWith([0x75, 0x73, 0x74, 0x61, 0x72, 0x20, 0x20, 0x00]))
return WrapperType.TapeArchive;
2024-04-24 18:53:10 -04:00
if (extension.Equals("tar", StringComparison.OrdinalIgnoreCase))
2025-08-28 09:11:04 -04:00
return WrapperType.TapeArchive;
2024-04-24 18:53:10 -04:00
#endregion
2025-07-24 10:19:20 -04:00
// TODO: Use constants from Models here
2024-04-24 18:53:10 -04:00
#region Textfile
// Not all textfiles can be determined through magic number
// HTML
2024-11-29 20:48:50 -05:00
if (magic.StartsWith([0x3c, 0x68, 0x74, 0x6d, 0x6c]))
return WrapperType.Textfile;
// HTML and XML
2024-11-29 20:48:50 -05:00
if (magic.StartsWith([0x3c, 0x21, 0x44, 0x4f, 0x43, 0x54, 0x59, 0x50, 0x45]))
return WrapperType.Textfile;
// InstallShield Compiled Rules
2024-11-29 20:48:50 -05:00
if (magic.StartsWith([0x61, 0x4C, 0x75, 0x5A]))
return WrapperType.Textfile;
// Microsoft Office File (old)
2024-11-29 20:48:50 -05:00
if (magic.StartsWith([0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1]))
return WrapperType.Textfile;
// Rich Text File
2024-11-29 20:48:50 -05:00
if (magic.StartsWith([0x7b, 0x5c, 0x72, 0x74, 0x66, 0x31]))
return WrapperType.Textfile;
// Windows Help File
2024-11-29 20:48:50 -05:00
if (magic.StartsWith([0x3F, 0x5F, 0x03, 0x00]))
return WrapperType.Textfile;
2025-10-27 22:43:56 -04:00
// XML
// "<?xml"
2024-11-29 20:48:50 -05:00
if (magic.StartsWith([0x3C, 0x3F, 0x78, 0x6D, 0x6C]))
return WrapperType.Textfile;
// "<?xml" in UTF-16 encoding
if (magic.StartsWith([0x3C, 0x00, 0x3F, 0x00, 0x78, 0x00, 0x6D, 0x00, 0x6C, 0x00]))
return WrapperType.Textfile;
// "<?xml" in UTF-16 encoding with byte order marks
if (magic.StartsWith([0xFF, 0xFE, 0x3C, 0x00, 0x3F, 0x00, 0x78, 0x00, 0x6D, 0x00, 0x6C, 0x00]))
return WrapperType.Textfile;
2024-04-24 18:53:10 -04:00
// "Description in Zip"
if (extension.Equals("diz", StringComparison.OrdinalIgnoreCase))
return WrapperType.Textfile;
2024-04-24 18:53:10 -04:00
// Generic textfile (no header)
if (extension.Equals("txt", StringComparison.OrdinalIgnoreCase))
return WrapperType.Textfile;
2024-04-24 18:53:10 -04:00
// HTML
if (extension.Equals("htm", StringComparison.OrdinalIgnoreCase))
return WrapperType.Textfile;
2024-04-24 18:53:10 -04:00
if (extension.Equals("html", StringComparison.OrdinalIgnoreCase))
return WrapperType.Textfile;
2024-04-24 18:53:10 -04:00
// InstallShield Script
if (extension.Equals("ins", StringComparison.OrdinalIgnoreCase))
return WrapperType.Textfile;
2024-04-24 18:53:10 -04:00
// Microsoft Office File (old)
if (extension.Equals("doc", StringComparison.OrdinalIgnoreCase))
return WrapperType.Textfile;
2024-04-24 18:53:10 -04:00
// Property list
if (extension.Equals("plist", StringComparison.OrdinalIgnoreCase))
return WrapperType.Textfile;
2024-04-24 18:53:10 -04:00
// Rich Text File
if (extension.Equals("rtf", StringComparison.OrdinalIgnoreCase))
return WrapperType.Textfile;
2024-04-24 18:53:10 -04:00
// Setup information
if (extension.Equals("inf", StringComparison.OrdinalIgnoreCase))
return WrapperType.Textfile;
2024-04-24 18:53:10 -04:00
// Windows Help File
if (extension.Equals("hlp", StringComparison.OrdinalIgnoreCase))
return WrapperType.Textfile;
2024-04-24 18:53:10 -04:00
// WZC
if (extension.Equals("wzc", StringComparison.OrdinalIgnoreCase))
return WrapperType.Textfile;
2024-04-24 18:53:10 -04:00
// XML
if (extension.Equals("xml", StringComparison.OrdinalIgnoreCase))
return WrapperType.Textfile;
2024-04-24 18:53:10 -04:00
#endregion
#region VBSP
2025-09-26 13:06:18 -04:00
if (magic.StartsWith(Data.Models.BSP.Constants.SignatureBytes))
return WrapperType.VBSP;
// Shares an extension with BSP
if (extension.Equals("bsp", StringComparison.OrdinalIgnoreCase))
return WrapperType.VBSP;
2024-04-24 18:53:10 -04:00
#endregion
#region VPK
2025-09-26 13:06:18 -04:00
if (magic.StartsWith(Data.Models.VPK.Constants.SignatureBytes))
return WrapperType.VPK;
2024-04-24 18:53:10 -04:00
// Common extension so this cannot be used accurately
// if (extension.Equals("vpk", StringComparison.OrdinalIgnoreCase))
// return WrapperType.VPK;
2024-04-24 18:53:10 -04:00
#endregion
#region WAD
2025-09-26 13:06:18 -04:00
if (magic.StartsWith(Data.Models.WAD3.Constants.SignatureBytes))
return WrapperType.WAD;
2024-04-24 18:53:10 -04:00
// Common extension so this cannot be used accurately
// if (extension.Equals("wad", StringComparison.OrdinalIgnoreCase))
// return WrapperType.WAD;
2024-04-24 18:53:10 -04:00
#endregion
Dolphin lib (#85) * Add GCZ, WIA/RVZ, and NintendoDisc (GameCube/Wii) format support Port of DolphinIsoLib into SabreTools.Serialization architecture: - Data.Models: GCZ/, WIA/, NintendoDisc/ model subdirectories (15 files) - Serialization.Readers: GCZ, WIA, NintendoDisc readers - Serialization.Writers: GCZ, WIA writers (structural metadata; full round-trip TODO) - Wrappers: NintendoDisc, GCZ, WIA wrappers with Encryption partial class - Wrappers: WiaRvzCompressionHelper (BZip2/LZMA/LZMA2/Zstd, net462+ guarded) - WrapperType + WrapperFactory: GCZ, WIA, NintendoDisc entries added GetInnerWrapper() decompression and NintendoDisc.Extraction FST extraction are stubbed with TODO comments pending full implementation. * Implement GetInnerWrapper for GCZ and WIA, full NintendoDisc extraction * Add GCZ/WIA/RVZ write pipeline and Nintendo disc compression helpers * Add WIA/RVZ table decompression, NintendoDisc/GCZ printing, NintendoDisc detection for .iso files * Fix NintendoDisc header layout, GC magic, and add embedded disc header to WIA/GCZ printing - Fix GCMagicWord: 0xC23D3C1F -> 0xC2339F3D (confirmed from Dolphin DiscUtils.h) - Fix disc header field layout to match Dolphin's confirmed offsets: MakerCode is bytes 4-5 of the 6-char GameId (no separate field at 0x006), DiscNumber at 0x006, DiscVersion at 0x007, unused region is 14 bytes (0x00A-0x017) - Update NintendoDisc reader: derive MakerCode from GameId[4..5], fix skip count - Add ParseDiscHeaderOnly() to reader for partial (short) stream parsing - Guard DisableHash/DisableEnc reads at the 0x080 boundary for 128-byte embedded headers - Guard DOL/FST skip for streams shorter than full 0x440 boot block - Fix WrapperFactory: NintendoDisc magic detection now precedes .iso -> ISO9660 fallback - Add GameId-prefix heuristic in WrapperFactory for GC discs lacking magic word - Add GameId-prefix platform fallback in reader for GC discs without GCMagicWord - Add DiscHeader property to WIA wrapper (parsed from Header2.DiscHeader bytes) - Add DiscHeader property to GCZ wrapper (decompresses first block only) - Add ReadDiscHeader() helper to GCZ for lightweight first-block decompression - Print embedded disc header (Game ID, Maker, Disc/Rev, Title) in WIA.Printing.cs and GCZ.Printing.cs * Fix Wii partition extraction: correct IV, FST size shift, partition naming - Block decryption: IV is at raw block offset 0x3D0 (still-encrypted), matching Dolphin/DolphinIsoLib WiiPartitionDecryptor.DecryptBlock exactly. - FST size field at boot.bin 0x428 is also stored >>2 on Wii; apply <<2 to get true byte size. - Partition folder naming now matches DolphinIsoLib WiiDiscExtractor exactly: type 0->GM+n, 1->UP+n, 2->CH+n, printable ASCII unknown->raw 4-char string, non-printable->P{index}. SSBB VC channels extract as HA8E, HA9E, etc. - ExtractionTool peek buffer increased from 16 to 32 bytes. Verified: SSBB GM0 extracts 5524 files, boot.bin/fst.bin byte-identical to Dolphin reference extraction. * Fix FST extraction: create zero-byte files instead of skipping them Files with fileSize=0 in the FST were silently skipped. Now they are created as empty files, matching Dolphin/DolphinIsoLib behavior. Verified: SSBB now extracts 5958 files with 0 missing, 0 extra, 0 size mismatches, and 0 hash mismatches vs DolphinIsoLib reference. * Add GCZ/WIA/RVZ virtual stream extraction via NintendoDisc wrapper * Address PR #85 review comments (Copilot + mnadareski) * Address PR #85 review comments * Replace custom endian helpers and SHA1 with SabreTools.IO equivalents * Update GCZ.Printing.cs * Update NintendoDisc.Printing.cs * Update WIA.Printing.cs * Add WIA/RVZ Wii partition crypto round-trip support - Add AesCbc internal helper (BouncyCastle AES-CBC encrypt/decrypt) - Add NintendoDisc.CommonKeyProvider hook for injectable test keys - Fix sha1.Terminate() missing in all three ComputeSha1 helpers in WIA.cs - Fix Wii partition dataOff alignment to 0x8000 boundary - Add WIA.EncryptWiiGroup (internal) for re-encrypting plaintext groups - Add WIA.DumpIso to WIA.Writing.cs (WIA/RVZ -> flat ISO conversion) - Add WiaVirtualStream on-demand group decompression - Add _preDecryptedReader bypass on NintendoDisc for WIA extraction path - Add WIATests.cs with Wii crypto round-trip test using synthetic data - Move DumpIso from WIA.Extraction.cs to WIA.Writing.cs - Bump DumpIso read buffer from 1 MiB to 2 MiB (aligns to WIA chunk size) - Add InternalsVisibleTo SabreTools.Wrappers.Test in csproj * Remove hardcoded Wii common keys from NintendoDisc.Encryption - Delete the embedded WiiCommonKeyRetail and WiiCommonKeyKorean byte arrays from NintendoDisc.Encryption.cs. - Make CommonKeyProvider public so any caller (not just tests) can inject keys; DecryptTitleKey now returns null when no key is available for the requested index rather than falling back to hardcoded values. - Add NintendoDiscEncryptionTests.cs: - Argument guard and no-provider tests for DecryptTitleKey. - Fake-key round-trip test (encrypt then decrypt with injected key). - Integration test that reads TestData/NintendoDisc/keys.json, verifies each key against hardcoded SHA256 constants, and skips silently if the file is absent or the keys do not match. - LoadKeyProvider helper (named JSON format, index-keyed). - Add [Collection(NintendoDisc)] to both NintendoDiscEncryptionTests and WIATests to prevent parallel access to the static CommonKeyProvider from racing between test classes. - Add TestData/NintendoDisc/keys.json.example documenting the expected key file format. - Add Newtonsoft.Json reference to SabreTools.Wrappers.Test.csproj. * Didn't actually commit the changes. My bad. Fixed. * Edited a comment * Added in XUnit outputs that show up in Test Viewer in VS --------- Co-authored-by: Matt Nadareski <mnadareski@outlook.com>
2026-05-12 09:41:32 -05:00
#region WIA
// WIA magic ("WIA\x01" stored little-endian: 0x01414957)
if (magic.StartsWith([0x57, 0x49, 0x41, 0x01]))
return WrapperType.WIA;
// RVZ magic ("RVZ\x01" stored little-endian: 0x015A5652)
if (magic.StartsWith([0x52, 0x56, 0x5A, 0x01]))
return WrapperType.RVZ;
if (extension.Equals("wia", StringComparison.OrdinalIgnoreCase))
return WrapperType.WIA;
if (extension.Equals("rvz", StringComparison.OrdinalIgnoreCase))
return WrapperType.RVZ;
#endregion
2026-03-13 11:40:01 -04:00
#region XboxExecutable
if (magic.StartsWith(Data.Models.XboxExecutable.Constants.MagicBytes))
return WrapperType.XboxExecutable;
if (extension.Equals("xbe", StringComparison.OrdinalIgnoreCase))
return WrapperType.XboxExecutable;
#endregion
#region XDVDFS
// TODO: Overload .iso wrapper to call both ISO and XDVDFS wrappers if present
if (extension.Equals("xiso", StringComparison.OrdinalIgnoreCase))
return WrapperType.XDVDFS;
if (extension.Equals("xdvdfs", StringComparison.OrdinalIgnoreCase))
return WrapperType.XDVDFS;
#endregion
#region XenonExecutable
if (magic.StartsWith(Data.Models.XenonExecutable.Constants.MagicBytes))
return WrapperType.XenonExecutable;
if (extension.Equals("xex", StringComparison.OrdinalIgnoreCase))
return WrapperType.XenonExecutable;
#endregion
2026-04-19 22:04:25 +09:00
#region XRD
if (magic.StartsWith(Data.Models.XRD.Constants.MagicBytes))
return WrapperType.XRD;
if (extension.Equals("xrd", StringComparison.OrdinalIgnoreCase))
return WrapperType.XRD;
#endregion
2024-04-24 18:53:10 -04:00
#region XZ
2025-09-29 22:56:53 -04:00
if (magic.StartsWith(Data.Models.XZ.Constants.HeaderSignatureBytes))
return WrapperType.XZ;
2024-04-24 18:53:10 -04:00
if (extension.Equals("xz", StringComparison.OrdinalIgnoreCase))
return WrapperType.XZ;
2024-04-24 18:53:10 -04:00
#endregion
#region XZP
2025-09-26 13:06:18 -04:00
if (magic.StartsWith(Data.Models.XZP.Constants.HeaderSignatureBytes))
return WrapperType.XZP;
2024-04-24 18:53:10 -04:00
if (extension.Equals("xzp", StringComparison.OrdinalIgnoreCase))
return WrapperType.XZP;
2024-04-24 18:53:10 -04:00
#endregion
2025-10-30 22:15:10 -04:00
#region ZSTD
2026-04-02 02:23:02 -04:00
// ZArchive magic is the final 4 bytes of the file: [0x16, 0x9F, 0x52, 0xD6]
if (extension.Equals("zar", StringComparison.OrdinalIgnoreCase))
return WrapperType.ZArchive;
#endregion
#region ZSTD
if (magic.StartsWith([null, 0xB5, 0x2F, 0xFD]))
return WrapperType.ZSTD;
#endregion
2024-04-24 18:53:10 -04:00
// We couldn't find a supported match
return WrapperType.UNKNOWN;
2024-04-24 18:53:10 -04:00
}
2026-05-22 09:24:03 -04:00
/// <summary>
/// Get a file wrapper based on an offset within a stream
/// </summary>
public static IWrapper? GetFileWrapper(Stream? data, long offset, string filename)
{
// Null streams can't create wrappers
if (data is null)
return null;
data.Seek(offset, SeekOrigin.Begin);
byte[] magic = data.PeekBytes(16);
string extension = Path.GetExtension(filename).TrimStart('.');
WrapperType ft = GetFileType(magic, extension);
return CreateWrapper(ft, data);
}
2024-04-24 18:53:10 -04:00
}
}