mirror of
https://github.com/SabreTools/SabreTools.Serialization.git
synced 2026-11-15 17:00:38 +00:00
* 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>
1229 lines
44 KiB
C#
1229 lines
44 KiB
C#
using System;
|
|
using System.IO;
|
|
using SabreTools.Data.Extensions;
|
|
using SabreTools.IO.Extensions;
|
|
using SabreTools.Matching;
|
|
using SabreTools.Numerics.Extensions;
|
|
|
|
namespace SabreTools.Wrappers
|
|
{
|
|
public static class WrapperFactory
|
|
{
|
|
/// <summary>
|
|
/// Create an instance of a wrapper based on file type
|
|
/// </summary>
|
|
public static IWrapper? CreateWrapper(WrapperType fileType, Stream? data)
|
|
{
|
|
return fileType switch
|
|
{
|
|
WrapperType.AACSMediaKeyBlock => AACSMediaKeyBlock.Create(data),
|
|
WrapperType.Atari7800Cart => Atari7800Cart.Create(data),
|
|
WrapperType.AtariLynxCart => AtariLynxCart.Create(data),
|
|
WrapperType.BDPlusSVM => BDPlusSVM.Create(data),
|
|
WrapperType.BFPK => BFPK.Create(data),
|
|
WrapperType.BSP => BSP.Create(data),
|
|
WrapperType.BZip2 => BZip2.Create(data),
|
|
WrapperType.CDROM => CreateCDROMImageWrapper(data),
|
|
WrapperType.CFB => CFB.Create(data),
|
|
WrapperType.CHD => CHD.Create(data),
|
|
WrapperType.CIA => CIA.Create(data),
|
|
WrapperType.Executable => CreateExecutableWrapper(data),
|
|
WrapperType.FDS => FDS.Create(data),
|
|
WrapperType.GCF => GCF.Create(data),
|
|
WrapperType.GCZ => GCZ.Create(data),
|
|
WrapperType.GZip => GZip.Create(data),
|
|
WrapperType.InstallShieldArchiveV3 => InstallShieldArchiveV3.Create(data),
|
|
WrapperType.InstallShieldCAB => InstallShieldCabinet.Create(data),
|
|
WrapperType.IRD => IRD.Create(data),
|
|
WrapperType.ISO9660 => CreateDiscImageWrapper(data),
|
|
WrapperType.LDSCRYPT => LDSCRYPT.Create(data),
|
|
WrapperType.LZKWAJ => LZKWAJ.Create(data),
|
|
WrapperType.LZQBasic => LZQBasic.Create(data),
|
|
WrapperType.LZSZDD => LZSZDD.Create(data),
|
|
WrapperType.MicrosoftCAB => MicrosoftCabinet.Create(data),
|
|
WrapperType.MoPaQ => MoPaQ.Create(data),
|
|
WrapperType.N3DS => N3DS.Create(data),
|
|
WrapperType.NCF => NCF.Create(data),
|
|
WrapperType.NESCart => NESCart.Create(data),
|
|
WrapperType.Nitro => Nitro.Create(data),
|
|
WrapperType.NintendoDisc => NintendoDisc.Create(data),
|
|
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),
|
|
WrapperType.QD => QD.Create(data),
|
|
WrapperType.Quantum => Quantum.Create(data),
|
|
WrapperType.RAR => RAR.Create(data),
|
|
WrapperType.RealArcadeInstaller => RealArcadeInstaller.Create(data),
|
|
WrapperType.RealArcadeMezzanine => RealArcadeMezzanine.Create(data),
|
|
WrapperType.SecuROMDFA => SecuROMDFA.Create(data),
|
|
WrapperType.SevenZip => SevenZip.Create(data),
|
|
WrapperType.Skeleton => Skeleton.Create(data),
|
|
WrapperType.SkuSis => SkuSis.Create(data),
|
|
WrapperType.SFFS => SFFS.Create(data),
|
|
WrapperType.SGA => SGA.Create(data),
|
|
WrapperType.Steam2Installer => Steam2Installer.Create(data),
|
|
WrapperType.STFS => STFS.Create(data),
|
|
WrapperType.TapeArchive => TapeArchive.Create(data),
|
|
WrapperType.VBSP => VBSP.Create(data),
|
|
WrapperType.VPK => VPK.Create(data),
|
|
WrapperType.WAD => WAD3.Create(data),
|
|
WrapperType.WiseOverlayHeader => WiseOverlayHeader.Create(data),
|
|
WrapperType.WiseScript => WiseScript.Create(data),
|
|
WrapperType.WIA => WIA.Create(data),
|
|
WrapperType.RVZ => WIA.Create(data),
|
|
WrapperType.XboxExecutable => XboxExecutable.Create(data),
|
|
WrapperType.XDVDFS => XDVDFS.Create(data),
|
|
WrapperType.XenonExecutable => XenonExecutable.Create(data),
|
|
WrapperType.XRD => XRD.Create(data),
|
|
WrapperType.XZ => XZ.Create(data),
|
|
WrapperType.XZP => XZP.Create(data),
|
|
WrapperType.ZArchive => ZArchive.Create(data),
|
|
WrapperType.ZSTD => ZSTD.Create(data),
|
|
|
|
// Unimplemented
|
|
WrapperType.UNKNOWN => null,
|
|
WrapperType.IniFile => null,// TODO: Implement wrapper
|
|
WrapperType.Textfile => null,// TODO: Implement wrapper
|
|
_ => null,
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create an instance of a wrapper based on the CDROM image type
|
|
/// </summary>
|
|
/// <param name="stream">Stream data to parse</param>
|
|
/// <returns>IWrapper representing the CDROM image, null on error</returns>
|
|
public static IWrapper? CreateCDROMImageWrapper(Stream? stream)
|
|
{
|
|
// If we have no stream
|
|
if (stream is null)
|
|
return null;
|
|
|
|
// Cache the current offset
|
|
long initialOffset = stream.Position;
|
|
|
|
// Attempt to open standard ISO9660 CDROM wrapper
|
|
var cdromWrapper = CDROM.Create(stream);
|
|
if (cdromWrapper is not null)
|
|
return cdromWrapper;
|
|
|
|
// Reset position in stream
|
|
stream.SeekIfPossible(initialOffset, SeekOrigin.Begin);
|
|
|
|
// Try to get a 3DO / M2 disc image wrapper
|
|
var operaDiscImageWrapper = OperaDiscImage.Create(stream);
|
|
if (operaDiscImageWrapper is not null)
|
|
return operaDiscImageWrapper;
|
|
|
|
// Reset position in stream
|
|
stream.SeekIfPossible(initialOffset, SeekOrigin.Begin);
|
|
|
|
// 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;
|
|
|
|
// No known filesystems found
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create an instance of a wrapper based on the disc image type
|
|
/// </summary>
|
|
/// <param name="stream">Stream data to parse</param>
|
|
/// <returns>IWrapper representing the disc image, null on error</returns>
|
|
public static IWrapper? CreateDiscImageWrapper(Stream? stream)
|
|
{
|
|
// If we have no stream
|
|
if (stream is null)
|
|
return null;
|
|
|
|
// Cache the current offset
|
|
long initialOffset = stream.Position;
|
|
|
|
// Try to get an Xbox disc image wrapper (must be before ISO9660)
|
|
var xboxWrapper = XboxISO.Create(stream);
|
|
if (xboxWrapper is not null)
|
|
return xboxWrapper;
|
|
|
|
// Reset position in stream
|
|
stream.SeekIfPossible(initialOffset, SeekOrigin.Begin);
|
|
|
|
// 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);
|
|
|
|
// Try to get a UDF wrapper (discs without an ISO9660 file system)
|
|
var udfWrapper = UDF.Create(stream);
|
|
if (udfWrapper is not null)
|
|
return udfWrapper;
|
|
|
|
// Reset position in stream
|
|
stream.SeekIfPossible(initialOffset, SeekOrigin.Begin);
|
|
|
|
// Try to get a 3DO / M2 disc image wrapper
|
|
var operaFSWrapper = OperaFS.Create(stream);
|
|
if (operaFSWrapper is not null)
|
|
return operaFSWrapper;
|
|
|
|
// Reset position in stream
|
|
stream.SeekIfPossible(initialOffset, SeekOrigin.Begin);
|
|
|
|
// Try to get a PC Engine CDROM disc image wrapper
|
|
// This reads a lot for detection, do this step later
|
|
var pcEngineCDROMWrapper = PCEngineCDROM.Create(stream);
|
|
if (pcEngineCDROMWrapper is not null)
|
|
return pcEngineCDROMWrapper;
|
|
|
|
// 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;
|
|
|
|
// No known filesystems found
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create an instance of a wrapper based on the executable type
|
|
/// </summary>
|
|
/// <param name="stream">Stream data to parse</param>
|
|
/// <returns>IWrapper representing the executable, null on error</returns>
|
|
public static IWrapper? CreateExecutableWrapper(Stream? stream)
|
|
{
|
|
// If we have no stream
|
|
if (stream is null)
|
|
return null;
|
|
|
|
// Cache the current offset
|
|
long initialOffset = stream.Position;
|
|
|
|
// Try to get an MS-DOS wrapper first
|
|
var wrapper = MSDOS.Create(stream);
|
|
if (wrapper is null || wrapper is not MSDOS msdos)
|
|
return null;
|
|
|
|
// Check for a valid new executable address
|
|
if (initialOffset + msdos.Model.Header.NewExeHeaderAddr >= stream.Length)
|
|
return wrapper;
|
|
|
|
// Try to read the executable info
|
|
stream.SeekIfPossible(initialOffset + msdos.Model.Header.NewExeHeaderAddr, SeekOrigin.Begin);
|
|
var magic = stream.ReadBytes(4);
|
|
|
|
// If we didn't get valid data at the offset
|
|
if (magic is null)
|
|
{
|
|
return wrapper;
|
|
}
|
|
|
|
// New Executable
|
|
else if (magic.StartsWith(Data.Models.NewExecutable.Constants.SignatureBytes))
|
|
{
|
|
stream.SeekIfPossible(initialOffset, SeekOrigin.Begin);
|
|
return NewExecutable.Create(stream);
|
|
}
|
|
|
|
// Linear Executable
|
|
else if (magic.StartsWith(Data.Models.LinearExecutable.Constants.LESignatureBytes)
|
|
|| magic.StartsWith(Data.Models.LinearExecutable.Constants.LXSignatureBytes))
|
|
{
|
|
stream.SeekIfPossible(initialOffset, SeekOrigin.Begin);
|
|
return LinearExecutable.Create(stream);
|
|
}
|
|
|
|
// Portable Executable
|
|
else if (magic.StartsWith(Data.Models.PortableExecutable.Constants.SignatureBytes))
|
|
{
|
|
stream.SeekIfPossible(initialOffset, SeekOrigin.Begin);
|
|
return PortableExecutable.Create(stream);
|
|
}
|
|
|
|
// Everything else fails
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the supported file type for a magic string and an extension
|
|
/// </summary>
|
|
/// <remarks>Recommend sending in 16 bytes to check</remarks>
|
|
public static WrapperType GetFileType(byte[]? magic, string? extension)
|
|
{
|
|
// If we have an invalid magic byte array and extension
|
|
if (magic is null || magic.Length == 0 || extension is null)
|
|
return WrapperType.UNKNOWN;
|
|
|
|
// Normalize the extension
|
|
extension = extension.TrimStart('.').Trim();
|
|
|
|
// TODO: Use constants from Models here
|
|
#region AACSMediaKeyBlock
|
|
|
|
// Block starting with verify media key record
|
|
if (magic.StartsWith([0x81, 0x00, 0x00, 0x14]))
|
|
return WrapperType.AACSMediaKeyBlock;
|
|
|
|
// Block starting with type and version record
|
|
if (magic.StartsWith([0x10, 0x00, 0x00, 0x0C]))
|
|
return WrapperType.AACSMediaKeyBlock;
|
|
|
|
// Shares an extension with INF setup information so it can't be used accurately
|
|
// Blu-ray
|
|
// if (extension.Equals("inf", StringComparison.OrdinalIgnoreCase))
|
|
// return WrapperType.AACSMediaKeyBlock;
|
|
|
|
// HD-DVD
|
|
if (extension.Equals("aacs", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.AACSMediaKeyBlock;
|
|
|
|
#endregion
|
|
|
|
// 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
|
|
|
|
#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
|
|
|
|
#region BDPlusSVM
|
|
|
|
if (magic.StartsWith(Data.Models.BDPlus.Constants.SignatureBytes))
|
|
return WrapperType.BDPlusSVM;
|
|
|
|
if (extension.Equals("svm", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.BDPlusSVM;
|
|
|
|
#endregion
|
|
|
|
#region BFPK
|
|
|
|
if (magic.StartsWith(Data.Models.BFPK.Constants.SignatureBytes))
|
|
return WrapperType.BFPK;
|
|
|
|
#endregion
|
|
|
|
// TODO: Use constants from Models here
|
|
#region BSP
|
|
|
|
// Shares a first 4 bytes with some .mc files
|
|
// Shares an extension with VBSP
|
|
if (magic.StartsWith([0x1d, 0x00, 0x00, 0x00]) && extension.Equals("bsp", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.BSP;
|
|
|
|
// Shares a first 4 bytes with some .mc files
|
|
// Shares an extension with VBSP
|
|
if (magic.StartsWith([0x1e, 0x00, 0x00, 0x00]) && extension.Equals("bsp", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.BSP;
|
|
|
|
#endregion
|
|
|
|
#region BZip2
|
|
|
|
if (magic.StartsWith(Data.Models.BZip2.Constants.SignatureBytes))
|
|
return WrapperType.BZip2;
|
|
|
|
if (extension.Equals("bz2", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.BZip2;
|
|
|
|
#endregion
|
|
|
|
#region CDROM
|
|
|
|
// Must come before skeleton extension check for ISO9660
|
|
// CDROM skeletons have same extension as ISO9660 skeletons
|
|
if (magic.StartsWith([0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00])
|
|
&& (extension.Equals("bin", StringComparison.OrdinalIgnoreCase)
|
|
|| extension.Equals("skeleton", StringComparison.OrdinalIgnoreCase)))
|
|
{
|
|
return WrapperType.CDROM;
|
|
}
|
|
|
|
// Some CD-ROM images have no sync bytes in pregap
|
|
if (magic.StartsWith([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
|
|
&& (extension.Equals("bin", StringComparison.OrdinalIgnoreCase)
|
|
|| extension.Equals("skeleton", StringComparison.OrdinalIgnoreCase)))
|
|
{
|
|
return WrapperType.CDROM;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region CFB
|
|
|
|
if (magic.StartsWith(Data.Models.CFB.Constants.SignatureBytes))
|
|
return WrapperType.CFB;
|
|
|
|
// Installer package
|
|
if (extension.Equals("msi", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.CFB;
|
|
|
|
// Merge module
|
|
else if (extension.Equals("msm", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.CFB;
|
|
|
|
// Patch Package
|
|
else if (extension.Equals("msp", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.CFB;
|
|
|
|
// Transform
|
|
else if (extension.Equals("mst", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.CFB;
|
|
|
|
// Patch Creation Properties
|
|
else if (extension.Equals("pcp", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.CFB;
|
|
|
|
#endregion
|
|
|
|
#region CHD
|
|
|
|
if (magic.StartsWith(Data.Models.CHD.Constants.SignatureBytes))
|
|
return WrapperType.CHD;
|
|
|
|
#endregion
|
|
|
|
#region CIA
|
|
|
|
if (extension.Equals("cia", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.CIA;
|
|
|
|
#endregion
|
|
|
|
#region Executable
|
|
|
|
// DOS MZ executable file format (and descendants)
|
|
if (magic.StartsWith(Data.Models.MSDOS.Constants.SignatureBytes))
|
|
return WrapperType.Executable;
|
|
|
|
// None of the following are supported yet
|
|
/*
|
|
// Executable and Linkable Format
|
|
if (magic.StartsWith([0x7f, 0x45, 0x4c, 0x46]))
|
|
return WrapperType.Executable;
|
|
|
|
// Mach-O binary (32-bit)
|
|
if (magic.StartsWith([0xfe, 0xed, 0xfa, 0xce]))
|
|
return WrapperType.Executable;
|
|
|
|
// Mach-O binary (32-bit, reverse byte ordering scheme)
|
|
if (magic.StartsWith([0xce, 0xfa, 0xed, 0xfe]))
|
|
return WrapperType.Executable;
|
|
|
|
// Mach-O binary (64-bit)
|
|
if (magic.StartsWith([0xfe, 0xed, 0xfa, 0xcf]))
|
|
return WrapperType.Executable;
|
|
|
|
// Mach-O binary (64-bit, reverse byte ordering scheme)
|
|
if (magic.StartsWith([0xcf, 0xfa, 0xed, 0xfe]))
|
|
return WrapperType.Executable;
|
|
|
|
// Prefrred Executable File Format
|
|
if (magic.StartsWith([0x4a, 0x6f, 0x79, 0x21, 0x70, 0x65, 0x66, 0x66]))
|
|
return WrapperType.Executable;
|
|
*/
|
|
|
|
// DOS MZ executable file format (and descendants)
|
|
if (extension.Equals("exe", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.Executable;
|
|
|
|
// DOS MZ library file format (and descendants)
|
|
if (extension.Equals("dll", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.Executable;
|
|
|
|
#endregion
|
|
|
|
#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
|
|
|
|
// TODO: Use constants from Models here
|
|
#region GCF
|
|
|
|
if (magic.StartsWith([0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00]))
|
|
return WrapperType.GCF;
|
|
|
|
if (extension.Equals("gcf", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.GCF;
|
|
|
|
#endregion
|
|
|
|
#region GCZ
|
|
|
|
// GCZ magic cookie (0xB10BC001 stored little-endian)
|
|
if (magic.StartsWith([0x01, 0xC0, 0x0B, 0xB1]))
|
|
return WrapperType.GCZ;
|
|
|
|
if (extension.Equals("gcz", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.GCZ;
|
|
|
|
#endregion
|
|
|
|
#region GZip
|
|
|
|
if (magic.StartsWith(Data.Models.GZIP.Constants.SignatureBytes))
|
|
return WrapperType.GZip;
|
|
|
|
if (extension.Equals("gz", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.GZip;
|
|
|
|
#endregion
|
|
|
|
#region IniFile
|
|
|
|
if (extension.Equals("ini", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.IniFile;
|
|
|
|
#endregion
|
|
|
|
#region InstallShieldArchiveV3
|
|
|
|
if (magic.StartsWith(Data.Models.InstallShieldArchiveV3.Constants.HeaderSignatureBytes))
|
|
return WrapperType.InstallShieldArchiveV3;
|
|
|
|
if (extension.Equals("z", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.InstallShieldArchiveV3;
|
|
|
|
#endregion
|
|
|
|
#region InstallShieldCAB
|
|
|
|
if (magic.StartsWith(Data.Models.InstallShieldCabinet.Constants.SignatureBytes))
|
|
return WrapperType.InstallShieldCAB;
|
|
|
|
// Both InstallShieldCAB and MicrosoftCAB share the same extension
|
|
|
|
#endregion
|
|
|
|
#region IRD
|
|
|
|
if (magic.StartsWith([0x33, 0x49, 0x52, 0x44]))
|
|
return WrapperType.IRD;
|
|
|
|
if (extension.Equals("ird", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.IRD;
|
|
|
|
#endregion
|
|
|
|
#region ISO9660
|
|
|
|
if (extension.Equals("iso", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.ISO9660;
|
|
|
|
#endregion
|
|
|
|
#region LDSCRYPT
|
|
|
|
if (magic.StartsWith(Data.Models.LDSCRYPT.Constants.SignatureBytes))
|
|
return WrapperType.LDSCRYPT;
|
|
|
|
#endregion
|
|
|
|
#region LZ
|
|
|
|
if (magic.StartsWith(Data.Models.LZ.Constants.KWAJSignatureBytes))
|
|
return WrapperType.LZKWAJ;
|
|
|
|
if (magic.StartsWith(Data.Models.LZ.Constants.QBasicSignatureBytes))
|
|
return WrapperType.LZQBasic;
|
|
|
|
if (magic.StartsWith(Data.Models.LZ.Constants.SZDDSignatureBytes))
|
|
return WrapperType.LZSZDD;
|
|
|
|
#endregion
|
|
|
|
#region MicrosoftCAB
|
|
|
|
if (magic.StartsWith(Data.Models.MicrosoftCabinet.Constants.SignatureBytes))
|
|
return WrapperType.MicrosoftCAB;
|
|
|
|
// Both InstallShieldCAB and MicrosoftCAB share the same extension
|
|
|
|
#endregion
|
|
|
|
#region MoPaQ
|
|
|
|
if (magic.StartsWith(Data.Models.MoPaQ.Constants.ArchiveHeaderSignatureBytes))
|
|
return WrapperType.MoPaQ;
|
|
|
|
if (magic.StartsWith(Data.Models.MoPaQ.Constants.UserDataSignatureBytes))
|
|
return WrapperType.MoPaQ;
|
|
|
|
if (extension.Equals("mpq", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.MoPaQ;
|
|
|
|
#endregion
|
|
|
|
#region N3DS
|
|
|
|
// 3DS cart image
|
|
if (extension.Equals("3ds", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.N3DS;
|
|
|
|
else if (extension.Equals("cci", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.N3DS;
|
|
|
|
// CIA package -- Not currently supported
|
|
// else if (extension.Equals("cia", StringComparison.OrdinalIgnoreCase))
|
|
// return WrapperType.N3DS;
|
|
|
|
#endregion
|
|
|
|
// TODO: Use constants from Models here
|
|
#region NCF
|
|
|
|
if (magic.StartsWith([0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00]))
|
|
return WrapperType.NCF;
|
|
|
|
if (extension.Equals("ncf", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.NCF;
|
|
|
|
#endregion
|
|
|
|
#region NESCart
|
|
|
|
// NES 1.0 and 2.0 image with header
|
|
if (magic.StartsWith(Data.Models.NES.Constants.CartSignatureBytes))
|
|
return WrapperType.NESCart;
|
|
|
|
// NES 1.0 and 2.0 image
|
|
if (extension.Equals("nes", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.NESCart;
|
|
|
|
#endregion
|
|
|
|
#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
|
|
|
|
#region Nitro
|
|
|
|
// DS cart image
|
|
if (extension.Equals("nds", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.Nitro;
|
|
|
|
// DS development cart image
|
|
else if (extension.Equals("srl", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.Nitro;
|
|
|
|
// DSi cart image
|
|
else if (extension.Equals("dsi", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.Nitro;
|
|
|
|
// iQue DS cart image
|
|
else if (extension.Equals("ids", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.Nitro;
|
|
|
|
#endregion
|
|
|
|
#region PAK
|
|
|
|
if (magic.StartsWith(Data.Models.PAK.Constants.SignatureBytes))
|
|
return WrapperType.PAK;
|
|
|
|
// Both PAK and Quantum share one extension
|
|
// if (extension.Equals("pak", StringComparison.OrdinalIgnoreCase))
|
|
// return WrapperType.PAK;
|
|
|
|
#endregion
|
|
|
|
// TODO: Use constants from Models here
|
|
#region PFF
|
|
|
|
// Version 0
|
|
if (magic.StartsWith([0x14, 0x00, 0x00, 0x00, 0x50, 0x46, 0x46, 0x30]))
|
|
return WrapperType.PFF;
|
|
|
|
// Version 2
|
|
if (magic.StartsWith([0x14, 0x00, 0x00, 0x00, 0x50, 0x46, 0x46, 0x32]))
|
|
return WrapperType.PFF;
|
|
|
|
// Version 3
|
|
if (magic.StartsWith([0x14, 0x00, 0x00, 0x00, 0x50, 0x46, 0x46, 0x33]))
|
|
return WrapperType.PFF;
|
|
|
|
// Version 4
|
|
if (magic.StartsWith([0x14, 0x00, 0x00, 0x00, 0x50, 0x46, 0x46, 0x34]))
|
|
return WrapperType.PFF;
|
|
|
|
if (extension.Equals("pff", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.PFF;
|
|
|
|
#endregion
|
|
|
|
// TODO: Use constants from Models here
|
|
#region PKZIP
|
|
|
|
// PKZIP (Unknown)
|
|
if (magic.StartsWith([0x50, 0x4b, 0x00, 0x00]))
|
|
return WrapperType.PKZIP;
|
|
|
|
// PKZIP
|
|
if (magic.StartsWith(Data.Models.PKZIP.Constants.LocalFileHeaderSignatureBytes))
|
|
return WrapperType.PKZIP;
|
|
|
|
// PKZIP (Empty Archive)
|
|
if (magic.StartsWith(Data.Models.PKZIP.Constants.EndOfCentralDirectoryRecordSignatureBytes))
|
|
return WrapperType.PKZIP;
|
|
else if (magic.StartsWith(Data.Models.PKZIP.Constants.EndOfCentralDirectoryRecord64SignatureBytes))
|
|
return WrapperType.PKZIP;
|
|
|
|
// PKZIP (Spanned Archive)
|
|
if (magic.StartsWith(Data.Models.PKZIP.Constants.DataDescriptorSignatureBytes))
|
|
return WrapperType.PKZIP;
|
|
|
|
// PKZIP
|
|
if (extension.Equals("zip", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.PKZIP;
|
|
|
|
// Android package
|
|
if (extension.Equals("apk", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.PKZIP;
|
|
|
|
// Java archive
|
|
if (extension.Equals("jar", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.PKZIP;
|
|
|
|
// Google Earth saved working session file
|
|
if (extension.Equals("kmz", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.PKZIP;
|
|
|
|
// KWord document
|
|
if (extension.Equals("kwd", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.PKZIP;
|
|
|
|
// Microsoft Office Open XML Format (OOXML) Document
|
|
if (extension.Equals("docx", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.PKZIP;
|
|
|
|
// Microsoft Office Open XML Format (OOXML) Presentation
|
|
if (extension.Equals("pptx", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.PKZIP;
|
|
|
|
// Microsoft Office Open XML Format (OOXML) Spreadsheet
|
|
if (extension.Equals("xlsx", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.PKZIP;
|
|
|
|
// OpenDocument text document
|
|
if (extension.Equals("odt", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.PKZIP;
|
|
|
|
// OpenDocument presentation
|
|
if (extension.Equals("odp", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.PKZIP;
|
|
|
|
// OpenDocument text document template
|
|
if (extension.Equals("ott", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.PKZIP;
|
|
|
|
// Microsoft Open XML paper specification file
|
|
if (extension.Equals("oxps", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.PKZIP;
|
|
|
|
// OpenOffice spreadsheet
|
|
if (extension.Equals("sxc", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.PKZIP;
|
|
|
|
// OpenOffice drawing
|
|
if (extension.Equals("sxd", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.PKZIP;
|
|
|
|
// OpenOffice presentation
|
|
if (extension.Equals("sxi", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.PKZIP;
|
|
|
|
// OpenOffice word processing
|
|
if (extension.Equals("sxw", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.PKZIP;
|
|
|
|
// StarOffice spreadsheet
|
|
if (extension.Equals("sxc", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.PKZIP;
|
|
|
|
// Windows Media compressed skin file
|
|
if (extension.Equals("wmz", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.PKZIP;
|
|
|
|
// Mozilla Browser Archive
|
|
if (extension.Equals("xpi", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.PKZIP;
|
|
|
|
// XML paper specification file
|
|
if (extension.Equals("xps", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.PKZIP;
|
|
|
|
// eXact Packager Models
|
|
if (extension.Equals("xpt", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.PKZIP;
|
|
|
|
#endregion
|
|
|
|
#region PLJ
|
|
|
|
// https://www.iana.org/assignments/media-types/audio/vnd.everad.plj
|
|
if (magic.StartsWith(Data.Models.PlayJ.Constants.SignatureBytes))
|
|
return WrapperType.PlayJAudioFile;
|
|
|
|
// https://www.iana.org/assignments/media-types/audio/vnd.everad.plj
|
|
if (extension.Equals("plj", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.PlayJAudioFile;
|
|
|
|
#endregion
|
|
|
|
#region QD
|
|
|
|
if (extension.Equals("qd", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.QD;
|
|
|
|
#endregion
|
|
|
|
#region Quantum
|
|
|
|
if (magic.StartsWith(Data.Models.Quantum.Constants.SignatureBytes))
|
|
return WrapperType.Quantum;
|
|
|
|
if (extension.Equals("q", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.Quantum;
|
|
|
|
// Both PAK and Quantum share one extension
|
|
// if (extension.Equals("pak", StringComparison.OrdinalIgnoreCase))
|
|
// return WrapperType.Quantum;
|
|
|
|
#endregion
|
|
|
|
#region RAR
|
|
|
|
// RAR archive version 1.50 onwards
|
|
if (magic.StartsWith(Data.Models.RAR.Constants.OldSignatureBytes))
|
|
return WrapperType.RAR;
|
|
|
|
// RAR archive version 5.0 onwards
|
|
if (magic.StartsWith(Data.Models.RAR.Constants.NewSignatureBytes))
|
|
return WrapperType.RAR;
|
|
|
|
if (extension.Equals("rar", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.RAR;
|
|
|
|
#endregion
|
|
|
|
#region RealArcade
|
|
|
|
// RASGI2.0
|
|
// Found in the ".rgs files in IA item "Nova_RealArcadeCD_USA".
|
|
if (magic.StartsWith(Data.Models.RealArcade.Constants.RgsSignatureBytes))
|
|
return WrapperType.RealArcadeInstaller;
|
|
|
|
// XZip2.0
|
|
// Found in the ".mez" files in IA item "Nova_RealArcadeCD_USA".
|
|
if (magic.StartsWith(Data.Models.RealArcade.Constants.MezzanineSignatureBytes))
|
|
return WrapperType.RealArcadeMezzanine;
|
|
|
|
#endregion
|
|
|
|
#region SecuROM DFA
|
|
|
|
if (magic.StartsWith(Data.Models.SecuROM.Constants.DFAMagicBytes))
|
|
return WrapperType.SecuROMDFA;
|
|
|
|
#endregion
|
|
|
|
#region SevenZip
|
|
|
|
if (magic.StartsWith(Data.Models.SevenZip.Constants.SignatureBytes))
|
|
return WrapperType.SevenZip;
|
|
|
|
if (extension.Equals("7z", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.SevenZip;
|
|
|
|
#endregion
|
|
|
|
#region SFFS
|
|
|
|
// Found in Redump entry 81756, confirmed to be "StarForce Filesystem" by PiD.
|
|
if (magic.StartsWith(Data.Models.StarForce.Constants.SignatureBytes))
|
|
return WrapperType.SFFS;
|
|
|
|
#endregion
|
|
|
|
#region SkuSis
|
|
|
|
if (magic.StartsWith(Data.Models.VDF.Constants.SteamSimSidSisSignatureBytes))
|
|
return WrapperType.SkuSis;
|
|
|
|
if (magic.StartsWith(Data.Models.VDF.Constants.SteamCsmCsdSisSignatureBytes))
|
|
return WrapperType.SkuSis;
|
|
|
|
#endregion
|
|
|
|
#region SGA
|
|
|
|
if (magic.StartsWith(Data.Models.SGA.Constants.SignatureBytes))
|
|
return WrapperType.SGA;
|
|
|
|
if (extension.Equals("sga", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.SGA;
|
|
|
|
#endregion
|
|
|
|
#region Skeleton
|
|
|
|
if (extension.Equals("skeleton", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.Skeleton;
|
|
|
|
#endregion
|
|
|
|
#region Steam2Installer
|
|
|
|
if (magic.StartsWith(Data.Models.Steam2Installer.Constants.SimSignatureBytes))
|
|
return WrapperType.Steam2Installer;
|
|
|
|
#endregion
|
|
|
|
#region STFS
|
|
|
|
// LIVE
|
|
if (magic.StartsWith(Data.Models.STFS.Constants.MagicBytesLIVE))
|
|
return WrapperType.STFS;
|
|
|
|
// PIRS
|
|
if (magic.StartsWith(Data.Models.STFS.Constants.MagicBytesPIRS))
|
|
return WrapperType.STFS;
|
|
|
|
// "CON "
|
|
if (magic.StartsWith(Data.Models.STFS.Constants.MagicBytesCON))
|
|
return WrapperType.STFS;
|
|
|
|
#endregion
|
|
|
|
// TODO: Use constants from Models here
|
|
#region TapeArchive
|
|
|
|
if (magic.StartsWith([0x75, 0x73, 0x74, 0x61, 0x72, 0x00, 0x30, 0x30]))
|
|
return WrapperType.TapeArchive;
|
|
|
|
if (magic.StartsWith([0x75, 0x73, 0x74, 0x61, 0x72, 0x20, 0x20, 0x00]))
|
|
return WrapperType.TapeArchive;
|
|
|
|
if (extension.Equals("tar", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.TapeArchive;
|
|
|
|
#endregion
|
|
|
|
// TODO: Use constants from Models here
|
|
#region Textfile
|
|
|
|
// Not all textfiles can be determined through magic number
|
|
|
|
// HTML
|
|
if (magic.StartsWith([0x3c, 0x68, 0x74, 0x6d, 0x6c]))
|
|
return WrapperType.Textfile;
|
|
|
|
// HTML and XML
|
|
if (magic.StartsWith([0x3c, 0x21, 0x44, 0x4f, 0x43, 0x54, 0x59, 0x50, 0x45]))
|
|
return WrapperType.Textfile;
|
|
|
|
// InstallShield Compiled Rules
|
|
if (magic.StartsWith([0x61, 0x4C, 0x75, 0x5A]))
|
|
return WrapperType.Textfile;
|
|
|
|
// Microsoft Office File (old)
|
|
if (magic.StartsWith([0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1]))
|
|
return WrapperType.Textfile;
|
|
|
|
// Rich Text File
|
|
if (magic.StartsWith([0x7b, 0x5c, 0x72, 0x74, 0x66, 0x31]))
|
|
return WrapperType.Textfile;
|
|
|
|
// Windows Help File
|
|
if (magic.StartsWith([0x3F, 0x5F, 0x03, 0x00]))
|
|
return WrapperType.Textfile;
|
|
|
|
// XML
|
|
// "<?xml"
|
|
if (magic.StartsWith([0x3C, 0x3F, 0x78, 0x6D, 0x6C]))
|
|
return WrapperType.Textfile;
|
|
|
|
// "<?xml" in UTF-16 encoding
|
|
if (magic.StartsWith([0x3C, 0x00, 0x3F, 0x00, 0x78, 0x00, 0x6D, 0x00, 0x6C, 0x00]))
|
|
return WrapperType.Textfile;
|
|
|
|
// "<?xml" in UTF-16 encoding with byte order marks
|
|
if (magic.StartsWith([0xFF, 0xFE, 0x3C, 0x00, 0x3F, 0x00, 0x78, 0x00, 0x6D, 0x00, 0x6C, 0x00]))
|
|
return WrapperType.Textfile;
|
|
|
|
// "Description in Zip"
|
|
if (extension.Equals("diz", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.Textfile;
|
|
|
|
// Generic textfile (no header)
|
|
if (extension.Equals("txt", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.Textfile;
|
|
|
|
// HTML
|
|
if (extension.Equals("htm", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.Textfile;
|
|
if (extension.Equals("html", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.Textfile;
|
|
|
|
// InstallShield Script
|
|
if (extension.Equals("ins", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.Textfile;
|
|
|
|
// Microsoft Office File (old)
|
|
if (extension.Equals("doc", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.Textfile;
|
|
|
|
// Property list
|
|
if (extension.Equals("plist", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.Textfile;
|
|
|
|
// Rich Text File
|
|
if (extension.Equals("rtf", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.Textfile;
|
|
|
|
// Setup information
|
|
if (extension.Equals("inf", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.Textfile;
|
|
|
|
// Windows Help File
|
|
if (extension.Equals("hlp", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.Textfile;
|
|
|
|
// WZC
|
|
if (extension.Equals("wzc", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.Textfile;
|
|
|
|
// XML
|
|
if (extension.Equals("xml", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.Textfile;
|
|
|
|
#endregion
|
|
|
|
#region VBSP
|
|
|
|
if (magic.StartsWith(Data.Models.BSP.Constants.SignatureBytes))
|
|
return WrapperType.VBSP;
|
|
|
|
// Shares an extension with BSP
|
|
if (extension.Equals("bsp", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.VBSP;
|
|
|
|
#endregion
|
|
|
|
#region VPK
|
|
|
|
if (magic.StartsWith(Data.Models.VPK.Constants.SignatureBytes))
|
|
return WrapperType.VPK;
|
|
|
|
// Common extension so this cannot be used accurately
|
|
// if (extension.Equals("vpk", StringComparison.OrdinalIgnoreCase))
|
|
// return WrapperType.VPK;
|
|
|
|
#endregion
|
|
|
|
#region WAD
|
|
|
|
if (magic.StartsWith(Data.Models.WAD3.Constants.SignatureBytes))
|
|
return WrapperType.WAD;
|
|
|
|
// Common extension so this cannot be used accurately
|
|
// if (extension.Equals("wad", StringComparison.OrdinalIgnoreCase))
|
|
// return WrapperType.WAD;
|
|
|
|
#endregion
|
|
|
|
#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
|
|
|
|
#region XboxExecutable
|
|
|
|
if (magic.StartsWith(Data.Models.XboxExecutable.Constants.MagicBytes))
|
|
return WrapperType.XboxExecutable;
|
|
|
|
if (extension.Equals("xbe", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.XboxExecutable;
|
|
|
|
#endregion
|
|
|
|
#region XDVDFS
|
|
|
|
// TODO: Overload .iso wrapper to call both ISO and XDVDFS wrappers if present
|
|
|
|
if (extension.Equals("xiso", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.XDVDFS;
|
|
|
|
if (extension.Equals("xdvdfs", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.XDVDFS;
|
|
|
|
#endregion
|
|
|
|
#region XenonExecutable
|
|
|
|
if (magic.StartsWith(Data.Models.XenonExecutable.Constants.MagicBytes))
|
|
return WrapperType.XenonExecutable;
|
|
|
|
if (extension.Equals("xex", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.XenonExecutable;
|
|
|
|
#endregion
|
|
|
|
#region XRD
|
|
|
|
if (magic.StartsWith(Data.Models.XRD.Constants.MagicBytes))
|
|
return WrapperType.XRD;
|
|
|
|
if (extension.Equals("xrd", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.XRD;
|
|
|
|
#endregion
|
|
|
|
#region XZ
|
|
|
|
if (magic.StartsWith(Data.Models.XZ.Constants.HeaderSignatureBytes))
|
|
return WrapperType.XZ;
|
|
|
|
if (extension.Equals("xz", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.XZ;
|
|
|
|
#endregion
|
|
|
|
#region XZP
|
|
|
|
if (magic.StartsWith(Data.Models.XZP.Constants.HeaderSignatureBytes))
|
|
return WrapperType.XZP;
|
|
|
|
if (extension.Equals("xzp", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.XZP;
|
|
|
|
#endregion
|
|
|
|
#region ZSTD
|
|
|
|
// ZArchive magic is the final 4 bytes of the file: [0x16, 0x9F, 0x52, 0xD6]
|
|
|
|
if (extension.Equals("zar", StringComparison.OrdinalIgnoreCase))
|
|
return WrapperType.ZArchive;
|
|
|
|
#endregion
|
|
|
|
#region ZSTD
|
|
|
|
if (magic.StartsWith([null, 0xB5, 0x2F, 0xFD]))
|
|
return WrapperType.ZSTD;
|
|
|
|
#endregion
|
|
|
|
// We couldn't find a supported match
|
|
return WrapperType.UNKNOWN;
|
|
}
|
|
|
|
/// <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);
|
|
}
|
|
}
|
|
}
|