From 741a1e13d0d82818609e7bf317daf15ce9584b3c Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Fri, 26 Sep 2025 22:27:27 -0400 Subject: [PATCH] Add framework for Spoon Installer extraction --- .../Wrappers/PortableExecutable.Extraction.cs | 72 ++++++++++++++++++- 1 file changed, 70 insertions(+), 2 deletions(-) diff --git a/SabreTools.Serialization/Wrappers/PortableExecutable.Extraction.cs b/SabreTools.Serialization/Wrappers/PortableExecutable.Extraction.cs index 0e60f63c..7dc36a85 100644 --- a/SabreTools.Serialization/Wrappers/PortableExecutable.Extraction.cs +++ b/SabreTools.Serialization/Wrappers/PortableExecutable.Extraction.cs @@ -14,7 +14,13 @@ namespace SabreTools.Serialization.Wrappers /// - Archives and executables in resource data /// - CExe-compressed resource data /// - SecuROM Matroschka package sections - /// - SFX archives (7z, Advanced Installer, MS-CAB, PKZIP, RAR) + /// - SFX archives + /// + 7z + /// + Advanced Installer + /// + MS-CAB + /// + PKZIP + /// + RAR + /// + Spoon Installer /// - Wise installers /// public bool Extract(string outputDirectory, bool includeDebug) @@ -23,10 +29,12 @@ namespace SabreTools.Serialization.Wrappers bool cexe = ExtractCExe(outputDirectory, includeDebug); bool matroschka = ExtractMatroschka(outputDirectory, includeDebug); bool resources = ExtractFromResources(outputDirectory, includeDebug); + bool spoon = ExtractSpoonInstaller(outputDirectory, includeDebug); bool wise = ExtractWise(outputDirectory, includeDebug); // Overlay can be skipped in some situations - bool overlay = cai || ExtractFromOverlay(outputDirectory, includeDebug); + bool overlay = cai || spoon + || ExtractFromOverlay(outputDirectory, includeDebug); return cai || cexe || matroschka || overlay || resources || wise; } @@ -522,6 +530,66 @@ namespace SabreTools.Serialization.Wrappers return MatroschkaPackage.Extract(outputDirectory, includeDebug); } + /// + /// Extract a Spoon Installer SFX overlay + /// + /// Output directory to write to + /// True to include debug data, false otherwise + /// True if extraction succeeded, false otherwise + public bool ExtractSpoonInstaller(string outputDirectory, bool includeDebug) + { + try + { + // Try to deserialize the source data + var deserializer = new Readers.SpoonInstaller(); + var sfx = deserializer.Deserialize(_dataSource); + if (sfx?.Entries == null) + return false; + + // Loop through the entries and extract + for (int i = 0; i < sfx.Entries.Length; i++) + { + var entry = sfx.Entries[i]; + + // Get the offset and compressed size + long offset = entry.FileOffset; + int size = (int)entry.CompressedSize; + + // Try to read the file data + byte[] data = ReadRangeFromSource(offset, size); + if (data.Length == 0) + continue; + + // TODO: Create a BZ2 extraction stream from this + + // Ensure directory separators are consistent + string filename = entry.Filename ?? $"FILE_{i}"; + if (Path.DirectorySeparatorChar == '\\') + filename = filename.Replace('/', '\\'); + else if (Path.DirectorySeparatorChar == '/') + filename = filename.Replace('\\', '/'); + + // Ensure the full output directory exists + filename = Path.Combine(outputDirectory, filename); + var directoryName = Path.GetDirectoryName(filename); + if (directoryName != null && !Directory.Exists(directoryName)) + Directory.CreateDirectory(directoryName); + + // Write the output file + var fs = File.Open(filename, FileMode.Create, FileAccess.Write, FileShare.ReadWrite); + fs.Write(data, 0, data.Length); + fs.Flush(); + } + + return true; + } + catch (Exception ex) + { + if (includeDebug) Console.Error.WriteLine(ex); + return false; + } + } + /// /// Extract data from a Wise installer ///