diff --git a/SabreTools.Serialization/Wrappers/WrapperFactory.cs b/SabreTools.Serialization/Wrappers/WrapperFactory.cs index 79c6557e..6f4ed2f8 100644 --- a/SabreTools.Serialization/Wrappers/WrapperFactory.cs +++ b/SabreTools.Serialization/Wrappers/WrapperFactory.cs @@ -116,15 +116,18 @@ namespace SabreTools.Serialization.Wrappers } /// - /// Get the supported file type for a magic string + /// Get the supported file type for a magic string and an extension /// /// Recommend sending in 16 bytes to check - public static WrapperType GetFileType(byte[] magic) + public static WrapperType GetFileType(byte[]? magic, string? extension) { - // If we have an invalid magic byte array - if (magic == null || magic.Length == 0) + // If we have an invalid magic byte array and extension + if (magic == null || magic.Length == 0 || extension == null) return WrapperType.UNKNOWN; + // Normalize the extension + extension = extension.TrimStart('.').Trim(); + // TODO: For all modelled types, use the constants instead of hardcoded values here #region AACSMediaKeyBlock @@ -136,6 +139,15 @@ namespace SabreTools.Serialization.Wrappers if (magic.StartsWith(new byte?[] { 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 #region BDPlusSVM @@ -143,6 +155,9 @@ namespace SabreTools.Serialization.Wrappers if (magic.StartsWith(new byte?[] { 0x42, 0x44, 0x53, 0x56, 0x4D, 0x5F, 0x43, 0x43 })) return WrapperType.BDPlusSVM; + if (extension.Equals("svm", StringComparison.OrdinalIgnoreCase)) + return WrapperType.BDPlusSVM; + #endregion #region BFPK @@ -154,7 +169,14 @@ namespace SabreTools.Serialization.Wrappers #region BSP - if (magic.StartsWith(new byte?[] { 0x1e, 0x00, 0x00, 0x00 })) + // Shares a first 4 bytes with some .mc files + // Shares an extension with VBSP + if (magic.StartsWith(new byte?[] { 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(new byte?[] { 0x1e, 0x00, 0x00, 0x00 }) && extension.Equals("bsp", StringComparison.OrdinalIgnoreCase)) return WrapperType.BSP; #endregion @@ -164,6 +186,9 @@ namespace SabreTools.Serialization.Wrappers if (magic.StartsWith(new byte?[] { 0x42, 0x52, 0x68 })) return WrapperType.BZip2; + if (extension.Equals("bz2", StringComparison.OrdinalIgnoreCase)) + return WrapperType.BZip2; + #endregion #region CFB @@ -171,11 +196,32 @@ namespace SabreTools.Serialization.Wrappers if (magic.StartsWith(new byte?[] { 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1 })) 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 CIA - // No magic checks for CIA + if (extension.Equals("cia", StringComparison.OrdinalIgnoreCase)) + return WrapperType.CIA; #endregion @@ -213,369 +259,6 @@ namespace SabreTools.Serialization.Wrappers return FileTypes.Executable; */ - #endregion - - #region GCF - - if (magic.StartsWith(new byte?[] { 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00 })) - return WrapperType.GCF; - - #endregion - - #region GZIP - - if (magic.StartsWith(new byte?[] { 0x1f, 0x8b })) - return WrapperType.GZIP; - - #endregion - - #region IniFile - - // No magic checks for IniFile - - #endregion - - #region InstallShieldArchiveV3 - - if (magic.StartsWith(new byte?[] { 0x13, 0x5D, 0x65, 0x8C })) - return WrapperType.InstallShieldArchiveV3; - - #endregion - - #region InstallShieldCAB - - if (magic.StartsWith(new byte?[] { 0x49, 0x53, 0x63 })) - return WrapperType.InstallShieldCAB; - - #endregion - - #region LDSCRYPT - - if (magic.StartsWith(new byte?[] { 0x4C, 0x44, 0x53, 0x43, 0x52, 0x59, 0x50, 0x54 })) - return WrapperType.LDSCRYPT; - - #endregion - - #region MicrosoftCAB - - if (magic.StartsWith(new byte?[] { 0x4d, 0x53, 0x43, 0x46 })) - return WrapperType.MicrosoftCAB; - - #endregion - - #region MicrosoftLZ - - if (magic.StartsWith(new byte?[] { 0x53, 0x5a, 0x44, 0x44, 0x88, 0xf0, 0x27, 0x33 })) - return WrapperType.MicrosoftLZ; - - #endregion - - #region MPQ - - if (magic.StartsWith(new byte?[] { 0x4d, 0x50, 0x51, 0x1a })) - return WrapperType.MoPaQ; - - if (magic.StartsWith(new byte?[] { 0x4d, 0x50, 0x51, 0x1b })) - return WrapperType.MoPaQ; - - #endregion - - #region N3DS - - // No magic checks for N3DS - - #endregion - - #region NCF - - if (magic.StartsWith(new byte?[] { 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00 })) - return WrapperType.NCF; - - #endregion - - #region Nitro - - // No magic checks for Nitro - - #endregion - - #region PAK - - if (magic.StartsWith(new byte?[] { 0x50, 0x41, 0x43, 0x4B })) - return WrapperType.PAK; - - #endregion - - #region PFF - - // Version 2 - if (magic.StartsWith(new byte?[] { 0x14, 0x00, 0x00, 0x00, 0x50, 0x46, 0x46, 0x32 })) - return WrapperType.PFF; - - // Version 3 - if (magic.StartsWith(new byte?[] { 0x14, 0x00, 0x00, 0x00, 0x50, 0x46, 0x46, 0x33 })) - return WrapperType.PFF; - - // Version 4 - if (magic.StartsWith(new byte?[] { 0x14, 0x00, 0x00, 0x00, 0x50, 0x46, 0x46, 0x34 })) - return WrapperType.PFF; - - #endregion - - #region PKZIP - - // PKZIP (Unknown) - if (magic.StartsWith(new byte?[] { 0x50, 0x4b, 0x00, 0x00 })) - return WrapperType.PKZIP; - - // PKZIP - if (magic.StartsWith(new byte?[] { 0x50, 0x4b, 0x03, 0x04 })) - return WrapperType.PKZIP; - - // PKZIP (Empty Archive) - if (magic.StartsWith(new byte?[] { 0x50, 0x4b, 0x05, 0x06 })) - return WrapperType.PKZIP; - - // PKZIP (Spanned Archive) - if (magic.StartsWith(new byte?[] { 0x50, 0x4b, 0x07, 0x08 })) - return WrapperType.PKZIP; - - #endregion - - #region PLJ - - // https://www.iana.org/assignments/media-types/audio/vnd.everad.plj - if (magic.StartsWith(new byte?[] { 0xFF, 0x9D, 0x53, 0x4B })) - return WrapperType.PlayJAudioFile; - - #endregion - - #region Quantum - - if (magic.StartsWith(new byte?[] { 0x44, 0x53 })) - return WrapperType.Quantum; - - #endregion - - #region RAR - - // RAR archive version 1.50 onwards - if (magic.StartsWith(new byte?[] { 0x52, 0x61, 0x72, 0x21, 0x1a, 0x07, 0x00 })) - return WrapperType.RAR; - - // RAR archive version 5.0 onwards - if (magic.StartsWith(new byte?[] { 0x52, 0x61, 0x72, 0x21, 0x1a, 0x07, 0x01, 0x00 })) - return WrapperType.RAR; - - #endregion - - #region RealArcade - - // RASGI2.0 - // Found in the ".rgs files in IA item "Nova_RealArcadeCD_USA". - if (magic.StartsWith(new byte?[] { 0x52, 0x41, 0x53, 0x47, 0x49, 0x32, 0x2E, 0x30 })) - return WrapperType.RealArcadeInstaller; - - // XZip2.0 - // Found in the ".mez" files in IA item "Nova_RealArcadeCD_USA". - if (magic.StartsWith(new byte?[] { 0x58, 0x5A, 0x69, 0x70, 0x32, 0x2E, 0x30 })) - return WrapperType.RealArcadeMezzanine; - - #endregion - - #region SevenZip - - if (magic.StartsWith(new byte?[] { 0x37, 0x7a, 0xbc, 0xaf, 0x27, 0x1c })) - return WrapperType.SevenZip; - - #endregion - - #region SFFS - - // Found in Redump entry 81756, confirmed to be "StarForce Filesystem" by PiD. - if (magic.StartsWith(new byte?[] { 0x53, 0x46, 0x46, 0x53 })) - return WrapperType.SFFS; - - #endregion - - #region SGA - - if (magic.StartsWith(new byte?[] { 0x5F, 0x41, 0x52, 0x43, 0x48, 0x49, 0x56, 0x45 })) - return WrapperType.SGA; - - #endregion - - #region TapeArchive - - if (magic.StartsWith(new byte?[] { 0x75, 0x73, 0x74, 0x61, 0x72, 0x00, 0x30, 0x30 })) - return WrapperType.TapeArchive; - - if (magic.StartsWith(new byte?[] { 0x75, 0x73, 0x74, 0x61, 0x72, 0x20, 0x20, 0x00 })) - return WrapperType.TapeArchive; - - #endregion - - #region Textfile - - // Not all textfiles can be determined through magic number - - // HTML - if (magic.StartsWith(new byte?[] { 0x3c, 0x68, 0x74, 0x6d, 0x6c })) - return WrapperType.Textfile; - - // HTML and XML - if (magic.StartsWith(new byte?[] { 0x3c, 0x21, 0x44, 0x4f, 0x43, 0x54, 0x59, 0x50, 0x45 })) - return WrapperType.Textfile; - - // InstallShield Compiled Rules - if (magic.StartsWith(new byte?[] { 0x61, 0x4C, 0x75, 0x5A })) - return WrapperType.Textfile; - - // Microsoft Office File (old) - if (magic.StartsWith(new byte?[] { 0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1 })) - return WrapperType.Textfile; - - // Rich Text File - if (magic.StartsWith(new byte?[] { 0x7b, 0x5c, 0x72, 0x74, 0x66, 0x31 })) - return WrapperType.Textfile; - - // Windows Help File - if (magic.StartsWith(new byte?[] { 0x3F, 0x5F, 0x03, 0x00 })) - return WrapperType.Textfile; - - // XML - // " - /// Get the supported file type for an extension - /// - /// This is less accurate than a magic string match - public static WrapperType GetFileType(string extension) - { - // If we have an invalid extension - if (string.IsNullOrEmpty(extension)) - return WrapperType.UNKNOWN; - - // Normalize the extension - extension = extension.TrimStart('.').Trim(); - - #region 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 - - #region BDPlusSVM - - if (extension.Equals("svm", StringComparison.OrdinalIgnoreCase)) - return WrapperType.BDPlusSVM; - - #endregion - - #region BFPK - - // No extensions registered for BFPK - - #endregion - - #region BSP - - // Shares an extension with VBSP so it can't be used accurately - // if (extension.Equals("bsp", StringComparison.OrdinalIgnoreCase)) - // return WrapperType.BSP; - - #endregion - - #region BZip2 - - if (extension.Equals("bz2", StringComparison.OrdinalIgnoreCase)) - return WrapperType.BZip2; - - #endregion - - #region 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 CIA - - if (extension.Equals("cia", StringComparison.OrdinalIgnoreCase)) - return WrapperType.CIA; - - #endregion - - #region Executable - // DOS MZ executable file format (and descendants) if (extension.Equals("exe", StringComparison.OrdinalIgnoreCase)) return WrapperType.Executable; @@ -588,6 +271,9 @@ namespace SabreTools.Serialization.Wrappers #region GCF + if (magic.StartsWith(new byte?[] { 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00 })) + return WrapperType.GCF; + if (extension.Equals("gcf", StringComparison.OrdinalIgnoreCase)) return WrapperType.GCF; @@ -595,6 +281,9 @@ namespace SabreTools.Serialization.Wrappers #region GZIP + if (magic.StartsWith(new byte?[] { 0x1f, 0x8b })) + return WrapperType.GZIP; + if (extension.Equals("gz", StringComparison.OrdinalIgnoreCase)) return WrapperType.GZIP; @@ -609,6 +298,9 @@ namespace SabreTools.Serialization.Wrappers #region InstallShieldArchiveV3 + if (magic.StartsWith(new byte?[] { 0x13, 0x5D, 0x65, 0x8C })) + return WrapperType.InstallShieldArchiveV3; + if (extension.Equals("z", StringComparison.OrdinalIgnoreCase)) return WrapperType.InstallShieldArchiveV3; @@ -616,19 +308,43 @@ namespace SabreTools.Serialization.Wrappers #region InstallShieldCAB - // No extensions registered for InstallShieldCAB + if (magic.StartsWith(new byte?[] { 0x49, 0x53, 0x63 })) + return WrapperType.InstallShieldCAB; + // Both InstallShieldCAB and MicrosoftCAB share the same extension #endregion + #region LDSCRYPT + + if (magic.StartsWith(new byte?[] { 0x4C, 0x44, 0x53, 0x43, 0x52, 0x59, 0x50, 0x54 })) + return WrapperType.LDSCRYPT; + + #endregion + #region MicrosoftCAB - // No extensions registered for InstallShieldCAB + if (magic.StartsWith(new byte?[] { 0x4d, 0x53, 0x43, 0x46 })) + return WrapperType.MicrosoftCAB; + // Both InstallShieldCAB and MicrosoftCAB share the same extension #endregion - #region MPQ + #region MicrosoftLZ + + if (magic.StartsWith(new byte?[] { 0x53, 0x5a, 0x44, 0x44, 0x88, 0xf0, 0x27, 0x33 })) + return WrapperType.MicrosoftLZ; + + #endregion + + #region MoPaQ + + if (magic.StartsWith(new byte?[] { 0x4d, 0x50, 0x51, 0x1a })) + return WrapperType.MoPaQ; + + if (magic.StartsWith(new byte?[] { 0x4d, 0x50, 0x51, 0x1b })) + return WrapperType.MoPaQ; if (extension.Equals("mpq", StringComparison.OrdinalIgnoreCase)) return WrapperType.MoPaQ; @@ -649,6 +365,9 @@ namespace SabreTools.Serialization.Wrappers #region NCF + if (magic.StartsWith(new byte?[] { 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00 })) + return WrapperType.NCF; + if (extension.Equals("ncf", StringComparison.OrdinalIgnoreCase)) return WrapperType.NCF; @@ -676,7 +395,9 @@ namespace SabreTools.Serialization.Wrappers #region PAK - // No extensions registered for PAK + if (magic.StartsWith(new byte?[] { 0x50, 0x41, 0x43, 0x4B })) + return WrapperType.PAK; + // Both PAK and Quantum share one extension // if (extension.Equals("pak", StringComparison.OrdinalIgnoreCase)) // return WrapperType.PAK; @@ -685,6 +406,18 @@ namespace SabreTools.Serialization.Wrappers #region PFF + // Version 2 + if (magic.StartsWith(new byte?[] { 0x14, 0x00, 0x00, 0x00, 0x50, 0x46, 0x46, 0x32 })) + return WrapperType.PFF; + + // Version 3 + if (magic.StartsWith(new byte?[] { 0x14, 0x00, 0x00, 0x00, 0x50, 0x46, 0x46, 0x33 })) + return WrapperType.PFF; + + // Version 4 + if (magic.StartsWith(new byte?[] { 0x14, 0x00, 0x00, 0x00, 0x50, 0x46, 0x46, 0x34 })) + return WrapperType.PFF; + if (extension.Equals("pff", StringComparison.OrdinalIgnoreCase)) return WrapperType.PFF; @@ -692,6 +425,22 @@ namespace SabreTools.Serialization.Wrappers #region PKZIP + // PKZIP (Unknown) + if (magic.StartsWith(new byte?[] { 0x50, 0x4b, 0x00, 0x00 })) + return WrapperType.PKZIP; + + // PKZIP + if (magic.StartsWith(new byte?[] { 0x50, 0x4b, 0x03, 0x04 })) + return WrapperType.PKZIP; + + // PKZIP (Empty Archive) + if (magic.StartsWith(new byte?[] { 0x50, 0x4b, 0x05, 0x06 })) + return WrapperType.PKZIP; + + // PKZIP (Spanned Archive) + if (magic.StartsWith(new byte?[] { 0x50, 0x4b, 0x07, 0x08 })) + return WrapperType.PKZIP; + // PKZIP if (extension.Equals("zip", StringComparison.OrdinalIgnoreCase)) return WrapperType.PKZIP; @@ -780,6 +529,10 @@ namespace SabreTools.Serialization.Wrappers #region PLJ + // https://www.iana.org/assignments/media-types/audio/vnd.everad.plj + if (magic.StartsWith(new byte?[] { 0xFF, 0x9D, 0x53, 0x4B })) + return WrapperType.PlayJAudioFile; + // https://www.iana.org/assignments/media-types/audio/vnd.everad.plj if (extension.Equals("plj", StringComparison.OrdinalIgnoreCase)) return WrapperType.PlayJAudioFile; @@ -788,6 +541,9 @@ namespace SabreTools.Serialization.Wrappers #region Quantum + if (magic.StartsWith(new byte?[] { 0x44, 0x53 })) + return WrapperType.Quantum; + if (extension.Equals("q", StringComparison.OrdinalIgnoreCase)) return WrapperType.Quantum; @@ -799,20 +555,56 @@ namespace SabreTools.Serialization.Wrappers #region RAR + // RAR archive version 1.50 onwards + if (magic.StartsWith(new byte?[] { 0x52, 0x61, 0x72, 0x21, 0x1a, 0x07, 0x00 })) + return WrapperType.RAR; + + // RAR archive version 5.0 onwards + if (magic.StartsWith(new byte?[] { 0x52, 0x61, 0x72, 0x21, 0x1a, 0x07, 0x01, 0x00 })) + 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(new byte?[] { 0x52, 0x41, 0x53, 0x47, 0x49, 0x32, 0x2E, 0x30 })) + return WrapperType.RealArcadeInstaller; + + // XZip2.0 + // Found in the ".mez" files in IA item "Nova_RealArcadeCD_USA". + if (magic.StartsWith(new byte?[] { 0x58, 0x5A, 0x69, 0x70, 0x32, 0x2E, 0x30 })) + return WrapperType.RealArcadeMezzanine; + + #endregion + #region SevenZip + if (magic.StartsWith(new byte?[] { 0x37, 0x7a, 0xbc, 0xaf, 0x27, 0x1c })) + 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(new byte?[] { 0x53, 0x46, 0x46, 0x53 })) + return WrapperType.SFFS; + + #endregion + #region SGA + if (magic.StartsWith(new byte?[] { 0x5F, 0x41, 0x52, 0x43, 0x48, 0x49, 0x56, 0x45 })) + return WrapperType.SGA; + if (extension.Equals("sga", StringComparison.OrdinalIgnoreCase)) return WrapperType.SGA; @@ -820,6 +612,12 @@ namespace SabreTools.Serialization.Wrappers #region TapeArchive + if (magic.StartsWith(new byte?[] { 0x75, 0x73, 0x74, 0x61, 0x72, 0x00, 0x30, 0x30 })) + return WrapperType.TapeArchive; + + if (magic.StartsWith(new byte?[] { 0x75, 0x73, 0x74, 0x61, 0x72, 0x20, 0x20, 0x00 })) + return WrapperType.TapeArchive; + if (extension.Equals("tar", StringComparison.OrdinalIgnoreCase)) return WrapperType.SevenZip; @@ -827,6 +625,37 @@ namespace SabreTools.Serialization.Wrappers #region Textfile + // Not all textfiles can be determined through magic number + + // HTML + if (magic.StartsWith(new byte?[] { 0x3c, 0x68, 0x74, 0x6d, 0x6c })) + return WrapperType.Textfile; + + // HTML and XML + if (magic.StartsWith(new byte?[] { 0x3c, 0x21, 0x44, 0x4f, 0x43, 0x54, 0x59, 0x50, 0x45 })) + return WrapperType.Textfile; + + // InstallShield Compiled Rules + if (magic.StartsWith(new byte?[] { 0x61, 0x4C, 0x75, 0x5A })) + return WrapperType.Textfile; + + // Microsoft Office File (old) + if (magic.StartsWith(new byte?[] { 0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1 })) + return WrapperType.Textfile; + + // Rich Text File + if (magic.StartsWith(new byte?[] { 0x7b, 0x5c, 0x72, 0x74, 0x66, 0x31 })) + return WrapperType.Textfile; + + // Windows Help File + if (magic.StartsWith(new byte?[] { 0x3F, 0x5F, 0x03, 0x00 })) + return WrapperType.Textfile; + + // XML + // "