2025-09-12 09:02:03 -04:00
using System ;
using System.IO ;
using SabreTools.Hashing ;
using SabreTools.IO.Compression.Deflate ;
using SabreTools.IO.Extensions ;
2026-03-24 19:17:25 -04:00
using SabreTools.Numerics.Extensions ;
2025-09-12 09:02:03 -04:00
2026-03-18 16:37:59 -04:00
namespace SabreTools.Wrappers
2025-09-12 09:02:03 -04:00
{
public partial class WiseSectionHeader : IExtractable
{
/// <inheritdoc/>
public bool Extract ( string outputDirectory , bool includeDebug )
{
// Extract the header-defined files
bool extracted = ExtractHeaderDefinedFiles ( outputDirectory , includeDebug ) ;
if ( ! extracted )
{
if ( includeDebug ) Console . Error . WriteLine ( "Could not extract header-defined files" ) ;
return false ;
}
return true ;
}
// Currently unaware of any NE samples. That said, as they wouldn't have a .WISE section, it's unclear how such
// samples could be identified.
/// <summary>
/// Extract the predefined, static files defined in the header
/// </summary>
/// <param name="outputDirectory">Output directory to write to</param>
/// <param name="includeDebug">True to include debug data, false otherwise</param>
/// <returns>True if the files extracted successfully, false otherwise</returns>
private bool ExtractHeaderDefinedFiles ( string outputDirectory , bool includeDebug )
{
lock ( _dataSourceLock )
{
// Seek to the compressed data offset
2025-10-27 22:43:56 -04:00
_dataSource . SeekIfPossible ( CompressedDataOffset , SeekOrigin . Begin ) ;
2025-09-12 09:02:03 -04:00
bool successful = true ;
// Extract first executable, if it exists
if ( ExtractFile ( "FirstExecutable.exe" , outputDirectory , FirstExecutableFileEntryLength , includeDebug ) ! = ExtractionStatus . GOOD )
successful = false ;
// Extract second executable, if it exists
// If there's a size provided for the second executable but no size for the first executable, the size of
// the second executable appears to be some unrelated value that's larger than the second executable
// actually is. Currently unable to extract properly in these cases, as no header value in such installers
// seems to actually correspond to the real size of the second executable.
if ( ExtractFile ( "SecondExecutable.exe" , outputDirectory , SecondExecutableFileEntryLength , includeDebug ) ! = ExtractionStatus . GOOD )
successful = false ;
// Extract third executable, if it exists
if ( ExtractFile ( "ThirdExecutable.exe" , outputDirectory , ThirdExecutableFileEntryLength , includeDebug ) ! = ExtractionStatus . GOOD )
successful = false ;
// Extract main MSI file
if ( ExtractFile ( "ExtractedMsi.msi" , outputDirectory , MsiFileEntryLength , includeDebug ) ! = ExtractionStatus . GOOD )
{
// Fallback- seek to the position that's the length of the MSI file entry from the end, then try and
// extract from there.
2025-10-27 22:43:56 -04:00
_dataSource . SeekIfPossible ( - MsiFileEntryLength + 1 , SeekOrigin . End ) ;
2025-09-12 09:02:03 -04:00
if ( ExtractFile ( "ExtractedMsi.msi" , outputDirectory , MsiFileEntryLength , includeDebug ) ! = ExtractionStatus . GOOD )
return false ; // The fallback also failed.
}
return successful ;
}
}
/// <summary>
/// Attempt to extract a file defined by a filename
/// </summary>
/// <param name="filename">Output filename, null to auto-generate</param>
/// <param name="outputDirectory">Output directory to write to</param>
/// <param name="entrySize">Expected size of the file plus crc32</param>
/// <param name="includeDebug">True to include debug data, false otherwise</param>
/// <returns>Extraction status representing the final state</returns>
/// <remarks>Assumes that the current stream position is the end of where the data lives</remarks>
private ExtractionStatus ExtractFile ( string filename ,
string outputDirectory ,
uint entrySize ,
bool includeDebug )
{
if ( includeDebug ) Console . WriteLine ( $"Attempting to extract {filename}" ) ;
// Extract the file
var destination = new MemoryStream ( ) ;
ExtractionStatus status ;
2026-01-25 14:32:49 -05:00
if ( ! ( Version is not null & & Version [ 1 ] = = 0x01 ) )
2025-09-12 09:02:03 -04:00
status = ExtractStreamWithChecksum ( destination , entrySize , includeDebug ) ;
else // hack for Codesited5.exe , very early and very strange.
status = ExtractStreamWithoutChecksum ( destination , entrySize , includeDebug ) ;
// If the extracted data is invalid
2026-01-25 14:30:18 -05:00
if ( status ! = ExtractionStatus . GOOD | | destination is null )
2025-09-12 09:02:03 -04:00
return status ;
// Ensure the full output directory exists
filename = Path . Combine ( outputDirectory , filename ) ;
var directoryName = Path . GetDirectoryName ( filename ) ;
2026-01-25 14:32:49 -05:00
if ( directoryName is not null & & ! Directory . Exists ( directoryName ) )
2025-09-12 09:02:03 -04:00
Directory . CreateDirectory ( directoryName ) ;
// Write the output file
File . WriteAllBytes ( filename , destination . ToArray ( ) ) ;
return status ;
}
/// <summary>
/// Extract source data with a trailing CRC-32 checksum
/// </summary>
/// <param name="destination">Stream where the file data will be written</param>
/// <param name="entrySize">Expected size of the file plus crc32</param>
/// <param name="includeDebug">True to include debug data, false otherwise</param>
/// <returns></returns>
private ExtractionStatus ExtractStreamWithChecksum ( Stream destination , uint entrySize , bool includeDebug )
{
// Debug output
if ( includeDebug ) Console . WriteLine ( $"Offset: {_dataSource.Position:X8}, Expected Read: {entrySize}, Expected Write:{entrySize - 4}" ) ; // clamp to zero
// Check the validity of the inputs
if ( entrySize = = 0 )
{
if ( includeDebug ) Console . Error . WriteLine ( "Not attempting to extract, expected to read 0 bytes" ) ;
return ExtractionStatus . GOOD ; // If size is 0, then it shouldn't be extracted
}
else if ( entrySize > ( _dataSource . Length - _dataSource . Position ) )
{
if ( includeDebug ) Console . Error . WriteLine ( $"Not attempting to extract, expected to read {entrySize} bytes but only {_dataSource.Position} bytes remain" ) ;
return ExtractionStatus . INVALID ;
}
// Extract the file
try
{
byte [ ] actual = _dataSource . ReadBytes ( ( int ) entrySize - 4 ) ;
uint expectedCrc32 = _dataSource . ReadUInt32 ( ) ;
// Debug output
if ( includeDebug ) Console . WriteLine ( $"Expected CRC-32: {expectedCrc32:X8}" ) ;
byte [ ] ? hashBytes = HashTool . GetByteArrayHashArray ( actual , HashType . CRC32 ) ;
2026-01-25 14:32:49 -05:00
if ( hashBytes is not null )
2025-09-12 09:02:03 -04:00
{
uint actualCrc32 = BitConverter . ToUInt32 ( hashBytes , 0 ) ;
// Debug output
if ( includeDebug ) Console . WriteLine ( $"Actual CRC-32: {actualCrc32:X8}" ) ;
if ( expectedCrc32 ! = actualCrc32 )
{
if ( includeDebug ) Console . Error . WriteLine ( "Mismatched CRC-32 values!" ) ;
return ExtractionStatus . BAD_CRC ;
}
}
2026-04-19 09:33:55 -04:00
destination . Write ( actual ) ;
2025-09-12 09:02:03 -04:00
return ExtractionStatus . GOOD ;
}
catch
{
if ( includeDebug ) Console . Error . WriteLine ( "Could not extract" ) ;
return ExtractionStatus . FAIL ;
}
}
/// <summary>
/// Extract source data without a trailing CRC-32 checksum
/// </summary>
/// <param name="destination">Stream where the file data will be written</param>
/// <param name="entrySize">Expected size of the file</param>
/// <param name="includeDebug">True to include debug data, false otherwise</param>
/// <returns></returns>
private ExtractionStatus ExtractStreamWithoutChecksum ( Stream destination , uint entrySize , bool includeDebug )
{
// Debug output
if ( includeDebug ) Console . WriteLine ( $"Offset: {_dataSource.Position:X8}, Expected Read: {entrySize}, Expected Write:{entrySize - 4}" ) ;
// Check the validity of the inputs
if ( entrySize = = 0 )
{
if ( includeDebug ) Console . Error . WriteLine ( "Not attempting to extract, expected to read 0 bytes" ) ;
return ExtractionStatus . GOOD ; // If size is 0, then it shouldn't be extracted
}
else if ( entrySize > ( _dataSource . Length - _dataSource . Position ) )
{
if ( includeDebug ) Console . Error . WriteLine ( $"Not attempting to extract, expected to read {entrySize} bytes but only {_dataSource.Position} bytes remain" ) ;
return ExtractionStatus . INVALID ;
}
// Extract the file
try
{
byte [ ] actual = _dataSource . ReadBytes ( ( int ) entrySize ) ;
// Debug output
if ( includeDebug ) Console . WriteLine ( "No CRC-32!" ) ;
2026-04-19 09:33:55 -04:00
destination . Write ( actual ) ;
2025-09-12 09:02:03 -04:00
return ExtractionStatus . GOOD ;
}
catch
{
if ( includeDebug ) Console . Error . WriteLine ( "Could not extract" ) ;
return ExtractionStatus . FAIL ;
}
}
}
}