Update packages and upstream changes

This commit is contained in:
Matt Nadareski
2025-09-01 16:43:21 -04:00
parent df8f3e7122
commit 556e1c972c
30 changed files with 5249 additions and 840 deletions

View File

@@ -66,7 +66,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="SabreTools.IO" Version="1.7.0" />
<PackageReference Include="SabreTools.IO" Version="1.7.1" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.8" Condition="!$(TargetFramework.StartsWith(`net2`)) AND !$(TargetFramework.StartsWith(`net3`)) AND !$(TargetFramework.StartsWith(`net40`)) AND !$(TargetFramework.StartsWith(`net452`))" />
</ItemGroup>

View File

@@ -32,7 +32,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="SabreTools.IO" Version="1.7.0" />
<PackageReference Include="SabreTools.IO" Version="1.7.1" />
<PackageReference Include="SabreTools.Hashing" Version="1.5.0" />
</ItemGroup>

View File

@@ -28,7 +28,7 @@
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="SabreTools.Hashing" Version="1.5.0" />
<PackageReference Include="SabreTools.Models" Version="1.6.1" />
<PackageReference Include="SabreTools.Models" Version="1.7.0" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

View File

@@ -71,25 +71,25 @@ namespace SabreTools.Serialization.Deserializers
if (obj.ID1 != ID1 || obj.ID2 != ID2)
return null;
obj.CM = (CompressionMethod)data.ReadByteValue();
obj.FLG = (Flags)data.ReadByteValue();
obj.MTIME = data.ReadUInt32LittleEndian();
obj.XFL = (ExtraFlags)data.ReadByteValue();
obj.OS = (OperatingSystem)data.ReadByteValue();
obj.CompressionMethod = (CompressionMethod)data.ReadByteValue();
obj.Flags = (Flags)data.ReadByteValue();
obj.LastModifiedTime = data.ReadUInt32LittleEndian();
obj.ExtraFlags = (ExtraFlags)data.ReadByteValue();
obj.OperatingSystem = (OperatingSystem)data.ReadByteValue();
#if NET20 || NET35
if ((obj.FLG & Flags.FEXTRA) != 0)
if ((obj.Flags & Flags.FEXTRA) != 0)
#else
if (obj.FLG.HasFlag(Flags.FEXTRA))
if (obj.Flags.HasFlag(Flags.FEXTRA))
#endif
{
obj.XLEN = data.ReadUInt16LittleEndian();
obj.ExtraLength = data.ReadUInt16LittleEndian();
// Cache the current position
long currentPosition = data.Position;
List<ExtraFieldData> extraFields = [];
while (data.Position < currentPosition + obj.XLEN)
while (data.Position < currentPosition + obj.ExtraLength)
{
var extraField = ParseExtraFieldData(data);
if (extraField == null)
@@ -102,24 +102,24 @@ namespace SabreTools.Serialization.Deserializers
}
#if NET20 || NET35
if ((obj.FLG & Flags.FNAME) != 0)
if ((obj.Flags & Flags.FNAME) != 0)
#else
if (obj.FLG.HasFlag(Flags.FNAME))
if (obj.Flags.HasFlag(Flags.FNAME))
#endif
obj.OriginalFileName = data.ReadNullTerminatedAnsiString();
#if NET20 || NET35
if ((obj.FLG & Flags.FCOMMENT) != 0)
if ((obj.Flags & Flags.FCOMMENT) != 0)
#else
if (obj.FLG.HasFlag(Flags.FCOMMENT))
if (obj.Flags.HasFlag(Flags.FCOMMENT))
#endif
obj.FileComment = data.ReadNullTerminatedAnsiString();
#if NET20 || NET35
if ((obj.FLG & Flags.FHCRC) != 0)
if ((obj.Flags & Flags.FHCRC) != 0)
#else
if (obj.FLG.HasFlag(Flags.FHCRC))
if (obj.Flags.HasFlag(Flags.FHCRC))
#endif
obj.CRC16 = data.ReadUInt16LittleEndian();
@@ -135,10 +135,10 @@ namespace SabreTools.Serialization.Deserializers
{
var obj = new ExtraFieldData();
obj.SI1 = data.ReadByteValue();
obj.SI2 = data.ReadByteValue();
obj.LEN = data.ReadUInt16LittleEndian();
obj.Data = data.ReadBytes(obj.LEN);
obj.SubfieldID1 = data.ReadByteValue();
obj.SubfieldID2 = data.ReadByteValue();
obj.Length = data.ReadUInt16LittleEndian();
obj.Data = data.ReadBytes(obj.Length);
return obj;
}
@@ -153,7 +153,7 @@ namespace SabreTools.Serialization.Deserializers
var obj = new Trailer();
obj.CRC32 = data.ReadUInt32LittleEndian();
obj.ISIZE = data.ReadUInt32LittleEndian();
obj.InputSize = data.ReadUInt32LittleEndian();
return obj;
}

View File

@@ -161,7 +161,7 @@ namespace SabreTools.Serialization.Deserializers
obj.BlockTableSizeLong = data.ReadUInt64LittleEndian();
obj.HiBlockTableSize = data.ReadUInt64LittleEndian();
obj.HetTableSize = data.ReadUInt64LittleEndian();
obj.BetTablesize = data.ReadUInt64LittleEndian();
obj.BetTableSize = data.ReadUInt64LittleEndian();
obj.RawChunkSize = data.ReadUInt32LittleEndian();
obj.BlockTableMD5 = data.ReadBytes(0x10);
@@ -194,7 +194,7 @@ namespace SabreTools.Serialization.Deserializers
byte[]? tableBytes = decrypter.LoadTable(data,
offset,
header.BetTableMD5,
(uint)header.BetTablesize,
(uint)header.BetTableSize,
92,
MPQ_KEY_BLOCK_TABLE,
out _);

View File

@@ -28,11 +28,7 @@ namespace SabreTools.Serialization.Deserializers
var archive = new Archive();
// Setup all of the collections
var localFileHeaders = new List<LocalFileHeader>();
var encryptionHeaders = new List<byte[]>();
var fileData = new List<byte[]>();
var dataDescriptors = new List<DataDescriptor>();
var zip64DataDescriptors = new List<DataDescriptor64>();
var localFiles = new List<LocalFile>();
var cdrs = new List<CentralDirectoryFileHeader>();
// Read all blocks
@@ -60,23 +56,13 @@ namespace SabreTools.Serialization.Deserializers
// Local File
case LocalFileHeaderSignature:
if (!ParseLocalFile(data,
out LocalFileHeader? localFileHeader,
out byte[]? encryptionHeader,
out byte[]? fileDatum,
out DataDescriptor? dataDescriptor,
out DataDescriptor64? dataDescriptor64))
{
break;
}
var lf = ParseLocalFile(data);
if (lf == null)
return null;
// Add the local file
validBlock = true;
localFileHeaders.Add(localFileHeader!);
encryptionHeaders.Add(encryptionHeader!);
fileData.Add(fileDatum!);
dataDescriptors.Add(dataDescriptor!);
zip64DataDescriptors.Add(dataDescriptor64!);
localFiles.Add(lf);
break;
// TODO: Implement
@@ -135,22 +121,11 @@ namespace SabreTools.Serialization.Deserializers
} while (data.Position < data.Length);
// If no blocks were read
if (localFileHeaders.Count == 0
&& encryptionHeaders.Count == 0
&& fileData.Count == 0
&& dataDescriptors.Count == 0
&& zip64DataDescriptors.Count == 0
&& cdrs.Count == 0)
{
if (localFiles.Count == 0 && cdrs.Count == 0)
return null;
}
// Assign the local files
archive.LocalFileHeaders = [.. localFileHeaders];
archive.EncryptionHeaders = [.. encryptionHeaders];
archive.FileData = [.. fileData];
archive.DataDescriptors = [.. dataDescriptors];
archive.ZIP64DataDescriptors = [.. zip64DataDescriptors];
archive.LocalFiles = [.. localFiles];
// Assign the central directory records
archive.CentralDirectoryHeaders = [.. cdrs];
@@ -245,10 +220,8 @@ namespace SabreTools.Serialization.Deserializers
if (extraBytes.Length != obj.ExtraFieldLength)
return null;
obj.ExtraField = extraBytes;
// TODO: This should live on the model instead of the byte representation
extraFields = ParseExtraFields(obj, obj.ExtraField);
extraFields = ParseExtraFields(obj, extraBytes);
}
if (obj.FileCommentLength > 0 && data.Position + obj.FileCommentLength <= data.Length)
{
@@ -436,25 +409,19 @@ namespace SabreTools.Serialization.Deserializers
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled local file on success, null on error</returns>
public static bool ParseLocalFile(Stream data,
out LocalFileHeader? localFileHeader,
out byte[]? encryptionHeader,
out byte[]? fileData,
out DataDescriptor? dataDescriptor,
out DataDescriptor64? dataDescriptor64)
public static LocalFile? ParseLocalFile(Stream data)
{
// Set default values
localFileHeader = null;
encryptionHeader = null;
fileData = null;
dataDescriptor = null;
dataDescriptor64 = null;
var obj = new LocalFile();
#region Local File Header
localFileHeader = ParseLocalFileHeader(data, out var extraFields);
// Try to read the header
var localFileHeader = ParseLocalFileHeader(data, out var extraFields);
if (localFileHeader == null)
return false;
return null;
// Assign the header
obj.LocalFileHeader = localFileHeader;
ulong compressedSize = localFileHeader.CompressedSize;
if (extraFields != null)
@@ -482,14 +449,17 @@ namespace SabreTools.Serialization.Deserializers
#endif
{
// Try to read the encryption header data -- TODO: Verify amount to read
encryptionHeader = data.ReadBytes(12);
if (encryptionHeader.Length != 12)
return false;
byte[] encryptionHeaders = data.ReadBytes(12);
if (encryptionHeaders.Length != 12)
return null;
// Set the encryption headers
obj.EncryptionHeaders = encryptionHeaders;
}
else
{
// Add the empty encryption header
encryptionHeader = [];
obj.EncryptionHeaders = [];
}
#endregion
@@ -497,9 +467,12 @@ namespace SabreTools.Serialization.Deserializers
#region File Data
// Try to read the file data
fileData = data.ReadBytes((int)compressedSize);
var fileData = data.ReadBytes((int)compressedSize);
if (fileData.Length < (long)compressedSize)
return false;
return null;
// Set the file data
obj.FileData = fileData;
#endregion
@@ -512,9 +485,9 @@ namespace SabreTools.Serialization.Deserializers
if (!localFileHeader.Flags.HasFlag(GeneralPurposeBitFlags.NoCRC))
#endif
{
dataDescriptor = new DataDescriptor();
dataDescriptor64 = new DataDescriptor64();
return true;
obj.DataDescriptor = new DataDescriptor();
obj.ZIP64DataDescriptor = new DataDescriptor64();
return obj;
}
// Read the signature
@@ -525,9 +498,9 @@ namespace SabreTools.Serialization.Deserializers
// Don't fail if descriptor is missing
if (signature != DataDescriptorSignature)
{
dataDescriptor = new DataDescriptor();
dataDescriptor64 = new DataDescriptor64();
return true;
obj.DataDescriptor = new DataDescriptor();
obj.ZIP64DataDescriptor = new DataDescriptor64();
return obj;
}
// Determine if the entry is ZIP64
@@ -536,22 +509,22 @@ namespace SabreTools.Serialization.Deserializers
// Try to parse the correct data descriptor
if (zip64)
{
dataDescriptor = new DataDescriptor();
dataDescriptor64 = ParseDataDescriptor64(data);
if (dataDescriptor64 == null)
return false;
obj.DataDescriptor = new DataDescriptor();
obj.ZIP64DataDescriptor = ParseDataDescriptor64(data);
if (obj.ZIP64DataDescriptor == null)
return null;
}
else
{
dataDescriptor = ParseDataDescriptor(data);
dataDescriptor64 = new DataDescriptor64();
if (dataDescriptor == null)
return false;
obj.DataDescriptor = ParseDataDescriptor(data);
obj.ZIP64DataDescriptor = new DataDescriptor64();
if (obj.DataDescriptor == null)
return null;
}
#endregion
return true;
return obj;
}
/// <summary>
@@ -602,10 +575,8 @@ namespace SabreTools.Serialization.Deserializers
if (extraBytes.Length != obj.ExtraFieldLength)
return null;
obj.ExtraField = extraBytes;
// TODO: This should live on the model instead of the byte representation
extraFields = ParseExtraFields(obj, obj.ExtraField);
extraFields = ParseExtraFields(obj, extraBytes);
}
return obj;
@@ -633,7 +604,7 @@ namespace SabreTools.Serialization.Deserializers
offset -= 2;
// Read based on the ID
var field = id switch
ExtensibleDataField? field = id switch
{
HeaderID.Zip64ExtendedInformation => ParseZip64ExtendedInformationExtraField(data, ref offset, header),
HeaderID.AVInfo => ParseUnknownExtraField(data, ref offset), // TODO: Implement model
@@ -724,7 +695,7 @@ namespace SabreTools.Serialization.Deserializers
offset -= 2;
// Read based on the ID
var field = id switch
ExtensibleDataField? field = id switch
{
HeaderID.Zip64ExtendedInformation => ParseZip64ExtendedInformationExtraField(data, ref offset, header),
HeaderID.AVInfo => ParseUnknownExtraField(data, ref offset), // TODO: Implement model
@@ -801,13 +772,15 @@ namespace SabreTools.Serialization.Deserializers
/// <param name="data">Byte array to parse</param>
/// <param name="offset">Offset into the byte array</param>
/// <returns>Filled unknown extras field on success, null on error</returns>
private static ExtensibleDataField? ParseUnknownExtraField(byte[] data, ref int offset)
private static UnknownExtraField? ParseUnknownExtraField(byte[] data, ref int offset)
{
_ = (HeaderID)data.ReadUInt16LittleEndian(ref offset);
ushort dataSize = data.ReadUInt16LittleEndian(ref offset);
_ = data.ReadBytes(ref offset, dataSize);
var obj = new UnknownExtraField();
return null;
obj.HeaderID = (HeaderID)data.ReadUInt16LittleEndian(ref offset);
obj.DataSize = data.ReadUInt16LittleEndian(ref offset);
obj.Data = data.ReadBytes(ref offset, obj.DataSize);
return obj;
}
/// <summary>
@@ -911,10 +884,10 @@ namespace SabreTools.Serialization.Deserializers
return null;
obj.DataSize = data.ReadUInt16LittleEndian(ref offset);
obj.BSize = data.ReadUInt32LittleEndian(ref offset);
obj.CType = data.ReadUInt16LittleEndian(ref offset);
obj.EACRC = data.ReadUInt32LittleEndian(ref offset);
obj.Var = data.ReadBytes(ref offset, obj.DataSize - 10);
obj.UncompressedBlockSize = data.ReadUInt32LittleEndian(ref offset);
obj.CompressionType = data.ReadUInt16LittleEndian(ref offset);
obj.CRC32 = data.ReadUInt32LittleEndian(ref offset);
obj.Data = data.ReadBytes(ref offset, obj.DataSize - 10);
return obj;
}
@@ -936,27 +909,23 @@ namespace SabreTools.Serialization.Deserializers
obj.DataSize = data.ReadUInt16LittleEndian(ref offset);
obj.Reserved = data.ReadUInt32LittleEndian(ref offset);
List<ushort> tags = [];
List<ushort> sizes = [];
List<byte[]> datas = [];
List<TagSizeVar> entries = [];
int bytesRemaining = obj.DataSize - 4;
while (bytesRemaining > 0)
{
ushort tag = data.ReadUInt16LittleEndian(ref offset);
ushort size = data.ReadUInt16LittleEndian(ref offset);
byte[] datum = data.ReadBytes(ref offset, size);
var entry = new TagSizeVar();
tags.Add(tag);
sizes.Add(size);
datas.Add(datum);
entry.Tag = data.ReadUInt16LittleEndian(ref offset);
entry.Size = data.ReadUInt16LittleEndian(ref offset);
entry.Var = data.ReadBytes(ref offset, entry.Size);
bytesRemaining -= 4 + size;
entries.Add(entry);
bytesRemaining -= 4 + entry.Size;
}
obj.Tags = [.. tags];
obj.Size = [.. sizes];
obj.Vars = null; // TODO: Fix once this becomes a proper array of arrays
obj.TagSizeVars = [.. entries];
return obj;
}
@@ -978,27 +947,23 @@ namespace SabreTools.Serialization.Deserializers
obj.DataSize = data.ReadUInt16LittleEndian(ref offset);
obj.CRC = data.ReadUInt32LittleEndian(ref offset);
List<ushort> tags = [];
List<ushort> sizes = [];
List<byte[]> datas = [];
List<TagSizeVar> entries = [];
int bytesRemaining = obj.DataSize - 4;
while (bytesRemaining > 0)
{
ushort tag = data.ReadUInt16LittleEndian(ref offset);
ushort size = data.ReadUInt16LittleEndian(ref offset);
byte[] datum = data.ReadBytes(ref offset, size);
var entry = new TagSizeVar();
tags.Add(tag);
sizes.Add(size);
datas.Add(datum);
entry.Tag = data.ReadUInt16LittleEndian(ref offset);
entry.Size = data.ReadUInt16LittleEndian(ref offset);
entry.Var = data.ReadBytes(ref offset, entry.Size);
bytesRemaining -= 4 + size;
entries.Add(entry);
bytesRemaining -= 4 + entry.Size;
}
obj.Tags = [.. tags];
obj.Size = [.. sizes];
obj.Vars = null; // TODO: Fix once this becomes a proper array of arrays
obj.TagSizeVars = [.. entries];
return obj;
}
@@ -1018,11 +983,11 @@ namespace SabreTools.Serialization.Deserializers
return null;
obj.DataSize = data.ReadUInt16LittleEndian(ref offset);
obj.Atime = data.ReadUInt32LittleEndian(ref offset);
obj.Mtime = data.ReadUInt32LittleEndian(ref offset);
obj.Uid = data.ReadUInt16LittleEndian(ref offset);
obj.Gid = data.ReadUInt16LittleEndian(ref offset);
obj.Var = data.ReadBytes(ref offset, obj.DataSize - 12);
obj.FileLastAccessTime = data.ReadUInt32LittleEndian(ref offset);
obj.FileLastModificationTime = data.ReadUInt32LittleEndian(ref offset);
obj.FileUserID = data.ReadUInt16LittleEndian(ref offset);
obj.FileGroupID = data.ReadUInt16LittleEndian(ref offset);
obj.Data = data.ReadBytes(ref offset, obj.DataSize - 12);
return obj;
}
@@ -1152,27 +1117,23 @@ namespace SabreTools.Serialization.Deserializers
obj.DataSize = data.ReadUInt16LittleEndian(ref offset);
List<ushort> tags = [];
List<ushort> sizes = [];
List<byte[]> datas = [];
List<TagSizeVar> entries = [];
int bytesRemaining = obj.DataSize - 4;
while (bytesRemaining > 0)
{
ushort tag = data.ReadUInt16LittleEndian(ref offset);
ushort size = data.ReadUInt16LittleEndian(ref offset);
byte[] datum = data.ReadBytes(ref offset, size);
var entry = new TagSizeVar();
tags.Add(tag);
sizes.Add(size);
datas.Add(datum);
entry.Tag = data.ReadUInt16LittleEndian(ref offset);
entry.Size = data.ReadUInt16LittleEndian(ref offset);
entry.Var = data.ReadBytes(ref offset, entry.Size);
bytesRemaining -= 4 + size;
entries.Add(entry);
bytesRemaining -= 4 + entry.Size;
}
obj.Tags = [.. tags];
obj.Size = [.. sizes];
obj.Vars = null; // TODO: Fix once this becomes a proper array of arrays
obj.TagSizeVars = [.. entries];
return obj;
}

View File

@@ -4,6 +4,7 @@ using System.IO;
using System.Text;
using SabreTools.IO.Extensions;
using SabreTools.Models.PortableExecutable;
using SabreTools.Models.PortableExecutable.COFFSymbolTableEntries;
using static SabreTools.Models.PortableExecutable.Constants;
namespace SabreTools.Serialization.Deserializers
@@ -371,6 +372,23 @@ namespace SabreTools.Serialization.Deserializers
return obj;
}
/// <summary>
/// Parse a Stream into a CLRTokenDefinition
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled CLRTokenDefinition on success, null on error</returns>
public static CLRTokenDefinition ParseCLRTokenDefinition(Stream data)
{
var obj = new CLRTokenDefinition();
obj.AuxFormat6AuxType = data.ReadByteValue();
obj.AuxFormat6Reserved1 = data.ReadByteValue();
obj.AuxFormat6SymbolTableIndex = data.ReadUInt32LittleEndian();
obj.AuxFormat6Reserved2 = data.ReadBytes(12);
return obj;
}
/// <summary>
/// Parse a Stream into a COFFFileHeader
/// </summary>
@@ -420,192 +438,15 @@ namespace SabreTools.Serialization.Deserializers
return obj;
}
/// <summary>
/// Parse a Stream into a COFFSymbolTableEntry
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled COFFSymbolTableEntry on success, null on error</returns>
/// <remarks>Standard COFF Symbol Table Entry</remarks>
public static COFFSymbolTableEntry? ParseCOFFSymbolTableEntryType0(Stream data, out int currentSymbolType)
{
var entry = new COFFSymbolTableEntry();
entry.ShortName = data.ReadBytes(8);
if (entry.ShortName != null)
entry.Zeroes = BitConverter.ToUInt32(entry.ShortName, 0);
if (entry.Zeroes == 0)
{
if (entry.ShortName != null)
entry.Offset = BitConverter.ToUInt32(entry.ShortName, 4);
entry.ShortName = null;
}
entry.Value = data.ReadUInt32LittleEndian();
entry.SectionNumber = data.ReadUInt16LittleEndian();
entry.SymbolType = (SymbolType)data.ReadUInt16LittleEndian();
entry.StorageClass = (StorageClass)data.ReadByteValue();
entry.NumberOfAuxSymbols = data.ReadByteValue();
if (entry.StorageClass == StorageClass.IMAGE_SYM_CLASS_EXTERNAL
&& entry.SymbolType == SymbolType.IMAGE_SYM_TYPE_FUNC
&& entry.SectionNumber > 0)
{
currentSymbolType = 1;
}
else if (entry.StorageClass == StorageClass.IMAGE_SYM_CLASS_FUNCTION
&& entry.ShortName != null
&& ((entry.ShortName[0] == 0x2E && entry.ShortName[1] == 0x62 && entry.ShortName[2] == 0x66) // .bf
|| (entry.ShortName[0] == 0x2E && entry.ShortName[1] == 0x65 && entry.ShortName[2] == 0x66))) // .ef
{
currentSymbolType = 2;
}
else if (entry.StorageClass == StorageClass.IMAGE_SYM_CLASS_EXTERNAL
&& entry.SectionNumber == (ushort)SectionNumber.IMAGE_SYM_UNDEFINED
&& entry.Value == 0)
{
currentSymbolType = 3;
}
else if (entry.StorageClass == StorageClass.IMAGE_SYM_CLASS_FILE)
{
// TODO: Symbol name should be ".file"
currentSymbolType = 4;
}
else if (entry.StorageClass == StorageClass.IMAGE_SYM_CLASS_STATIC)
{
// TODO: Should have the name of a section (like ".text")
currentSymbolType = 5;
}
else if (entry.StorageClass == StorageClass.IMAGE_SYM_CLASS_CLR_TOKEN)
{
currentSymbolType = 6;
}
else
{
currentSymbolType = -1;
return null;
}
return entry;
}
/// <summary>
/// Parse a Stream into a COFFSymbolTableEntry
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled COFFSymbolTableEntry on success, null on error</returns>
/// <remarks>Auxiliary Format 1: Function Definitions</remarks>
public static COFFSymbolTableEntry ParseCOFFSymbolTableEntryType1(Stream data)
{
var obj = new COFFSymbolTableEntry();
obj.AuxFormat1TagIndex = data.ReadUInt32LittleEndian();
obj.AuxFormat1TotalSize = data.ReadUInt32LittleEndian();
obj.AuxFormat1PointerToLinenumber = data.ReadUInt32LittleEndian();
obj.AuxFormat1PointerToNextFunction = data.ReadUInt32LittleEndian();
obj.AuxFormat1Unused = data.ReadUInt16LittleEndian();
return obj;
}
/// <summary>
/// Parse a Stream into a COFFSymbolTableEntry
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled COFFSymbolTableEntry on success, null on error</returns>
/// <remarks>Auxiliary Format 2: .bf and .ef Symbols</remarks>
public static COFFSymbolTableEntry ParseCOFFSymbolTableEntryType2(Stream data)
{
var obj = new COFFSymbolTableEntry();
obj.AuxFormat2Unused1 = data.ReadUInt32LittleEndian();
obj.AuxFormat2Linenumber = data.ReadUInt16LittleEndian();
obj.AuxFormat2Unused2 = data.ReadBytes(6);
obj.AuxFormat2PointerToNextFunction = data.ReadUInt32LittleEndian();
obj.AuxFormat2Unused3 = data.ReadUInt16LittleEndian();
return obj;
}
/// <summary>
/// Parse a Stream into a COFFSymbolTableEntry
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled COFFSymbolTableEntry on success, null on error</returns>
/// <remarks>Auxiliary Format 3: Weak Externals</remarks>
public static COFFSymbolTableEntry ParseCOFFSymbolTableEntryType3(Stream data)
{
var obj = new COFFSymbolTableEntry();
obj.AuxFormat3TagIndex = data.ReadUInt32LittleEndian();
obj.AuxFormat3Characteristics = data.ReadUInt32LittleEndian();
obj.AuxFormat3Unused = data.ReadBytes(10);
return obj;
}
/// <summary>
/// Parse a Stream into a COFFSymbolTableEntry
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled COFFSymbolTableEntry on success, null on error</returns>
/// <remarks>Auxiliary Format 4: Files</remarks>
public static COFFSymbolTableEntry ParseCOFFSymbolTableEntryType4(Stream data)
{
var obj = new COFFSymbolTableEntry();
obj.AuxFormat4FileName = data.ReadBytes(18);
return obj;
}
/// <summary>
/// Parse a Stream into a COFFSymbolTableEntry
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled COFFSymbolTableEntry on success, null on error</returns>
/// <remarks>Auxiliary Format 5: Section Definitions</remarks>
public static COFFSymbolTableEntry ParseCOFFSymbolTableEntryType5(Stream data)
{
var obj = new COFFSymbolTableEntry();
obj.AuxFormat5Length = data.ReadUInt32LittleEndian();
obj.AuxFormat5NumberOfRelocations = data.ReadUInt16LittleEndian();
obj.AuxFormat5NumberOfLinenumbers = data.ReadUInt16LittleEndian();
obj.AuxFormat5CheckSum = data.ReadUInt32LittleEndian();
obj.AuxFormat5Number = data.ReadUInt16LittleEndian();
obj.AuxFormat5Selection = data.ReadByteValue();
obj.AuxFormat5Unused = data.ReadBytes(3);
return obj;
}
/// <summary>
/// Parse a Stream into a COFFSymbolTableEntry
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled COFFSymbolTableEntry on success, null on error</returns>
/// <remarks>Auxiliary Format 6: CLR Token Definition</remarks>
public static COFFSymbolTableEntry ParseCOFFSymbolTableEntryType6(Stream data)
{
var obj = new COFFSymbolTableEntry();
obj.AuxFormat6AuxType = data.ReadByteValue();
obj.AuxFormat6Reserved1 = data.ReadByteValue();
obj.AuxFormat6SymbolTableIndex = data.ReadUInt32LittleEndian();
obj.AuxFormat6Reserved2 = data.ReadBytes(12);
return obj;
}
/// <summary>
/// Parse a Stream into a COFF symbol table
/// </summary>
/// <param name="data">Stream to parse</param>
/// <param name="count">Number of COFF symbol table entries to read</param>
/// <returns>Filled COFF symbol table on success, null on error</returns>
public static COFFSymbolTableEntry[]? ParseCOFFSymbolTable(Stream data, uint count)
public static BaseEntry[]? ParseCOFFSymbolTable(Stream data, uint count)
{
var obj = new COFFSymbolTableEntry[count];
var obj = new BaseEntry[count];
int auxSymbolsRemaining = 0;
int currentSymbolType = 0;
@@ -615,7 +456,7 @@ namespace SabreTools.Serialization.Deserializers
// Standard COFF Symbol Table Entry
if (currentSymbolType == 0)
{
var entry = ParseCOFFSymbolTableEntryType0(data, out currentSymbolType);
var entry = ParseStandardRecord(data, out currentSymbolType);
if (entry == null)
return null;
@@ -632,42 +473,42 @@ namespace SabreTools.Serialization.Deserializers
// Auxiliary Format 1: Function Definitions
else if (currentSymbolType == 1)
{
obj[i] = ParseCOFFSymbolTableEntryType1(data); ;
obj[i] = ParseFunctionDefinition(data);
auxSymbolsRemaining--;
}
// Auxiliary Format 2: .bf and .ef Symbols
else if (currentSymbolType == 2)
{
obj[i] = ParseCOFFSymbolTableEntryType2(data); ;
obj[i] = ParseDescriptor(data);
auxSymbolsRemaining--;
}
// Auxiliary Format 3: Weak Externals
else if (currentSymbolType == 3)
{
obj[i] = ParseCOFFSymbolTableEntryType3(data); ;
obj[i] = ParseWeakExternal(data);
auxSymbolsRemaining--;
}
// Auxiliary Format 4: Files
else if (currentSymbolType == 4)
{
obj[i] = ParseCOFFSymbolTableEntryType4(data); ;
obj[i] = ParseFileRecord(data);
auxSymbolsRemaining--;
}
// Auxiliary Format 5: Section Definitions
else if (currentSymbolType == 5)
{
obj[i] = ParseCOFFSymbolTableEntryType5(data); ;
obj[i] = ParseSectionDefinition(data);
auxSymbolsRemaining--;
}
// Auxiliary Format 6: CLR Token Definition
else if (currentSymbolType == 6)
{
obj[i] = ParseCOFFSymbolTableEntryType6(data); ;
obj[i] = ParseCLRTokenDefinition(data);
auxSymbolsRemaining--;
}
@@ -768,6 +609,24 @@ namespace SabreTools.Serialization.Deserializers
return obj;
}
/// <summary>
/// Parse a Stream into a Descriptor
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled Descriptor on success, null on error</returns>
public static Descriptor ParseDescriptor(Stream data)
{
var obj = new Descriptor();
obj.Unused1 = data.ReadUInt32LittleEndian();
obj.Linenumber = data.ReadUInt16LittleEndian();
obj.Unused2 = data.ReadBytes(6);
obj.PointerToNextFunction = data.ReadUInt32LittleEndian();
obj.Unused3 = data.ReadUInt16LittleEndian();
return obj;
}
/// <summary>
/// Parse a Stream into a ExportAddressTableEntry
/// </summary>
@@ -885,7 +744,7 @@ namespace SabreTools.Serialization.Deserializers
for (int i = 0; i < directoryTable.NumberOfNamePointers; i++)
{
long nameAddress = initialOffset
+ obj.NamePointerTable.Pointers[i].ConvertVirtualAddress(sections); ;
+ obj.NamePointerTable.Pointers[i].ConvertVirtualAddress(sections);
data.Seek(nameAddress, SeekOrigin.Begin);
string? str = data.ReadNullTerminatedAnsiString();
@@ -898,6 +757,38 @@ namespace SabreTools.Serialization.Deserializers
return obj;
}
/// <summary>
/// Parse a Stream into a FileRecord
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled FileRecord on success, null on error</returns>
public static FileRecord ParseFileRecord(Stream data)
{
var obj = new FileRecord();
obj.FileName = data.ReadBytes(18);
return obj;
}
/// <summary>
/// Parse a Stream into a FunctionDefinition
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled FunctionDefinition on success, null on error</returns>
public static FunctionDefinition ParseFunctionDefinition(Stream data)
{
var obj = new FunctionDefinition();
obj.TagIndex = data.ReadUInt32LittleEndian();
obj.TotalSize = data.ReadUInt32LittleEndian();
obj.PointerToLinenumber = data.ReadUInt32LittleEndian();
obj.PointerToNextFunction = data.ReadUInt32LittleEndian();
obj.Unused = data.ReadUInt16LittleEndian();
return obj;
}
/// <summary>
/// Parse a Stream into a HintNameTableEntry
/// </summary>
@@ -1499,6 +1390,26 @@ namespace SabreTools.Serialization.Deserializers
return obj;
}
/// <summary>
/// Parse a Stream into a SectionDefinition
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled SectionDefinition on success, null on error</returns>
public static SectionDefinition ParseSectionDefinition(Stream data)
{
var obj = new SectionDefinition();
obj.Length = data.ReadUInt32LittleEndian();
obj.NumberOfRelocations = data.ReadUInt16LittleEndian();
obj.NumberOfLinenumbers = data.ReadUInt16LittleEndian();
obj.CheckSum = data.ReadUInt32LittleEndian();
obj.Number = data.ReadUInt16LittleEndian();
obj.Selection = data.ReadByteValue();
obj.Unused = data.ReadBytes(3);
return obj;
}
/// <summary>
/// Parse a Stream into a SectionHeader
/// </summary>
@@ -1531,5 +1442,88 @@ namespace SabreTools.Serialization.Deserializers
return obj;
}
/// <summary>
/// Parse a Stream into a StandardRecord
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled StandardRecord on success, null on error</returns>
public static StandardRecord? ParseStandardRecord(Stream data, out int currentSymbolType)
{
var entry = new StandardRecord();
entry.ShortName = data.ReadBytes(8);
if (entry.ShortName != null)
entry.Zeroes = BitConverter.ToUInt32(entry.ShortName, 0);
if (entry.Zeroes == 0)
{
if (entry.ShortName != null)
entry.Offset = BitConverter.ToUInt32(entry.ShortName, 4);
entry.ShortName = null;
}
entry.Value = data.ReadUInt32LittleEndian();
entry.SectionNumber = data.ReadUInt16LittleEndian();
entry.SymbolType = (SymbolType)data.ReadUInt16LittleEndian();
entry.StorageClass = (StorageClass)data.ReadByteValue();
entry.NumberOfAuxSymbols = data.ReadByteValue();
if (entry.StorageClass == StorageClass.IMAGE_SYM_CLASS_EXTERNAL
&& entry.SymbolType == SymbolType.IMAGE_SYM_TYPE_FUNC
&& entry.SectionNumber > 0)
{
currentSymbolType = 1;
}
else if (entry.StorageClass == StorageClass.IMAGE_SYM_CLASS_FUNCTION
&& entry.ShortName != null
&& ((entry.ShortName[0] == 0x2E && entry.ShortName[1] == 0x62 && entry.ShortName[2] == 0x66) // .bf
|| (entry.ShortName[0] == 0x2E && entry.ShortName[1] == 0x65 && entry.ShortName[2] == 0x66))) // .ef
{
currentSymbolType = 2;
}
else if (entry.StorageClass == StorageClass.IMAGE_SYM_CLASS_EXTERNAL
&& entry.SectionNumber == (ushort)SectionNumber.IMAGE_SYM_UNDEFINED
&& entry.Value == 0)
{
currentSymbolType = 3;
}
else if (entry.StorageClass == StorageClass.IMAGE_SYM_CLASS_FILE)
{
// TODO: Symbol name should be ".file"
currentSymbolType = 4;
}
else if (entry.StorageClass == StorageClass.IMAGE_SYM_CLASS_STATIC)
{
// TODO: Should have the name of a section (like ".text")
currentSymbolType = 5;
}
else if (entry.StorageClass == StorageClass.IMAGE_SYM_CLASS_CLR_TOKEN)
{
currentSymbolType = 6;
}
else
{
currentSymbolType = -1;
return null;
}
return entry;
}
/// <summary>
/// Parse a Stream into a WeakExternal
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled WeakExternal on success, null on error</returns>
public static WeakExternal ParseWeakExternal(Stream data)
{
var obj = new WeakExternal();
obj.TagIndex = data.ReadUInt32LittleEndian();
obj.Characteristics = data.ReadUInt32LittleEndian();
obj.Unused = data.ReadBytes(10);
return obj;
}
}
}

View File

@@ -70,19 +70,15 @@ namespace SabreTools.Serialization.Deserializers
obj.Header = header;
// TODO: Replace this with AlignToBoundary when IO is updated
// Align to the block size
while (data.Position % 512 != 0 && data.Position < data.Length)
{
_ = data.ReadByteValue();
}
data.AlignToBoundary(512);
#endregion
#region Blocks
// Exit if the size is invalid
string sizeOctalString = Encoding.ASCII.GetString(header.Size!).TrimEnd('\0');
string sizeOctalString = header.Size!.TrimEnd('\0');
if (sizeOctalString.Length == 0)
return obj;
@@ -120,12 +116,18 @@ namespace SabreTools.Serialization.Deserializers
byte[] filenameBytes = data.ReadBytes(100);
obj.FileName = Encoding.ASCII.GetString(filenameBytes);
obj.Mode = data.ReadBytes(8);
obj.UID = data.ReadBytes(8);
obj.GID = data.ReadBytes(8);
obj.Size = data.ReadBytes(12);
obj.ModifiedTime = data.ReadBytes(12);
obj.Checksum = data.ReadBytes(8);
byte[] modeBytes = data.ReadBytes(8);
obj.Mode = Encoding.ASCII.GetString(modeBytes);
byte[] uidBytes = data.ReadBytes(8);
obj.UID = Encoding.ASCII.GetString(uidBytes);
byte[] gidBytes = data.ReadBytes(8);
obj.GID = Encoding.ASCII.GetString(gidBytes);
byte[] sizeBytes = data.ReadBytes(12);
obj.Size = Encoding.ASCII.GetString(sizeBytes);
byte[] modifiedBytes = data.ReadBytes(12);
obj.ModifiedTime = Encoding.ASCII.GetString(modifiedBytes);
byte[] checksumBytes = data.ReadBytes(8);
obj.Checksum = Encoding.ASCII.GetString(checksumBytes);
obj.TypeFlag = (TypeFlag)data.ReadByteValue();
byte[] linkNameBytes = data.ReadBytes(100);
obj.LinkName = Encoding.ASCII.GetString(linkNameBytes);
@@ -172,9 +174,7 @@ namespace SabreTools.Serialization.Deserializers
var obj = new Block();
// TODO: Assign this to obj.Data directly when Models is updated
byte[] temp = data.ReadBytes(512);
Array.Copy(temp, 0, obj.Data, 0, 512);
obj.Data = data.ReadBytes(512);
return obj;
}

View File

@@ -0,0 +1,150 @@
using System;
using System.IO;
using System.Text;
using SabreTools.IO.Extensions;
using SabreTools.Models.WiseInstaller;
namespace SabreTools.Serialization.Deserializers
{
public class WiseOverlayHeader : BaseBinaryDeserializer<OverlayHeader>
{
/// <inheritdoc/>
public override OverlayHeader? Deserialize(Stream? data)
{
// If the data is invalid
if (data == null || !data.CanRead)
return null;
try
{
// Cache the current offset
long initialOffset = data.Position;
var overlayHeader = ParseOverlayHeader(data);
// WiseColors.dib
if (overlayHeader.DibDeflatedSize >= data.Length)
return null;
else if (overlayHeader.DibDeflatedSize > overlayHeader.DibInflatedSize)
return null;
// WiseScript.bin
if (overlayHeader.WiseScriptDeflatedSize == 0)
return null;
else if (overlayHeader.WiseScriptDeflatedSize >= data.Length)
return null;
else if (overlayHeader.WiseScriptDeflatedSize > overlayHeader.WiseScriptInflatedSize)
return null;
// WISE0001.DLL
if (overlayHeader.WiseDllDeflatedSize >= data.Length)
return null;
// FILE00XX.DAT
if (overlayHeader.FinalFileDeflatedSize == 0)
return null;
else if (overlayHeader.FinalFileDeflatedSize >= data.Length)
return null;
else if (overlayHeader.FinalFileDeflatedSize > overlayHeader.FinalFileInflatedSize)
return null;
// Valid for older overlay headers
if (overlayHeader.Endianness == 0x0000 && overlayHeader.InitTextLen == 0)
return overlayHeader;
if (overlayHeader.Endianness != Endianness.LittleEndian && overlayHeader.Endianness != Endianness.BigEndian)
return null;
return overlayHeader;
}
catch
{
// Ignore the actual error
return null;
}
}
/// <summary>
/// Parse a Stream into an OverlayHeader
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled OverlayHeader on success, null on error</returns>
private static OverlayHeader ParseOverlayHeader(Stream data)
{
var header = new OverlayHeader();
header.DllNameLen = data.ReadByteValue();
if (header.DllNameLen > 0)
{
byte[] dllName = data.ReadBytes(header.DllNameLen);
header.DllName = Encoding.ASCII.GetString(dllName);
header.DllSize = data.ReadUInt32LittleEndian();
}
// Read as a single block
header.Flags = (OverlayHeaderFlags)data.ReadUInt32LittleEndian();
// Read as a single block
header.GraphicsData = data.ReadBytes(12);
// Read as a single block
header.WiseScriptExitEventOffset = data.ReadUInt32LittleEndian();
header.WiseScriptCancelEventOffset = data.ReadUInt32LittleEndian();
// Read as a single block
header.WiseScriptInflatedSize = data.ReadUInt32LittleEndian();
header.WiseScriptDeflatedSize = data.ReadUInt32LittleEndian();
header.WiseDllDeflatedSize = data.ReadUInt32LittleEndian();
header.Ctl3d32DeflatedSize = data.ReadUInt32LittleEndian();
header.SomeData4DeflatedSize = data.ReadUInt32LittleEndian();
header.RegToolDeflatedSize = data.ReadUInt32LittleEndian();
header.ProgressDllDeflatedSize = data.ReadUInt32LittleEndian();
header.SomeData7DeflatedSize = data.ReadUInt32LittleEndian();
header.SomeData8DeflatedSize = data.ReadUInt32LittleEndian();
header.SomeData9DeflatedSize = data.ReadUInt32LittleEndian();
header.SomeData10DeflatedSize = data.ReadUInt32LittleEndian();
header.FinalFileDeflatedSize = data.ReadUInt32LittleEndian();
header.FinalFileInflatedSize = data.ReadUInt32LittleEndian();
header.EOF = data.ReadUInt32LittleEndian();
// Newer installers read this and DibInflatedSize in the above block
header.DibDeflatedSize = data.ReadUInt32LittleEndian();
// Handle older overlay data
if (header.DibDeflatedSize > data.Length)
{
header.DibDeflatedSize = 0;
data.Seek(-4, SeekOrigin.Current);
return header;
}
header.DibInflatedSize = data.ReadUInt32LittleEndian();
// Peek at the next 2 bytes
ushort peek = data.ReadUInt16LittleEndian();
data.Seek(-2, SeekOrigin.Current);
// If the next value is a known Endianness
if (Enum.IsDefined(typeof(Endianness), peek))
{
header.Endianness = (Endianness)data.ReadUInt16LittleEndian();
}
else
{
// The first two values are part of the sizes block above
header.InstallScriptDeflatedSize = data.ReadUInt32LittleEndian();
header.CharacterSet = (CharacterSet)data.ReadUInt32LittleEndian();
header.Endianness = (Endianness)data.ReadUInt16LittleEndian();
}
// Endianness and init text len are read in a single block
header.InitTextLen = data.ReadByteValue();
if (header.InitTextLen > 0)
{
byte[] initText = data.ReadBytes(header.InitTextLen);
header.InitText = Encoding.ASCII.GetString(initText);
}
return header;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,332 @@
using System.Collections.Generic;
using System.IO;
using System.Text;
using SabreTools.IO.Extensions;
using SabreTools.Matching;
using SabreTools.Models.WiseInstaller;
using static SabreTools.Models.WiseInstaller.Constants;
namespace SabreTools.Serialization.Deserializers
{
public class WiseSectionHeader : BaseBinaryDeserializer<SectionHeader>
{
/// <inheritdoc/>
public override SectionHeader? Deserialize(Stream? data)
{
// If the data is invalid
if (data == null || !data.CanRead)
return null;
try
{
// Cache the current offset
long initialOffset = data.Position;
var header = ParseSectionHeader(data, initialOffset);
if (header == null)
return null;
// Main MSI file
// If these go wrong, then there actually is a major issue, and the fallback won't work.
if (header.MsiFileEntryLength == 0)
return null;
else if (header.MsiFileEntryLength >= data.Length)
return null;
// First executable file
if (header.FirstExecutableFileEntryLength >= data.Length)
return header;
// Second executable file
if (header.SecondExecutableFileEntryLength >= data.Length)
return header;
return header;
}
catch
{
// Could header somehow be returned here too?
return null;
}
}
/// <summary>
/// Parse a Stream into a WiseSectionHeader
/// </summary>
/// <param name="data">Stream to parse</param>
/// <param name="initialOffset">Initial offset to use in address comparisons</param>
/// <returns>Filled WiseSectionHeader on success, null on error</returns>
public static SectionHeader? ParseSectionHeader(Stream data, long initialOffset)
{
var header = new SectionHeader();
// Setup required variables
int wisOffset = -1;
int headerLength = -1;
// Find offset of "WIS", determine header length, read presumed version value
foreach (int offset in WisOffsets)
{
data.Seek(initialOffset + offset, 0);
byte[] checkBytes = data.ReadBytes(3);
if (!checkBytes.EqualsExactly(WisString))
continue;
headerLength = WiseSectionHeaderLengthDictionary[offset];
int versionOffset = WiseSectionVersionOffsetDictionary[offset];
data.Seek(initialOffset + offset - versionOffset, 0);
header.Version = data.ReadBytes(versionOffset);
wisOffset = offset;
}
bool earlyReturn = false;
// If the header is invalid
if (header.Version == null)
earlyReturn = true;
if (wisOffset < 0)
earlyReturn = true;
if (headerLength < 0)
earlyReturn = true;
//Seek back to the beginning of the section
data.Seek(initialOffset, 0);
// Read common values
header.UnknownDataSize = data.ReadUInt32LittleEndian();
header.SecondExecutableFileEntryLength = data.ReadUInt32LittleEndian();
header.UnknownValue2 = data.ReadUInt32LittleEndian();
header.UnknownValue3 = data.ReadUInt32LittleEndian();
header.UnknownValue4 = data.ReadUInt32LittleEndian();
header.FirstExecutableFileEntryLength = data.ReadUInt32LittleEndian();
header.MsiFileEntryLength = data.ReadUInt32LittleEndian();
if (earlyReturn)
{
return header;
}
if (headerLength > 6)
{
header.UnknownValue7 = data.ReadUInt32LittleEndian();
header.UnknownValue8 = data.ReadUInt32LittleEndian();
}
if (headerLength > 8)
{
header.ThirdExecutableFileEntryLength = data.ReadUInt32LittleEndian();
header.UnknownValue10 = data.ReadUInt32LittleEndian();
header.UnknownValue11 = data.ReadUInt32LittleEndian();
header.UnknownValue12 = data.ReadUInt32LittleEndian();
header.UnknownValue13 = data.ReadUInt32LittleEndian();
header.UnknownValue14 = data.ReadUInt32LittleEndian();
header.UnknownValue15 = data.ReadUInt32LittleEndian();
header.UnknownValue16 = data.ReadUInt32LittleEndian();
header.UnknownValue17 = data.ReadUInt32LittleEndian();
}
if (headerLength > 17)
{
header.UnknownValue18 = data.ReadUInt32LittleEndian();
}
// Seek to the WIS string offset
data.Seek(initialOffset + wisOffset, SeekOrigin.Begin);
// Read the consistent strings
header.TmpString = data.ReadNullTerminatedAnsiString();
header.GuidString = data.ReadNullTerminatedAnsiString();
// Parse the pre-string section
int preStringBytesSize = GetPreStringBytesSize(data, header, wisOffset);
if (preStringBytesSize <= 0)
return header;
// Read the pre-string bytes
header.PreStringValues = data.ReadBytes(preStringBytesSize);
// Try to read the string arrays
// TODO: Count size of string section for later size verification
byte[][]? stringArrays = ParseStringTable(data, header.PreStringValues);
if (stringArrays == null)
return header;
// Set the string arrays
header.Strings = stringArrays;
// Not sure what this data is. Might be a wisescript?
// TODO: Should really be done in the wrapper, but almost everything there is static so there's no good place\
if (header.UnknownDataSize != 0)
data.Seek(header.UnknownDataSize, SeekOrigin.Current);
return header;
}
/// <summary>
/// Get the pre-string bytes size, if possible
/// </summary>
/// <param name="data">Stream to parse</param>
/// <param name="header">Section header to get information from</param>
/// <param name="wisOffset">Offset to the WIS string relative to the start of the header</param>
/// <returns>The size of the pre-string section</returns>
/// <remarks>
/// This method also sets <see cref="SectionHeader.NonWiseVersion"/> and
/// <see cref="SectionHeader.PreFontValue"/>, if possible.
/// </remarks>
private static int GetPreStringBytesSize(Stream data, SectionHeader header, int wisOffset)
{
// Handle a case that shouldn't happen
if (header.Version == null)
return 0;
// TODO: better way to figure out how far it's needed to advance?
int versionSize;
if (header.Version[header.Version.Length - 1] == 0x02)
versionSize = header.Version[header.Version.Length - 3];
else
versionSize = header.Version[header.Version.Length - 2];
// Third byte seems to indicate size of NonWiseVer
if (versionSize <= 1)
{
byte[] stringBytes = data.ReadBytes(versionSize);
header.NonWiseVersion = Encoding.ASCII.GetString(stringBytes);
if (wisOffset <= 77)
header.PreFontValue = data.ReadBytes(2);
else
header.PreFontValue = data.ReadBytes(4);
}
// If that third byte is 0x01, no NonWiseVersion string is present
else
{
header.PreFontValue = data.ReadBytes(3);
}
header.FontSize = data.ReadByte();
int preStringBytesSize = WiseSectionPreStringBytesSize[wisOffset];
// Hack for Codesited5.exe , very early and very strange.
if (header.Version[1] == 0x01)
preStringBytesSize = 2;
return preStringBytesSize;
}
/// <summary>
/// Parse the string table, if possible
/// </summary>
/// <param name="data"></param>
/// <param name="header"></param>
/// <param name="preStringBytesSize"></param>
/// <returns>The filled string table on success, false otherwise</returns>
private static byte[][]? ParseStringTable(Stream data, byte[] preStringValues)
{
// Setup the loop variables
List<byte[]> stringList = [];
int counter = 0;
bool endNow = false;
bool languageSection = false;
int languageSectionCounter = 0;
// Iterate pre-string byte array
while (counter < preStringValues.Length)
{
// Read the next byte value
byte currentByte = preStringValues[counter];
if (currentByte == 0x00)
break;
// Now doing third byte after language section begins
if (currentByte == 0x01 && languageSectionCounter == 2)
{
int extraLanguages = preStringValues[counter + 1];
for (int i = 0; i < extraLanguages; i++)
{
byte[]? incrementBytes = data.ReadBytes(2);
string? extraLanguageString = data.ReadNullTerminatedAnsiString();
if (extraLanguageString == null)
return null;
byte[]? extraLanguageStringArray = Encoding.ASCII.GetBytes(extraLanguageString);
stringList.Add(incrementBytes);
stringList.Add(extraLanguageStringArray);
}
break;
}
// Prepends non-string-size indicators
else if (currentByte == 0x01)
{
// 01 01 01 01: entering font section
// 01 5D 5C 01: link section; 5D and 5C are string sizes
int oneCount = 1;
counter++;
for (int i = counter; i <= preStringValues.Length; i++)
{
if (i == preStringValues.Length)
{
byte checkForZero;
do
{
checkForZero = data.ReadByteValue();
} while (checkForZero == 0x00);
data.Seek(-1, SeekOrigin.Current);
endNow = true;
break;
}
currentByte = preStringValues[counter];
// 0x01 followed by one more 0x01 seems to indicate to skip 2 null bytes, but 0x01 followed by
// three more 0x01 seems to indicate an unspecified length of null bytes that must be skipped.
// It has already been observed it mean 22 or 27 between 2 samples.
// If you encounter a null byte in the actual pre-string byte array, it seems to always be
// after you've read all the strings successfully.
if (currentByte == 0x00)
{
endNow = true;
break;
}
else if (currentByte > 0x01)
{
byte checkForZero;
do
{
checkForZero = data.ReadByteValue();
} while (checkForZero == 0x00);
data.Seek(-1, SeekOrigin.Current);
break;
}
else
{
oneCount++;
}
counter++;
}
if (oneCount == 4)
languageSection = true;
}
// If there was an issue
if (endNow)
break;
// Read and add the string as a byte array
byte[] currentString = data.ReadBytes(currentByte);
stringList.Add(currentString);
counter++;
if (languageSection)
languageSectionCounter++;
}
return [.. stringList];
}
}
}

View File

@@ -5,6 +5,7 @@ using System.Text;
using System.Xml.Serialization;
using SabreTools.IO.Extensions;
using SabreTools.Models.PortableExecutable;
using SabreTools.Models.PortableExecutable.ResourceEntries;
namespace SabreTools.Serialization
{

View File

@@ -0,0 +1,64 @@
using System.Text.RegularExpressions;
namespace SabreTools.Serialization
{
public static partial class Extensions
{
/// <summary>
/// Convert a Wise function ID to the formal action name
/// </summary>
/// <param name="functionId">Function ID to convert</param>
/// <returns>The formal action name on success, null otherwise</returns>
public static string? FromWiseFunctionId(this string? functionId)
{
return functionId switch
{
"f0" => "Add Directory to PATH",
"f1" => "Add to AUTOEXEC.BAT",
"f2" => "Add to CONFIG.SYS",
"f3" => "Add to SYSTEM.INI",
"f8" => "Read INI Value",
"f9" => "Get Registry Key Value",
"f10" => "Register Font",
"f11" => "Win32 System Directory",
"f12" => "Check Configuration",
"f13" => "Search for File",
"f15" => "Read/Write Binary File",
"f16" => "Set Variable",
"f17" => "Get Environment Variable",
"f19" => "Check if File/Dir Exists",
"f20" => "Set File Attributes",
"f21" => "Set Files/Buffers",
"f22" => "Find File in Path",
"f23" => "Check Disk Space",
"f25" => "Insert Line Into Text File",
"f27" => "Parse String",
"f28" => "Exit Installation",
"f29" => "Self-Register OCXs/DLLs",
"f30" => "Install DirectX Components",
"f31" => "Wizard Block",
"f33" => "Read/Update Text File",
"f34" => "Post to HTTP Server",
"f35" => "Prompt for Filename",
"f36" => "Start/Stop Service",
"f38" => "Check HTTP Connection",
// Undefined function IDs
"f4" => $"UNDEFINED {functionId}",
"f5" => $"UNDEFINED {functionId}",
"f6" => $"UNDEFINED {functionId}",
"f7" => $"UNDEFINED {functionId}",
"f14" => $"UNDEFINED {functionId}",
"f18" => $"UNDEFINED {functionId}",
"f24" => $"UNDEFINED {functionId}",
"f26" => $"UNDEFINED {functionId}",
"f32" => $"UNDEFINED {functionId}",
"f37" => $"UNDEFINED {functionId}",
// External DLL
null => null,
_ => Regex.IsMatch(functionId, @"^f[0-9]{1,2}$") ? $"UNDEFINED {functionId}" : $"External: {functionId}",
};
}
}
}

View File

@@ -71,6 +71,9 @@ namespace SabreTools.Serialization
Wrapper.VBSP item => item.PrettyPrint(),
Wrapper.VPK item => item.PrettyPrint(),
Wrapper.WAD3 item => item.PrettyPrint(),
Wrapper.WiseOverlayHeader item => item.PrettyPrint(),
Wrapper.WiseScript item => item.PrettyPrint(),
// TODO: Add implementation for WiseSectionHeader
Wrapper.XeMID item => item.PrettyPrint(),
Wrapper.XMID item => item.PrettyPrint(),
Wrapper.XZP item => item.PrettyPrint(),
@@ -123,6 +126,9 @@ namespace SabreTools.Serialization
Wrapper.VBSP item => item.ExportJSON(),
Wrapper.VPK item => item.ExportJSON(),
Wrapper.WAD3 item => item.ExportJSON(),
Wrapper.WiseOverlayHeader item => item.ExportJSON(),
Wrapper.WiseScript item => item.ExportJSON(),
// TODO: Add implementation for WiseSectionHeader
Wrapper.XeMID item => item.ExportJSON(),
Wrapper.XMID item => item.ExportJSON(),
Wrapper.XZP item => item.ExportJSON(),
@@ -503,6 +509,26 @@ namespace SabreTools.Serialization
return builder;
}
/// <summary>
/// Export the item information as pretty-printed text
/// </summary>
private static StringBuilder PrettyPrint(this Wrapper.WiseOverlayHeader item)
{
var builder = new StringBuilder();
WiseOverlayHeader.Print(builder, item.Model);
return builder;
}
/// <summary>
/// Export the item information as pretty-printed text
/// </summary>
private static StringBuilder PrettyPrint(this Wrapper.WiseScript item)
{
var builder = new StringBuilder();
WiseScript.Print(builder, item.Model);
return builder;
}
/// <summary>
/// Export the item information as pretty-printed text
/// </summary>

View File

@@ -34,12 +34,12 @@ namespace SabreTools.Serialization.Printers
builder.AppendLine(header.ID1, " ID1");
builder.AppendLine(header.ID2, " ID1");
builder.AppendLine($" Compression method: {header.CM} (0x{(byte)header.CM:X2})");
builder.AppendLine($" Flags: {header.FLG} (0x{(byte)header.FLG:X2})");
builder.AppendLine(header.MTIME, " Last modified time");
builder.AppendLine($" Extra flags: {header.XFL} (0x{(byte)header.XFL:X2})");
builder.AppendLine($" Operating system: {header.OS} (0x{(byte)header.OS:X2})");
builder.AppendLine(header.XLEN, " Extra length");
builder.AppendLine($" Compression method: {header.CompressionMethod} (0x{(byte)header.CompressionMethod:X2})");
builder.AppendLine($" Flags: {header.Flags} (0x{(byte)header.Flags:X2})");
builder.AppendLine(header.LastModifiedTime, " Last modified time");
builder.AppendLine($" Extra flags: {header.ExtraFlags} (0x{(byte)header.ExtraFlags:X2})");
builder.AppendLine($" Operating system: {header.OperatingSystem} (0x{(byte)header.OperatingSystem:X2})");
builder.AppendLine(header.ExtraLength, " Extra length");
Print(builder, header.ExtraField);
builder.AppendLine(header.OriginalFileName, " Original file name");
builder.AppendLine(header.FileComment, " File comment");
@@ -63,9 +63,9 @@ namespace SabreTools.Serialization.Printers
var entry = entries[i];
builder.AppendLine($" Extra Field {i}:");
builder.AppendLine(entry.SI1, " Subfield ID1");
builder.AppendLine(entry.SI2, " Subfield ID2");
builder.AppendLine(entry.LEN, " Length");
builder.AppendLine(entry.SubfieldID1, " Subfield ID1");
builder.AppendLine(entry.SubfieldID2, " Subfield ID2");
builder.AppendLine(entry.Length, " Length");
builder.AppendLine(entry.Data, " Data");
}
}
@@ -82,7 +82,7 @@ namespace SabreTools.Serialization.Printers
}
builder.AppendLine(trailer.CRC32, " CRC-32");
builder.AppendLine(trailer.ISIZE, " Input size");
builder.AppendLine(trailer.InputSize, " Input size");
builder.AppendLine();
}
}

View File

@@ -73,7 +73,7 @@ namespace SabreTools.Serialization.Printers
builder.AppendLine(header.BlockTableSizeLong, " Block table size long");
builder.AppendLine(header.HiBlockTableSize, " Hi-block table size");
builder.AppendLine(header.HetTableSize, " HET table size");
builder.AppendLine(header.BetTablesize, " BET table size"); // TODO: Fix casing
builder.AppendLine(header.BetTableSize, " BET table size");
builder.AppendLine(header.RawChunkSize, " Raw chunk size");
builder.AppendLine(header.BlockTableMD5, " Block table MD5");
builder.AppendLine(header.HashTableMD5, " Hash table MD5");

View File

@@ -16,12 +16,7 @@ namespace SabreTools.Serialization.Printers
builder.AppendLine("-------------------------");
builder.AppendLine();
Print(builder,
archive.LocalFileHeaders,
archive.EncryptionHeaders,
archive.FileData,
archive.DataDescriptors,
archive.ZIP64DataDescriptors);
Print(builder, archive.LocalFiles);
Print(builder, archive.EndOfCentralDirectoryRecord);
Print(builder, archive.ZIP64EndOfCentralDirectoryLocator);
Print(builder, archive.ZIP64EndOfCentralDirectoryRecord);
@@ -29,78 +24,77 @@ namespace SabreTools.Serialization.Printers
Print(builder, archive.ArchiveExtraDataRecord);
}
private static void Print(StringBuilder builder,
LocalFileHeader[]? localFileHeaders,
byte[][]? encryptionHeaders,
byte[][]? fileData,
DataDescriptor[]? dataDescriptors,
DataDescriptor64[]? zip64DataDescriptors)
private static void Print(StringBuilder builder, LocalFile[]? localFiles)
{
builder.AppendLine(" Local File Information:");
builder.AppendLine(" -------------------------");
if (localFileHeaders == null || localFileHeaders.Length == 0)
if (localFiles == null || localFiles.Length == 0)
{
builder.AppendLine(" No local files");
builder.AppendLine();
return;
}
if (encryptionHeaders == null || localFileHeaders.Length > encryptionHeaders.Length
|| fileData == null || localFileHeaders.Length > fileData.Length)
for (int i = 0; i < localFiles.Length; i++)
{
builder.AppendLine(" Mismatch in local file array values");
builder.AppendLine();
}
for (int i = 0; i < localFileHeaders.Length; i++)
{
var localFileHeader = localFileHeaders[i];
var encryptionHeader = encryptionHeaders != null && i < encryptionHeaders.Length ? encryptionHeaders[i] : null;
var fileDatum = fileData != null && i < fileData.Length ? fileData[i] : null;
var dataDescriptor = dataDescriptors != null && i < dataDescriptors.Length ? dataDescriptors[i] : null;
var zip64DataDescriptor = zip64DataDescriptors != null && i < zip64DataDescriptors.Length ? zip64DataDescriptors[i] : null;
Print(builder, localFileHeader, encryptionHeader, fileDatum, dataDescriptor, zip64DataDescriptor, i);
var localFile = localFiles[i];
Print(builder, localFile, i);
}
builder.AppendLine();
}
private static void Print(StringBuilder builder,
LocalFileHeader localFileHeader,
byte[]? encryptionHeader,
byte[]? fileData,
DataDescriptor? dataDescriptor,
DataDescriptor64? zip64DataDescriptor,
int index)
private static void Print(StringBuilder builder, LocalFile localFile, int index)
{
builder.AppendLine($" Local File Entry {index}");
builder.AppendLine(localFileHeader.Signature, " [Local File Header] Signature");
builder.AppendLine(localFileHeader.Version, " [Local File Header] Version");
builder.AppendLine($" [Local File Header] Flags: {localFileHeader.Flags} (0x{localFileHeader.Flags:X})");
builder.AppendLine($" [Local File Header] Compression method: {localFileHeader.CompressionMethod} (0x{localFileHeader.CompressionMethod:X})");
builder.AppendLine(localFileHeader.LastModifedFileTime, " [Local File Header] Last modified file time"); // TODO: Parse from MS-DOS
builder.AppendLine(localFileHeader.LastModifiedFileDate, " [Local File Header] Last modified file date"); // TODO: Parse from MS-DOS
builder.AppendLine(localFileHeader.CRC32, " [Local File Header] CRC-32");
builder.AppendLine(localFileHeader.CompressedSize, " [Local File Header] Compressed size");
builder.AppendLine(localFileHeader.UncompressedSize, " [Local File Header] Uncompressed size");
builder.AppendLine(localFileHeader.FileNameLength, " [Local File Header] File name length");
builder.AppendLine(localFileHeader.ExtraFieldLength, " [Local File Header] Extra field length");
builder.AppendLine(localFileHeader.FileName, " [Local File Header] File name");
var extraFields = Deserializers.PKZIP.ParseExtraFields(localFileHeader, localFileHeader.ExtraField);
Print(builder, " [Local File Header] Extra Fields", extraFields);
#region Local File Header
if (encryptionHeader == null)
var localFileHeader = localFile.LocalFileHeader;
if (localFileHeader == null)
{
builder.AppendLine(" [Encryption Header]: [NULL]");
builder.AppendLine(" [Local File Header] [NULL]");
}
else
{
builder.AppendLine(encryptionHeader.Length, " [Encryption Header] Length");
builder.AppendLine(encryptionHeader, " [Encryption Header] Data");
builder.AppendLine(localFileHeader.Signature, " [Local File Header] Signature");
builder.AppendLine(localFileHeader.Version, " [Local File Header] Version");
builder.AppendLine($" [Local File Header] Flags: {localFileHeader.Flags} (0x{localFileHeader.Flags:X})");
builder.AppendLine($" [Local File Header] Compression method: {localFileHeader.CompressionMethod} (0x{localFileHeader.CompressionMethod:X})");
builder.AppendLine(localFileHeader.LastModifedFileTime, " [Local File Header] Last modified file time"); // TODO: Parse from MS-DOS
builder.AppendLine(localFileHeader.LastModifiedFileDate, " [Local File Header] Last modified file date"); // TODO: Parse from MS-DOS
builder.AppendLine(localFileHeader.CRC32, " [Local File Header] CRC-32");
builder.AppendLine(localFileHeader.CompressedSize, " [Local File Header] Compressed size");
builder.AppendLine(localFileHeader.UncompressedSize, " [Local File Header] Uncompressed size");
builder.AppendLine(localFileHeader.FileNameLength, " [Local File Header] File name length");
builder.AppendLine(localFileHeader.ExtraFieldLength, " [Local File Header] Extra field length");
builder.AppendLine(localFileHeader.FileName, " [Local File Header] File name");
// TODO: Reenable this when models are fixed
// var extraFields = Deserializers.PKZIP.ParseExtraFields(localFileHeader, localFileHeader.ExtraField);
// Print(builder, " [Local File Header] Extra Fields", extraFields);
}
#endregion
#region Encryption Headers
var encryptionHeaders = localFile.EncryptionHeaders;
if (encryptionHeaders == null)
{
builder.AppendLine(" [Encryption Headers]: [NULL]");
}
else
{
builder.AppendLine(encryptionHeaders.Length, " [Encryption Headers] Length");
builder.AppendLine(encryptionHeaders, " [Encryption Headers] Data");
}
#endregion
#region File Data
var fileData = localFile.FileData;
if (fileData == null)
{
builder.AppendLine(" [File Data]: [NULL]");
@@ -111,6 +105,11 @@ namespace SabreTools.Serialization.Printers
//builder.AppendLine(fileData, " [File Data] Data");
}
#endregion
#region Data Descriptor
var dataDescriptor = localFile.DataDescriptor;
if (dataDescriptor == null)
{
builder.AppendLine(" [Data Descriptor]: [NULL]");
@@ -123,6 +122,7 @@ namespace SabreTools.Serialization.Printers
builder.AppendLine(dataDescriptor.UncompressedSize, $" [Data Descriptor] Uncompressed size");
}
var zip64DataDescriptor = localFile.ZIP64DataDescriptor;
if (zip64DataDescriptor == null)
{
builder.AppendLine(" [ZIP64 Data Descriptor]: [NULL]");
@@ -134,6 +134,8 @@ namespace SabreTools.Serialization.Printers
builder.AppendLine(zip64DataDescriptor.CompressedSize, $" [ZIP64 Data Descriptor] Compressed size");
builder.AppendLine(zip64DataDescriptor.UncompressedSize, $" [ZIP64 Data Descriptor] Uncompressed size");
}
#endregion
}
private static void Print(StringBuilder builder, EndOfCentralDirectoryRecord? record)
@@ -240,8 +242,9 @@ namespace SabreTools.Serialization.Printers
builder.AppendLine(entry.FileName, " File name");
builder.AppendLine(entry.FileComment, " File comment");
var extraFields = Deserializers.PKZIP.ParseExtraFields(entry, entry.ExtraField);
Print(builder, " Extra Fields", extraFields);
// TODO: Reenable this when models are fixed
// var extraFields = Deserializers.PKZIP.ParseExtraFields(entry, entry.ExtraField);
// Print(builder, " Extra Fields", extraFields);
}
builder.AppendLine();
@@ -312,7 +315,7 @@ namespace SabreTools.Serialization.Printers
case DataStreamAlignment field: Print(builder, field); break;
case MicrosoftOpenPackagingGrowthHint field: Print(builder, field); break;
default: Print(builder, entry); break;
case UnknownExtraField field: Print(builder, field); break;
}
}
@@ -329,35 +332,31 @@ namespace SabreTools.Serialization.Printers
private static void Print(StringBuilder builder, OS2ExtraField field)
{
builder.AppendLine(field.BSize, " Uncompressed block size");
builder.AppendLine(field.CType, " Compression type");
builder.AppendLine(field.EACRC, " CRC-32");
builder.AppendLine(field.Var, " Data");
builder.AppendLine(field.UncompressedBlockSize, " Uncompressed block size");
builder.AppendLine(field.CompressionType, " Compression type");
builder.AppendLine(field.CRC32, " CRC-32");
builder.AppendLine(field.Data, " Data");
}
private static void Print(StringBuilder builder, NTFSExtraField field)
{
builder.AppendLine(field.Reserved, " Reserved");
builder.AppendLine(field.Tags, " Tags");
builder.AppendLine(field.Size, " Size");
builder.AppendLine(field.Vars, " Vars");
Print(builder, field.TagSizeVars);
}
private static void Print(StringBuilder builder, OpenVMSExtraField field)
{
builder.AppendLine(field.CRC, " CRC-32");
builder.AppendLine(field.Tags, " Tags");
builder.AppendLine(field.Size, " Size");
builder.AppendLine(field.Vars, " Vars");
Print(builder, field.TagSizeVars);
}
private static void Print(StringBuilder builder, UnixExtraField field)
{
builder.AppendLine(field.Atime, " File last access time");
builder.AppendLine(field.Mtime, " File last modification time");
builder.AppendLine(field.Uid, " File user ID");
builder.AppendLine(field.Gid, " File group ID");
builder.AppendLine(field.Var, " Data");
builder.AppendLine(field.FileLastAccessTime, " File last access time");
builder.AppendLine(field.FileLastModificationTime, " File last modification time");
builder.AppendLine(field.FileUserID, " File user ID");
builder.AppendLine(field.FileGroupID, " File group ID");
builder.AppendLine(field.Data, " Data");
}
private static void Print(StringBuilder builder, PatchDescriptorExtraField field)
@@ -396,9 +395,7 @@ namespace SabreTools.Serialization.Printers
private static void Print(StringBuilder builder, RecordManagementControls field)
{
builder.AppendLine(field.Tags, " Tags");
builder.AppendLine(field.Size, " Size");
builder.AppendLine(field.Vars, " Vars");
Print(builder, field.TagSizeVars);
}
private static void Print(StringBuilder builder, PKCS7EncryptionRecipientCertificateList field)
@@ -487,9 +484,29 @@ namespace SabreTools.Serialization.Printers
builder.AppendLine(field.Padding, " Padding");
}
private static void Print(StringBuilder builder, ExtensibleDataField field)
private static void Print(StringBuilder builder, UnknownExtraField field)
{
// TODO: Print byte data here
builder.AppendLine(field.Data, " Data");
}
private static void Print(StringBuilder builder, TagSizeVar[]? tuples)
{
builder.AppendLine(" Tag/Size/Var Tuples:");
builder.AppendLine(" -------------------------");
if (tuples == null)
{
builder.AppendLine(" No tuples");
return;
}
for (int i = 0; i < tuples.Length; i++)
{
var tuple = tuples[i];
builder.AppendLine(tuple.Tag, " Tags");
builder.AppendLine(tuple.Size, " Size");
builder.AppendLine(tuple.Var, " Vars");
}
}
#endregion

View File

@@ -6,6 +6,8 @@ using SabreTools.ASN1;
using SabreTools.IO.Extensions;
using SabreTools.Matching;
using SabreTools.Models.PortableExecutable;
using SabreTools.Models.PortableExecutable.COFFSymbolTableEntries;
using SabreTools.Models.PortableExecutable.ResourceEntries;
using SabreTools.Serialization.Interfaces;
namespace SabreTools.Serialization.Printers
@@ -310,7 +312,7 @@ namespace SabreTools.Serialization.Printers
builder.AppendLine();
}
private static void Print(StringBuilder builder, COFFSymbolTableEntry[]? entries)
private static void Print(StringBuilder builder, BaseEntry[]? entries)
{
builder.AppendLine(" COFF Symbol Table Information:");
builder.AppendLine(" -------------------------");
@@ -321,127 +323,99 @@ namespace SabreTools.Serialization.Printers
return;
}
int auxSymbolsRemaining = 0;
int currentSymbolType = 0;
for (int i = 0; i < entries.Length; i++)
{
var entry = entries[i];
builder.AppendLine($" COFF Symbol Table Entry {i} (Subtype {currentSymbolType})");
if (currentSymbolType == 0)
switch (entry)
{
if (entry.ShortName != null)
{
builder.AppendLine(entry.ShortName, " Short name", Encoding.ASCII);
}
else
{
builder.AppendLine(entry.Zeroes, " Zeroes");
builder.AppendLine(entry.Offset, " Offset");
}
builder.AppendLine(entry.Value, " Value");
builder.AppendLine(entry.SectionNumber, " Section number");
builder.AppendLine($" Symbol type: {entry.SymbolType} (0x{entry.SymbolType:X})");
builder.AppendLine($" Storage class: {entry.StorageClass} (0x{entry.StorageClass:X})");
builder.AppendLine(entry.NumberOfAuxSymbols, " Number of aux symbols");
auxSymbolsRemaining = entry.NumberOfAuxSymbols;
if (auxSymbolsRemaining == 0)
continue;
if (entry.StorageClass == StorageClass.IMAGE_SYM_CLASS_EXTERNAL
&& entry.SymbolType == SymbolType.IMAGE_SYM_TYPE_FUNC
&& entry.SectionNumber > 0)
{
currentSymbolType = 1;
}
else if (entry.StorageClass == StorageClass.IMAGE_SYM_CLASS_FUNCTION
&& entry.ShortName != null
&& ((entry.ShortName[0] == 0x2E && entry.ShortName[1] == 0x62 && entry.ShortName[2] == 0x66) // .bf
|| (entry.ShortName[0] == 0x2E && entry.ShortName[1] == 0x65 && entry.ShortName[2] == 0x66))) // .ef
{
currentSymbolType = 2;
}
else if (entry.StorageClass == StorageClass.IMAGE_SYM_CLASS_EXTERNAL
&& entry.SectionNumber == (ushort)SectionNumber.IMAGE_SYM_UNDEFINED
&& entry.Value == 0)
{
currentSymbolType = 3;
}
else if (entry.StorageClass == StorageClass.IMAGE_SYM_CLASS_FILE)
{
// TODO: Symbol name should be ".file"
currentSymbolType = 4;
}
else if (entry.StorageClass == StorageClass.IMAGE_SYM_CLASS_STATIC)
{
// TODO: Should have the name of a section (like ".text")
currentSymbolType = 5;
}
else if (entry.StorageClass == StorageClass.IMAGE_SYM_CLASS_CLR_TOKEN)
{
currentSymbolType = 6;
}
case StandardRecord item: Print(builder, item, i); break;
case FunctionDefinition item: Print(builder, item, i); break;
case Descriptor item: Print(builder, item, i); break;
case WeakExternal item: Print(builder, item, i); break;
case FileRecord item: Print(builder, item, i); break;
case SectionDefinition item: Print(builder, item, i); break;
case CLRTokenDefinition item: Print(builder, item, i); break;
}
else if (currentSymbolType == 1)
{
builder.AppendLine(entry.AuxFormat1TagIndex, " Tag index");
builder.AppendLine(entry.AuxFormat1TotalSize, " Total size");
builder.AppendLine(entry.AuxFormat1PointerToLinenumber, " Pointer to linenumber");
builder.AppendLine(entry.AuxFormat1PointerToNextFunction, " Pointer to next function");
builder.AppendLine(entry.AuxFormat1Unused, " Unused");
auxSymbolsRemaining--;
}
else if (currentSymbolType == 2)
{
builder.AppendLine(entry.AuxFormat2Unused1, " Unused");
builder.AppendLine(entry.AuxFormat2Linenumber, " Linenumber");
builder.AppendLine(entry.AuxFormat2Unused2, " Unused");
builder.AppendLine(entry.AuxFormat2PointerToNextFunction, " Pointer to next function");
builder.AppendLine(entry.AuxFormat2Unused3, " Unused");
auxSymbolsRemaining--;
}
else if (currentSymbolType == 3)
{
builder.AppendLine(entry.AuxFormat3TagIndex, " Tag index");
builder.AppendLine(entry.AuxFormat3Characteristics, " Characteristics");
builder.AppendLine(entry.AuxFormat3Unused, " Unused");
auxSymbolsRemaining--;
}
else if (currentSymbolType == 4)
{
builder.AppendLine(entry.AuxFormat4FileName, " File name", Encoding.ASCII);
auxSymbolsRemaining--;
}
else if (currentSymbolType == 5)
{
builder.AppendLine(entry.AuxFormat5Length, " Length");
builder.AppendLine(entry.AuxFormat5NumberOfRelocations, " Number of relocations");
builder.AppendLine(entry.AuxFormat5NumberOfLinenumbers, " Number of linenumbers");
builder.AppendLine(entry.AuxFormat5CheckSum, " Checksum");
builder.AppendLine(entry.AuxFormat5Number, " Number");
builder.AppendLine(entry.AuxFormat5Selection, " Selection");
builder.AppendLine(entry.AuxFormat5Unused, " Unused");
auxSymbolsRemaining--;
}
else if (currentSymbolType == 6)
{
builder.AppendLine(entry.AuxFormat6AuxType, " Aux type");
builder.AppendLine(entry.AuxFormat6Reserved1, " Reserved");
builder.AppendLine(entry.AuxFormat6SymbolTableIndex, " Symbol table index");
builder.AppendLine(entry.AuxFormat6Reserved2, " Reserved");
auxSymbolsRemaining--;
}
// If we hit the last aux symbol, go back to normal format
if (auxSymbolsRemaining == 0)
currentSymbolType = 0;
}
builder.AppendLine();
}
private static void Print(StringBuilder builder, StandardRecord entry, int i)
{
builder.AppendLine($" COFF Symbol Table Entry {i} (Standard Record)");
if (entry.ShortName != null)
{
builder.AppendLine(entry.ShortName, " Short name", Encoding.ASCII);
}
else
{
builder.AppendLine(entry.Zeroes, " Zeroes");
builder.AppendLine(entry.Offset, " Offset");
}
builder.AppendLine(entry.Value, " Value");
builder.AppendLine(entry.SectionNumber, " Section number");
builder.AppendLine($" Symbol type: {entry.SymbolType} (0x{entry.SymbolType:X})");
builder.AppendLine($" Storage class: {entry.StorageClass} (0x{entry.StorageClass:X})");
builder.AppendLine(entry.NumberOfAuxSymbols, " Number of aux symbols");
}
private static void Print(StringBuilder builder, FunctionDefinition entry, int i)
{
builder.AppendLine($" COFF Symbol Table Entry {i} (Function Definition)");
builder.AppendLine(entry.TagIndex, " Tag index");
builder.AppendLine(entry.TotalSize, " Total size");
builder.AppendLine(entry.PointerToLinenumber, " Pointer to linenumber");
builder.AppendLine(entry.PointerToNextFunction, " Pointer to next function");
builder.AppendLine(entry.Unused, " Unused");
}
private static void Print(StringBuilder builder, Descriptor entry, int i)
{
builder.AppendLine($" COFF Symbol Table Entry {i} (.bf and .ef Symbol)");
builder.AppendLine(entry.Unused1, " Unused");
builder.AppendLine(entry.Linenumber, " Linenumber");
builder.AppendLine(entry.Unused2, " Unused");
builder.AppendLine(entry.PointerToNextFunction, " Pointer to next function");
builder.AppendLine(entry.Unused3, " Unused");
}
private static void Print(StringBuilder builder, WeakExternal entry, int i)
{
builder.AppendLine($" COFF Symbol Table Entry {i} (Weak External)");
builder.AppendLine(entry.TagIndex, " Tag index");
builder.AppendLine(entry.Characteristics, " Characteristics");
builder.AppendLine(entry.Unused, " Unused");
}
private static void Print(StringBuilder builder, FileRecord entry, int i)
{
builder.AppendLine($" COFF Symbol Table Entry {i} (File)");
builder.AppendLine(entry.FileName, " File name", Encoding.ASCII);
}
private static void Print(StringBuilder builder, SectionDefinition entry, int i)
{
builder.AppendLine($" COFF Symbol Table Entry {i} (Section Defintion)");
builder.AppendLine(entry.Length, " Length");
builder.AppendLine(entry.NumberOfRelocations, " Number of relocations");
builder.AppendLine(entry.NumberOfLinenumbers, " Number of linenumbers");
builder.AppendLine(entry.CheckSum, " Checksum");
builder.AppendLine(entry.Number, " Number");
builder.AppendLine(entry.Selection, " Selection");
builder.AppendLine(entry.Unused, " Unused");
}
private static void Print(StringBuilder builder, CLRTokenDefinition entry, int i)
{
builder.AppendLine($" COFF Symbol Table Entry {i} (CLR Token Defintion)");
builder.AppendLine(entry.AuxFormat6AuxType, " Aux type");
builder.AppendLine(entry.AuxFormat6Reserved1, " Reserved");
builder.AppendLine(entry.AuxFormat6SymbolTableIndex, " Symbol table index");
builder.AppendLine(entry.AuxFormat6Reserved2, " Reserved");
}
private static void Print(StringBuilder builder, COFFStringTable? stringTable)
{
builder.AppendLine(" COFF String Table Information:");

View File

@@ -20,20 +20,18 @@ namespace SabreTools.Serialization.Printers
Print(builder, file.Entries);
}
// TODO: Fix the entry type when Models is updated
//private static void Print(StringBuilder builder, Entry[]? entries)
private static void Print(StringBuilder builder, List<Entry>? entries)
private static void Print(StringBuilder builder, Entry[]? entries)
{
builder.AppendLine(" Entries Information:");
builder.AppendLine(" -------------------------");
if (entries == null || entries.Count == 0)
if (entries == null || entries.Length == 0)
{
builder.AppendLine(" No entries");
builder.AppendLine();
return;
}
for (int i = 0; i < entries.Count; i++)
for (int i = 0; i < entries.Length; i++)
{
var entry = entries[i];
@@ -75,20 +73,18 @@ namespace SabreTools.Serialization.Printers
builder.AppendLine();
}
// TODO: Fix the entry type when Models is updated
// private static void Print(StringBuilder builder, Block[]? entries)
private static void Print(StringBuilder builder, List<Block>? entries)
private static void Print(StringBuilder builder, Block[]? entries)
{
builder.AppendLine(" Blocks:");
builder.AppendLine(" -------------------------");
if (entries == null || entries.Count == 0)
if (entries == null || entries.Length == 0)
{
builder.AppendLine(" No blocks");
builder.AppendLine();
return;
}
for (int i = 0; i < entries.Count; i++)
for (int i = 0; i < entries.Length; i++)
{
var block = entries[i];
if (block.Data == null)

View File

@@ -0,0 +1,58 @@
using System.Text;
using SabreTools.Models.WiseInstaller;
using SabreTools.Serialization.Interfaces;
namespace SabreTools.Serialization.Printers
{
public class WiseOverlayHeader : IPrinter<OverlayHeader>
{
/// <inheritdoc/>
public void PrintInformation(StringBuilder builder, OverlayHeader model)
=> Print(builder, model);
public static void Print(StringBuilder builder, OverlayHeader overlayHeader)
{
#if NET20 || NET35
bool pkzip = (overlayHeader.Flags & OverlayHeaderFlags.WISE_FLAG_PK_ZIP) != 0;
#else
bool pkzip = overlayHeader.Flags.HasFlag(OverlayHeaderFlags.WISE_FLAG_PK_ZIP);
#endif
builder.AppendLine("Wise Installer Overlay Header Information:");
builder.AppendLine("-------------------------");
builder.AppendLine(overlayHeader.DllNameLen, "DLL name length");
builder.AppendLine(overlayHeader.DllName, "DLL name");
builder.AppendLine(overlayHeader.DllSize, "DLL size");
builder.AppendLine($"Flags: {overlayHeader.Flags} (0x{(uint)overlayHeader.Flags:X4})");
builder.AppendLine(pkzip, " Uses PKZIP containers");
builder.AppendLine(overlayHeader.GraphicsData, "Graphics data");
builder.AppendLine(overlayHeader.WiseScriptExitEventOffset, "Wise script exit event offset");
builder.AppendLine(overlayHeader.WiseScriptCancelEventOffset, "Wise script cancel event offset");
builder.AppendLine(overlayHeader.WiseScriptInflatedSize, "Wise script inflated size");
builder.AppendLine(overlayHeader.WiseScriptDeflatedSize, "Wise script deflated size");
builder.AppendLine(overlayHeader.WiseDllDeflatedSize, "Wise DLL deflated size");
builder.AppendLine(overlayHeader.Ctl3d32DeflatedSize, "CTL3D32.DLL deflated size");
builder.AppendLine(overlayHeader.SomeData4DeflatedSize, "FILE0004 deflated size");
builder.AppendLine(overlayHeader.RegToolDeflatedSize, "Ocxreg32.EXE deflated size");
builder.AppendLine(overlayHeader.ProgressDllDeflatedSize, "PROGRESS.DLL deflated size");
builder.AppendLine(overlayHeader.SomeData7DeflatedSize, "FILE0007 deflated size");
builder.AppendLine(overlayHeader.SomeData8DeflatedSize, "FILE0008 deflated size");
builder.AppendLine(overlayHeader.SomeData9DeflatedSize, "FILE0009 deflated size");
builder.AppendLine(overlayHeader.SomeData10DeflatedSize, "FILE000A deflated size");
builder.AppendLine(overlayHeader.FinalFileDeflatedSize, "FILE000{n}.DAT deflated size");
builder.AppendLine(overlayHeader.FinalFileInflatedSize, "FILE000{n}.DAT inflated size");
builder.AppendLine(overlayHeader.EOF, "EOF");
builder.AppendLine(overlayHeader.DibDeflatedSize, "DIB deflated size");
builder.AppendLine(overlayHeader.DibInflatedSize, "DIB inflated size");
builder.AppendLine(overlayHeader.InstallScriptDeflatedSize, "Install script deflated size");
if (overlayHeader.CharacterSet != null)
builder.AppendLine($"Character set: {overlayHeader.CharacterSet} (0x{(uint)overlayHeader.CharacterSet:X4})");
else
builder.AppendLine((uint?)null, $"Character set");
builder.AppendLine($"Endianness: {overlayHeader.Endianness} (0x{(uint)overlayHeader.Endianness:X4})");
builder.AppendLine(overlayHeader.InitTextLen, "Init text length");
builder.AppendLine(overlayHeader.InitText, "Init text");
builder.AppendLine();
}
}
}

View File

@@ -0,0 +1,839 @@
using System.Text;
using SabreTools.Models.WiseInstaller;
using SabreTools.Models.WiseInstaller.Actions;
using SabreTools.Serialization.Interfaces;
namespace SabreTools.Serialization.Printers
{
public class WiseScript : IPrinter<ScriptFile>
{
/// <inheritdoc/>
public void PrintInformation(StringBuilder builder, ScriptFile model)
=> Print(builder, model);
public static void Print(StringBuilder builder, ScriptFile scriptFile)
{
builder.AppendLine("Wise Installer Script File Information:");
builder.AppendLine("-------------------------");
builder.AppendLine();
Print(builder, scriptFile.Header);
Print(builder, scriptFile.States);
}
private static void Print(StringBuilder builder, ScriptHeader? header)
{
builder.AppendLine(" Header Information:");
builder.AppendLine(" -------------------------");
if (header == null)
{
builder.AppendLine(" No header");
builder.AppendLine();
return;
}
builder.AppendLine(header.Flags, " Flags");
builder.AppendLine(header.UnknownU16_1, " UnknownU16_1");
builder.AppendLine(header.UnknownU16_2, " UnknownU16_2");
builder.AppendLine(header.SomeOffset1, " SomeOffset1");
builder.AppendLine(header.SomeOffset2, " SomeOffset2");
builder.AppendLine(header.UnknownBytes_2, " UnknownBytes_2");
builder.AppendLine(header.DateTime, " Datetime");
builder.AppendLine(header.VariableLengthData, " Variable length data");
builder.AppendLine(header.FTPURL, " FTP URL");
builder.AppendLine(header.LogPathname, " Log pathname");
builder.AppendLine(header.MessageFont, " Font");
builder.AppendLine(header.FontSize, " Font size");
builder.AppendLine(header.Unknown_2, " Unknown_2");
builder.AppendLine(header.LanguageCount, " Language count");
builder.AppendLine();
builder.AppendLine(" Header strings");
builder.AppendLine(" -------------------------");
if (header.HeaderStrings == null || header.HeaderStrings.Length == 0)
{
builder.AppendLine(" No header strings");
}
else
{
for (int i = 0; i < header.HeaderStrings.Length; i++)
{
var entry = header.HeaderStrings[i];
builder.AppendLine($" Header String {i}: {entry}");
}
}
builder.AppendLine();
}
private static void Print(StringBuilder builder, MachineState[]? entries)
{
builder.AppendLine(" State Machine Information:");
builder.AppendLine(" -------------------------");
if (entries == null || entries.Length == 0)
{
builder.AppendLine(" No state machine items");
return;
}
for (int i = 0; i < entries.Length; i++)
{
var entry = entries[i];
builder.AppendLine($" State Machine Entry {i}:");
builder.AppendLine($" Op: {entry.Op} (0x{(byte)entry.Op:X2})");
switch (entry.Data)
{
case InstallFile data: Print(builder, data); break;
case NoOp data: Print(builder, data); break;
case DisplayMessage data: Print(builder, data); break;
case UserDefinedActionStep data: Print(builder, data); break;
case EditIniFile data: Print(builder, data); break;
case DisplayBillboard data: Print(builder, data); break;
case ExecuteProgram data: Print(builder, data); break;
case EndBlockStatement data: Print(builder, data); break;
case CallDllFunction data: Print(builder, data); break;
case EditRegistry data: Print(builder, data); break;
case DeleteFile data: Print(builder, data); break;
case IfWhileStatement data: Print(builder, data); break;
case ElseStatement data: Print(builder, data); break;
case StartUserDefinedAction data: Print(builder, data); break;
case EndUserDefinedAction data: Print(builder, data); break;
case CreateDirectory data: Print(builder, data); break;
case CopyLocalFile data: Print(builder, data); break;
case CustomDialogSet data: Print(builder, data); break;
case GetSystemInformation data: Print(builder, data); break;
case GetTemporaryFilename data: Print(builder, data); break;
case PlayMultimediaFile data: Print(builder, data); break;
case NewEvent data: Print(builder, data); break;
case Unknown0x19 data: Print(builder, data); break;
case ConfigODBCDataSource data: Print(builder, data); break;
case IncludeScript data: Print(builder, data); break;
case AddTextToInstallLog data: Print(builder, data); break;
case RenameFileDirectory data: Print(builder, data); break;
case OpenCloseInstallLog data: Print(builder, data); break;
case ElseIfStatement data: Print(builder, data); break;
case Unknown0x24 data: Print(builder, data); break;
case Unknown0x25 data: Print(builder, data); break;
// Should never happen
case InvalidOperation data: Print(builder, data); break;
default: builder.AppendLine(" Data: [NULL]"); break;
}
}
}
#region State Actions
private static void Print(StringBuilder builder, InstallFile data)
{
builder.AppendLine($" Data: InstallFile");
builder.AppendLine(data.Flags, $" Flags");
builder.AppendLine(data.DeflateStart, $" Deflate start");
builder.AppendLine(data.DeflateEnd, $" Deflate end");
builder.AppendLine(data.Date, $" Date");
builder.AppendLine(data.Time, $" Time");
builder.AppendLine(data.InflatedSize, $" Inflated size");
builder.AppendLine(data.Operand_7, $" Unknown");
builder.AppendLine(data.Crc32, $" CRC-32");
builder.AppendLine(data.DestinationPathname, $" Destination pathname");
builder.AppendLine($" File texts");
builder.AppendLine(" -------------------------");
if (data.Description == null || data.Description.Length == 0)
{
builder.AppendLine(" No file texts");
}
else
{
for (int i = 0; i < data.Description.Length; i++)
{
var entry = data.Description[i];
builder.AppendLine($" File Text {i}: {entry}");
}
}
builder.AppendLine(data.Source, $" Source");
builder.AppendLine();
}
private static void Print(StringBuilder builder, NoOp data)
{
builder.AppendLine($" Data: NoOp");
builder.AppendLine();
}
private static void Print(StringBuilder builder, DisplayMessage data)
{
builder.AppendLine($" Data: DisplayMessage");
builder.AppendLine(data.Flags, $" Flags");
builder.AppendLine($" Title/Text strings");
builder.AppendLine(" -------------------------");
if (data.TitleText == null || data.TitleText.Length == 0)
{
builder.AppendLine(" No title/text strings");
}
else
{
for (int i = 0; i < data.TitleText.Length; i++)
{
var entry = data.TitleText[i];
builder.AppendLine($" Title/Text String {i}: {entry}");
}
}
builder.AppendLine();
}
private static void Print(StringBuilder builder, UserDefinedActionStep data)
{
builder.AppendLine($" Data: UserDefinedActionStep");
builder.AppendLine(data.Flags, $" Count");
builder.AppendLine($" Script lines");
builder.AppendLine(" -------------------------");
if (data.ScriptLines == null || data.ScriptLines.Length == 0)
{
builder.AppendLine(" No script lines");
}
else
{
for (int i = 0; i < data.ScriptLines.Length; i++)
{
var entry = data.ScriptLines[i];
builder.AppendLine($" Script Line {i}: {entry}");
}
}
builder.AppendLine();
}
private static void Print(StringBuilder builder, EditIniFile data)
{
builder.AppendLine($" Data: EditIniFile");
builder.AppendLine(data.Pathname, $" Pathname");
builder.AppendLine(data.Section, $" Section");
builder.AppendLine(data.Values, $" Values");
builder.AppendLine();
}
private static void Print(StringBuilder builder, DisplayBillboard data)
{
builder.AppendLine($" Data: DisplayBillboard");
builder.AppendLine(data.Flags, $" Flags");
builder.AppendLine($" Deflate info:");
builder.AppendLine($" -------------------------");
if (data.DeflateInfo == null || data.DeflateInfo.Length == 0)
{
builder.AppendLine(" No deflate info items");
}
else
{
for (int i = 0; i < data.DeflateInfo.Length; i++)
{
var entry = data.DeflateInfo[i];
Print(builder, entry, 8, i);
}
}
builder.AppendLine(data.Terminator, $" Terminator");
builder.AppendLine();
}
private static void Print(StringBuilder builder, ExecuteProgram data)
{
builder.AppendLine($" Data: ExecuteProgram");
builder.AppendLine(data.Flags, $" Flags");
builder.AppendLine(data.Pathname, $" Pathname");
builder.AppendLine(data.CommandLine, $" Command Line");
builder.AppendLine(data.DefaultDirectory, $" Default Directory");
builder.AppendLine();
}
private static void Print(StringBuilder builder, EndBlockStatement data)
{
builder.AppendLine($" Data: EndBlockStatement");
builder.AppendLine(data.Operand_1, $" Operand 1");
builder.AppendLine();
}
private static void Print(StringBuilder builder, CallDllFunction data)
{
builder.AppendLine($" Data: CallDllFunction");
builder.AppendLine(data.Flags, $" Flags");
builder.AppendLine(data.DllPath, $" DLL path");
builder.AppendLine(data.FunctionName, $" Function name");
builder.AppendLine(data.FunctionName.FromWiseFunctionId(), $" Derived action name");
builder.AppendLine(data.Operand_4, $" Operand 4");
builder.AppendLine(data.ReturnVariable, $" Return variable");
builder.AppendLine($" Entries");
builder.AppendLine(" -------------------------");
if (data.Entries == null || data.Entries.Length == 0)
{
builder.AppendLine(" No entry data");
}
else
{
for (int i = 0; i < data.Entries.Length; i++)
{
var entry = data.Entries[i];
switch (entry)
{
case AddDirectoryToPath args: Print(builder, args, i); break;
case AddToAutoexecBat args: Print(builder, args, i); break;
case AddToConfigSys args: Print(builder, args, i); break;
case AddToSystemIni args: Print(builder, args, i); break;
case ReadIniValue args: Print(builder, args, i); break;
case GetRegistryKeyValue args: Print(builder, args, i); break;
case RegisterFont args: Print(builder, args, i); break;
case Win32SystemDirectory args: Print(builder, args, i); break;
case CheckConfiguration args: Print(builder, args, i); break;
case SearchForFile args: Print(builder, args, i); break;
case ReadWriteBinaryFile args: Print(builder, args, i); break;
case SetVariable args: Print(builder, args, i); break;
case GetEnvironmentVariable args: Print(builder, args, i); break;
case CheckIfFileDirExists args: Print(builder, args, i); break;
case SetFileAttributes args: Print(builder, args, i); break;
case SetFilesBuffers args: Print(builder, args, i); break;
case FindFileInPath args: Print(builder, args, i); break;
case CheckDiskSpace args: Print(builder, args, i); break;
case InsertLineIntoTextFile args: Print(builder, args, i); break;
case ParseString args: Print(builder, args, i); break;
case ExitInstallation args: Print(builder, args, i); break;
case SelfRegisterOCXsDLLs args: Print(builder, args, i); break;
case InstallDirectXComponents args: Print(builder, args, i); break;
case WizardBlockLoop args: Print(builder, args, i); break;
case ReadUpdateTextFile args: Print(builder, args, i); break;
case PostToHttpServer args: Print(builder, args, i); break;
case PromptForFilename args: Print(builder, args, i); break;
case StartStopService args: Print(builder, args, i); break;
case CheckHttpConnection args: Print(builder, args, i); break;
case ExternalDllCall args: Print(builder, args, i); break;
// Should never happen
default: builder.AppendLine($" Entry {i}: [NULL]"); break;
}
}
}
builder.AppendLine();
}
private static void Print(StringBuilder builder, EditRegistry data)
{
builder.AppendLine($" Data: EditRegistry");
builder.AppendLine(data.FlagsAndRoot, $" Flags and root");
builder.AppendLine(data.DataType, $" Data type");
builder.AppendLine(data.UnknownFsllib, $" Unknown");
builder.AppendLine(data.Key, $" Key");
builder.AppendLine(data.NewValue, $" New value");
builder.AppendLine(data.ValueName, $" Value name");
builder.AppendLine();
}
private static void Print(StringBuilder builder, DeleteFile data)
{
builder.AppendLine($" Data: DeleteFile");
builder.AppendLine(data.Flags, $" Flags");
builder.AppendLine(data.Pathname, $" Pathname");
builder.AppendLine();
}
private static void Print(StringBuilder builder, IfWhileStatement data)
{
builder.AppendLine($" Data: IfWhileStatement");
builder.AppendLine(data.Flags, $" Flags");
builder.AppendLine(data.Variable, $" Variable");
builder.AppendLine(data.Value, $" Value");
builder.AppendLine();
}
private static void Print(StringBuilder builder, ElseStatement data)
{
builder.AppendLine($" Data: ElseStatement");
builder.AppendLine();
}
private static void Print(StringBuilder builder, StartUserDefinedAction data)
{
builder.AppendLine($" Data: StartUserDefinedAction");
builder.AppendLine();
}
private static void Print(StringBuilder builder, EndUserDefinedAction data)
{
builder.AppendLine($" Data: EndUserDefinedAction");
builder.AppendLine();
}
private static void Print(StringBuilder builder, CreateDirectory data)
{
builder.AppendLine($" Data: CreateDirectory");
builder.AppendLine(data.Pathname, $" Pathname");
builder.AppendLine();
}
private static void Print(StringBuilder builder, CopyLocalFile data)
{
builder.AppendLine($" Data: CopyLocalFile");
builder.AppendLine(data.Flags, $" Flags");
builder.AppendLine(data.Padding, $" Padding");
builder.AppendLine(data.Destination, $" Destination");
builder.AppendLine($" Descriptions");
builder.AppendLine(" -------------------------");
if (data.Description == null || data.Description.Length == 0)
{
builder.AppendLine(" No descriptions");
}
else
{
for (int i = 0; i < data.Description.Length; i++)
{
var entry = data.Description[i];
builder.AppendLine($" Description {i}: {entry}");
}
}
builder.AppendLine();
builder.AppendLine(data.Source, $" Source");
builder.AppendLine();
}
private static void Print(StringBuilder builder, CustomDialogSet data)
{
builder.AppendLine($" Data: CustomDialogSet");
builder.AppendLine(data.DeflateStart, $" Deflate start");
builder.AppendLine(data.DeflateEnd, $" Deflate end");
builder.AppendLine(data.InflatedSize, $" Inflated size");
builder.AppendLine(data.DisplayVariable, $" Display variable");
builder.AppendLine(data.Name, $" Name");
builder.AppendLine();
}
private static void Print(StringBuilder builder, GetSystemInformation data)
{
builder.AppendLine($" Data: GetSystemInformation");
builder.AppendLine(data.Flags, $" Variable");
builder.AppendLine(data.Variable, $" Variable");
builder.AppendLine(data.Pathname, $" Pathname");
builder.AppendLine();
}
private static void Print(StringBuilder builder, GetTemporaryFilename data)
{
builder.AppendLine($" Data: GetTemporaryFilename");
builder.AppendLine(data.Variable, $" Variable");
builder.AppendLine();
}
private static void Print(StringBuilder builder, PlayMultimediaFile data)
{
builder.AppendLine($" Data: PlayMultimediaFile");
builder.AppendLine(data.Flags, $" Flags");
builder.AppendLine(data.XPosition, $" X position");
builder.AppendLine(data.YPosition, $" Y position");
builder.AppendLine(data.Pathname, $" Pathname");
builder.AppendLine();
}
private static void Print(StringBuilder builder, NewEvent data)
{
builder.AppendLine($" Data: NewEvent");
builder.AppendLine(data.Padding, $" Padding");
builder.AppendLine();
}
private static void Print(StringBuilder builder, Unknown0x19 data)
{
builder.AppendLine($" Data: Unknown0x19");
builder.AppendLine(data.Operand_1, $" Unknown");
builder.AppendLine(data.Operand_2, $" Unknown");
builder.AppendLine(data.Operand_3, $" Unknown");
builder.AppendLine(data.Operand_4, $" Unknown");
builder.AppendLine();
}
private static void Print(StringBuilder builder, ConfigODBCDataSource data)
{
builder.AppendLine($" Data: ConfigODBCDataSource");
builder.AppendLine(data.Flags, $" Flags");
builder.AppendLine(data.FileFormat, $" File format");
builder.AppendLine(data.ConnectionString, $" Connection string");
builder.AppendLine();
}
private static void Print(StringBuilder builder, IncludeScript data)
{
builder.AppendLine($" Data: IncludeScript");
builder.AppendLine(data.Count, $" Count");
builder.AppendLine();
}
private static void Print(StringBuilder builder, AddTextToInstallLog data)
{
builder.AppendLine($" Data: AddTextToInstallLog");
builder.AppendLine(data.Text, $" Text");
builder.AppendLine();
}
private static void Print(StringBuilder builder, RenameFileDirectory data)
{
builder.AppendLine($" Data: RenameFileDirectory");
builder.AppendLine(data.OldPathname, $" Old pathname");
builder.AppendLine(data.NewFileName, $" New file name");
builder.AppendLine();
}
private static void Print(StringBuilder builder, OpenCloseInstallLog data)
{
builder.AppendLine($" Data: OpenCloseInstallLog");
builder.AppendLine(data.Flags, $" Flags");
builder.AppendLine(data.LogName, $" Log name");
builder.AppendLine();
}
private static void Print(StringBuilder builder, ElseIfStatement data)
{
builder.AppendLine($" Data: ElseIfStatement");
builder.AppendLine(data.Operator, $" Operator");
builder.AppendLine(data.Variable, $" Variable");
builder.AppendLine(data.Value, $" Value");
builder.AppendLine();
}
private static void Print(StringBuilder builder, Unknown0x24 data)
{
builder.AppendLine($" Data: Unknown0x24");
builder.AppendLine();
}
private static void Print(StringBuilder builder, Unknown0x25 data)
{
builder.AppendLine($" Data: Unknown0x25");
builder.AppendLine();
}
private static void Print(StringBuilder builder, InvalidOperation data)
{
builder.AppendLine($" Data: InvalidOperation");
builder.AppendLine();
}
#endregion
#region Function Actions
private static void Print(StringBuilder builder, AddDirectoryToPath data, int i)
{
builder.AppendLine($" Entry {i}: AddDirectoryToPath");
builder.AppendLine(data.DataFlags, $" Data flags");
builder.AppendLine(data.Directory, $" Directory");
builder.AppendLine();
}
private static void Print(StringBuilder builder, AddToAutoexecBat data, int i)
{
builder.AppendLine($" Entry {i}: AddToAutoexecBat");
builder.AppendLine(data.DataFlags, $" Data flags");
builder.AppendLine(data.FileToEdit, $" File to edit");
builder.AppendLine(data.TextToInsert, $" Text to insert");
builder.AppendLine(data.SearchForText, $" Search for text");
builder.AppendLine(data.CommentText, $" Comment text");
builder.AppendLine(data.LineNumber, $" Line number");
builder.AppendLine();
}
private static void Print(StringBuilder builder, AddToConfigSys data, int i)
{
builder.AppendLine($" Entry {i}: AddToConfigSys");
builder.AppendLine(data.DataFlags, $" Data flags");
builder.AppendLine(data.FileToEdit, $" File to edit");
builder.AppendLine(data.TextToInsert, $" Text to insert");
builder.AppendLine(data.SearchForText, $" Search for text");
builder.AppendLine(data.CommentText, $" Comment text");
builder.AppendLine(data.LineNumber, $" Line number");
builder.AppendLine();
}
private static void Print(StringBuilder builder, AddToSystemIni data, int i)
{
builder.AppendLine($" Entry {i}: AddToSystemIni");
builder.AppendLine(data.DeviceName, $" Device name");
builder.AppendLine();
}
private static void Print(StringBuilder builder, ReadIniValue data, int i)
{
builder.AppendLine($" Entry {i}: ReadIniValue");
builder.AppendLine(data.DataFlags, $" Data flags");
builder.AppendLine(data.Variable, $" Variable");
builder.AppendLine(data.Pathname, $" Pathname");
builder.AppendLine(data.Section, $" Section");
builder.AppendLine(data.Item, $" Item");
builder.AppendLine(data.DefaultValue, $" Default value");
builder.AppendLine();
}
private static void Print(StringBuilder builder, GetRegistryKeyValue data, int i)
{
builder.AppendLine($" Entry {i}: GetRegistryKeyValue");
builder.AppendLine(data.DataFlags, $" Data flags");
builder.AppendLine(data.Variable, $" Variable");
builder.AppendLine(data.Key, $" Key");
builder.AppendLine(data.Default, $" Default");
builder.AppendLine(data.ValueName, $" Value name");
builder.AppendLine(data.Root, $" Root");
builder.AppendLine();
}
private static void Print(StringBuilder builder, RegisterFont data, int i)
{
builder.AppendLine($" Entry {i}: RegisterFont");
builder.AppendLine(data.FontFileName, $" Font file name");
builder.AppendLine(data.FontName, $" Font name");
builder.AppendLine();
}
private static void Print(StringBuilder builder, Win32SystemDirectory data, int i)
{
builder.AppendLine($" Entry {i}: Win32SystemDirectory");
builder.AppendLine(data.VariableName, $" Variable name");
builder.AppendLine();
}
private static void Print(StringBuilder builder, CheckConfiguration data, int i)
{
builder.AppendLine($" Entry {i}: CheckConfiguration");
builder.AppendLine(data.DataFlags, $" Data flags");
builder.AppendLine(data.Message, $" Message");
builder.AppendLine(data.Title, $" Title");
builder.AppendLine();
}
private static void Print(StringBuilder builder, SearchForFile data, int i)
{
builder.AppendLine($" Entry {i}: SearchForFile");
builder.AppendLine(data.DataFlags, $" Data flags");
builder.AppendLine(data.Variable, $" Variable");
builder.AppendLine(data.FileName, $" File name");
builder.AppendLine(data.DefaultValue, $" Default value");
builder.AppendLine(data.MessageText, $" Message text");
builder.AppendLine();
}
private static void Print(StringBuilder builder, ReadWriteBinaryFile data, int i)
{
builder.AppendLine($" Entry {i}: ReadWriteBinaryFile");
builder.AppendLine(data.DataFlags, $" Data flags");
builder.AppendLine(data.FilePathname, $" File pathname");
builder.AppendLine(data.VariableName, $" Variable name");
builder.AppendLine(data.FileOffset, $" File offset");
builder.AppendLine(data.MaxLength, $" Max length");
builder.AppendLine();
}
private static void Print(StringBuilder builder, SetVariable data, int i)
{
builder.AppendLine($" Entry {i}: SetVariable");
builder.AppendLine(data.DataFlags, $" Data flags");
builder.AppendLine(data.Variable, $" Variable");
builder.AppendLine(data.Value, $" Value");
builder.AppendLine();
}
private static void Print(StringBuilder builder, GetEnvironmentVariable data, int i)
{
builder.AppendLine($" Entry {i}: GetEnvironmentVariable");
builder.AppendLine(data.DataFlags, $" Data flags");
builder.AppendLine(data.Variable, $" Variable");
builder.AppendLine(data.Environment, $" Environment");
builder.AppendLine(data.DefaultValue, $" Default value");
builder.AppendLine();
}
private static void Print(StringBuilder builder, CheckIfFileDirExists data, int i)
{
builder.AppendLine($" Entry {i}: CheckIfFileDirExists");
builder.AppendLine(data.DataFlags, $" Data flags");
builder.AppendLine(data.Pathname, $" Pathname");
builder.AppendLine(data.Message, $" Message");
builder.AppendLine(data.Title, $" Title");
builder.AppendLine();
}
private static void Print(StringBuilder builder, SetFileAttributes data, int i)
{
builder.AppendLine($" Entry {i}: SetFileAttributes");
builder.AppendLine(data.DataFlags, $" Data flags");
builder.AppendLine(data.FilePathname, $" File pathname");
builder.AppendLine();
}
private static void Print(StringBuilder builder, SetFilesBuffers data, int i)
{
builder.AppendLine($" Entry {i}: SetFilesBuffers");
builder.AppendLine(data.MinimumFiles, $" Minimum files");
builder.AppendLine(data.MinimumBuffers, $" Minimum buffers");
builder.AppendLine();
}
private static void Print(StringBuilder builder, FindFileInPath data, int i)
{
builder.AppendLine($" Entry {i}: FindFileInPath");
builder.AppendLine(data.DataFlags, $" Data flags");
builder.AppendLine(data.VariableName, $" Variable name");
builder.AppendLine(data.FileName, $" File name");
builder.AppendLine(data.DefaultValue, $" Default value");
builder.AppendLine(data.SearchDirectories, $" Search directories");
builder.AppendLine(data.Description, $" Description");
builder.AppendLine();
}
private static void Print(StringBuilder builder, CheckDiskSpace data, int i)
{
builder.AppendLine($" Entry {i}: CheckDiskSpace");
builder.AppendLine(data.DataFlags, $" Data flags");
builder.AppendLine(data.ReserveSpace, $" Reserve space");
builder.AppendLine(data.StatusVariable, $" Status variable");
builder.AppendLine(data.ComponentVariables, $" Component variables");
builder.AppendLine();
}
private static void Print(StringBuilder builder, InsertLineIntoTextFile data, int i)
{
builder.AppendLine($" Entry {i}: InsertLineIntoTextFile");
builder.AppendLine(data.DataFlags, $" Data flags");
builder.AppendLine(data.FileToEdit, $" File to edit");
builder.AppendLine(data.TextToInsert, $" Text to insert");
builder.AppendLine(data.SearchForText, $" Search for text");
builder.AppendLine(data.CommentText, $" Comment text");
builder.AppendLine(data.LineNumber, $" Line number");
builder.AppendLine();
}
private static void Print(StringBuilder builder, ParseString data, int i)
{
builder.AppendLine($" Entry {i}: ParseString");
builder.AppendLine(data.DataFlags, $" Data flags");
builder.AppendLine(data.Source, $" Source");
builder.AppendLine(data.PatternPosition, $" Pattern position");
builder.AppendLine(data.DestinationVariable1, $" Destination variable 1");
builder.AppendLine(data.DestinationVariable2, $" Destination variable 2");
builder.AppendLine();
}
private static void Print(StringBuilder builder, ExitInstallation data, int i)
{
builder.AppendLine($" Entry {i}: ExitInstallation");
builder.AppendLine();
}
private static void Print(StringBuilder builder, SelfRegisterOCXsDLLs data, int i)
{
builder.AppendLine($" Entry {i}: SelfRegisterOCXsDLLs");
builder.AppendLine(data.DataFlags, $" Data flags");
builder.AppendLine(data.Description, $" Description");
builder.AppendLine();
}
private static void Print(StringBuilder builder, InstallDirectXComponents data, int i)
{
builder.AppendLine($" Entry {i}: InstallDirectXComponents");
builder.AppendLine(data.DataFlags, $" Data flags");
builder.AppendLine(data.RootPath, $" Root path");
builder.AppendLine(data.LibraryPath, $" Library path");
builder.AppendLine(data.SizeOrOffsetOrFlag, $" Size or offset");
builder.AppendLine();
}
private static void Print(StringBuilder builder, WizardBlockLoop data, int i)
{
// TODO: Fix this when the model is updated
builder.AppendLine($" Entry {i}: WizardBlockLoop");
builder.AppendLine(data.DataFlags, $" Data flags");
builder.AppendLine(data.DirectionVariable, $" Direction variable");
builder.AppendLine(data.DisplayVariable, $" Display variable");
builder.AppendLine(data.XPosition, $" X position");
builder.AppendLine(data.YPosition, $" Y position");
builder.AppendLine(data.FillerColor, $" Filler color");
builder.AppendLine(data.Operand_6, $" Operand 6");
builder.AppendLine(data.Operand_7, $" Operand 7");
builder.AppendLine(data.Operand_8, $" Operand 8");
builder.AppendLine(data.DialogVariableValueCompare, $" Dialog variable value compare");
builder.AppendLine();
}
private static void Print(StringBuilder builder, ReadUpdateTextFile data, int i)
{
builder.AppendLine($" Entry {i}: ReadUpdateTextFile");
builder.AppendLine(data.DataFlags, $" Data flags");
builder.AppendLine(data.Variable, $" Variable");
builder.AppendLine(data.Pathname, $" Pathname");
builder.AppendLine(data.LanguageStrings, $" Language strings");
builder.AppendLine();
}
private static void Print(StringBuilder builder, PostToHttpServer data, int i)
{
builder.AppendLine($" Entry {i}: PostToHttpServer");
builder.AppendLine(data.DataFlags, $" Data flags");
builder.AppendLine(data.URL, $" URL");
builder.AppendLine(data.PostData, $" POST data");
builder.AppendLine();
}
private static void Print(StringBuilder builder, PromptForFilename data, int i)
{
builder.AppendLine($" Entry {i}: PromptForFilename");
builder.AppendLine(data.DataFlags, $" Data flags");
builder.AppendLine(data.DestinationVariable, $" Destination variable");
builder.AppendLine(data.DefaultExtension, $" Default extension");
builder.AppendLine(data.DialogTitle, $" Dialog title");
builder.AppendLine(data.FilterList, $" Filter list");
builder.AppendLine();
}
private static void Print(StringBuilder builder, StartStopService data, int i)
{
builder.AppendLine($" Entry {i}: StartStopService");
builder.AppendLine(data.Operation, $" Operation");
builder.AppendLine(data.ServiceName, $" Service name");
builder.AppendLine();
}
private static void Print(StringBuilder builder, CheckHttpConnection data, int i)
{
builder.AppendLine($" Entry {i}: CheckHttpConnection");
builder.AppendLine(data.UrlToCheck, $" URL to check");
builder.AppendLine(data.Win32ErrorTextVariable, $" Win32 error text variable");
builder.AppendLine(data.Win32ErrorNumberVariable, $" Win32 error number variable");
builder.AppendLine(data.Win16ErrorTextVariable, $" Win16 error text variable");
builder.AppendLine(data.Win16ErrorNumberVariable, $" Win16 error number variable");
builder.AppendLine();
}
private static void Print(StringBuilder builder, ExternalDllCall data, int i)
{
builder.AppendLine($" Entry {i}: ExternalDllCall");
if (data.Args == null)
builder.AppendLine((string?)null, $" Args");
else
builder.AppendLine(string.Join(", ", data.Args), $" Args");
builder.AppendLine();
}
#endregion
#region Additional
private static void Print(StringBuilder builder, DeflateEntry data, int indent, int index = -1)
{
string padding = string.Empty.PadLeft(indent, ' ');
if (index >= 0)
builder.AppendLine($"{padding}Deflate info {index}");
else
builder.AppendLine($"{padding}Deflate info");
builder.AppendLine($"{padding}-------------------------");
builder.AppendLine(data.DeflateStart, $"{padding} Deflate start");
builder.AppendLine(data.DeflateEnd, $"{padding} Deflate end");
builder.AppendLine(data.InflatedSize, $"{padding} Inflated size");
builder.AppendLine();
}
#endregion
}
}

View File

@@ -65,8 +65,8 @@
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="SabreTools.ASN1" Version="1.6.0" />
<PackageReference Include="SabreTools.Hashing" Version="1.5.0" />
<PackageReference Include="SabreTools.IO" Version="1.7.0" />
<PackageReference Include="SabreTools.Models" Version="1.6.1" />
<PackageReference Include="SabreTools.IO" Version="1.7.1" />
<PackageReference Include="SabreTools.Models" Version="1.7.0" />
<PackageReference Include="SabreTools.Matching" Version="1.6.0" />
<PackageReference Include="SharpCompress" Version="0.40.0" Condition="!$(TargetFramework.StartsWith(`net2`)) AND !$(TargetFramework.StartsWith(`net3`)) AND !$(TargetFramework.StartsWith(`net40`)) AND !$(TargetFramework.StartsWith(`net452`))" />
</ItemGroup>

View File

@@ -42,7 +42,7 @@ namespace SabreTools.Serialization.Wrappers
_dataOffset = 10;
// Add extra lengths
_dataOffset += Header.XLEN;
_dataOffset += Header.ExtraLength;
if (Header.OriginalFileName != null)
_dataOffset += Header.OriginalFileName.Length + 1;
if (Header.FileComment != null)
@@ -152,9 +152,9 @@ namespace SabreTools.Serialization.Wrappers
}
// Ensure that DEFLATE is being used
if (Header.CM != CompressionMethod.Deflate)
if (Header.CompressionMethod != CompressionMethod.Deflate)
{
if (includeDebug) Console.Error.WriteLine($"Invalid compression method {Header.CM} detected, only DEFLATE is supported. Skipping...");
if (includeDebug) Console.Error.WriteLine($"Invalid compression method {Header.CompressionMethod} detected, only DEFLATE is supported. Skipping...");
return false;
}

View File

@@ -35,9 +35,8 @@ namespace SabreTools.Serialization.Wrappers
/// <inheritdoc cref="Archive.ZIP64EndOfCentralDirectoryRecord"/>
public EndOfCentralDirectoryRecord64? ZIP64EndOfCentralDirectoryRecord => Model.ZIP64EndOfCentralDirectoryRecord;
/// <inheritdoc cref="Archive.LocalFileHeaders"/>
/// TODO: Use LocalFiles when Models is updated
public LocalFileHeader[]? LocalFiles => Model.LocalFileHeaders;
/// <inheritdoc cref="Archive.LocalFiles"/>
public LocalFile[]? LocalFiles => Model.LocalFiles;
#endregion

View File

@@ -5,6 +5,7 @@ using System.Text;
using SabreTools.IO.Compression.zlib;
using SabreTools.IO.Extensions;
using SabreTools.Matching;
using SabreTools.Models.PortableExecutable.ResourceEntries;
using SabreTools.Serialization.Interfaces;
namespace SabreTools.Serialization.Wrappers
@@ -763,12 +764,12 @@ namespace SabreTools.Serialization.Wrappers
/// <summary>
/// Cached version info data
/// </summary>
private Models.PortableExecutable.VersionInfo? _versionInfo = null;
private VersionInfo? _versionInfo = null;
/// <summary>
/// Cached assembly manifest data
/// </summary>
private Models.PortableExecutable.AssemblyManifest? _assemblyManifest = null;
private AssemblyManifest? _assemblyManifest = null;
/// <summary>
/// Lock object for reading from the source
@@ -875,7 +876,7 @@ namespace SabreTools.Serialization.Wrappers
return null;
// Try to find a key that matches
Models.PortableExecutable.StringData? match = null;
StringData? match = null;
foreach (var st in stringTable)
{
if (st.Children == null || st.Length == 0)
@@ -894,7 +895,7 @@ namespace SabreTools.Serialization.Wrappers
/// Get the assembly manifest, if possible
/// </summary>
/// <returns>Assembly manifest object, null on error</returns>
private Models.PortableExecutable.AssemblyManifest? GetAssemblyManifest()
private AssemblyManifest? GetAssemblyManifest()
{
// Use the cached data if possible
if (_assemblyManifest != null)
@@ -1443,18 +1444,18 @@ namespace SabreTools.Serialization.Wrappers
/// </summary>
/// <param name="title">Dialog box title to check for</param>
/// <returns>List of matching resources</returns>
public List<Models.PortableExecutable.DialogBoxResource?> FindDialogByTitle(string title)
public List<DialogBoxResource?> FindDialogByTitle(string title)
{
// Ensure that we have the resource data cached
if (ResourceData == null)
return [];
var resources = new List<Models.PortableExecutable.DialogBoxResource?>();
var resources = new List<DialogBoxResource?>();
foreach (var resource in ResourceData.Values)
{
if (resource == null)
continue;
if (resource is not Models.PortableExecutable.DialogBoxResource dbr || dbr == null)
if (resource is not DialogBoxResource dbr || dbr == null)
continue;
if (dbr.DialogTemplate?.TitleResource?.Contains(title) ?? false)
@@ -1471,18 +1472,18 @@ namespace SabreTools.Serialization.Wrappers
/// </summary>
/// <param name="title">Dialog box item title to check for</param>
/// <returns>List of matching resources</returns>
public List<Models.PortableExecutable.DialogBoxResource?> FindDialogBoxByItemTitle(string title)
public List<DialogBoxResource?> FindDialogBoxByItemTitle(string title)
{
// Ensure that we have the resource data cached
if (ResourceData == null)
return [];
var resources = new List<Models.PortableExecutable.DialogBoxResource?>();
var resources = new List<DialogBoxResource?>();
foreach (var resource in ResourceData.Values)
{
if (resource == null)
continue;
if (resource is not Models.PortableExecutable.DialogBoxResource dbr || dbr == null)
if (resource is not DialogBoxResource dbr || dbr == null)
continue;
if (dbr.DialogItemTemplates != null)

View File

@@ -1,6 +1,5 @@
using System;
using System.IO;
using System.Text;
using SabreTools.Models.TAR;
using SabreTools.Serialization.Interfaces;
@@ -186,7 +185,7 @@ namespace SabreTools.Serialization.Wrappers
}
// Get the file size
string sizeOctalString = Encoding.ASCII.GetString(header.Size!).TrimEnd('\0');
string sizeOctalString = header.Size!.TrimEnd('\0');
if (sizeOctalString.Length == 0)
{
if (includeDebug) Console.WriteLine($"Entry {i} has an invalid size, skipping...");
@@ -204,9 +203,9 @@ namespace SabreTools.Serialization.Wrappers
while (entrySize > 0)
{
// Exit early if block number is invalid
if (blockNumber >= entry.Blocks.Count)
if (blockNumber >= entry.Blocks.Length)
{
if (includeDebug) Console.Error.WriteLine($"Invalid block number {i + 1} of {entry.Blocks.Count}, file may be incomplete!");
if (includeDebug) Console.Error.WriteLine($"Invalid block number {i + 1} of {entry.Blocks.Length}, file may be incomplete!");
break;
}

View File

@@ -1,255 +0,0 @@
using System;
using System.IO;
// TODO: Remove when IO is updated
namespace SabreTools.IO.Streams
{
/// <summary>
/// Stream representing a view into a source
/// </summary>
public class ViewStream : Stream
{
#region Properties
/// <inheritdoc/>
public override bool CanRead => true;
/// <inheritdoc/>
public override bool CanSeek => _source.CanSeek;
/// <inheritdoc/>
public override bool CanWrite => false;
/// <summary>
/// Filename from the source, if possible
/// </summary>
public string? Filename
{
get
{
// A subset of streams have a filename
if (_source is FileStream fs)
return fs.Name;
else if (_source is ViewStream vs)
return vs.Filename;
return null;
}
}
/// <inheritdoc/>
public override long Length => _length;
/// <inheritdoc/>
public override long Position
{
get
{
// Handle 0-length sources
if (_length <= 0)
return 0;
return _source.Position - _initialPosition;
}
set
{
// Handle 0-length sources
if (_length <= 0)
{
_source.Position = 0;
return;
}
long position = value;
// Handle out-of-bounds seeks
if (position < 0)
position = 0;
else if (position >= _length)
position = _length - 1;
_source.Position = _initialPosition + position;
}
}
#endregion
#region Instance Variables
/// <summary>
/// Initial position within the underlying data
/// </summary>
protected long _initialPosition;
/// <summary>
/// Usable length in the underlying data
/// </summary>
protected long _length;
/// <summary>
/// Source data
/// </summary>
protected Stream _source;
/// <summary>
/// Lock object for reading from the source
/// </summary>
private readonly object _sourceLock = new();
#endregion
#region Constructors
/// <summary>
/// Construct a new ViewStream from a Stream
/// </summary>
public ViewStream(Stream data, long offset)
{
if (!data.CanRead)
throw new ArgumentException(nameof(data));
if (offset < 0 || offset > data.Length)
throw new ArgumentOutOfRangeException(nameof(offset));
_source = data;
_initialPosition = offset;
_length = data.Length - offset;
_source.Seek(_initialPosition, SeekOrigin.Begin);
}
/// <summary>
/// Construct a new ViewStream from a Stream
/// </summary>
public ViewStream(Stream data, long offset, long length)
{
if (!data.CanRead)
throw new ArgumentException(nameof(data));
if (offset < 0 || offset > data.Length)
throw new ArgumentOutOfRangeException(nameof(offset));
if (length < 0 || offset + length > data.Length)
throw new ArgumentOutOfRangeException(nameof(length));
_source = data;
_initialPosition = offset;
_length = length;
_source.Seek(_initialPosition, SeekOrigin.Begin);
}
/// <summary>
/// Construct a new ViewStream from a byte array
/// </summary>
public ViewStream(byte[] data, long offset)
{
if (offset < 0 || offset > data.Length)
throw new ArgumentOutOfRangeException(nameof(offset));
long length = data.Length - offset;
_source = new MemoryStream(data, (int)offset, (int)length);
_initialPosition = 0;
_length = length;
_source.Seek(_initialPosition, SeekOrigin.Begin);
}
/// <summary>
/// Construct a new ViewStream from a byte array
/// </summary>
public ViewStream(byte[] data, long offset, long length)
{
if (offset < 0 || offset > data.Length)
throw new ArgumentOutOfRangeException(nameof(offset));
if (length < 0 || offset + length > data.Length)
throw new ArgumentOutOfRangeException(nameof(length));
_source = new MemoryStream(data, (int)offset, (int)length);
_initialPosition = 0;
_length = length;
_source.Seek(_initialPosition, SeekOrigin.Begin);
}
#endregion
#region Data
/// <summary>
/// Check if a data segment is valid in the data source
/// </summary>
/// <param name="offset">Position in the source</param>
/// <param name="count">Length of the data to check</param>
/// <returns>True if the positional data is valid, false otherwise</returns>
public bool SegmentValid(long offset, long count)
{
if (offset < 0 || offset > Length)
return false;
if (count < 0 || offset + count > Length)
return false;
return true;
}
#endregion
#region Stream Implementations
/// <inheritdoc/>
public override void Flush()
=> throw new NotImplementedException();
/// <inheritdoc/>
public override int Read(byte[] buffer, int offset, int count)
{
// Invalid cases always return 0
if (buffer.Length == 0)
return 0;
if (offset < 0 || offset >= buffer.Length)
return 0;
if (count < 0 || offset + count > buffer.Length)
return 0;
// Short-circuit 0-byte reads
if (count == 0)
return 0;
try
{
lock (_sourceLock)
{
return _source.Read(buffer, offset, count);
}
}
catch
{
// Absorb the error
return 0;
}
}
/// <inheritdoc/>
public override long Seek(long offset, SeekOrigin origin)
{
// Handle the "seek"
switch (origin)
{
case SeekOrigin.Begin: Position = offset; break;
case SeekOrigin.Current: Position += offset; break;
case SeekOrigin.End: Position = _length + offset - 1; break;
default: throw new ArgumentException($"Invalid value for {nameof(origin)}");
}
return Position;
}
/// <inheritdoc/>
public override void SetLength(long value)
=> throw new NotImplementedException();
/// <inheritdoc/>
public override void Write(byte[] buffer, int offset, int count)
=> throw new NotImplementedException();
#endregion
}
}

View File

@@ -0,0 +1,623 @@
using System;
using System.IO;
using System.Text;
using SabreTools.IO.Compression.Deflate;
using SabreTools.IO.Extensions;
using SabreTools.IO.Streams;
using SabreTools.Matching;
using SabreTools.Models.WiseInstaller;
namespace SabreTools.Serialization.Wrappers
{
public class WiseOverlayHeader : WrapperBase<OverlayHeader>
{
#region Descriptive Properties
/// <inheritdoc/>
public override string DescriptionString => "Wise Installer Overlay Header";
#endregion
#region Extension Properties
/// <inheritdoc cref="OverlayHeader.Ctl3d32DeflatedSize"/>
public uint Ctl3d32DeflatedSize => Model.Ctl3d32DeflatedSize;
/// <inheritdoc cref="OverlayHeader.DibDeflatedSize"/>
public uint DibDeflatedSize => Model.DibDeflatedSize;
/// <inheritdoc cref="OverlayHeader.DibInflatedSize"/>
public uint DibInflatedSize => Model.DibInflatedSize;
/// <inheritdoc cref="OverlayHeader.FinalFileDeflatedSize"/>
public uint FinalFileDeflatedSize => Model.FinalFileDeflatedSize;
/// <inheritdoc cref="OverlayHeader.FinalFileInflatedSize"/>
public uint FinalFileInflatedSize => Model.FinalFileInflatedSize;
/// <inheritdoc cref="OverlayHeader.Flags"/>
public OverlayHeaderFlags Flags => Model.Flags;
/// <inheritdoc cref="OverlayHeader.InstallScriptDeflatedSize"/>
public uint InstallScriptDeflatedSize => Model.InstallScriptDeflatedSize ?? 0;
/// <summary>
/// Indicates if data is packed in PKZIP containers
/// </summary>
public bool IsPKZIP
{
get
{
#if NET20 || NET35
return (Flags & OverlayHeaderFlags.WISE_FLAG_PK_ZIP) != 0;
#else
return Flags.HasFlag(OverlayHeaderFlags.WISE_FLAG_PK_ZIP);
#endif
}
}
/// <inheritdoc cref="OverlayHeader.ProgressDllDeflatedSize"/>
public uint ProgressDllDeflatedSize => Model.ProgressDllDeflatedSize;
/// <inheritdoc cref="OverlayHeader.SomeData4DeflatedSize"/>
public uint SomeData4DeflatedSize => Model.SomeData4DeflatedSize;
/// <inheritdoc cref="OverlayHeader.SomeData7DeflatedSize"/>
public uint SomeData7DeflatedSize => Model.SomeData7DeflatedSize;
/// <inheritdoc cref="OverlayHeader.SomeData8DeflatedSize"/>
public uint SomeData8DeflatedSize => Model.SomeData8DeflatedSize;
/// <inheritdoc cref="OverlayHeader.SomeData9DeflatedSize"/>
public uint SomeData9DeflatedSize => Model.SomeData9DeflatedSize;
/// <inheritdoc cref="OverlayHeader.SomeData10DeflatedSize"/>
public uint SomeData10DeflatedSize => Model.SomeData10DeflatedSize;
/// <inheritdoc cref="OverlayHeader.RegToolDeflatedSize"/>
public uint RegToolDeflatedSize => Model.RegToolDeflatedSize;
/// <inheritdoc cref="OverlayHeader.WiseDllDeflatedSize"/>
public uint WiseDllDeflatedSize => Model.WiseDllDeflatedSize;
/// <inheritdoc cref="OverlayHeader.WiseScriptDeflatedSize"/>
public uint WiseScriptDeflatedSize => Model.WiseScriptDeflatedSize;
/// <inheritdoc cref="OverlayHeader.WiseScriptInflatedSize"/>
public uint WiseScriptInflatedSize => Model.WiseScriptInflatedSize;
#endregion
#region Constructors
/// <inheritdoc/>
public WiseOverlayHeader(OverlayHeader? model, byte[]? data, int offset)
: base(model, data, offset)
{
// All logic is handled by the base class
}
/// <inheritdoc/>
public WiseOverlayHeader(OverlayHeader? model, Stream? data)
: base(model, data)
{
// All logic is handled by the base class
}
/// <summary>
/// Create a Wise installer overlay header from a byte array and offset
/// </summary>
/// <param name="data">Byte array representing the header</param>
/// <param name="offset">Offset within the array to parse</param>
/// <returns>A Wise installer overlay header wrapper on success, null on failure</returns>
public static WiseOverlayHeader? Create(byte[]? data, int offset)
{
// If the data is invalid
if (data == null || data.Length == 0)
return null;
// If the offset is out of bounds
if (offset < 0 || offset >= data.Length)
return null;
// Create a memory stream and use that
var dataStream = new MemoryStream(data, offset, data.Length - offset);
return Create(dataStream);
}
/// <summary>
/// Create a Wise installer overlay header from a Stream
/// </summary>
/// <param name="data">Stream representing the header</param>
/// <returns>A Wise installer overlay header wrapper on success, null on failure</returns>
public static WiseOverlayHeader? Create(Stream? data)
{
// If the data is invalid
if (data == null || !data.CanRead)
return null;
try
{
var model = Deserializers.WiseOverlayHeader.DeserializeStream(data);
if (model == null)
return null;
return new WiseOverlayHeader(model, data);
}
catch
{
return null;
}
}
#endregion
#region Extraction
/// <summary>
/// Extract all files from a Wise installer to an output directory
/// </summary>
/// <param name="filename">Input filename to read from</param>
/// <param name="outputDirectory">Output directory to write to</param>
/// <param name="includeDebug">True to include debug data, false otherwise</param>
/// <returns>True if all files extracted, false otherwise</returns>
public static bool ExtractAll(string? filename, string outputDirectory, bool includeDebug)
{
// If the filename is invalid
if (filename == null)
return false;
// If the file could not be opened
if (!OpenFile(filename, includeDebug, out var stream))
return false;
// Get the source directory
string? sourceDirectory = Path.GetDirectoryName(Path.GetFullPath(filename));
return ExtractAll(stream, sourceDirectory, outputDirectory, includeDebug);
}
/// <summary>
/// Extract all files from a Wise installer to an output directory
/// </summary>
/// <param name="data">Stream representing the Wise installer</param>
/// <param name="outputDirectory">Output directory to write to</param>
/// <param name="includeDebug">True to include debug data, false otherwise</param>
/// <returns>True if all files extracted, false otherwise</returns>
public static bool ExtractAll(Stream? data, string outputDirectory, bool includeDebug)
=> ExtractAll(data, sourceDirectory: null, outputDirectory, includeDebug);
/// <summary>
/// Extract all files from a Wise installer to an output directory
/// </summary>
/// <param name="data">Stream representing the Wise installer</param>
/// <param name="sourceDirectory">Directory where installer files live, if possible</param>
/// <param name="outputDirectory">Output directory to write to</param>
/// <param name="includeDebug">True to include debug data, false otherwise</param>
/// <returns>True if all files extracted, false otherwise</returns>
public static bool ExtractAll(Stream? data, string? sourceDirectory, string outputDirectory, bool includeDebug)
{
// If the data is invalid
if (data == null || !data.CanRead)
return false;
// Attempt to get the overlay header
if (!FindOverlayHeader(data, includeDebug, out var header) || header == null)
{
if (includeDebug) Console.Error.WriteLine("Could not parse the overlay header");
return false;
}
// Extract the header-defined files
bool extracted = header.ExtractHeaderDefinedFiles(data, outputDirectory, includeDebug, out long dataStart);
if (!extracted)
{
if (includeDebug) Console.Error.WriteLine("Could not extract header-defined files");
return false;
}
// Open the script file from the output directory
var scriptStream = File.OpenRead(Path.Combine(outputDirectory, "WiseScript.bin"));
var script = WiseScript.Create(scriptStream);
if (script == null)
{
if (includeDebug) Console.Error.WriteLine("Could not parse WiseScript.bin");
return false;
}
// Process the state machine
return script.ProcessStateMachine(data,
sourceDirectory,
dataStart,
outputDirectory,
header.IsPKZIP,
includeDebug);
}
/// <summary>
/// Find the overlay header from the Wise installer, if possible
/// </summary>
/// <param name="data">Stream representing the Wise installer</param>
/// <param name="includeDebug">True to include debug data, false otherwise</param>
/// <param name="header">The found overlay header on success, null otherwise</param>
/// <returns>True if the header was found and valid, false otherwise</returns>
public static bool FindOverlayHeader(Stream data, bool includeDebug, out WiseOverlayHeader? header)
{
// Set the default header value
header = null;
// Attempt to deserialize the file as either NE or PE
var wrapper = WrapperFactory.CreateExecutableWrapper(data);
if (wrapper is NewExecutable ne)
{
return FindOverlayHeader(data, ne, includeDebug, out header);
}
else if (wrapper is PortableExecutable pe)
{
return FindOverlayHeader(data, pe, includeDebug, out header);
}
else
{
if (includeDebug) Console.Error.WriteLine("Only NE and PE executables are supported");
return false;
}
}
/// <summary>
/// Find the overlay header from a NE Wise installer, if possible
/// </summary>
/// <param name="data">Stream representing the Wise installer</param>
/// <param name="nex">Wrapper representing the NE</param>
/// <param name="includeDebug">True to include debug data, false otherwise</param>
/// <param name="header">The found overlay header on success, null otherwise</param>
/// <returns>True if the header was found and valid, false otherwise</returns>
public static bool FindOverlayHeader(Stream data, NewExecutable nex, bool includeDebug, out WiseOverlayHeader? header)
{
// Set the default header value
header = null;
// Get the overlay offset
long overlayOffset = nex.OverlayAddress;
if (overlayOffset < 0 || overlayOffset >= data.Length)
{
if (includeDebug) Console.Error.WriteLine("Could not parse the overlay header");
return false;
}
// Attempt to get the overlay header
data.Seek(overlayOffset, SeekOrigin.Begin);
header = Create(data);
if (header != null)
return true;
// Align and loop to see if it can be found
data.Seek(overlayOffset, SeekOrigin.Begin);
data.AlignToBoundary(0x10);
overlayOffset = data.Position;
while (data.Position < data.Length)
{
data.Seek(overlayOffset, SeekOrigin.Begin);
header = Create(data);
if (header != null)
return true;
overlayOffset += 0x10;
}
header = null;
return false;
}
/// <summary>
/// Find the overlay header from a PE Wise installer, if possible
/// </summary>
/// <param name="data">Stream representing the Wise installer</param>
/// <param name="pex">Wrapper representing the PE</param>
/// <param name="includeDebug">True to include debug data, false otherwise</param>
/// <param name="header">The found overlay header on success, null otherwise</param>
/// <returns>True if the header was found and valid, false otherwise</returns>
public static bool FindOverlayHeader(Stream data, PortableExecutable pex, bool includeDebug, out WiseOverlayHeader? header)
{
// Set the default header value
header = null;
// Get the overlay offset
long overlayOffset = pex.OverlayAddress;
// Attempt to get the overlay header
if (overlayOffset >= 0 && overlayOffset < pex.Length)
{
data.Seek(overlayOffset, SeekOrigin.Begin);
header = Create(data);
if (header != null)
return true;
}
// Check section data
foreach (var section in pex.Model.SectionTable ?? [])
{
string sectionName = Encoding.ASCII.GetString(section.Name ?? []).TrimEnd('\0');
long sectionOffset = section.VirtualAddress.ConvertVirtualAddress(pex.Model.SectionTable);
data.Seek(sectionOffset, SeekOrigin.Begin);
header = Create(data);
if (header != null)
return true;
// Check after the resource table
if (sectionName == ".rsrc")
{
// Data immediately following
long afterResourceOffset = sectionOffset + section.SizeOfRawData;
data.Seek(afterResourceOffset, SeekOrigin.Begin);
header = Create(data);
if (header != null)
return true;
// Data following padding data
data.Seek(afterResourceOffset, SeekOrigin.Begin);
_ = data.ReadNullTerminatedAnsiString();
header = Create(data);
if (header != null)
return true;
}
}
// If there are no resources
if (pex.Model.OptionalHeader?.ResourceTable == null || pex.ResourceData == null)
return false;
// Get the resources that have an executable signature
bool exeResources = false;
foreach (var kvp in pex.ResourceData)
{
if (kvp.Value == null || kvp.Value is not byte[] ba)
continue;
if (!ba.StartsWith(Models.MSDOS.Constants.SignatureBytes))
continue;
exeResources = true;
break;
}
// If there are no executable resources
if (!exeResources)
{
if (includeDebug) Console.Error.WriteLine("Could not find the overlay header");
return false;
}
// Get the raw resource table offset
long resourceTableOffset = pex.Model.OptionalHeader.ResourceTable.VirtualAddress.ConvertVirtualAddress(pex.Model.SectionTable);
if (resourceTableOffset <= 0)
{
if (includeDebug) Console.Error.WriteLine("Could not find the overlay header");
return false;
}
// Search the resource table data for the offset
long resourceOffset = -1;
data.Seek(resourceTableOffset, SeekOrigin.Begin);
while (data.Position < resourceTableOffset + pex.Model.OptionalHeader.ResourceTable.Size && data.Position < data.Length)
{
ushort possibleSignature = data.ReadUInt16();
if (possibleSignature == Models.MSDOS.Constants.SignatureUInt16)
{
resourceOffset = data.Position - 2;
break;
}
data.Seek(-1, SeekOrigin.Current);
}
// If there was no valid offset, somehow
if (resourceOffset == -1)
{
if (includeDebug) Console.Error.WriteLine("Could not find the overlay header");
return false;
}
// Parse the executable and recurse
data.Seek(resourceOffset, SeekOrigin.Begin);
var resourceExe = WrapperFactory.CreateExecutableWrapper(data);
if (resourceExe is not PortableExecutable resourcePex)
{
if (includeDebug) Console.Error.WriteLine("Could not find the overlay header");
return false;
}
return FindOverlayHeader(data, resourcePex, includeDebug, out header);
}
/// <summary>
/// Open a potential WISE installer file and any additional files
/// </summary>
/// <param name="filename">Input filename or base name to read from</param>
/// <param name="includeDebug">True to include debug data, false otherwise</param>
/// <returns>True if the file could be opened, false otherwise</returns>
private static bool OpenFile(string filename, bool includeDebug, out ReadOnlyCompositeStream? stream)
{
// If the file exists as-is
if (File.Exists(filename))
{
var fileStream = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
stream = new ReadOnlyCompositeStream([fileStream]);
// Debug statement
if (includeDebug) Console.WriteLine($"File {filename} was found and opened");
// Strip the extension and rebuild
string? directory = Path.GetDirectoryName(filename);
filename = Path.GetFileNameWithoutExtension(filename);
if (directory != null)
filename = Path.Combine(directory, filename);
}
// If the base name was provided, try to open the associated exe
else if (File.Exists($"{filename}.EXE"))
{
var fileStream = File.Open($"{filename}.EXE", FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
stream = new ReadOnlyCompositeStream([fileStream]);
// Debug statement
if (includeDebug) Console.WriteLine($"File {filename}.EXE was found and opened");
}
else if (File.Exists($"{filename}.exe"))
{
var fileStream = File.Open($"{filename}.exe", FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
stream = new ReadOnlyCompositeStream([fileStream]);
// Debug statement
if (includeDebug) Console.WriteLine($"File {filename}.exe was found and opened");
}
// Otherwise, the file cannot be opened
else
{
stream = null;
return false;
}
// Get the pattern for file naming
string filePattern = string.Empty;
bool longDigits = false;
byte fileno = 0;
bool foundStart = false;
for (; fileno < 3; fileno++)
{
if (File.Exists($"{filename}.W0{fileno}"))
{
foundStart = true;
filePattern = $"{filename}.W";
longDigits = false;
break;
}
else if (File.Exists($"{filename}.w0{fileno}"))
{
foundStart = true;
filePattern = $"{filename}.w";
longDigits = false;
break;
}
else if (File.Exists($"{filename}.00{fileno}"))
{
foundStart = true;
filePattern = $"{filename}.";
longDigits = true;
break;
}
}
// If no starting part has been found
if (!foundStart)
return true;
// Loop through and try to read all additional files
for (; ; fileno++)
{
string nextPart = longDigits ? $"{filePattern}{fileno:D3}" : $"{filePattern}{fileno:D2}";
if (!File.Exists(nextPart))
{
if (includeDebug) Console.WriteLine($"Part {nextPart} was not found");
break;
}
// Debug statement
if (includeDebug) Console.WriteLine($"Part {nextPart} was found and appended");
var fileStream = File.Open(nextPart, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
stream.AddStream(fileStream);
}
return true;
}
/// <summary>
/// Extract the predefined, static files defined in the header
/// </summary>
/// <param name="data">Stream representing the Wise installer</param>
/// <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(Stream data, string outputDirectory, bool includeDebug, out long dataStart)
{
// TODO: This needs to seek to the end of the header before extracting
// Determine where the remaining compressed data starts
dataStart = data.Position;
// Extract WiseColors.dib, if it exists
var expected = new DeflateInfo { InputSize = DibDeflatedSize, OutputSize = DibInflatedSize, Crc32 = 0 };
if (InflateWrapper.ExtractFile(data, "WiseColors.dib", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL)
return false;
// Extract WiseScript.bin
expected = new DeflateInfo { InputSize = WiseScriptDeflatedSize, OutputSize = WiseScriptInflatedSize, Crc32 = 0 };
if (InflateWrapper.ExtractFile(data, "WiseScript.bin", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL)
return false;
// Extract WISE0001.DLL, if it exists
expected = new DeflateInfo { InputSize = WiseDllDeflatedSize, OutputSize = -1, Crc32 = 0 };
if (InflateWrapper.ExtractFile(data, "WISE0001.DLL", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL)
return false;
// Extract CTL3D32.DLL, if it exists
expected = new DeflateInfo { InputSize = Ctl3d32DeflatedSize, OutputSize = -1, Crc32 = 0 };
if (InflateWrapper.ExtractFile(data, "CTL3D32.DLL", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL)
return false;
// Extract FILE0004, if it exists
expected = new DeflateInfo { InputSize = SomeData4DeflatedSize, OutputSize = -1, Crc32 = 0 };
if (InflateWrapper.ExtractFile(data, "FILE0004", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL)
return false;
// Extract Ocxreg32.EXE, if it exists
expected = new DeflateInfo { InputSize = RegToolDeflatedSize, OutputSize = -1, Crc32 = 0 };
if (InflateWrapper.ExtractFile(data, "Ocxreg32.EXE", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL)
return false;
// Extract PROGRESS.DLL, if it exists
expected = new DeflateInfo { InputSize = ProgressDllDeflatedSize, OutputSize = -1, Crc32 = 0 };
if (InflateWrapper.ExtractFile(data, "PROGRESS.DLL", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL)
return false;
// Extract FILE0007, if it exists
expected = new DeflateInfo { InputSize = SomeData7DeflatedSize, OutputSize = -1, Crc32 = 0 };
if (InflateWrapper.ExtractFile(data, "FILE0007", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL)
return false;
// Extract FILE0008, if it exists
expected = new DeflateInfo { InputSize = SomeData8DeflatedSize, OutputSize = -1, Crc32 = 0 };
if (InflateWrapper.ExtractFile(data, "FILE0008", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL)
return false;
// Extract FILE0009, if it exists
expected = new DeflateInfo { InputSize = SomeData9DeflatedSize, OutputSize = -1, Crc32 = 0 };
if (InflateWrapper.ExtractFile(data, "FILE0009", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL)
return false;
// Extract FILE000A, if it exists
expected = new DeflateInfo { InputSize = SomeData10DeflatedSize, OutputSize = -1, Crc32 = 0 };
if (InflateWrapper.ExtractFile(data, "FILE000A", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL)
return false;
// Extract install script, if it exists
expected = new DeflateInfo { InputSize = InstallScriptDeflatedSize, OutputSize = -1, Crc32 = 0 };
if (InflateWrapper.ExtractFile(data, "INSTALL_SCRIPT", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL)
return false;
// Extract FILE000{n}.DAT, if it exists
expected = new DeflateInfo { InputSize = FinalFileDeflatedSize, OutputSize = FinalFileInflatedSize, Crc32 = 0 };
if (InflateWrapper.ExtractFile(data, IsPKZIP ? null : "FILE00XX.DAT", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL)
return false;
dataStart = data.Position;
return true;
}
#endregion
}
}

View File

@@ -0,0 +1,485 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using SabreTools.IO.Compression.Deflate;
using SabreTools.IO.Extensions;
using SabreTools.Models.WiseInstaller;
using SabreTools.Models.WiseInstaller.Actions;
namespace SabreTools.Serialization.Wrappers
{
public class WiseScript : WrapperBase<ScriptFile>
{
#region Descriptive Properties
/// <inheritdoc/>
public override string DescriptionString => "Wise Installer Script File";
#endregion
#region Extension Properties
/// <inheritdoc cref="ScriptHeader.DateTime"/>
public uint DateTime => Model.Header?.DateTime ?? 0;
/// <inheritdoc cref="ScriptHeader.Flags"/>
public ushort Flags => Model.Header?.Flags ?? 0;
/// <inheritdoc cref="ScriptHeader.FontSize"/>
public uint FontSize => Model.Header?.FontSize ?? 0;
/// <inheritdoc cref="ScriptHeader.FTPURL"/>
public string? FTPURL => Model.Header?.FTPURL;
/// <inheritdoc cref="ScriptFile.HeaderStrings"/>
public string[]? HeaderStrings => Model.Header?.HeaderStrings;
/// <inheritdoc cref="ScriptHeader.LanguageCount"/>
public byte LanguageCount => Model.Header?.LanguageCount ?? 0;
/// <inheritdoc cref="ScriptHeader.LogPathname"/>
public string? LogPathname => Model.Header?.LogPathname;
/// <inheritdoc cref="ScriptHeader.MessageFont"/>
public string? MessageFont => Model.Header?.MessageFont;
/// <inheritdoc cref="ScriptFile.States"/>
public MachineState[]? States => Model.States;
/// <inheritdoc cref="ScriptHeader.UnknownU16_1"/>
public ushort UnknownU16_1 => Model.Header?.UnknownU16_1 ?? 0;
/// <inheritdoc cref="ScriptHeader.UnknownU16_2"/>
public ushort UnknownU16_2 => Model.Header?.UnknownU16_2 ?? 0;
/// <inheritdoc cref="ScriptFile.VariableLengthData"/>
public byte[]? VariableLengthData => Model.Header?.VariableLengthData;
#endregion
#region Constructors
/// <inheritdoc/>
public WiseScript(ScriptFile? model, byte[]? data, int offset)
: base(model, data, offset)
{
// All logic is handled by the base class
}
/// <inheritdoc/>
public WiseScript(ScriptFile? model, Stream? data)
: base(model, data)
{
// All logic is handled by the base class
}
/// <summary>
/// Create a Wise installer script file from a byte array and offset
/// </summary>
/// <param name="data">Byte array representing the script</param>
/// <param name="offset">Offset within the array to parse</param>
/// <returns>A Wise installer script file wrapper on success, null on failure</returns>
public static WiseScript? Create(byte[]? data, int offset)
{
// If the data is invalid
if (data == null || data.Length == 0)
return null;
// If the offset is out of bounds
if (offset < 0 || offset >= data.Length)
return null;
// Create a memory stream and use that
var dataStream = new MemoryStream(data, offset, data.Length - offset);
return Create(dataStream);
}
/// <summary>
/// Create a Wise installer script file from a Stream
/// </summary>
/// <param name="data">Stream representing the script</param>
/// <returns>A Wise installer script file wrapper on success, null on failure</returns>
public static WiseScript? Create(Stream? data)
{
// If the data is invalid
if (data == null || !data.CanRead)
return null;
try
{
var model = Deserializers.WiseScript.DeserializeStream(data);
if (model == null)
return null;
return new WiseScript(model, data);
}
catch
{
return null;
}
}
#endregion
#region Processing
/// <summary>
/// Process the state machine and perform all required actions
/// </summary>
/// <param name="data">Stream representing the Wise installer</param>
/// <param name="sourceDirectory">Directory where installer files live, if possible</param>
/// <param name="script">Parsed script to retrieve information from</param>
/// <param name="dataStart">Start of the deflated data</param>
/// <param name="outputDirectory">Output directory to write to</param>
/// <param name="isPkzip">Indicates if PKZIP containers are used</param>
/// <param name="includeDebug">True to include debug data, false otherwise</param>
/// <returns>True if there were no errors during processing, false otherwise</returns>
public bool ProcessStateMachine(Stream data,
string? sourceDirectory,
long dataStart,
string outputDirectory,
bool isPkzip,
bool includeDebug)
{
// If the state machine is invalid
if (States == null || States.Length == 0)
return false;
// Initialize important loop information
int normalFileCount = 0;
Dictionary<string, string> environment = [];
if (sourceDirectory != null)
environment.Add("INST", sourceDirectory);
// Loop through the state machine and process
foreach (var state in States)
{
switch (state.Op)
{
case OperationCode.InstallFile:
if (state.Data is not InstallFile fileHeader)
return false;
// Try to extract to the output directory
ExtractFile(data, dataStart, fileHeader, ++normalFileCount, outputDirectory, isPkzip, includeDebug);
break;
case OperationCode.EditIniFile:
if (state.Data is not EditIniFile editIniFile)
return false;
// Try to write to the output directory
WriteIniData(editIniFile, outputDirectory, ++normalFileCount);
break;
case OperationCode.DisplayBillboard:
if (state.Data is not DisplayBillboard displayBillboard)
return false;
// Try to extract to the output directory
ExtractFile(data, dataStart, displayBillboard, ++normalFileCount, outputDirectory, isPkzip, includeDebug);
break;
case OperationCode.DeleteFile:
if (state.Data is not DeleteFile deleteFile)
return false;
if (includeDebug) Console.WriteLine($"File {deleteFile.Pathname} is supposed to be deleted");
break;
case OperationCode.CreateDirectory:
if (state.Data is not CreateDirectory createDirectory)
return false;
if (createDirectory.Pathname == null)
return false;
try
{
if (includeDebug) Console.WriteLine($"Directory {createDirectory.Pathname} is being created");
// Ensure directory separators are consistent
string newDirectoryName = Path.Combine(outputDirectory, createDirectory.Pathname);
if (Path.DirectorySeparatorChar == '\\')
newDirectoryName = newDirectoryName.Replace('/', '\\');
else if (Path.DirectorySeparatorChar == '/')
newDirectoryName = newDirectoryName.Replace('\\', '/');
// Perform path replacements
foreach (var kvp in environment)
{
newDirectoryName = newDirectoryName.Replace($"%{kvp.Key}%", kvp.Value);
}
newDirectoryName = newDirectoryName.Replace("%", string.Empty);
// Remove wildcards from end of the path
if (newDirectoryName.EndsWith("*.*"))
newDirectoryName = newDirectoryName.Substring(0, newDirectoryName.Length - 4);
Directory.CreateDirectory(newDirectoryName);
}
catch
{
if (includeDebug) Console.WriteLine($"Directory {createDirectory.Pathname} could not be created!");
}
break;
case OperationCode.CopyLocalFile:
if (state.Data is not CopyLocalFile copyLocalFile)
return false;
if (copyLocalFile.Source == null)
return false;
if (copyLocalFile.Destination == null)
return false;
try
{
if (includeDebug) Console.WriteLine($"File {copyLocalFile.Source} is being copied to {copyLocalFile.Destination}");
// Ensure directory separators are consistent
string oldFilePath = copyLocalFile.Source;
if (Path.DirectorySeparatorChar == '\\')
oldFilePath = oldFilePath.Replace('/', '\\');
else if (Path.DirectorySeparatorChar == '/')
oldFilePath = oldFilePath.Replace('\\', '/');
// Perform path replacements
foreach (var kvp in environment)
{
oldFilePath = oldFilePath.Replace($"%{kvp.Key}%", kvp.Value);
}
oldFilePath = oldFilePath.Replace("%", string.Empty);
// Sanity check
if (!File.Exists(oldFilePath))
{
if (includeDebug) Console.WriteLine($"File {copyLocalFile.Source} is supposed to be copied to {copyLocalFile.Destination}, but it does not exist!");
break;
}
// Ensure directory separators are consistent
string newFilePath = Path.Combine(outputDirectory, copyLocalFile.Destination);
if (Path.DirectorySeparatorChar == '\\')
newFilePath = newFilePath.Replace('/', '\\');
else if (Path.DirectorySeparatorChar == '/')
newFilePath = newFilePath.Replace('\\', '/');
// Perform path replacements
foreach (var kvp in environment)
{
newFilePath = newFilePath.Replace($"%{kvp.Key}%", kvp.Value);
}
newFilePath = newFilePath.Replace("%", string.Empty);
// Sanity check
string? newFileDirectory = Path.GetDirectoryName(newFilePath);
if (newFileDirectory != null && !Directory.Exists(newFileDirectory))
Directory.CreateDirectory(newFileDirectory);
File.Copy(oldFilePath, newFilePath);
}
catch
{
if (includeDebug) Console.WriteLine($"File {copyLocalFile.Source} could not be copied!");
}
break;
case OperationCode.CustomDialogSet:
if (state.Data is not CustomDialogSet customDialogSet)
return false;
// Try to extract to the output directory
++normalFileCount;
ExtractFile(data, dataStart, customDialogSet, outputDirectory, isPkzip, includeDebug);
break;
case OperationCode.GetTemporaryFilename:
if (state.Data is not GetTemporaryFilename getTemporaryFilename)
return false;
if (getTemporaryFilename.Variable != null)
environment[getTemporaryFilename.Variable] = Guid.NewGuid().ToString();
break;
case OperationCode.AddTextToInstallLog:
if (state.Data is not AddTextToInstallLog addTextToInstallLog)
return false;
if (includeDebug) Console.WriteLine($"INSTALL.LOG: {addTextToInstallLog.Text}");
break;
default:
break;
}
}
return true;
}
#endregion
#region Extraction
/// <summary>
/// Attempt to extract a file defined by a file header
/// </summary>
/// <param name="data">Stream representing the Wise installer</param>
/// <param name="dataStart">Start of the deflated data</param>
/// <param name="obj">Deflate information</param>
/// <param name="index">File index for automatic naming</param>
/// <param name="outputDirectory">Output directory to write to</param>
/// <param name="isPkzip">Indicates if PKZIP containers are used</param>
/// <param name="includeDebug">True to include debug data, false otherwise</param>
/// <returns>True if the file extracted successfully, false otherwise</returns>
public static ExtractionStatus ExtractFile(Stream data,
long dataStart,
InstallFile obj,
int index,
string outputDirectory,
bool isPkzip,
bool includeDebug)
{
// Get expected values
var expected = new DeflateInfo
{
InputSize = obj.DeflateEnd - obj.DeflateStart,
OutputSize = obj.InflatedSize,
Crc32 = obj.Crc32,
};
// Perform path replacements
string filename = obj.DestinationPathname ?? $"WISE{index:X4}";
filename = filename.Replace("%", string.Empty);
data.Seek(dataStart + obj.DeflateStart, SeekOrigin.Begin);
return InflateWrapper.ExtractFile(data,
filename,
outputDirectory,
expected,
isPkzip,
includeDebug);
}
/// <summary>
/// Attempt to extract a file defined by a file header
/// </summary>
/// <param name="data">Stream representing the Wise installer</param>
/// <param name="dataStart">Start of the deflated data</param>
/// <param name="obj">Deflate information</param>
/// <param name="index">File index for automatic naming</param>
/// <param name="outputDirectory">Output directory to write to</param>
/// <param name="isPkzip">Indicates if PKZIP containers are used</param>
/// <param name="includeDebug">True to include debug data, false otherwise</param>
/// <returns>True if the file extracted successfully, false otherwise</returns>
public static ExtractionStatus ExtractFile(Stream data,
long dataStart,
DisplayBillboard obj,
int index,
string outputDirectory,
bool isPkzip,
bool includeDebug)
{
// Get the generated base name
string baseName = $"CustomBillboardSet_{obj.Flags:X4}-{obj.Operand_2}-{obj.Operand_3}";
// If there are no deflate objects
if (obj.DeflateInfo == null)
{
if (includeDebug) Console.WriteLine($"Skipping {baseName} because the deflate object array is null!");
return ExtractionStatus.FAIL;
}
// Loop through the values
for (int i = 0; i < obj.DeflateInfo.Length; i++)
{
// Get the deflate info object
var info = obj.DeflateInfo[i];
// Get expected values
var expected = new DeflateInfo
{
InputSize = info.DeflateEnd - info.DeflateStart,
OutputSize = info.InflatedSize,
Crc32 = 0,
};
// Perform path replacements
string filename = $"{baseName}{index:X4}";
data.Seek(dataStart + info.DeflateStart, SeekOrigin.Begin);
_ = InflateWrapper.ExtractFile(data, filename, outputDirectory, expected, isPkzip, includeDebug);
}
// Always return good -- TODO: Fix this
return ExtractionStatus.GOOD;
}
/// <summary>
/// Attempt to extract a file defined by a file header
/// </summary>
/// <param name="data">Stream representing the Wise installer</param>
/// <param name="dataStart">Start of the deflated data</param>
/// <param name="obj">Deflate information</param>
/// <param name="outputDirectory">Output directory to write to</param>
/// <param name="isPkzip">Indicates if PKZIP containers are used</param>
/// <param name="includeDebug">True to include debug data, false otherwise</param>
/// <returns>True if the file extracted successfully, false otherwise</returns>
public static ExtractionStatus ExtractFile(Stream data,
long dataStart,
CustomDialogSet obj,
string outputDirectory,
bool isPkzip,
bool includeDebug)
{
// Get expected values
var expected = new DeflateInfo
{
InputSize = obj.DeflateEnd - obj.DeflateStart,
OutputSize = obj.InflatedSize,
Crc32 = 0,
};
// Perform path replacements
string filename = $"CustomDialogSet_{obj.DisplayVariable}-{obj.Name}";
filename = filename.Replace("%", string.Empty);
data.Seek(dataStart + obj.DeflateStart, SeekOrigin.Begin);
return InflateWrapper.ExtractFile(data, filename, outputDirectory, expected, isPkzip, includeDebug);
}
/// <summary>
/// Attempt to write INI data to the correct file
/// </summary>
/// <param name="obj">INI information</param>
/// <param name="outputDirectory">Output directory to write to</param>
/// <param name="index">File index for automatic naming</param>
private static void WriteIniData(EditIniFile obj, string outputDirectory, int index)
{
// Ensure directory separators are consistent
string iniFilePath = obj.Pathname ?? $"WISE{index:X4}.ini";
if (Path.DirectorySeparatorChar == '\\')
iniFilePath = iniFilePath.Replace('/', '\\');
else if (Path.DirectorySeparatorChar == '/')
iniFilePath = iniFilePath.Replace('\\', '/');
// Ignore path replacements
iniFilePath = iniFilePath.Replace("%", string.Empty);
// Ensure the full output directory exists
iniFilePath = Path.Combine(outputDirectory, iniFilePath);
var directoryName = Path.GetDirectoryName(iniFilePath);
if (directoryName != null && !Directory.Exists(directoryName))
Directory.CreateDirectory(directoryName);
using (var iniFile = File.Open(iniFilePath, FileMode.Append, FileAccess.Write, FileShare.ReadWrite))
{
iniFile.Write(Encoding.ASCII.GetBytes($"[{obj.Section}]\n"));
iniFile.Write(Encoding.ASCII.GetBytes($"{obj.Values ?? string.Empty}\n"));
iniFile.Flush();
}
}
#endregion
}
}

View File

@@ -0,0 +1,359 @@
using System;
using System.IO;
using SabreTools.Hashing;
using SabreTools.IO.Compression.Deflate;
using SabreTools.IO.Extensions;
using SabreTools.Models.WiseInstaller;
using SabreTools.Serialization.Interfaces;
namespace SabreTools.Serialization.Wrappers
{
public class WiseSectionHeader : WrapperBase<SectionHeader>, IExtractable
{
#region Descriptive Properties
/// <inheritdoc/>
public override string DescriptionString => "Self-Extracting Wise Installer Header";
#endregion
#region Extension Properties
/// <inheritdoc cref="SectionHeader.UnknownDataSize"/>
public uint UnknownDataSize => Model.UnknownDataSize;
/// <inheritdoc cref="SectionHeader.SecondExecutableFileEntryLength"/> // TODO: VERIFY ON CHANGE
public uint SecondExecutableFileEntryLength => Model.SecondExecutableFileEntryLength;
/// <inheritdoc cref="SectionHeader.UnknownValue2"/>
public uint UnknownValue2 => Model.UnknownValue2;
/// <inheritdoc cref="SectionHeader.UnknownValue3"/>
public uint UnknownValue3 => Model.UnknownValue3;
/// <inheritdoc cref="SectionHeader.UnknownValue4"/>
public uint UnknownValue4 => Model.UnknownValue4;
/// <inheritdoc cref="SectionHeader.FirstExecutableFileEntryLength"/>
public uint FirstExecutableFileEntryLength => Model.FirstExecutableFileEntryLength; // TODO: VERIFY ON CHANGE
/// <inheritdoc cref="SectionHeader.MsiFileEntryLength"/>
public uint MsiFileEntryLength => Model.MsiFileEntryLength;
/// <inheritdoc cref="SectionHeader.UnknownValue7"/>
public uint UnknownValue7 => Model.UnknownValue7;
/// <inheritdoc cref="SectionHeader.UnknownValue8"/>
public uint UnknownValue8 => Model.UnknownValue8;
/// <inheritdoc cref="SectionHeader.ThirdExecutableFileEntryLength"/>
public uint ThirdExecutableFileEntryLength => Model.ThirdExecutableFileEntryLength;
/// <inheritdoc cref="SectionHeader.UnknownValue10"/>
public uint UnknownValue10 => Model.UnknownValue10;
/// <inheritdoc cref="SectionHeader.UnknownValue11"/>
public uint UnknownValue11 => Model.UnknownValue11;
/// <inheritdoc cref="SectionHeader.UnknownValue12"/>
public uint UnknownValue12 => Model.UnknownValue12;
/// <inheritdoc cref="SectionHeader.UnknownValue13"/>
public uint UnknownValue13 => Model.UnknownValue13;
/// <inheritdoc cref="SectionHeader.UnknownValue14"/>
public uint UnknownValue14 => Model.UnknownValue14;
/// <inheritdoc cref="SectionHeader.UnknownValue15"/>
public uint UnknownValue15 => Model.UnknownValue15;
/// <inheritdoc cref="SectionHeader.UnknownValue16"/>
public uint UnknownValue16 => Model.UnknownValue16;
/// <inheritdoc cref="SectionHeader.UnknownValue17"/>
public uint UnknownValue17 => Model.UnknownValue17;
/// <inheritdoc cref="SectionHeader.UnknownValue18"/>
public uint UnknownValue18 => Model.UnknownValue18;
/// <inheritdoc cref="SectionHeader.Version"/>
public byte[]? Version => Model.Version;
/// <inheritdoc cref="SectionHeader.PreStringValues"/>
public byte[]? PreStringValues => Model.PreStringValues;
/// <inheritdoc cref="SectionHeader.Strings"/>
public byte[][]? Strings => Model.Strings;
#endregion
#region Constructors
/// <inheritdoc/>
public WiseSectionHeader(SectionHeader? model, byte[]? data, int offset)
: base(model, data, offset)
{
// All logic is handled by the base class
}
/// <inheritdoc/>
public WiseSectionHeader(SectionHeader? model, Stream? data)
: base(model, data)
{
// All logic is handled by the base class
}
/// <summary>
/// Create a Wise Self-Extracting installer .WISE section from a byte array and offset
/// </summary>
/// <param name="data">Byte array representing the section</param>
/// <param name="offset">Offset within the array to parse</param>
/// <returns>A Wise Self-Extracting installer .WISE section wrapper on success, null on failure</returns>
public static WiseSectionHeader? Create(byte[]? data, int offset)
{
// If the data is invalid
if (data == null || data.Length == 0)
return null;
// If the offset is out of bounds
if (offset < 0 || offset >= data.Length)
return null;
// Create a memory stream and use that
var dataStream = new MemoryStream(data, offset, data.Length - offset);
return Create(dataStream);
}
/// <summary>
/// Create a Wise Self-Extracting installer .WISE section from a Stream
/// </summary>
/// <param name="data">Stream representing the section</param>
/// <returns>A Wise Self-Extracting installer .WISE section wrapper on success, null on failure</returns>
public static WiseSectionHeader? Create(Stream? data)
{
// If the data is invalid
if (data == null || !data.CanRead)
return null;
try
{
var model = Deserializers.WiseSectionHeader.DeserializeStream(data);
if (model == null)
return null;
return new WiseSectionHeader(model, data);
}
catch
{
return null;
}
}
#endregion
#region Extraction
/// <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)
{
// TODO: All reads need to be sequential. At the moment, the data position is at the start of the header;
// TODO: it needs to be wherever the header parser was when it finished. This applies to all logic in the
// TODO: WiseSectionHeader deserializer and wrapper code.
// Extract first executable, if it exists
if (ExtractFile("FirstExecutable.exe", outputDirectory, FirstExecutableFileEntryLength, includeDebug) != ExtractionStatus.GOOD)
return 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)
return false;
// Extract third executable, if it exists
if (ExtractFile("ThirdExecutable.exe", outputDirectory, ThirdExecutableFileEntryLength, includeDebug) != ExtractionStatus.GOOD)
return false;
// Extract main MSI file
if (ExtractFile("ExtoutputDirectory: ractedMsi.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.
_dataSource.Seek(-MsiFileEntryLength, SeekOrigin.End);
if (ExtractFile("ExtractedMsi.msi", outputDirectory, MsiFileEntryLength, includeDebug) != ExtractionStatus.GOOD)
return false; // The fallback also failed.
}
return true;
}
/// <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;
if (!(Version != null && Version[1] == 0x01))
{
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
if (status != ExtractionStatus.GOOD || destination == null)
return status;
// 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
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);
if (hashBytes != null)
{
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;
}
}
destination.Write(actual, 0, actual.Length);
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!");
destination.Write(actual, 0, actual.Length);
return ExtractionStatus.GOOD;
}
catch
{
if (includeDebug) Console.Error.WriteLine("Could not extract");
return ExtractionStatus.FAIL;
}
}
#endregion
}
}