2024-04-24 18:53:10 -04:00
|
|
|
using System;
|
2024-04-24 19:15:28 -04:00
|
|
|
using System.IO;
|
2026-05-14 20:08:32 -04:00
|
|
|
using SabreTools.Data.Extensions;
|
2024-04-24 19:15:28 -04:00
|
|
|
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
|
|
|
|
2026-03-18 16:55:48 -04:00
|
|
|
namespace SabreTools.Wrappers
|
2024-04-24 18:53:10 -04:00
|
|
|
{
|
2024-04-24 19:15:28 -04:00
|
|
|
public static class WrapperFactory
|
2024-04-24 18:53:10 -04:00
|
|
|
{
|
2024-04-24 19:15:28 -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-04-24 19:15:28 -04:00
|
|
|
{
|
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),
|
2025-08-23 11:55:54 -04:00
|
|
|
WrapperType.BZip2 => BZip2.Create(data),
|
2026-04-28 02:24:30 +09:00
|
|
|
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),
|
2025-08-23 11:55:54 -04:00
|
|
|
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),
|
2026-04-13 22:39:29 +09:00
|
|
|
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),
|
2025-08-23 11:55:54 -04:00
|
|
|
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),
|
2025-08-23 11:55:54 -04:00
|
|
|
WrapperType.SevenZip => SevenZip.Create(data),
|
2025-10-31 00:46:45 +09:00
|
|
|
WrapperType.Skeleton => Skeleton.Create(data),
|
2026-01-28 21:30:08 -05:00
|
|
|
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),
|
2026-07-06 06:21:17 -07:00
|
|
|
WrapperType.Steam2Installer => Steam2Installer.Create(data),
|
2026-04-08 11:14:39 +09:00
|
|
|
WrapperType.STFS => STFS.Create(data),
|
2025-08-23 11:55:54 -04:00
|
|
|
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),
|
2026-03-29 11:59:30 +09:00
|
|
|
WrapperType.XDVDFS => XDVDFS.Create(data),
|
2026-04-06 23:43:32 +09:00
|
|
|
WrapperType.XenonExecutable => XenonExecutable.Create(data),
|
2026-04-19 22:04:25 +09:00
|
|
|
WrapperType.XRD => XRD.Create(data),
|
2025-08-23 11:55:54 -04:00
|
|
|
WrapperType.XZ => XZ.Create(data),
|
2024-11-13 02:42:14 -05:00
|
|
|
WrapperType.XZP => XZP.Create(data),
|
2026-04-02 15:18:47 +09:00
|
|
|
WrapperType.ZArchive => ZArchive.Create(data),
|
2025-10-29 22:01:23 -04:00
|
|
|
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,
|
|
|
|
|
};
|
2024-04-24 19:15:28 -04:00
|
|
|
}
|
|
|
|
|
|
2026-04-28 02:24:30 +09:00
|
|
|
/// <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);
|
|
|
|
|
|
2026-04-28 02:24:30 +09:00
|
|
|
// 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 02:24:30 +09:00
|
|
|
|
2026-04-28 22:24:36 +09:00
|
|
|
// No known filesystems found
|
|
|
|
|
return null;
|
2026-04-28 02:24:30 +09:00
|
|
|
}
|
|
|
|
|
|
2026-04-11 20:56:46 -04:00
|
|
|
/// <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)
|
2026-04-13 22:39:29 +09:00
|
|
|
var xboxWrapper = XboxISO.Create(stream);
|
2026-04-13 09:43:40 -04:00
|
|
|
if (xboxWrapper is not null)
|
2026-04-13 22:39:29 +09:00
|
|
|
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);
|
|
|
|
|
|
2026-04-28 02:24:30 +09:00
|
|
|
// 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-04-11 20:56:46 -04:00
|
|
|
|
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;
|
2026-04-11 20:56:46 -04:00
|
|
|
}
|
|
|
|
|
|
2024-04-24 19:15:28 -04:00
|
|
|
/// <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)
|
2024-04-24 19:15:28 -04:00
|
|
|
return null;
|
|
|
|
|
|
2025-08-19 09:18:50 -04:00
|
|
|
// Cache the current offset
|
|
|
|
|
long initialOffset = stream.Position;
|
|
|
|
|
|
2024-04-24 19:15:28 -04:00
|
|
|
// 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)
|
2024-04-24 19:15:28 -04:00
|
|
|
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)
|
2024-04-24 19:15:28 -04:00
|
|
|
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);
|
2024-04-24 19:15:28 -04:00
|
|
|
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)
|
2024-04-24 19:15:28 -04:00
|
|
|
{
|
|
|
|
|
return wrapper;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// New Executable
|
2025-09-26 13:06:18 -04:00
|
|
|
else if (magic.StartsWith(Data.Models.NewExecutable.Constants.SignatureBytes))
|
2024-04-24 19:15:28 -04:00
|
|
|
{
|
2025-10-27 22:43:56 -04:00
|
|
|
stream.SeekIfPossible(initialOffset, SeekOrigin.Begin);
|
2024-04-24 19:15:28 -04:00
|
|
|
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))
|
2024-04-24 19:15:28 -04:00
|
|
|
{
|
2025-10-27 22:43:56 -04:00
|
|
|
stream.SeekIfPossible(initialOffset, SeekOrigin.Begin);
|
2024-04-24 19:15:28 -04:00
|
|
|
return LinearExecutable.Create(stream);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Portable Executable
|
2025-09-26 13:06:18 -04:00
|
|
|
else if (magic.StartsWith(Data.Models.PortableExecutable.Constants.SignatureBytes))
|
2024-04-24 19:15:28 -04:00
|
|
|
{
|
2025-10-27 22:43:56 -04:00
|
|
|
stream.SeekIfPossible(initialOffset, SeekOrigin.Begin);
|
2024-04-24 19:15:28 -04:00
|
|
|
return PortableExecutable.Create(stream);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Everything else fails
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-24 18:53:10 -04:00
|
|
|
/// <summary>
|
2024-05-09 21:34:58 -04:00
|
|
|
/// 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>
|
2024-05-09 21:34:58 -04:00
|
|
|
public static WrapperType GetFileType(byte[]? magic, string? extension)
|
2024-04-24 18:53:10 -04:00
|
|
|
{
|
2024-05-09 21:34:58 -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)
|
2024-04-24 19:15:28 -04:00
|
|
|
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
|
|
|
|
|
|
2024-05-09 21:34:58 -04:00
|
|
|
// Block starting with verify media key record
|
2024-11-29 20:48:50 -05:00
|
|
|
if (magic.StartsWith([0x81, 0x00, 0x00, 0x14]))
|
2024-05-09 21:34:58 -04:00
|
|
|
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]))
|
2024-05-09 21:34:58 -04:00
|
|
|
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))
|
2024-04-24 19:18:38 -04:00
|
|
|
// return WrapperType.AACSMediaKeyBlock;
|
2024-04-24 18:53:10 -04:00
|
|
|
|
|
|
|
|
// HD-DVD
|
|
|
|
|
if (extension.Equals("aacs", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
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))
|
2024-05-09 21:34:58 -04:00
|
|
|
return WrapperType.BDPlusSVM;
|
|
|
|
|
|
2024-04-24 18:53:10 -04:00
|
|
|
if (extension.Equals("svm", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
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))
|
2024-05-09 21:34:58 -04:00
|
|
|
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
|
|
|
|
|
|
2024-05-09 21:34:58 -04:00
|
|
|
// 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))
|
2024-05-09 21:34:58 -04:00
|
|
|
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))
|
2024-05-09 21:34:58 -04:00
|
|
|
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))
|
2024-05-09 21:34:58 -04:00
|
|
|
return WrapperType.BZip2;
|
|
|
|
|
|
2024-04-24 18:53:10 -04:00
|
|
|
if (extension.Equals("bz2", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
return WrapperType.BZip2;
|
2024-04-24 18:53:10 -04:00
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
2025-11-03 09:21:19 +09:00
|
|
|
#region CDROM
|
|
|
|
|
|
2026-04-28 02:24:30 +09:00
|
|
|
// 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)))
|
|
|
|
|
{
|
2025-11-03 09:21:19 +09:00
|
|
|
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;
|
|
|
|
|
}
|
2025-11-03 09:21:19 +09:00
|
|
|
|
|
|
|
|
#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))
|
2024-05-09 21:34:58 -04:00
|
|
|
return WrapperType.CFB;
|
|
|
|
|
|
2024-04-24 18:53:10 -04:00
|
|
|
// Installer package
|
|
|
|
|
if (extension.Equals("msi", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
return WrapperType.CFB;
|
2024-04-24 18:53:10 -04:00
|
|
|
|
|
|
|
|
// Merge module
|
|
|
|
|
else if (extension.Equals("msm", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
return WrapperType.CFB;
|
2024-04-24 18:53:10 -04:00
|
|
|
|
|
|
|
|
// Patch Package
|
|
|
|
|
else if (extension.Equals("msp", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
return WrapperType.CFB;
|
2024-04-24 18:53:10 -04:00
|
|
|
|
|
|
|
|
// Transform
|
|
|
|
|
else if (extension.Equals("mst", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
return WrapperType.CFB;
|
2024-04-24 18:53:10 -04:00
|
|
|
|
|
|
|
|
// Patch Creation Properties
|
|
|
|
|
else if (extension.Equals("pcp", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
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))
|
2024-04-24 19:15:28 -04:00
|
|
|
return WrapperType.CIA;
|
2024-04-24 18:53:10 -04:00
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Executable
|
|
|
|
|
|
2024-05-09 21:34:58 -04:00
|
|
|
// DOS MZ executable file format (and descendants)
|
2025-09-26 13:06:18 -04:00
|
|
|
if (magic.StartsWith(Data.Models.MSDOS.Constants.SignatureBytes))
|
2024-05-09 21:34:58 -04:00
|
|
|
return WrapperType.Executable;
|
|
|
|
|
|
|
|
|
|
// None of the following are supported yet
|
2024-11-29 20:48:50 -05:00
|
|
|
/*
|
2024-05-09 21:34:58 -04:00
|
|
|
// Executable and Linkable Format
|
2024-11-29 20:48:50 -05:00
|
|
|
if (magic.StartsWith([0x7f, 0x45, 0x4c, 0x46]))
|
|
|
|
|
return WrapperType.Executable;
|
2024-05-09 21:34:58 -04:00
|
|
|
|
|
|
|
|
// Mach-O binary (32-bit)
|
2024-11-29 20:48:50 -05:00
|
|
|
if (magic.StartsWith([0xfe, 0xed, 0xfa, 0xce]))
|
|
|
|
|
return WrapperType.Executable;
|
2024-05-09 21:34:58 -04:00
|
|
|
|
|
|
|
|
// 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;
|
2024-05-09 21:34:58 -04:00
|
|
|
|
|
|
|
|
// Mach-O binary (64-bit)
|
2024-11-29 20:48:50 -05:00
|
|
|
if (magic.StartsWith([0xfe, 0xed, 0xfa, 0xcf]))
|
|
|
|
|
return WrapperType.Executable;
|
2024-05-09 21:34:58 -04:00
|
|
|
|
|
|
|
|
// 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;
|
2024-05-09 21:34:58 -04:00
|
|
|
|
|
|
|
|
// 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-05-09 21:34:58 -04:00
|
|
|
*/
|
|
|
|
|
|
2024-04-24 18:53:10 -04:00
|
|
|
// DOS MZ executable file format (and descendants)
|
|
|
|
|
if (extension.Equals("exe", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
return WrapperType.Executable;
|
2024-04-24 18:53:10 -04:00
|
|
|
|
|
|
|
|
// DOS MZ library file format (and descendants)
|
|
|
|
|
if (extension.Equals("dll", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
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]))
|
2024-05-09 21:34:58 -04:00
|
|
|
return WrapperType.GCF;
|
|
|
|
|
|
2024-04-24 18:53:10 -04:00
|
|
|
if (extension.Equals("gcf", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
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
|
|
|
|
|
|
2025-08-23 11:55:54 -04:00
|
|
|
#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))
|
2025-08-23 11:55:54 -04:00
|
|
|
return WrapperType.GZip;
|
2024-05-09 21:34:58 -04:00
|
|
|
|
2024-04-24 18:53:10 -04:00
|
|
|
if (extension.Equals("gz", StringComparison.OrdinalIgnoreCase))
|
2025-08-23 11:55:54 -04:00
|
|
|
return WrapperType.GZip;
|
2024-04-24 18:53:10 -04:00
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region IniFile
|
|
|
|
|
|
|
|
|
|
if (extension.Equals("ini", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
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))
|
2024-05-09 21:34:58 -04:00
|
|
|
return WrapperType.InstallShieldArchiveV3;
|
|
|
|
|
|
2024-04-24 18:53:10 -04:00
|
|
|
if (extension.Equals("z", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
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))
|
2024-05-09 21:34:58 -04:00
|
|
|
return WrapperType.InstallShieldCAB;
|
|
|
|
|
|
2024-04-24 18:53:10 -04:00
|
|
|
// Both InstallShieldCAB and MicrosoftCAB share the same extension
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
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
|
|
|
|
|
|
2024-05-09 21:34:58 -04:00
|
|
|
#region LDSCRYPT
|
|
|
|
|
|
2025-09-26 13:06:18 -04:00
|
|
|
if (magic.StartsWith(Data.Models.LDSCRYPT.Constants.SignatureBytes))
|
2024-05-09 21:34:58 -04:00
|
|
|
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;
|
2024-05-09 21:34:58 -04:00
|
|
|
|
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
|
2024-05-09 21:34:58 -04:00
|
|
|
|
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
|
2024-05-09 21:34:58 -04:00
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region MoPaQ
|
|
|
|
|
|
2025-09-26 13:06:18 -04:00
|
|
|
if (magic.StartsWith(Data.Models.MoPaQ.Constants.ArchiveHeaderSignatureBytes))
|
2024-05-09 21:34:58 -04:00
|
|
|
return WrapperType.MoPaQ;
|
|
|
|
|
|
2025-09-26 13:06:18 -04:00
|
|
|
if (magic.StartsWith(Data.Models.MoPaQ.Constants.UserDataSignatureBytes))
|
2024-05-09 21:34:58 -04:00
|
|
|
return WrapperType.MoPaQ;
|
2024-04-24 18:53:10 -04:00
|
|
|
|
|
|
|
|
if (extension.Equals("mpq", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
return WrapperType.MoPaQ;
|
2024-04-24 18:53:10 -04:00
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region N3DS
|
|
|
|
|
|
|
|
|
|
// 3DS cart image
|
|
|
|
|
if (extension.Equals("3ds", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
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))
|
2024-04-24 19:18:38 -04:00
|
|
|
// 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]))
|
2024-05-09 21:34:58 -04:00
|
|
|
return WrapperType.NCF;
|
|
|
|
|
|
2024-04-24 18:53:10 -04:00
|
|
|
if (extension.Equals("ncf", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
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))
|
2024-04-24 19:15:28 -04:00
|
|
|
return WrapperType.Nitro;
|
2024-04-24 18:53:10 -04:00
|
|
|
|
|
|
|
|
// DS development cart image
|
|
|
|
|
else if (extension.Equals("srl", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
return WrapperType.Nitro;
|
2024-04-24 18:53:10 -04:00
|
|
|
|
|
|
|
|
// DSi cart image
|
|
|
|
|
else if (extension.Equals("dsi", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
return WrapperType.Nitro;
|
2024-04-24 18:53:10 -04:00
|
|
|
|
|
|
|
|
// iQue DS cart image
|
|
|
|
|
else if (extension.Equals("ids", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
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))
|
2024-05-09 21:34:58 -04:00
|
|
|
return WrapperType.PAK;
|
|
|
|
|
|
2024-04-24 18:53:10 -04:00
|
|
|
// Both PAK and Quantum share one extension
|
|
|
|
|
// if (extension.Equals("pak", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:18:38 -04:00
|
|
|
// 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;
|
|
|
|
|
|
2024-05-09 21:34:58 -04:00
|
|
|
// Version 2
|
2024-11-29 20:48:50 -05:00
|
|
|
if (magic.StartsWith([0x14, 0x00, 0x00, 0x00, 0x50, 0x46, 0x46, 0x32]))
|
2024-05-09 21:34:58 -04:00
|
|
|
return WrapperType.PFF;
|
|
|
|
|
|
|
|
|
|
// Version 3
|
2024-11-29 20:48:50 -05:00
|
|
|
if (magic.StartsWith([0x14, 0x00, 0x00, 0x00, 0x50, 0x46, 0x46, 0x33]))
|
2024-05-09 21:34:58 -04:00
|
|
|
return WrapperType.PFF;
|
|
|
|
|
|
|
|
|
|
// Version 4
|
2024-11-29 20:48:50 -05:00
|
|
|
if (magic.StartsWith([0x14, 0x00, 0x00, 0x00, 0x50, 0x46, 0x46, 0x34]))
|
2024-05-09 21:34:58 -04:00
|
|
|
return WrapperType.PFF;
|
|
|
|
|
|
2024-04-24 18:53:10 -04:00
|
|
|
if (extension.Equals("pff", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
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
|
|
|
|
|
|
2024-05-09 21:34:58 -04:00
|
|
|
// PKZIP (Unknown)
|
2024-11-29 20:48:50 -05:00
|
|
|
if (magic.StartsWith([0x50, 0x4b, 0x00, 0x00]))
|
2024-05-09 21:34:58 -04:00
|
|
|
return WrapperType.PKZIP;
|
|
|
|
|
|
|
|
|
|
// PKZIP
|
2025-09-26 13:06:18 -04:00
|
|
|
if (magic.StartsWith(Data.Models.PKZIP.Constants.LocalFileHeaderSignatureBytes))
|
2024-05-09 21:34:58 -04:00
|
|
|
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))
|
2024-05-09 21:34:58 -04:00
|
|
|
return WrapperType.PKZIP;
|
|
|
|
|
|
|
|
|
|
// PKZIP (Spanned Archive)
|
2025-09-26 13:06:18 -04:00
|
|
|
if (magic.StartsWith(Data.Models.PKZIP.Constants.DataDescriptorSignatureBytes))
|
2024-05-09 21:34:58 -04:00
|
|
|
return WrapperType.PKZIP;
|
|
|
|
|
|
2024-04-24 18:53:10 -04:00
|
|
|
// PKZIP
|
|
|
|
|
if (extension.Equals("zip", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
return WrapperType.PKZIP;
|
2024-04-24 18:53:10 -04:00
|
|
|
|
|
|
|
|
// Android package
|
|
|
|
|
if (extension.Equals("apk", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
return WrapperType.PKZIP;
|
2024-04-24 18:53:10 -04:00
|
|
|
|
|
|
|
|
// Java archive
|
|
|
|
|
if (extension.Equals("jar", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
return WrapperType.PKZIP;
|
2024-04-24 18:53:10 -04:00
|
|
|
|
|
|
|
|
// Google Earth saved working session file
|
|
|
|
|
if (extension.Equals("kmz", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
return WrapperType.PKZIP;
|
2024-04-24 18:53:10 -04:00
|
|
|
|
|
|
|
|
// KWord document
|
|
|
|
|
if (extension.Equals("kwd", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
return WrapperType.PKZIP;
|
2024-04-24 18:53:10 -04:00
|
|
|
|
|
|
|
|
// Microsoft Office Open XML Format (OOXML) Document
|
|
|
|
|
if (extension.Equals("docx", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
return WrapperType.PKZIP;
|
2024-04-24 18:53:10 -04:00
|
|
|
|
|
|
|
|
// Microsoft Office Open XML Format (OOXML) Presentation
|
|
|
|
|
if (extension.Equals("pptx", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
return WrapperType.PKZIP;
|
2024-04-24 18:53:10 -04:00
|
|
|
|
|
|
|
|
// Microsoft Office Open XML Format (OOXML) Spreadsheet
|
|
|
|
|
if (extension.Equals("xlsx", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
return WrapperType.PKZIP;
|
2024-04-24 18:53:10 -04:00
|
|
|
|
|
|
|
|
// OpenDocument text document
|
|
|
|
|
if (extension.Equals("odt", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
return WrapperType.PKZIP;
|
2024-04-24 18:53:10 -04:00
|
|
|
|
|
|
|
|
// OpenDocument presentation
|
|
|
|
|
if (extension.Equals("odp", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
return WrapperType.PKZIP;
|
2024-04-24 18:53:10 -04:00
|
|
|
|
|
|
|
|
// OpenDocument text document template
|
|
|
|
|
if (extension.Equals("ott", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
return WrapperType.PKZIP;
|
2024-04-24 18:53:10 -04:00
|
|
|
|
|
|
|
|
// Microsoft Open XML paper specification file
|
|
|
|
|
if (extension.Equals("oxps", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
return WrapperType.PKZIP;
|
2024-04-24 18:53:10 -04:00
|
|
|
|
|
|
|
|
// OpenOffice spreadsheet
|
|
|
|
|
if (extension.Equals("sxc", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
return WrapperType.PKZIP;
|
2024-04-24 18:53:10 -04:00
|
|
|
|
|
|
|
|
// OpenOffice drawing
|
|
|
|
|
if (extension.Equals("sxd", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
return WrapperType.PKZIP;
|
2024-04-24 18:53:10 -04:00
|
|
|
|
|
|
|
|
// OpenOffice presentation
|
|
|
|
|
if (extension.Equals("sxi", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
return WrapperType.PKZIP;
|
2024-04-24 18:53:10 -04:00
|
|
|
|
|
|
|
|
// OpenOffice word processing
|
|
|
|
|
if (extension.Equals("sxw", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
return WrapperType.PKZIP;
|
2024-04-24 18:53:10 -04:00
|
|
|
|
|
|
|
|
// StarOffice spreadsheet
|
|
|
|
|
if (extension.Equals("sxc", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
return WrapperType.PKZIP;
|
2024-04-24 18:53:10 -04:00
|
|
|
|
|
|
|
|
// Windows Media compressed skin file
|
|
|
|
|
if (extension.Equals("wmz", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
return WrapperType.PKZIP;
|
2024-04-24 18:53:10 -04:00
|
|
|
|
|
|
|
|
// Mozilla Browser Archive
|
|
|
|
|
if (extension.Equals("xpi", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
return WrapperType.PKZIP;
|
2024-04-24 18:53:10 -04:00
|
|
|
|
|
|
|
|
// XML paper specification file
|
|
|
|
|
if (extension.Equals("xps", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
return WrapperType.PKZIP;
|
2024-04-24 18:53:10 -04:00
|
|
|
|
|
|
|
|
// eXact Packager Models
|
|
|
|
|
if (extension.Equals("xpt", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
return WrapperType.PKZIP;
|
2024-04-24 18:53:10 -04:00
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region PLJ
|
|
|
|
|
|
2024-05-09 21:34:58 -04:00
|
|
|
// 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))
|
2024-05-09 21:34:58 -04:00
|
|
|
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))
|
2024-04-24 19:15:28 -04:00
|
|
|
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))
|
2024-05-09 21:34:58 -04:00
|
|
|
return WrapperType.Quantum;
|
|
|
|
|
|
2024-04-24 18:53:10 -04:00
|
|
|
if (extension.Equals("q", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
return WrapperType.Quantum;
|
2024-04-24 18:53:10 -04:00
|
|
|
|
|
|
|
|
// Both PAK and Quantum share one extension
|
|
|
|
|
// if (extension.Equals("pak", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:18:38 -04:00
|
|
|
// return WrapperType.Quantum;
|
2024-04-24 18:53:10 -04:00
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region RAR
|
|
|
|
|
|
2024-05-09 21:34:58 -04:00
|
|
|
// RAR archive version 1.50 onwards
|
2025-09-26 13:06:18 -04:00
|
|
|
if (magic.StartsWith(Data.Models.RAR.Constants.OldSignatureBytes))
|
2024-05-09 21:34:58 -04:00
|
|
|
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))
|
2024-05-09 21:34:58 -04:00
|
|
|
return WrapperType.RAR;
|
|
|
|
|
|
2024-04-24 18:53:10 -04:00
|
|
|
if (extension.Equals("rar", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
return WrapperType.RAR;
|
2024-04-24 18:53:10 -04:00
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
2024-05-09 21:34:58 -04:00
|
|
|
#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))
|
2024-05-09 21:34:58 -04:00
|
|
|
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))
|
2024-05-09 21:34:58 -04:00
|
|
|
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))
|
2024-05-09 21:34:58 -04:00
|
|
|
return WrapperType.SevenZip;
|
|
|
|
|
|
2024-04-24 18:53:10 -04:00
|
|
|
if (extension.Equals("7z", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
return WrapperType.SevenZip;
|
2024-04-24 18:53:10 -04:00
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
2024-05-09 21:34:58 -04:00
|
|
|
#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))
|
2024-05-09 21:34:58 -04:00
|
|
|
return WrapperType.SFFS;
|
|
|
|
|
|
2025-10-27 22:43:56 -04:00
|
|
|
#endregion
|
2024-05-09 21:34:58 -04:00
|
|
|
|
2026-01-28 21:30:08 -05:00
|
|
|
#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))
|
2024-05-09 21:34:58 -04:00
|
|
|
return WrapperType.SGA;
|
|
|
|
|
|
2024-04-24 18:53:10 -04:00
|
|
|
if (extension.Equals("sga", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
return WrapperType.SGA;
|
2024-04-24 18:53:10 -04:00
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
2025-10-31 00:46:45 +09:00
|
|
|
#region Skeleton
|
|
|
|
|
|
|
|
|
|
if (extension.Equals("skeleton", StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
return WrapperType.Skeleton;
|
|
|
|
|
|
|
|
|
|
#endregion
|
2026-04-08 11:14:39 +09:00
|
|
|
|
2026-07-06 06:21:17 -07:00
|
|
|
#region Steam2Installer
|
|
|
|
|
|
|
|
|
|
if (magic.StartsWith(Data.Models.Steam2Installer.Constants.SimSignatureBytes))
|
|
|
|
|
return WrapperType.Steam2Installer;
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
2026-04-08 11:14:39 +09:00
|
|
|
#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-10-31 00:46:45 +09:00
|
|
|
|
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]))
|
2024-05-09 21:34:58 -04:00
|
|
|
return WrapperType.TapeArchive;
|
|
|
|
|
|
2024-11-29 20:48:50 -05:00
|
|
|
if (magic.StartsWith([0x75, 0x73, 0x74, 0x61, 0x72, 0x20, 0x20, 0x00]))
|
2024-05-09 21:34:58 -04:00
|
|
|
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
|
|
|
|
|
|
2024-05-09 21:34:58 -04:00
|
|
|
// 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]))
|
2024-05-09 21:34:58 -04:00
|
|
|
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]))
|
2024-05-09 21:34:58 -04:00
|
|
|
return WrapperType.Textfile;
|
|
|
|
|
|
|
|
|
|
// InstallShield Compiled Rules
|
2024-11-29 20:48:50 -05:00
|
|
|
if (magic.StartsWith([0x61, 0x4C, 0x75, 0x5A]))
|
2024-05-09 21:34:58 -04:00
|
|
|
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]))
|
2024-05-09 21:34:58 -04:00
|
|
|
return WrapperType.Textfile;
|
|
|
|
|
|
|
|
|
|
// Rich Text File
|
2024-11-29 20:48:50 -05:00
|
|
|
if (magic.StartsWith([0x7b, 0x5c, 0x72, 0x74, 0x66, 0x31]))
|
2024-05-09 21:34:58 -04:00
|
|
|
return WrapperType.Textfile;
|
|
|
|
|
|
|
|
|
|
// Windows Help File
|
2024-11-29 20:48:50 -05:00
|
|
|
if (magic.StartsWith([0x3F, 0x5F, 0x03, 0x00]))
|
2024-05-09 21:34:58 -04:00
|
|
|
return WrapperType.Textfile;
|
|
|
|
|
|
2025-10-27 22:43:56 -04:00
|
|
|
// XML
|
2024-05-09 21:34:58 -04:00
|
|
|
// "<?xml"
|
2024-11-29 20:48:50 -05:00
|
|
|
if (magic.StartsWith([0x3C, 0x3F, 0x78, 0x6D, 0x6C]))
|
2024-05-09 21:34:58 -04:00
|
|
|
return WrapperType.Textfile;
|
|
|
|
|
|
2025-09-21 12:10:58 -04:00
|
|
|
// "<?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))
|
2024-04-24 19:15:28 -04:00
|
|
|
return WrapperType.Textfile;
|
2024-04-24 18:53:10 -04:00
|
|
|
|
|
|
|
|
// Generic textfile (no header)
|
|
|
|
|
if (extension.Equals("txt", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
return WrapperType.Textfile;
|
2024-04-24 18:53:10 -04:00
|
|
|
|
|
|
|
|
// HTML
|
|
|
|
|
if (extension.Equals("htm", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
return WrapperType.Textfile;
|
2024-04-24 18:53:10 -04:00
|
|
|
if (extension.Equals("html", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
return WrapperType.Textfile;
|
2024-04-24 18:53:10 -04:00
|
|
|
|
|
|
|
|
// InstallShield Script
|
|
|
|
|
if (extension.Equals("ins", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
return WrapperType.Textfile;
|
2024-04-24 18:53:10 -04:00
|
|
|
|
|
|
|
|
// Microsoft Office File (old)
|
|
|
|
|
if (extension.Equals("doc", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
return WrapperType.Textfile;
|
2024-04-24 18:53:10 -04:00
|
|
|
|
|
|
|
|
// Property list
|
|
|
|
|
if (extension.Equals("plist", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
return WrapperType.Textfile;
|
2024-04-24 18:53:10 -04:00
|
|
|
|
|
|
|
|
// Rich Text File
|
|
|
|
|
if (extension.Equals("rtf", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
return WrapperType.Textfile;
|
2024-04-24 18:53:10 -04:00
|
|
|
|
|
|
|
|
// Setup information
|
|
|
|
|
if (extension.Equals("inf", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
return WrapperType.Textfile;
|
2024-04-24 18:53:10 -04:00
|
|
|
|
|
|
|
|
// Windows Help File
|
|
|
|
|
if (extension.Equals("hlp", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
return WrapperType.Textfile;
|
2024-04-24 18:53:10 -04:00
|
|
|
|
|
|
|
|
// WZC
|
|
|
|
|
if (extension.Equals("wzc", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
return WrapperType.Textfile;
|
2024-04-24 18:53:10 -04:00
|
|
|
|
|
|
|
|
// XML
|
|
|
|
|
if (extension.Equals("xml", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
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))
|
2024-05-09 21:34:58 -04:00
|
|
|
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))
|
2024-05-09 21:34:58 -04:00
|
|
|
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))
|
2024-04-24 19:18:38 -04:00
|
|
|
// 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))
|
2024-05-09 21:34:58 -04:00
|
|
|
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))
|
2024-04-24 19:18:38 -04:00
|
|
|
// 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
|
|
|
|
|
|
2026-03-29 11:59:30 +09:00
|
|
|
#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
|
|
|
|
|
|
2026-04-06 23:43:32 +09:00
|
|
|
#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))
|
2024-05-09 21:34:58 -04:00
|
|
|
return WrapperType.XZ;
|
|
|
|
|
|
2024-04-24 18:53:10 -04:00
|
|
|
if (extension.Equals("xz", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
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))
|
2024-05-09 21:34:58 -04:00
|
|
|
return WrapperType.XZP;
|
|
|
|
|
|
2024-04-24 18:53:10 -04:00
|
|
|
if (extension.Equals("xzp", StringComparison.OrdinalIgnoreCase))
|
2024-04-24 19:15:28 -04:00
|
|
|
return WrapperType.XZP;
|
2024-04-24 18:53:10 -04:00
|
|
|
|
|
|
|
|
#endregion
|
2025-10-30 22:15:10 -04:00
|
|
|
|
2025-10-29 22:01:23 -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]
|
2026-04-02 15:18:47 +09:00
|
|
|
|
|
|
|
|
if (extension.Equals("zar", StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
return WrapperType.ZArchive;
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region ZSTD
|
|
|
|
|
|
2025-10-29 22:01:23 -04:00
|
|
|
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
|
2024-04-24 19:15:28 -04:00
|
|
|
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
|
|
|
}
|
2024-04-24 19:15:28 -04:00
|
|
|
}
|