mirror of
https://github.com/SabreTools/SabreTools.Serialization.git
synced 2026-11-15 17:00:38 +00:00
* Look for the Wise overlay header before opening the file again ExtractWiseOverlay (and ExtractWise for NE) opened a second stream on the executable for multi-part Wise installers before checking for a Wise overlay header at all. For any other executable that stream was never closed, so the file stayed open until the stream was finalized. On Windows such a file cannot be deleted, so an extraction folder that contains it is left behind in TEMP. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Close the Wise script and the multipart file after extraction ExtractWiseOverlay (and ExtractWise for NE) never closed WiseScript.bin, which it opens from the output directory, nor the stream with the installer and its parts, so these files stayed open until their streams were finalized. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Keep the original block that opens the multipart file Close the multipart stream with a separate using declaration after the block instead of rewriting it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
277 lines
11 KiB
C#
277 lines
11 KiB
C#
using System;
|
|
using System.IO;
|
|
using SabreTools.IO.Extensions;
|
|
using SabreTools.Matching;
|
|
using SabreTools.Numerics.Extensions;
|
|
|
|
namespace SabreTools.Wrappers
|
|
{
|
|
public partial class NewExecutable : IExtractable
|
|
{
|
|
/// <inheritdoc/>
|
|
/// <remarks>
|
|
/// This extracts the following data:
|
|
/// - Archives and executables in the overlay
|
|
/// - Wise installers
|
|
/// </remarks>
|
|
public bool Extract(string outputDirectory, bool includeDebug)
|
|
{
|
|
bool overlay = ExtractFromOverlay(outputDirectory, includeDebug);
|
|
bool wise = ExtractWise(outputDirectory, includeDebug);
|
|
|
|
return overlay | wise;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extract data from the overlay
|
|
/// </summary>
|
|
/// <param name="outputDirectory">Output directory to write to</param>
|
|
/// <param name="includeDebug">True to include debug data, false otherwise</param>
|
|
/// <returns>True if extraction succeeded, false otherwise</returns>
|
|
public bool ExtractFromOverlay(string outputDirectory, bool includeDebug)
|
|
{
|
|
try
|
|
{
|
|
// Cache the overlay data for easier reading
|
|
var overlayData = OverlayData;
|
|
if (overlayData.Length == 0)
|
|
return false;
|
|
|
|
// Set the output variables
|
|
int overlayOffset = 0;
|
|
string extension = string.Empty;
|
|
|
|
// Only process the overlay if it is recognized
|
|
for (; overlayOffset < 0x400 && overlayOffset < overlayData.Length - 0x10; overlayOffset++)
|
|
{
|
|
int temp = overlayOffset;
|
|
byte[] overlaySample = overlayData.ReadBytes(ref temp, 0x10);
|
|
|
|
if (overlaySample.StartsWith(Data.Models.SevenZip.Constants.SignatureBytes))
|
|
{
|
|
extension = "7z";
|
|
break;
|
|
}
|
|
else if (overlaySample.StartsWith([0x3B, 0x21, 0x40, 0x49, 0x6E, 0x73, 0x74, 0x61, 0x6C, 0x6C]))
|
|
{
|
|
// 7-zip SFX script -- ";!@Install" to ";!@InstallEnd@!"
|
|
overlayOffset = overlayData.FirstPosition([0x3B, 0x21, 0x40, 0x49, 0x6E, 0x73, 0x74, 0x61, 0x6C, 0x6C, 0x45, 0x6E, 0x64, 0x40, 0x21]);
|
|
if (overlayOffset == -1)
|
|
return false;
|
|
|
|
overlayOffset += 15;
|
|
extension = "7z";
|
|
break;
|
|
}
|
|
else if (overlaySample.StartsWith(Data.Models.BZip2.Constants.SignatureBytes))
|
|
{
|
|
extension = "bz2";
|
|
break;
|
|
}
|
|
else if (overlaySample.StartsWith(Data.Models.CFB.Constants.SignatureBytes))
|
|
{
|
|
// Assume embedded CFB files are MSI
|
|
extension = "msi";
|
|
break;
|
|
}
|
|
else if (overlaySample.StartsWith([0x1F, 0x8B]))
|
|
{
|
|
extension = "gz";
|
|
break;
|
|
}
|
|
else if (overlaySample.StartsWith(Data.Models.MicrosoftCabinet.Constants.SignatureBytes))
|
|
{
|
|
extension = "cab";
|
|
break;
|
|
}
|
|
else if (overlaySample.StartsWith(Data.Models.PKZIP.Constants.LocalFileHeaderSignatureBytes))
|
|
{
|
|
extension = "zip";
|
|
break;
|
|
}
|
|
else if (overlaySample.StartsWith(Data.Models.PKZIP.Constants.EndOfCentralDirectoryRecordSignatureBytes))
|
|
{
|
|
extension = "zip";
|
|
break;
|
|
}
|
|
else if (overlaySample.StartsWith(Data.Models.PKZIP.Constants.EndOfCentralDirectoryRecord64SignatureBytes))
|
|
{
|
|
extension = "zip";
|
|
break;
|
|
}
|
|
else if (overlaySample.StartsWith(Data.Models.PKZIP.Constants.DataDescriptorSignatureBytes))
|
|
{
|
|
extension = "zip";
|
|
break;
|
|
}
|
|
else if (overlaySample.StartsWith(Data.Models.RAR.Constants.OldSignatureBytes))
|
|
{
|
|
extension = "rar";
|
|
break;
|
|
}
|
|
else if (overlaySample.StartsWith(Data.Models.RAR.Constants.NewSignatureBytes))
|
|
{
|
|
extension = "rar";
|
|
break;
|
|
}
|
|
else if (overlaySample.StartsWith([0x55, 0x48, 0x41, 0x06]))
|
|
{
|
|
extension = "uha";
|
|
break;
|
|
}
|
|
else if (overlaySample.StartsWith([0x3C, 0x3F, 0x78, 0x6D, 0x6C]))
|
|
{
|
|
extension = "xml";
|
|
break;
|
|
}
|
|
else if (overlaySample.StartsWith([0x3C, 0x00, 0x3F, 0x00, 0x78, 0x00, 0x6D, 0x00, 0x6C, 0x00]))
|
|
{
|
|
extension = "xml";
|
|
break;
|
|
}
|
|
else if (overlaySample.StartsWith([0xFF, 0xFE, 0x3C, 0x00, 0x3F, 0x00, 0x78, 0x00, 0x6D, 0x00, 0x6C, 0x00]))
|
|
{
|
|
extension = "xml";
|
|
break;
|
|
}
|
|
else if (overlaySample.StartsWith(Data.Models.XZ.Constants.HeaderSignatureBytes))
|
|
{
|
|
extension = "xz";
|
|
break;
|
|
}
|
|
else if (overlaySample.StartsWith(Data.Models.MSDOS.Constants.SignatureBytes))
|
|
{
|
|
extension = "bin"; // exe/dll
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If the extension is unset
|
|
if (extension.Length == 0)
|
|
return false;
|
|
|
|
// Create the temp filename
|
|
string tempFile = $"embedded_overlay.{extension}";
|
|
if (Filename is not null)
|
|
tempFile = $"{Path.GetFileName(Filename)}-{tempFile}";
|
|
|
|
tempFile = Path.Combine(outputDirectory, tempFile);
|
|
var directoryName = Path.GetDirectoryName(tempFile);
|
|
if (directoryName is not null && !Directory.Exists(directoryName))
|
|
Directory.CreateDirectory(directoryName);
|
|
|
|
// Write the resource data to a temp file
|
|
using var tempStream = File.Open(tempFile, FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
|
|
|
|
// If the overlay is partially cached, read it from the source in blocks
|
|
if (OverlaySize > overlayData.Length)
|
|
{
|
|
long currentOffset = OverlayAddress + overlayOffset;
|
|
long bytesLeft = OverlaySize - overlayOffset;
|
|
|
|
while (bytesLeft > 0)
|
|
{
|
|
int bytesToRead = (int)Math.Min(0x4000, bytesLeft);
|
|
byte[] buffer = ReadRangeFromSource(currentOffset, bytesToRead);
|
|
if (buffer.Length == 0)
|
|
break;
|
|
|
|
tempStream.Write(buffer);
|
|
tempStream.Flush();
|
|
|
|
currentOffset += bytesToRead;
|
|
bytesLeft -= bytesToRead;
|
|
}
|
|
}
|
|
|
|
// Otherwise, read from the cached data
|
|
else
|
|
{
|
|
tempStream.Write(overlayData, overlayOffset, overlayData.Length - overlayOffset);
|
|
tempStream.Flush();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
if (includeDebug) Console.Error.WriteLine(ex);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extract data from a Wise installer
|
|
/// </summary>
|
|
/// <param name="outputDirectory">Output directory to write to</param>
|
|
/// <param name="includeDebug">True to include debug data, false otherwise</param>
|
|
/// <returns>True if extraction succeeded, false otherwise</returns>
|
|
public bool ExtractWise(string outputDirectory, bool includeDebug)
|
|
{
|
|
// Try to find the overlay header
|
|
long offset = FindWiseOverlayHeader();
|
|
if (offset <= 0 || offset >= Length)
|
|
return false;
|
|
|
|
// Get the source data for reading
|
|
Stream source = _dataSource;
|
|
if (Filename is not null)
|
|
{
|
|
// Try to open a multipart file
|
|
if (WiseOverlayHeader.OpenFile(Filename, includeDebug, out var temp) && temp is not null)
|
|
source = temp;
|
|
}
|
|
|
|
// Close the multipart file again on return
|
|
using var multipart = source == _dataSource ? null : source;
|
|
|
|
return ExtractWiseOverlay(outputDirectory, includeDebug, source, offset);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extract using Wise overlay
|
|
/// </summary>
|
|
/// <param name="outputDirectory">Output directory to write to</param>
|
|
/// <param name="includeDebug">True to include debug data, false otherwise</param>
|
|
/// <param name="source">Potentially multi-part stream to read</param>
|
|
/// <param name="offset">Offset to the start of the overlay header</param>
|
|
/// <returns>True if extraction succeeded, false otherwise</returns>
|
|
private bool ExtractWiseOverlay(string outputDirectory, bool includeDebug, Stream source, long offset)
|
|
{
|
|
// Seek to the overlay and parse
|
|
source.SeekIfPossible(offset, SeekOrigin.Begin);
|
|
var header = WiseOverlayHeader.Create(source);
|
|
if (header is null)
|
|
{
|
|
if (includeDebug) Console.Error.WriteLine("Could not parse the overlay header");
|
|
return false;
|
|
}
|
|
|
|
// Extract the header-defined files
|
|
bool extracted = header.ExtractHeaderDefinedFiles(outputDirectory, includeDebug);
|
|
if (!extracted)
|
|
{
|
|
if (includeDebug) Console.Error.WriteLine("Could not extract header-defined files");
|
|
return false;
|
|
}
|
|
|
|
// Open the script file from the output directory
|
|
using var scriptStream = File.OpenRead(Path.Combine(outputDirectory, "WiseScript.bin"));
|
|
var script = WiseScript.Create(scriptStream);
|
|
if (script is null)
|
|
{
|
|
if (includeDebug) Console.Error.WriteLine("Could not parse WiseScript.bin");
|
|
return false;
|
|
}
|
|
|
|
// Get the source directory
|
|
string? sourceDirectory = null;
|
|
if (Filename is not null)
|
|
sourceDirectory = Path.GetDirectoryName(Path.GetFullPath(Filename));
|
|
|
|
// Process the state machine
|
|
return script.ProcessStateMachine(header, sourceDirectory, outputDirectory, includeDebug);
|
|
}
|
|
}
|
|
}
|