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
{