diff --git a/SabreTools.Serialization/Wrappers/PortableExecutable.cs b/SabreTools.Serialization/Wrappers/PortableExecutable.cs index cb2a3c89..400b995c 100644 --- a/SabreTools.Serialization/Wrappers/PortableExecutable.cs +++ b/SabreTools.Serialization/Wrappers/PortableExecutable.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Text; +using SabreTools.IO.Compression.zlib; using SabreTools.IO.Extensions; using SabreTools.Matching; using SabreTools.Serialization.Interfaces; @@ -1084,12 +1085,140 @@ namespace SabreTools.Serialization.Wrappers #region Extraction /// - /// This only extracts overlay and resource data + /// + /// This extracts the following data: + /// - Archives and executables in the overlay + /// - Archives and executables in resource data + /// - CExe-compressed resource data + /// public bool Extract(string outputDirectory, bool includeDebug) { + bool cexe = ExtractCExe(outputDirectory, includeDebug); bool overlay = ExtractFromOverlay(outputDirectory, includeDebug); bool resources = ExtractFromResources(outputDirectory, includeDebug); - return overlay || resources; + return cexe || overlay || resources; + } + + /// + /// Extract a CExe-compressed executable + /// + /// Output directory to write to + /// True to include debug data, false otherwise + /// True if extraction succeeded, false otherwise + public bool ExtractCExe(string outputDirectory, bool includeDebug) + { + try + { + // Get all resources of type 99 with index 2 + var resources = FindResourceByNamedType("99, 2"); + if (resources == null || resources.Count == 0) + return false; + + // Get the first resource of type 99 with index 2 + var payload = resources[0]; + if (payload == null || payload.Length == 0) + return false; + + // Create the output data buffer + byte[]? data = []; + + // If we had the decompression DLL included, it's zlib + if (FindResourceByNamedType("99, 1").Count > 0) + data = ExtractCExeZlib(payload); + else + data = ExtractCExeLZ(payload); + + // If we have no data + if (data == null) + return false; + + // Create the temp filename + string tempFile = string.IsNullOrEmpty(Filename) ? "temp.sxe" : $"{Path.GetFileNameWithoutExtension(Filename)}.sxe"; + tempFile = Path.Combine(outputDirectory, tempFile); + var directoryName = Path.GetDirectoryName(tempFile); + if (directoryName != null && !Directory.Exists(directoryName)) + Directory.CreateDirectory(directoryName); + + // Write the file data to a temp file + var tempStream = File.Open(tempFile, FileMode.Create, FileAccess.Write, FileShare.ReadWrite); + tempStream.Write(data, 0, data.Length); + + return true; + } + catch (Exception ex) + { + if (includeDebug) Console.Error.WriteLine(ex); + return false; + } + } + + /// + /// Extract CExe data compressed with LZ + /// + /// Resource data to inflate + /// Inflated data on success, null otherwise + private byte[]? ExtractCExeLZ(byte[] resource) + { + try + { + var decompressor = IO.Compression.SZDD.Decompressor.CreateSZDD(resource); + var dataStream = new MemoryStream(); + decompressor.CopyTo(dataStream); + return dataStream.ToArray(); + } + catch + { + // Reset the data + return null; + } + } + + /// + /// Extract CExe data compressed with zlib + /// + /// Resource data to inflate + /// Inflated data on success, null otherwise + private byte[]? ExtractCExeZlib(byte[] resource) + { + try + { + // Inflate the data into the buffer + var zstream = new ZLib.z_stream_s(); + byte[] data = new byte[resource.Length * 4]; + unsafe + { + fixed (byte* payloadPtr = resource) + fixed (byte* dataPtr = data) + { + zstream.next_in = payloadPtr; + zstream.avail_in = (uint)resource.Length; + zstream.total_in = (uint)resource.Length; + zstream.next_out = dataPtr; + zstream.avail_out = (uint)data.Length; + zstream.total_out = 0; + + ZLib.inflateInit_(zstream, ZLib.zlibVersion(), resource.Length); + int zret = ZLib.inflate(zstream, 1); + ZLib.inflateEnd(zstream); + } + } + + // Trim the buffer to the proper size + uint read = zstream.total_out; +#if NETFRAMEWORK + var temp = new byte[read]; + Array.Copy(data, temp, read); + data = temp; +#else + data = new ReadOnlySpan(data, 0, (int)read).ToArray(); +#endif + return data; + } + catch + { + // Reset the data + return null; + } } /// @@ -1097,7 +1226,7 @@ namespace SabreTools.Serialization.Wrappers /// /// Output directory to write to /// True to include debug data, false otherwise - private bool ExtractFromOverlay(string outputDirectory, bool includeDebug) + public bool ExtractFromOverlay(string outputDirectory, bool includeDebug) { try { @@ -1152,7 +1281,7 @@ namespace SabreTools.Serialization.Wrappers /// /// Output directory to write to /// True to include debug data, false otherwise - private bool ExtractFromResources(string outputDirectory, bool includeDebug) + public bool ExtractFromResources(string outputDirectory, bool includeDebug) { try {