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