From b7a8cac71ed62473d39b543448e256430a987cd1 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Thu, 18 Apr 2024 12:35:39 -0400 Subject: [PATCH] Port test code from BOS --- SabreTools.Printing.sln | 6 + Test/FileTypes.cs | 812 ++++++++++++++++++++++++++++++++++++++ Test/Options.cs | 119 ++++++ Test/Program.cs | 141 +++++++ Test/SupportedFileType.cs | 203 ++++++++++ Test/Test.csproj | 24 ++ Test/WrapperFactory.cs | 116 ++++++ 7 files changed, 1421 insertions(+) create mode 100644 Test/FileTypes.cs create mode 100644 Test/Options.cs create mode 100644 Test/Program.cs create mode 100644 Test/SupportedFileType.cs create mode 100644 Test/Test.csproj create mode 100644 Test/WrapperFactory.cs diff --git a/SabreTools.Printing.sln b/SabreTools.Printing.sln index 479fc7b..3bf45a5 100644 --- a/SabreTools.Printing.sln +++ b/SabreTools.Printing.sln @@ -5,6 +5,8 @@ VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SabreTools.Printing", "SabreTools.Printing\SabreTools.Printing.csproj", "{4274AA02-00A0-48F1-98C4-B3286E547246}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test", "Test\Test.csproj", "{F7DE19C8-9E62-443D-BBD0-A846405E3903}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -18,5 +20,9 @@ Global {4274AA02-00A0-48F1-98C4-B3286E547246}.Debug|Any CPU.Build.0 = Debug|Any CPU {4274AA02-00A0-48F1-98C4-B3286E547246}.Release|Any CPU.ActiveCfg = Release|Any CPU {4274AA02-00A0-48F1-98C4-B3286E547246}.Release|Any CPU.Build.0 = Release|Any CPU + {F7DE19C8-9E62-443D-BBD0-A846405E3903}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F7DE19C8-9E62-443D-BBD0-A846405E3903}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F7DE19C8-9E62-443D-BBD0-A846405E3903}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F7DE19C8-9E62-443D-BBD0-A846405E3903}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/Test/FileTypes.cs b/Test/FileTypes.cs new file mode 100644 index 0000000..66cb8aa --- /dev/null +++ b/Test/FileTypes.cs @@ -0,0 +1,812 @@ +using System; +using SabreTools.Matching; + +namespace Test +{ + internal static class FileTypes + { + /// + /// Get the supported file type for a magic string + /// + /// Recommend sending in 16 bytes to check + public static SupportedFileType GetFileType(byte[] magic) + { + // If we have an invalid magic byte array + if (magic == null || magic.Length == 0) + return SupportedFileType.UNKNOWN; + + // TODO: For all modelled types, use the constants instead of hardcoded values here + #region AACSMediaKeyBlock + + // Block starting with verify media key record + if (magic.StartsWith(new byte?[] { 0x81, 0x00, 0x00, 0x14 })) + return SupportedFileType.AACSMediaKeyBlock; + + // Block starting with type and version record + if (magic.StartsWith(new byte?[] { 0x10, 0x00, 0x00, 0x0C })) + return SupportedFileType.AACSMediaKeyBlock; + + #endregion + + #region BDPlusSVM + + if (magic.StartsWith(new byte?[] { 0x42, 0x44, 0x53, 0x56, 0x4D, 0x5F, 0x43, 0x43 })) + return SupportedFileType.BDPlusSVM; + + #endregion + + #region BFPK + + if (magic.StartsWith(new byte?[] { 0x42, 0x46, 0x50, 0x4b })) + return SupportedFileType.BFPK; + + #endregion + + #region BSP + + if (magic.StartsWith(new byte?[] { 0x1e, 0x00, 0x00, 0x00 })) + return SupportedFileType.BSP; + + #endregion + + #region BZip2 + + if (magic.StartsWith(new byte?[] { 0x42, 0x52, 0x68 })) + return SupportedFileType.BZip2; + + #endregion + + #region CFB + + if (magic.StartsWith(new byte?[] { 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1 })) + return SupportedFileType.CFB; + + #endregion + + #region CIA + + // No magic checks for CIA + + #endregion + + #region Executable + + // DOS MZ executable file format (and descendants) + if (magic.StartsWith(new byte?[] { 0x4d, 0x5a })) + return SupportedFileType.Executable; + + /* + // None of the following are supported in scans yet + + // Executable and Linkable Format + if (magic.StartsWith(new byte?[] { 0x7f, 0x45, 0x4c, 0x46 })) + return FileTypes.Executable; + + // Mach-O binary (32-bit) + if (magic.StartsWith(new byte?[] { 0xfe, 0xed, 0xfa, 0xce })) + return FileTypes.Executable; + + // Mach-O binary (32-bit, reverse byte ordering scheme) + if (magic.StartsWith(new byte?[] { 0xce, 0xfa, 0xed, 0xfe })) + return FileTypes.Executable; + + // Mach-O binary (64-bit) + if (magic.StartsWith(new byte?[] { 0xfe, 0xed, 0xfa, 0xcf })) + return FileTypes.Executable; + + // Mach-O binary (64-bit, reverse byte ordering scheme) + if (magic.StartsWith(new byte?[] { 0xcf, 0xfa, 0xed, 0xfe })) + return FileTypes.Executable; + + // Prefrred Executable File Format + if (magic.StartsWith(new byte?[] { 0x4a, 0x6f, 0x79, 0x21, 0x70, 0x65, 0x66, 0x66 })) + return FileTypes.Executable; + */ + + #endregion + + #region GCF + + if (magic.StartsWith(new byte?[] { 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00 })) + return SupportedFileType.GCF; + + #endregion + + #region GZIP + + if (magic.StartsWith(new byte?[] { 0x1f, 0x8b })) + return SupportedFileType.GZIP; + + #endregion + + #region IniFile + + // No magic checks for IniFile + + #endregion + + #region InstallShieldArchiveV3 + + if (magic.StartsWith(new byte?[] { 0x13, 0x5D, 0x65, 0x8C })) + return SupportedFileType.InstallShieldArchiveV3; + + #endregion + + #region InstallShieldCAB + + if (magic.StartsWith(new byte?[] { 0x49, 0x53, 0x63 })) + return SupportedFileType.InstallShieldCAB; + + #endregion + + #region LDSCRYPT + + if (magic.StartsWith(new byte?[] { 0x4C, 0x44, 0x53, 0x43, 0x52, 0x59, 0x50, 0x54 })) + return SupportedFileType.LDSCRYPT; + + #endregion + + #region MicrosoftCAB + + if (magic.StartsWith(new byte?[] { 0x4d, 0x53, 0x43, 0x46 })) + return SupportedFileType.MicrosoftCAB; + + #endregion + + #region MicrosoftLZ + + if (magic.StartsWith(new byte?[] { 0x53, 0x5a, 0x44, 0x44, 0x88, 0xf0, 0x27, 0x33 })) + return SupportedFileType.MicrosoftLZ; + + #endregion + + #region MPQ + + if (magic.StartsWith(new byte?[] { 0x4d, 0x50, 0x51, 0x1a })) + return SupportedFileType.MPQ; + + if (magic.StartsWith(new byte?[] { 0x4d, 0x50, 0x51, 0x1b })) + return SupportedFileType.MPQ; + + #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 SupportedFileType.NCF; + + #endregion + + #region Nitro + + // No magic checks for Nitro + + #endregion + + #region PAK + + if (magic.StartsWith(new byte?[] { 0x50, 0x41, 0x43, 0x4B })) + return SupportedFileType.PAK; + + #endregion + + #region PFF + + // Version 2 + if (magic.StartsWith(new byte?[] { 0x14, 0x00, 0x00, 0x00, 0x50, 0x46, 0x46, 0x32 })) + return SupportedFileType.PFF; + + // Version 3 + if (magic.StartsWith(new byte?[] { 0x14, 0x00, 0x00, 0x00, 0x50, 0x46, 0x46, 0x33 })) + return SupportedFileType.PFF; + + // Version 4 + if (magic.StartsWith(new byte?[] { 0x14, 0x00, 0x00, 0x00, 0x50, 0x46, 0x46, 0x34 })) + return SupportedFileType.PFF; + + #endregion + + #region PKZIP + + // PKZIP (Unknown) + if (magic.StartsWith(new byte?[] { 0x50, 0x4b, 0x00, 0x00 })) + return SupportedFileType.PKZIP; + + // PKZIP + if (magic.StartsWith(new byte?[] { 0x50, 0x4b, 0x03, 0x04 })) + return SupportedFileType.PKZIP; + + // PKZIP (Empty Archive) + if (magic.StartsWith(new byte?[] { 0x50, 0x4b, 0x05, 0x06 })) + return SupportedFileType.PKZIP; + + // PKZIP (Spanned Archive) + if (magic.StartsWith(new byte?[] { 0x50, 0x4b, 0x07, 0x08 })) + return SupportedFileType.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 SupportedFileType.PLJ; + + #endregion + + #region Quantum + + if (magic.StartsWith(new byte?[] { 0x44, 0x53 })) + return SupportedFileType.Quantum; + + #endregion + + #region RAR + + // RAR archive version 1.50 onwards + if (magic.StartsWith(new byte?[] { 0x52, 0x61, 0x72, 0x21, 0x1a, 0x07, 0x00 })) + return SupportedFileType.RAR; + + // RAR archive version 5.0 onwards + if (magic.StartsWith(new byte?[] { 0x52, 0x61, 0x72, 0x21, 0x1a, 0x07, 0x01, 0x00 })) + return SupportedFileType.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 SupportedFileType.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 SupportedFileType.RealArcadeMezzanine; + + #endregion + + #region SevenZip + + if (magic.StartsWith(new byte?[] { 0x37, 0x7a, 0xbc, 0xaf, 0x27, 0x1c })) + return SupportedFileType.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 SupportedFileType.SFFS; + + #endregion + + #region SGA + + if (magic.StartsWith(new byte?[] { 0x5F, 0x41, 0x52, 0x43, 0x48, 0x49, 0x56, 0x45 })) + return SupportedFileType.SGA; + + #endregion + + #region TapeArchive + + if (magic.StartsWith(new byte?[] { 0x75, 0x73, 0x74, 0x61, 0x72, 0x00, 0x30, 0x30 })) + return SupportedFileType.TapeArchive; + + if (magic.StartsWith(new byte?[] { 0x75, 0x73, 0x74, 0x61, 0x72, 0x20, 0x20, 0x00 })) + return SupportedFileType.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 SupportedFileType.Textfile; + + // HTML and XML + if (magic.StartsWith(new byte?[] { 0x3c, 0x21, 0x44, 0x4f, 0x43, 0x54, 0x59, 0x50, 0x45 })) + return SupportedFileType.Textfile; + + // InstallShield Compiled Rules + if (magic.StartsWith(new byte?[] { 0x61, 0x4C, 0x75, 0x5A })) + return SupportedFileType.Textfile; + + // Microsoft Office File (old) + if (magic.StartsWith(new byte?[] { 0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1 })) + return SupportedFileType.Textfile; + + // Rich Text File + if (magic.StartsWith(new byte?[] { 0x7b, 0x5c, 0x72, 0x74, 0x66, 0x31 })) + return SupportedFileType.Textfile; + + // Windows Help File + if (magic.StartsWith(new byte?[] { 0x3F, 0x5F, 0x03, 0x00 })) + return SupportedFileType.Textfile; + + // XML + // " + /// Get the supported file type for an extension + /// + /// This is less accurate than a magic string match + public static SupportedFileType GetFileType(string extension) + { + // If we have an invalid extension + if (string.IsNullOrEmpty(extension)) + return SupportedFileType.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 SupportedFileType.AACSMediaKeyBlock; + + // HD-DVD + if (extension.Equals("aacs", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.AACSMediaKeyBlock; + + #endregion + + #region BDPlusSVM + + if (extension.Equals("svm", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.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 SupportedFileType.BSP; + + #endregion + + #region BZip2 + + if (extension.Equals("bz2", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.BZip2; + + #endregion + + #region CFB + + // Installer package + if (extension.Equals("msi", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.CFB; + + // Merge module + else if (extension.Equals("msm", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.CFB; + + // Patch Package + else if (extension.Equals("msp", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.CFB; + + // Transform + else if (extension.Equals("mst", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.CFB; + + // Patch Creation Properties + else if (extension.Equals("pcp", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.CFB; + + #endregion + + #region CIA + + if (extension.Equals("cia", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.CIA; + + #endregion + + #region Executable + + // DOS MZ executable file format (and descendants) + if (extension.Equals("exe", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.Executable; + + // DOS MZ library file format (and descendants) + if (extension.Equals("dll", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.Executable; + + #endregion + + #region GCF + + if (extension.Equals("gcf", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.GCF; + + #endregion + + #region GZIP + + if (extension.Equals("gz", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.GZIP; + + #endregion + + #region IniFile + + if (extension.Equals("ini", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.IniFile; + + #endregion + + #region InstallShieldArchiveV3 + + if (extension.Equals("z", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.InstallShieldArchiveV3; + + #endregion + + #region InstallShieldCAB + + // No extensions registered for InstallShieldCAB + // Both InstallShieldCAB and MicrosoftCAB share the same extension + + #endregion + + #region MicrosoftCAB + + // No extensions registered for InstallShieldCAB + // Both InstallShieldCAB and MicrosoftCAB share the same extension + + #endregion + + #region MPQ + + if (extension.Equals("mpq", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.MPQ; + + #endregion + + #region N3DS + + // 3DS cart image + if (extension.Equals("3ds", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.N3DS; + + // CIA package -- Not currently supported + // else if (extension.Equals("cia", StringComparison.OrdinalIgnoreCase)) + // return SupportedFileType.N3DS; + + #endregion + + #region NCF + + if (extension.Equals("ncf", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.NCF; + + #endregion + + #region Nitro + + // DS cart image + if (extension.Equals("nds", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.Nitro; + + // DS development cart image + else if (extension.Equals("srl", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.Nitro; + + // DSi cart image + else if (extension.Equals("dsi", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.Nitro; + + // iQue DS cart image + else if (extension.Equals("ids", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.Nitro; + + #endregion + + #region PAK + + // No extensions registered for PAK + // Both PAK and Quantum share one extension + // if (extension.Equals("pak", StringComparison.OrdinalIgnoreCase)) + // return SupportedFileType.PAK; + + #endregion + + #region PFF + + if (extension.Equals("pff", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.PFF; + + #endregion + + #region PKZIP + + // PKZIP + if (extension.Equals("zip", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.PKZIP; + + // Android package + if (extension.Equals("apk", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.PKZIP; + + // Java archive + if (extension.Equals("jar", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.PKZIP; + + // Google Earth saved working session file + if (extension.Equals("kmz", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.PKZIP; + + // KWord document + if (extension.Equals("kwd", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.PKZIP; + + // Microsoft Office Open XML Format (OOXML) Document + if (extension.Equals("docx", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.PKZIP; + + // Microsoft Office Open XML Format (OOXML) Presentation + if (extension.Equals("pptx", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.PKZIP; + + // Microsoft Office Open XML Format (OOXML) Spreadsheet + if (extension.Equals("xlsx", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.PKZIP; + + // OpenDocument text document + if (extension.Equals("odt", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.PKZIP; + + // OpenDocument presentation + if (extension.Equals("odp", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.PKZIP; + + // OpenDocument text document template + if (extension.Equals("ott", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.PKZIP; + + // Microsoft Open XML paper specification file + if (extension.Equals("oxps", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.PKZIP; + + // OpenOffice spreadsheet + if (extension.Equals("sxc", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.PKZIP; + + // OpenOffice drawing + if (extension.Equals("sxd", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.PKZIP; + + // OpenOffice presentation + if (extension.Equals("sxi", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.PKZIP; + + // OpenOffice word processing + if (extension.Equals("sxw", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.PKZIP; + + // StarOffice spreadsheet + if (extension.Equals("sxc", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.PKZIP; + + // Windows Media compressed skin file + if (extension.Equals("wmz", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.PKZIP; + + // Mozilla Browser Archive + if (extension.Equals("xpi", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.PKZIP; + + // XML paper specification file + if (extension.Equals("xps", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.PKZIP; + + // eXact Packager Models + if (extension.Equals("xpt", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.PKZIP; + + #endregion + + #region PLJ + + // https://www.iana.org/assignments/media-types/audio/vnd.everad.plj + if (extension.Equals("plj", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.PLJ; + + #endregion + + #region Quantum + + if (extension.Equals("q", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.Quantum; + + // Both PAK and Quantum share one extension + // if (extension.Equals("pak", StringComparison.OrdinalIgnoreCase)) + // return SupportedFileType.Quantum; + + #endregion + + #region RAR + + if (extension.Equals("rar", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.RAR; + + #endregion + + #region SevenZip + + if (extension.Equals("7z", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.SevenZip; + + #endregion + + #region SGA + + if (extension.Equals("sga", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.SGA; + + #endregion + + #region TapeArchive + + if (extension.Equals("tar", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.SevenZip; + + #endregion + + #region Textfile + + // "Description in Zip" + if (extension.Equals("diz", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.Textfile; + + // Generic textfile (no header) + if (extension.Equals("txt", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.Textfile; + + // HTML + if (extension.Equals("htm", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.Textfile; + if (extension.Equals("html", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.Textfile; + + // InstallShield Script + if (extension.Equals("ins", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.Textfile; + + // Microsoft Office File (old) + if (extension.Equals("doc", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.Textfile; + + // Property list + if (extension.Equals("plist", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.Textfile; + + // Rich Text File + if (extension.Equals("rtf", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.Textfile; + + // Setup information + if (extension.Equals("inf", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.Textfile; + + // Windows Help File + if (extension.Equals("hlp", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.Textfile; + + // WZC + if (extension.Equals("wzc", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.Textfile; + + // XML + if (extension.Equals("xml", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.Textfile; + + + + #endregion + + #region VBSP + + // Shares an extension with BSP so it can't be used accurately + // if (extension.Equals("bsp", StringComparison.OrdinalIgnoreCase)) + // return SupportedFileType.VBSP; + + #endregion + + #region VPK + + // Common extension so this cannot be used accurately + // if (extension.Equals("vpk", StringComparison.OrdinalIgnoreCase)) + // return SupportedFileType.VPK; + + #endregion + + #region WAD + + // Common extension so this cannot be used accurately + // if (extension.Equals("wad", StringComparison.OrdinalIgnoreCase)) + // return SupportedFileType.WAD; + + #endregion + + #region XZ + + if (extension.Equals("xz", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.XZ; + + #endregion + + #region XZP + + if (extension.Equals("xzp", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.XZP; + + #endregion + + // We couldn't find a supported match + return SupportedFileType.UNKNOWN; + } + } +} \ No newline at end of file diff --git a/Test/Options.cs b/Test/Options.cs new file mode 100644 index 0000000..1678ca3 --- /dev/null +++ b/Test/Options.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; + +namespace Test +{ + /// + /// Set of options for the test executable + /// + internal sealed class Options + { + #region Properties + + /// + /// Enable debug output for relevant operations + /// + public bool Debug { get; private set; } = false; + + /// + /// Set of input paths to use for operations + /// + public List InputPaths { get; private set; } = []; + +#if NETCOREAPP3_1_OR_GREATER + /// + /// Enable JSON output + /// + public bool Json { get; private set; } = false; +#endif + + #endregion + + /// + /// Parse commandline arguments into an Options object + /// + public static Options? ParseOptions(string[] args) + { + // If we have invalid arguments + if (args == null || args.Length == 0) + return null; + + // Create an Options object + var options = new Options(); + + // Parse the features + int index = 0; + for (; index < args.Length; index++) + { + string arg = args[index]; + bool featureFound = false; + switch (arg) + { + case "-?": + case "-h": + case "--help": + return null; + + default: + break; + } + + // If the flag wasn't a feature + if (!featureFound) + break; + } + + // Parse the options and paths + for (; index < args.Length; index++) + { + string arg = args[index]; + switch (arg) + { + case "-d": + case "--debug": + options.Debug = true; + break; + + case "-j": + case "--json": +#if NET6_0_OR_GREATER + options.Json = true; +#else + Console.WriteLine("JSON output not available in .NET Framework"); +#endif + break; + + default: + options.InputPaths.Add(arg); + break; + } + } + + // Validate we have any input paths to work on + if (options.InputPaths.Count == 0) + { + Console.WriteLine("At least one path is required!"); + return null; + } + + return options; + } + + /// + /// Display help text + /// + public static void DisplayHelp() + { + Console.WriteLine("SabreTools.Printing Test Program"); + Console.WriteLine(); + Console.WriteLine("test.exe file|directory ..."); + Console.WriteLine(); + Console.WriteLine("Options:"); + Console.WriteLine("-?, -h, --help Display this help text and quit"); + Console.WriteLine("-d, --debug Enable debug mode"); +#if NET6_0_OR_GREATER + Console.WriteLine("-j, --json Print executable info as JSON"); +#endif + } + } +} \ No newline at end of file diff --git a/Test/Program.cs b/Test/Program.cs new file mode 100644 index 0000000..d479d94 --- /dev/null +++ b/Test/Program.cs @@ -0,0 +1,141 @@ +using System; +using System.IO; +using SabreTools.IO.Extensions; +using SabreTools.Printing; + +namespace Test +{ + public static class Program + { + public static void Main(string[] args) + { + // Get the options from the arguments + var options = Options.ParseOptions(args); + + // If we have an invalid state + if (options == null) + { + Options.DisplayHelp(); + Console.WriteLine("Press enter to close the program..."); + Console.ReadLine(); + return; + } + + // Loop through the input paths + foreach (string inputPath in args) + { +#if NETFRAMEWORK + PrintPathInfo(inputPath, false, options.Debug); +#else + PrintPathInfo(inputPath, options.Json, options.Debug); +#endif + } + } + + /// + /// Wrapper to print information for a single path + /// + /// File or directory path + /// Enable JSON output, if supported + /// Enable debug output + private static void PrintPathInfo(string path, bool json, bool debug) + { + Console.WriteLine($"Checking possible path: {path}"); + + // Check if the file or directory exists + if (File.Exists(path)) + { + PrintFileInfo(path, json, debug); + } + else if (Directory.Exists(path)) + { +#if NET20 || NET35 + foreach (string file in Directory.GetFiles(path, "*", SearchOption.AllDirectories)) +#else + foreach (string file in Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories)) +#endif + { + PrintFileInfo(file, json, debug); + } + } + else + { + Console.WriteLine($"{path} does not exist, skipping..."); + } + } + + /// + /// Print information for a single file, if possible + /// + private static void PrintFileInfo(string file, bool json, bool debug) + { + Console.WriteLine($"Attempting to print info for {file}"); + + try + { + using Stream stream = File.Open(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + + // Read the first 8 bytes + byte[]? magic = stream.ReadBytes(8); + stream.Seek(0, SeekOrigin.Begin); + + // Get the file type + SupportedFileType ft = FileTypes.GetFileType(magic ?? []); + if (ft == SupportedFileType.UNKNOWN) + { + string extension = Path.GetExtension(file).TrimStart('.'); + ft = FileTypes.GetFileType(extension); + } + + // Print out the file format + Console.WriteLine($"File format found: {ft}"); + + // Setup the wrapper to print + var wrapper = WrapperFactory.CreateWrapper(ft, stream); + + // If we don't have a wrapper + if (wrapper == null) + { + Console.WriteLine($"Either {ft} is not supported or something went wrong during parsing!"); + Console.WriteLine(); + return; + } + + // Get the base info output name + string filenameBase = $"info-{DateTime.Now:yyyy-MM-dd_HHmmss.ffff}"; + +#if NET6_0_OR_GREATER + // If we have the JSON flag + if (json) + { + // Create the output data + string serializedData = wrapper.ExportJSON(); + Console.WriteLine(serializedData); + + // Write the output data + using var jsw = new StreamWriter(File.OpenWrite($"{filenameBase}.json")); + jsw.WriteLine(serializedData); + } +#endif + + // Create the output data + var builder = wrapper.ExportStringBuilder(); + if (builder == null) + { + Console.WriteLine("No item information could be generated"); + return; + } + + // Write the output data + Console.WriteLine(builder); + using var sw = new StreamWriter(File.OpenWrite($"{filenameBase}.txt")); + sw.WriteLine(builder.ToString()); + } + catch (Exception ex) + { + Console.WriteLine(debug ? ex : "[Exception opening file, please try again]"); + Console.WriteLine(); + } + } + } +} \ No newline at end of file diff --git a/Test/SupportedFileType.cs b/Test/SupportedFileType.cs new file mode 100644 index 0000000..038e09a --- /dev/null +++ b/Test/SupportedFileType.cs @@ -0,0 +1,203 @@ +namespace Test +{ + /// + /// Subset of file types that are supported by the library + /// + internal enum SupportedFileType + { + /// + /// Unknown or unsupported + /// + UNKNOWN, + + /// + /// AACS media key block + /// + AACSMediaKeyBlock, + + /// + /// BD+ SVM + /// + BDPlusSVM, + + /// + /// BFPK custom archive + /// + BFPK, + + /// + /// Half-Life Level + /// + BSP, + + /// + /// bzip2 archive + /// + BZip2, + + /// + /// Compound File Binary + /// + CFB, + + /// + /// CTR Importable Archive + /// + CIA, + + /// + /// Executable or library + /// + Executable, + + /// + /// Half-Life Game Cache File + /// + GCF, + + /// + /// gzip archive + /// + GZIP, + + /// + /// Key-value pair INI file + /// + IniFile, + + /// + /// InstallShield archive v3 + /// + InstallShieldArchiveV3, + + /// + /// InstallShield cabinet file + /// + InstallShieldCAB, + + /// + /// Link Data Security encrypted file + /// + LDSCRYPT, + + /// + /// Microsoft cabinet file + /// + MicrosoftCAB, + + /// + /// Microsoft LZ-compressed file + /// + MicrosoftLZ, + + /// + /// MPQ game data archive + /// + MPQ, + + /// + /// Nintendo 3DS cart image + /// + N3DS, + + /// + /// Half-Life No Cache File + /// + NCF, + + /// + /// Nintendo DS/DSi cart image + /// + Nitro, + + /// + /// Half-Life Package File + /// + PAK, + + /// + /// NovaLogic Game Archive Format + /// + PFF, + + /// + /// PKWARE ZIP archive and derivatives + /// + PKZIP, + + /// + /// PlayJ audio file + /// + PLJ, + + /// + /// Quantum archive + /// + Quantum, + + /// + /// RAR archive + /// + RAR, + + /// + /// RealArcade Installer + /// + RealArcadeInstaller, + + /// + /// RealArcade Mezzanine + /// + RealArcadeMezzanine, + + /// + /// 7-zip archive + /// + SevenZip, + + /// + /// StarForce FileSystem file + /// + SFFS, + + /// + /// SGA + /// + SGA, + + /// + /// Tape archive + /// + TapeArchive, + + /// + /// Various generic textfile formats + /// + Textfile, + + /// + /// Half-Life 2 Level + /// + VBSP, + + /// + /// Valve Package File + /// + VPK, + + /// + /// Half-Life Texture Package File + /// + WAD, + + /// + /// xz archive + /// + XZ, + + /// + /// Xbox Package File + /// + XZP, + } +} \ No newline at end of file diff --git a/Test/Test.csproj b/Test/Test.csproj new file mode 100644 index 0000000..fb85686 --- /dev/null +++ b/Test/Test.csproj @@ -0,0 +1,24 @@ + + + + net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0 + win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64 + Exe + false + false + latest + enable + true + true + + + + + + + + + + + + diff --git a/Test/WrapperFactory.cs b/Test/WrapperFactory.cs new file mode 100644 index 0000000..7b4855e --- /dev/null +++ b/Test/WrapperFactory.cs @@ -0,0 +1,116 @@ +using System.IO; +using SabreTools.IO.Extensions; +using SabreTools.Matching; +using SabreTools.Serialization.Interfaces; +using SabreTools.Serialization.Wrappers; + +namespace Test +{ + internal static class WrapperFactory + { + /// + /// Create an instance of a wrapper based on file type + /// + public static IWrapper? CreateWrapper(SupportedFileType fileType, Stream? data) + { + switch (fileType) + { + case SupportedFileType.AACSMediaKeyBlock: return AACSMediaKeyBlock.Create(data); + case SupportedFileType.BDPlusSVM: return BDPlusSVM.Create(data); + case SupportedFileType.BFPK: return BFPK.Create(data); + case SupportedFileType.BSP: return BSP.Create(data); + //case SupportedFileType.BZip2: return BZip2.Create(data); + case SupportedFileType.CFB: return CFB.Create(data); + case SupportedFileType.CIA: return CIA.Create(data); + case SupportedFileType.Executable: return CreateExecutableWrapper(data); + case SupportedFileType.GCF: return GCF.Create(data); + //case SupportedFileType.GZIP: return GZIP.Create(data); + //case SupportedFileType.IniFile: return IniFile.Create(data); + //case SupportedFileType.InstallShieldArchiveV3: return InstallShieldArchiveV3.Create(data); + case SupportedFileType.InstallShieldCAB: return InstallShieldCabinet.Create(data); + //case SupportedFileType.LDSCRYPT: return LDSCRYPT.Create(data); + case SupportedFileType.MicrosoftCAB: return MicrosoftCabinet.Create(data); + //case SupportedFileType.MicrosoftLZ: return MicrosoftLZ.Create(data); + case SupportedFileType.MPQ: return MoPaQ.Create(data); + case SupportedFileType.N3DS: return N3DS.Create(data); + case SupportedFileType.NCF: return NCF.Create(data); + case SupportedFileType.Nitro: return Nitro.Create(data); + case SupportedFileType.PAK: return PAK.Create(data); + case SupportedFileType.PFF: return PFF.Create(data); + //case SupportedFileType.PIC: return PIC.Create(data); + //case SupportedFileType.PKZIP: return PKZIP.Create(data); + case SupportedFileType.PLJ: return PlayJAudioFile.Create(data); + //case SupportedFileType.PLJPlaylist: return PlayJPlaylist.Create(data); + case SupportedFileType.Quantum: return Quantum.Create(data); + //case SupportedFileType.RAR: return RAR.Create(data); + //case SupportedFileType.SevenZip: return SevenZip.Create(data); + //case SupportedFileType.SFFS: return SFFS.Create(data); + case SupportedFileType.SGA: return SGA.Create(data); + //case SupportedFileType.TapeArchive: return TapeArchive.Create(data); + //case SupportedFileType.Textfile: return Textfile.Create(data); + case SupportedFileType.VBSP: return VBSP.Create(data); + case SupportedFileType.VPK: return VPK.Create(data); + case SupportedFileType.WAD: return WAD.Create(data); + //case SupportedFileType.XZ: return XZ.Create(data); + case SupportedFileType.XZP: return XZP.Create(data); + default: return null; + } + } + + /// + /// Create an instance of a wrapper based on the executable type + /// + /// Stream data to parse + /// IWrapper representing the executable, null on error + public static IWrapper? CreateExecutableWrapper(Stream? stream) + { + // If we have no stream + if (stream == null) + return null; + + // Try to get an MS-DOS wrapper first + var wrapper = MSDOS.Create(stream); + if (wrapper == null || !(wrapper is MSDOS msdos)) + return null; + + // Check for a valid new executable address + if (msdos.Model.Header?.NewExeHeaderAddr == null || msdos.Model.Header.NewExeHeaderAddr >= stream.Length) + return wrapper; + + // Try to read the executable info + stream.Seek(msdos.Model.Header.NewExeHeaderAddr, SeekOrigin.Begin); + var magic = stream.ReadBytes(4); + + // If we didn't get valid data at the offset + if (magic == null) + { + return wrapper; + } + + // New Executable + else if (magic.StartsWith(SabreTools.Models.NewExecutable.Constants.SignatureBytes)) + { + stream.Seek(0, SeekOrigin.Begin); + return NewExecutable.Create(stream); + } + + // Linear Executable + else if (magic.StartsWith(SabreTools.Models.LinearExecutable.Constants.LESignatureBytes) + || magic.StartsWith(SabreTools.Models.LinearExecutable.Constants.LXSignatureBytes)) + { + stream.Seek(0, SeekOrigin.Begin); + return LinearExecutable.Create(stream); + } + + // Portable Executable + else if (magic.StartsWith(SabreTools.Models.PortableExecutable.Constants.SignatureBytes)) + { + stream.Seek(0, SeekOrigin.Begin); + return PortableExecutable.Create(stream); + } + + // Everything else fails + return null; + } + } +}