using System; using System.IO; using System.Linq; using System.Text; using BinaryObjectScanner.Compression; using BinaryObjectScanner.Utilities; using BinaryObjectScanner.Wrappers; using OpenMcdf; using SharpCompress.Archives; using SharpCompress.Archives.GZip; using SharpCompress.Archives.Rar; using SharpCompress.Archives.SevenZip; using SharpCompress.Archives.Tar; using SharpCompress.Archives.Zip; using SharpCompress.Compressors; using SharpCompress.Compressors.BZip2; using SharpCompress.Compressors.Xz; using UnshieldSharp.Archive; using UnshieldSharp.Cabinet; namespace Test { internal static class Extractor { /// /// Wrapper to extract data for a single path /// /// File or directory path /// Output directory path public static void ExtractPath(string path, string outputDirectory) { Console.WriteLine($"Checking possible path: {path}"); // Check if the file or directory exists if (File.Exists(path)) { ExtractFile(path, outputDirectory); } else if (Directory.Exists(path)) { foreach (string file in Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories)) { ExtractFile(file, outputDirectory); } } else { Console.WriteLine($"{path} does not exist, skipping..."); } } /// /// Print information for a single file, if possible /// private static void ExtractFile(string file, string outputDirectory) { Console.WriteLine($"Attempting to extract all files from {file}"); using (Stream stream = File.Open(file, FileMode.Open, FileAccess.Read, FileShare.Read)) { // Read the first 8 bytes byte[] magic = stream.ReadBytes(8); stream.Seek(0, SeekOrigin.Begin); // Get the file type SupportedFileType ft = FileTypes.GetFileType(magic); // Executables technically can be "extracted", but let's ignore that // TODO: Support executables that include other stuff // 7-zip if (ft == SupportedFileType.SevenZip) { // Build the archive information Console.WriteLine("Extracting 7-zip contents"); Console.WriteLine(); // If the 7-zip file itself fails try { using (SevenZipArchive sevenZipFile = SevenZipArchive.Open(stream)) { foreach (var entry in sevenZipFile.Entries) { // If an individual entry fails try { // If we have a directory, skip it if (entry.IsDirectory) continue; string tempFile = Path.Combine(outputDirectory, entry.Key); entry.WriteToFile(tempFile); } catch (Exception ex) { Console.WriteLine($"Something went wrong extracting 7-zip entry {entry.Key}: {ex}"); Console.WriteLine(); } } } } catch (Exception ex) { Console.WriteLine($"Something went wrong extracting 7-zip: {ex}"); Console.WriteLine(); } } // BFPK archive else if (ft == SupportedFileType.BFPK) { // Build the BFPK information Console.WriteLine("Extracting BFPK contents"); Console.WriteLine(); var bfpk = BFPK.Create(stream); if (bfpk == null) { Console.WriteLine("Something went wrong parsing BFPK archive"); Console.WriteLine(); return; } try { // Extract the BFPK contents to the directory bfpk.ExtractAll(outputDirectory); } catch (Exception ex) { Console.WriteLine($"Something went wrong extracting BFPK archive: {ex}"); Console.WriteLine(); } } // BSP else if (ft == SupportedFileType.BSP) { // Build the BSP information Console.WriteLine("Extracting BSP contents"); Console.WriteLine(); var bsp = BSP.Create(stream); if (bsp == null) { Console.WriteLine("Something went wrong parsing BSP"); Console.WriteLine(); return; } try { // Extract the BSP contents to the directory bsp.ExtractAllLumps(outputDirectory); bsp.ExtractAllTextures(outputDirectory); } catch (Exception ex) { Console.WriteLine($"Something went wrong extracting BSP: {ex}"); Console.WriteLine(); } } // bzip2 else if (ft == SupportedFileType.BZip2) { // Build the bzip2 information Console.WriteLine("Extracting bzip2 contents"); Console.WriteLine(); using (var bz2File = new BZip2Stream(stream, CompressionMode.Decompress, true)) { // If an individual entry fails try { string tempFile = Path.Combine(outputDirectory, Guid.NewGuid().ToString()); using (FileStream fs = File.OpenWrite(tempFile)) { bz2File.CopyTo(fs); } } catch (Exception ex) { Console.WriteLine($"Something went wrong extracting bzip2: {ex}"); Console.WriteLine(); } } } // CFB else if (ft == SupportedFileType.CFB) { // Build the installer information Console.WriteLine("Extracting CFB contents"); Console.WriteLine(); // If the CFB file itself fails try { using (CompoundFile cf = new CompoundFile(stream, CFSUpdateMode.ReadOnly, CFSConfiguration.Default)) { cf.RootStorage.VisitEntries((e) => { if (!e.IsStream) return; var str = cf.RootStorage.GetStream(e.Name); if (str == null) return; byte[] strData = str.GetData(); if (strData == null) return; string decoded = BinaryObjectScanner.FileType.CFB.DecodeStreamName(e.Name).TrimEnd('\0'); byte[] nameBytes = Encoding.UTF8.GetBytes(e.Name); // UTF-8 encoding of 0x4840. if (nameBytes[0] == 0xe4 && nameBytes[1] == 0xa1 && nameBytes[2] == 0x80) decoded = decoded.Substring(3); foreach (char c in Path.GetInvalidFileNameChars()) { decoded = decoded.Replace(c, '_'); } string filename = Path.Combine(outputDirectory, decoded); using (Stream fs = File.OpenWrite(filename)) { fs.Write(strData, 0, strData.Length); } }, recursive: true); } } catch (Exception ex) { Console.WriteLine($"Something went wrong extracting CFB: {ex}"); Console.WriteLine(); } } // GCF else if (ft == SupportedFileType.GCF) { // Build the GCF information Console.WriteLine("Extracting GCF contents"); Console.WriteLine(); var gcf = GCF.Create(stream); if (gcf == null) { Console.WriteLine("Something went wrong parsing GCF"); Console.WriteLine(); return; } try { // Extract the GCF contents to the directory gcf.ExtractAll(outputDirectory); } catch (Exception ex) { Console.WriteLine($"Something went wrong extracting GCF: {ex}"); Console.WriteLine(); } } // gzip else if (ft == SupportedFileType.GZIP) { // Build the gzip information Console.WriteLine("Extracting gzip contents"); Console.WriteLine(); using (var zipFile = GZipArchive.Open(stream)) { foreach (var entry in zipFile.Entries) { // If an individual entry fails try { // If we have a directory, skip it if (entry.IsDirectory) continue; string tempFile = Path.Combine(outputDirectory, entry.Key); entry.WriteToFile(tempFile); } catch (Exception ex) { Console.WriteLine($"Something went wrong extracting gzip entry {entry.Key}: {ex}"); Console.WriteLine(); } } } } // InstallShield Archive V3 (Z) else if (ft == SupportedFileType.InstallShieldArchiveV3) { // Build the InstallShield Archive V3 information Console.WriteLine("Extracting InstallShield Archive V3 contents"); Console.WriteLine(); // If the cab file itself fails try { var archive = new InstallShieldArchiveV3(file); foreach (var cfile in archive.Files.Select(kvp => kvp.Value)) { // If an individual entry fails try { string tempFile = Path.Combine(outputDirectory, cfile.FullPath); if (!Directory.Exists(Path.GetDirectoryName(tempFile))) Directory.CreateDirectory(Path.GetDirectoryName(tempFile)); (byte[] fileContents, string error) = archive.Extract(cfile.FullPath); if (!string.IsNullOrWhiteSpace(error)) continue; using (FileStream fs = File.OpenWrite(tempFile)) { fs.Write(fileContents, 0, fileContents.Length); } } catch (Exception ex) { Console.WriteLine($"Something went wrong extracting InstallShield Archive V3 entry {cfile.Name}: {ex}"); Console.WriteLine(); } } } catch (Exception ex) { Console.WriteLine($"Something went wrong extracting InstallShield Archive V3: {ex}"); Console.WriteLine(); } } // IS-CAB archive else if (ft == SupportedFileType.InstallShieldCAB) { // Build the archive information Console.WriteLine("Extracting IS-CAB contents"); Console.WriteLine(); // If the cab file itself fails try { var cabfile = UnshieldSharp.Cabinet.InstallShieldCabinet.Open(file); for (int i = 0; i < cabfile.FileCount; i++) { // If an individual entry fails try { string filename = cabfile.FileName(i); string tempFile; try { tempFile = Path.Combine(outputDirectory, filename); } catch { tempFile = Path.Combine(outputDirectory, $"BAD_FILENAME{i}"); } cabfile.FileSave(i, tempFile); } catch (Exception ex) { Console.WriteLine($"Something went wrong extracting IS-CAB entry {i}: {ex}"); Console.WriteLine(); } } } catch (Exception ex) { Console.WriteLine($"Something went wrong extracting IS-CAB: {ex}"); Console.WriteLine(); } } // Microsoft Cabinet archive else if (ft == SupportedFileType.MicrosoftCAB) { // Build the cabinet information Console.WriteLine("Extracting MS-CAB contents"); Console.WriteLine(); var cabinet = MicrosoftCabinet.Create(stream); if (cabinet == null) { Console.WriteLine("Something went wrong parsing MS-CAB archive"); Console.WriteLine(); return; } try { // Extract the MS-CAB contents to the directory cabinet.ExtractAll(outputDirectory); } catch (Exception ex) { Console.WriteLine($"Something went wrong extracting MS-CAB: {ex}"); Console.WriteLine(); } } // Microsoft LZ / LZ32 else if (ft == SupportedFileType.MicrosoftLZ) { // Build the Microsoft LZ / LZ32 information Console.WriteLine("Extracting Microsoft LZ / LZ32 contents"); Console.WriteLine(); // If the LZ file itself fails try { byte[] data = LZ.Decompress(stream); // Create the temp filename string tempFile = "temp.bin"; if (!string.IsNullOrEmpty(file)) { string expandedFilePath = LZ.GetExpandedName(file, out _); tempFile = Path.GetFileName(expandedFilePath).TrimEnd('\0'); if (tempFile.EndsWith(".ex")) tempFile += "e"; else if (tempFile.EndsWith(".dl")) tempFile += "l"; } tempFile = Path.Combine(outputDirectory, tempFile); // Write the file data to a temp file using (Stream tempStream = File.Open(tempFile, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)) { tempStream.Write(data, 0, data.Length); } } catch (Exception ex) { Console.WriteLine($"Something went wrong extracting Microsoft LZ / LZ32: {ex}"); Console.WriteLine(); } } #if NET48 // MoPaQ (MPQ) archive else if (ft == SupportedFileType.MPQ) { // Build the archive information Console.WriteLine("Extracting MoPaQ contents"); Console.WriteLine(); // If the MPQ file itself fails try { using (var mpqArchive = new StormLibSharp.MpqArchive(file, FileAccess.Read)) { // Try to open the listfile string listfile = null; StormLibSharp.MpqFileStream listStream = mpqArchive.OpenFile("(listfile)"); // If we can't read the listfile, we just return if (!listStream.CanRead) { Console.WriteLine("Could not read the listfile, extraction halted!"); Console.WriteLine(); } // Read the listfile in for processing using (StreamReader sr = new StreamReader(listStream)) { listfile = sr.ReadToEnd(); } // Split the listfile by newlines string[] listfileLines = listfile.Replace("\r\n", "\n").Split('\n'); // Loop over each entry foreach (string sub in listfileLines) { // If an individual entry fails try { string tempFile = Path.Combine(outputDirectory, sub); Directory.CreateDirectory(Path.GetDirectoryName(tempFile)); mpqArchive.ExtractFile(sub, tempFile); } catch (Exception ex) { Console.WriteLine($"Something went wrong extracting MoPaQ entry {sub}: {ex}"); Console.WriteLine(); } } } } catch (Exception ex) { Console.WriteLine($"Something went wrong extracting MoPaQ: {ex}"); Console.WriteLine(); } } #endif // PAK else if (ft == SupportedFileType.PAK) { // Build the archive information Console.WriteLine("Extracting PAK contents"); Console.WriteLine(); var pak = PAK.Create(stream); if (pak == null) { Console.WriteLine("Something went wrong parsing PAK"); Console.WriteLine(); return; } try { // Extract the PAK contents to the directory pak.ExtractAll(outputDirectory); } catch (Exception ex) { Console.WriteLine($"Something went wrong extracting PAK: {ex}"); Console.WriteLine(); } } // PFF else if (ft == SupportedFileType.PFF) { // Build the archive information Console.WriteLine("Extracting PFF contents"); Console.WriteLine(); var pff = PFF.Create(stream); if (pff == null) { Console.WriteLine("Something went wrong parsing PFF"); Console.WriteLine(); return; } try { // Extract the PFF contents to the directory pff.ExtractAll(outputDirectory); } catch (Exception ex) { Console.WriteLine($"Something went wrong extracting PFF: {ex}"); Console.WriteLine(); } } // PKZIP else if (ft == SupportedFileType.PKZIP) { // Build the archive information Console.WriteLine("Extracting PKZIP contents"); Console.WriteLine(); // If the zip file itself fails try { using (ZipArchive zipFile = ZipArchive.Open(stream)) { foreach (var entry in zipFile.Entries) { // If an individual entry fails try { // If we have a directory, skip it if (entry.IsDirectory) continue; string tempFile = Path.Combine(outputDirectory, entry.Key); Directory.CreateDirectory(Path.GetDirectoryName(tempFile)); entry.WriteToFile(tempFile); } catch (Exception ex) { Console.WriteLine($"Something went wrong extracting PKZIP entry {entry.Key}: {ex}"); Console.WriteLine(); } } } } catch (Exception ex) { Console.WriteLine($"Something went wrong extracting PKZIP: {ex}"); Console.WriteLine(); } } // RAR else if (ft == SupportedFileType.RAR) { // Build the archive information Console.WriteLine("Extracting RAR contents"); Console.WriteLine(); // If the rar file itself fails try { using (RarArchive rarFile = RarArchive.Open(stream)) { foreach (var entry in rarFile.Entries) { // If an individual entry fails try { // If we have a directory, skip it if (entry.IsDirectory) continue; string tempFile = Path.Combine(outputDirectory, entry.Key); entry.WriteToFile(tempFile); } catch (Exception ex) { Console.WriteLine($"Something went wrong extracting RAR entry {entry.Key}: {ex}"); Console.WriteLine(); } } } } catch (Exception ex) { Console.WriteLine($"Something went wrong extracting RAR: {ex}"); Console.WriteLine(); } } // SGA else if (ft == SupportedFileType.SGA) { // Build the archive information Console.WriteLine("Extracting SGA contents"); Console.WriteLine(); var sga = SGA.Create(stream); if (sga == null) { Console.WriteLine("Something went wrong parsing SGA"); Console.WriteLine(); return; } try { // Extract the SGA contents to the directory sga.ExtractAll(outputDirectory); } catch (Exception ex) { Console.WriteLine($"Something went wrong extracting MS-CAB: {ex}"); Console.WriteLine(); } } // Tape Archive else if (ft == SupportedFileType.RAR) { // Build the archive information Console.WriteLine("Extracting Tape Archive contents"); Console.WriteLine(); // If the rar file itself fails try { using (TarArchive tarFile = TarArchive.Open(stream)) { foreach (var entry in tarFile.Entries) { // If an individual entry fails try { // If we have a directory, skip it if (entry.IsDirectory) continue; string tempFile = Path.Combine(outputDirectory, entry.Key); entry.WriteToFile(tempFile); } catch (Exception ex) { Console.WriteLine($"Something went wrong extracting Tape Archive entry {entry.Key}: {ex}"); Console.WriteLine(); } } } } catch (Exception ex) { Console.WriteLine($"Something went wrong extracting Tape Archive: {ex}"); Console.WriteLine(); } } // VBSP else if (ft == SupportedFileType.VBSP) { // Build the archive information Console.WriteLine("Extracting VBSP contents"); Console.WriteLine(); var vbsp = VBSP.Create(stream); if (vbsp == null) { Console.WriteLine("Something went wrong parsing VBSP"); Console.WriteLine(); return; } try { // Extract the VBSP contents to the directory vbsp.ExtractAllLumps(outputDirectory); } catch (Exception ex) { Console.WriteLine($"Something went wrong extracting MS-CAB: {ex}"); Console.WriteLine(); } } // VPK else if (ft == SupportedFileType.VPK) { // Build the archive information Console.WriteLine("Extracting VPK contents"); Console.WriteLine(); var vpk = VPK.Create(stream); if (vpk == null) { Console.WriteLine("Something went wrong parsing VPK"); Console.WriteLine(); return; } try { // Extract the VPK contents to the directory vpk.ExtractAll(outputDirectory); } catch (Exception ex) { Console.WriteLine($"Something went wrong extracting MS-CAB: {ex}"); Console.WriteLine(); } } // WAD else if (ft == SupportedFileType.WAD) { // Build the archive information Console.WriteLine("Extracting WAD contents"); Console.WriteLine(); var wad = WAD.Create(stream); if (wad == null) { Console.WriteLine("Something went wrong parsing WAD"); Console.WriteLine(); return; } try { // Extract the WAD contents to the directory wad.ExtractAllLumps(outputDirectory); } catch (Exception ex) { Console.WriteLine($"Something went wrong extracting MS-CAB: {ex}"); Console.WriteLine(); } } // xz else if (ft == SupportedFileType.RAR) { // Build the xz information Console.WriteLine("Extracting xz contents"); Console.WriteLine(); using (var xzFile = new XZStream(stream)) { // If an individual entry fails try { string tempFile = Path.Combine(outputDirectory, Guid.NewGuid().ToString()); using (FileStream fs = File.OpenWrite(tempFile)) { xzFile.CopyTo(fs); } } catch (Exception ex) { Console.WriteLine($"Something went wrong extracting xz: {ex}"); Console.WriteLine(); } } } // XZP else if (ft == SupportedFileType.XZP) { // Build the archive information Console.WriteLine("Extracting XZP contents"); Console.WriteLine(); var xzp = XZP.Create(stream); if (xzp == null) { Console.WriteLine("Something went wrong parsing XZP"); Console.WriteLine(); return; } try { // Extract the XZP contents to the directory xzp.ExtractAll(outputDirectory); } catch (Exception ex) { Console.WriteLine($"Something went wrong extracting MS-CAB: {ex}"); Console.WriteLine(); } } // Everything else else { Console.WriteLine("Not a supported extractable file format, skipping..."); Console.WriteLine(); return; } } } } }