diff --git a/ExtractionTool/ExtractionTool.csproj b/ExtractionTool/ExtractionTool.csproj
index c2c24edf..1e8d30a6 100644
--- a/ExtractionTool/ExtractionTool.csproj
+++ b/ExtractionTool/ExtractionTool.csproj
@@ -66,7 +66,7 @@
-
+
diff --git a/InfoPrint/InfoPrint.csproj b/InfoPrint/InfoPrint.csproj
index ead9fd5e..c0b4e1ce 100644
--- a/InfoPrint/InfoPrint.csproj
+++ b/InfoPrint/InfoPrint.csproj
@@ -32,7 +32,7 @@
-
+
diff --git a/SabreTools.Serialization.Test/SabreTools.Serialization.Test.csproj b/SabreTools.Serialization.Test/SabreTools.Serialization.Test.csproj
index 663ac6c4..5b383306 100644
--- a/SabreTools.Serialization.Test/SabreTools.Serialization.Test.csproj
+++ b/SabreTools.Serialization.Test/SabreTools.Serialization.Test.csproj
@@ -28,7 +28,7 @@
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/SabreTools.Serialization/Deserializers/GZip.cs b/SabreTools.Serialization/Deserializers/GZip.cs
index 186f46f9..35d810cd 100644
--- a/SabreTools.Serialization/Deserializers/GZip.cs
+++ b/SabreTools.Serialization/Deserializers/GZip.cs
@@ -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 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;
}
diff --git a/SabreTools.Serialization/Deserializers/MoPaQ.cs b/SabreTools.Serialization/Deserializers/MoPaQ.cs
index ef93f86d..0c042217 100644
--- a/SabreTools.Serialization/Deserializers/MoPaQ.cs
+++ b/SabreTools.Serialization/Deserializers/MoPaQ.cs
@@ -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 _);
diff --git a/SabreTools.Serialization/Deserializers/PKZIP.cs b/SabreTools.Serialization/Deserializers/PKZIP.cs
index cdb4ec7b..628b6f0a 100644
--- a/SabreTools.Serialization/Deserializers/PKZIP.cs
+++ b/SabreTools.Serialization/Deserializers/PKZIP.cs
@@ -28,11 +28,7 @@ namespace SabreTools.Serialization.Deserializers
var archive = new Archive();
// Setup all of the collections
- var localFileHeaders = new List();
- var encryptionHeaders = new List();
- var fileData = new List();
- var dataDescriptors = new List();
- var zip64DataDescriptors = new List();
+ var localFiles = new List();
var cdrs = new List();
// 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
///
/// Stream to parse
/// Filled local file on success, null on error
- 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;
}
///
@@ -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
/// Byte array to parse
/// Offset into the byte array
/// Filled unknown extras field on success, null on error
- 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;
}
///
@@ -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 tags = [];
- List sizes = [];
- List datas = [];
+ List 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 tags = [];
- List sizes = [];
- List datas = [];
+ List 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 tags = [];
- List sizes = [];
- List datas = [];
+ List 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;
}
diff --git a/SabreTools.Serialization/Deserializers/PortableExecutable.cs b/SabreTools.Serialization/Deserializers/PortableExecutable.cs
index 59cd84d3..cfb5c887 100644
--- a/SabreTools.Serialization/Deserializers/PortableExecutable.cs
+++ b/SabreTools.Serialization/Deserializers/PortableExecutable.cs
@@ -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;
}
+ ///
+ /// Parse a Stream into a CLRTokenDefinition
+ ///
+ /// Stream to parse
+ /// Filled CLRTokenDefinition on success, null on error
+ 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;
+ }
+
///
/// Parse a Stream into a COFFFileHeader
///
@@ -420,192 +438,15 @@ namespace SabreTools.Serialization.Deserializers
return obj;
}
- ///
- /// Parse a Stream into a COFFSymbolTableEntry
- ///
- /// Stream to parse
- /// Filled COFFSymbolTableEntry on success, null on error
- /// Standard COFF Symbol Table Entry
- 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;
- }
-
- ///
- /// Parse a Stream into a COFFSymbolTableEntry
- ///
- /// Stream to parse
- /// Filled COFFSymbolTableEntry on success, null on error
- /// Auxiliary Format 1: Function Definitions
- 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;
- }
-
- ///
- /// Parse a Stream into a COFFSymbolTableEntry
- ///
- /// Stream to parse
- /// Filled COFFSymbolTableEntry on success, null on error
- /// Auxiliary Format 2: .bf and .ef Symbols
- 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;
- }
-
- ///
- /// Parse a Stream into a COFFSymbolTableEntry
- ///
- /// Stream to parse
- /// Filled COFFSymbolTableEntry on success, null on error
- /// Auxiliary Format 3: Weak Externals
- 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;
- }
-
- ///
- /// Parse a Stream into a COFFSymbolTableEntry
- ///
- /// Stream to parse
- /// Filled COFFSymbolTableEntry on success, null on error
- /// Auxiliary Format 4: Files
- public static COFFSymbolTableEntry ParseCOFFSymbolTableEntryType4(Stream data)
- {
- var obj = new COFFSymbolTableEntry();
-
- obj.AuxFormat4FileName = data.ReadBytes(18);
-
- return obj;
- }
-
- ///
- /// Parse a Stream into a COFFSymbolTableEntry
- ///
- /// Stream to parse
- /// Filled COFFSymbolTableEntry on success, null on error
- /// Auxiliary Format 5: Section Definitions
- 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;
- }
-
- ///
- /// Parse a Stream into a COFFSymbolTableEntry
- ///
- /// Stream to parse
- /// Filled COFFSymbolTableEntry on success, null on error
- /// Auxiliary Format 6: CLR Token Definition
- 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;
- }
-
///
/// Parse a Stream into a COFF symbol table
///
/// Stream to parse
/// Number of COFF symbol table entries to read
/// Filled COFF symbol table on success, null on error
- 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;
}
+ ///
+ /// Parse a Stream into a Descriptor
+ ///
+ /// Stream to parse
+ /// Filled Descriptor on success, null on error
+ 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;
+ }
+
///
/// Parse a Stream into a ExportAddressTableEntry
///
@@ -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;
}
+ ///
+ /// Parse a Stream into a FileRecord
+ ///
+ /// Stream to parse
+ /// Filled FileRecord on success, null on error
+ public static FileRecord ParseFileRecord(Stream data)
+ {
+ var obj = new FileRecord();
+
+ obj.FileName = data.ReadBytes(18);
+
+ return obj;
+ }
+
+ ///
+ /// Parse a Stream into a FunctionDefinition
+ ///
+ /// Stream to parse
+ /// Filled FunctionDefinition on success, null on error
+ 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;
+ }
+
///
/// Parse a Stream into a HintNameTableEntry
///
@@ -1499,6 +1390,26 @@ namespace SabreTools.Serialization.Deserializers
return obj;
}
+ ///
+ /// Parse a Stream into a SectionDefinition
+ ///
+ /// Stream to parse
+ /// Filled SectionDefinition on success, null on error
+ 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;
+ }
+
///
/// Parse a Stream into a SectionHeader
///
@@ -1531,5 +1442,88 @@ namespace SabreTools.Serialization.Deserializers
return obj;
}
+
+ ///
+ /// Parse a Stream into a StandardRecord
+ ///
+ /// Stream to parse
+ /// Filled StandardRecord on success, null on error
+ 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;
+ }
+
+ ///
+ /// Parse a Stream into a WeakExternal
+ ///
+ /// Stream to parse
+ /// Filled WeakExternal on success, null on error
+ 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;
+ }
}
}
diff --git a/SabreTools.Serialization/Deserializers/TapeArchive.cs b/SabreTools.Serialization/Deserializers/TapeArchive.cs
index 28359da1..accecf26 100644
--- a/SabreTools.Serialization/Deserializers/TapeArchive.cs
+++ b/SabreTools.Serialization/Deserializers/TapeArchive.cs
@@ -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;
}
diff --git a/SabreTools.Serialization/Deserializers/WiseOverlayHeader.cs b/SabreTools.Serialization/Deserializers/WiseOverlayHeader.cs
new file mode 100644
index 00000000..ccb6af2a
--- /dev/null
+++ b/SabreTools.Serialization/Deserializers/WiseOverlayHeader.cs
@@ -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
+ {
+ ///
+ 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;
+ }
+ }
+
+ ///
+ /// Parse a Stream into an OverlayHeader
+ ///
+ /// Stream to parse
+ /// Filled OverlayHeader on success, null on error
+ 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;
+ }
+ }
+}
diff --git a/SabreTools.Serialization/Deserializers/WiseScript.cs b/SabreTools.Serialization/Deserializers/WiseScript.cs
new file mode 100644
index 00000000..863b0b92
--- /dev/null
+++ b/SabreTools.Serialization/Deserializers/WiseScript.cs
@@ -0,0 +1,1786 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using SabreTools.IO.Extensions;
+using SabreTools.Models.WiseInstaller;
+using SabreTools.Models.WiseInstaller.Actions;
+
+namespace SabreTools.Serialization.Deserializers
+{
+ public class WiseScript : BaseBinaryDeserializer
+ {
+ ///
+ public override ScriptFile? 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 script = new ScriptFile();
+
+ #region Header
+
+ var header = ParseScriptHeader(data);
+ script.Header = header;
+
+ #endregion
+
+ #region State Machine
+
+ var states = ParseStateMachine(data, header);
+ if (states == null)
+ return null;
+
+ script.States = states;
+
+ #endregion
+
+ return script;
+ }
+ catch
+ {
+ // Ignore the actual error
+ return null;
+ }
+ }
+
+ ///
+ /// Parse a Stream into a ScriptHeader
+ ///
+ /// Stream to parse
+ /// Filled ScriptHeader on success, null on error
+ private static ScriptHeader ParseScriptHeader(Stream data)
+ {
+ // Cache the current position in case of a trimmed header
+ long current = data.Position;
+
+ var header = new ScriptHeader();
+
+ // Attempt to read strings at 0x12 (Short)
+ data.Seek(current + 0x12, SeekOrigin.Begin);
+ string? ftpUrl = data.ReadNullTerminatedAnsiString();
+ string? logPath = data.ReadNullTerminatedAnsiString();
+ string? messageFont = data.ReadNullTerminatedAnsiString();
+ data.Seek(current, SeekOrigin.Begin);
+
+ // If the strings are valid
+ if ((ftpUrl != null && (ftpUrl.Length == 0 || ftpUrl.Split('.').Length > 2))
+ && (logPath != null && (logPath.Length == 0 || logPath.StartsWith("%")))
+ && (messageFont != null && (messageFont.Length == 0 || !IsTypicalControlCode(messageFont, strict: true)))
+ && !(ftpUrl.Length == 0 && logPath.Length == 0 && messageFont.Length == 0))
+ {
+ // TODO: Figure out if this maps to existing fields
+ header.Flags = data.ReadByteValue();
+ header.UnknownU16_1 = data.ReadUInt16LittleEndian();
+ header.UnknownU16_2 = data.ReadUInt16LittleEndian();
+ header.DateTime = data.ReadUInt32LittleEndian();
+ header.VariableLengthData = data.ReadBytes(9);
+
+ goto ReadStrings;
+ }
+
+ // Attempt to read strings at 0x26 (Middle)
+ data.Seek(current + 0x26, SeekOrigin.Begin);
+ ftpUrl = data.ReadNullTerminatedAnsiString();
+ logPath = data.ReadNullTerminatedAnsiString();
+ messageFont = data.ReadNullTerminatedAnsiString();
+ data.Seek(current, SeekOrigin.Begin);
+
+ // If the strings are valid
+ if ((ftpUrl != null && (ftpUrl.Length == 0 || ftpUrl.Split('.').Length > 2))
+ && (logPath != null && (logPath.Length == 0 || logPath.StartsWith("%")))
+ && (messageFont != null && (messageFont.Length == 0 || !IsTypicalControlCode(messageFont, strict: true)))
+ && !(ftpUrl.Length == 0 && logPath.Length == 0 && messageFont.Length == 0))
+ {
+ header.Flags = data.ReadByteValue();
+ header.UnknownU16_1 = data.ReadUInt16LittleEndian();
+ header.UnknownU16_2 = data.ReadUInt16LittleEndian();
+ header.SomeOffset1 = data.ReadUInt32LittleEndian();
+ header.SomeOffset2 = data.ReadUInt32LittleEndian();
+ header.UnknownBytes_2 = data.ReadBytes(4);
+ header.DateTime = data.ReadUInt32LittleEndian();
+ header.VariableLengthData = data.ReadBytes(17);
+
+ goto ReadStrings;
+ }
+
+ // Attempt to read strings at 0x34 (Long)
+ data.Seek(current + 0x34, SeekOrigin.Begin);
+ ftpUrl = data.ReadNullTerminatedAnsiString();
+ logPath = data.ReadNullTerminatedAnsiString();
+ messageFont = data.ReadNullTerminatedAnsiString();
+ data.Seek(current, SeekOrigin.Begin);
+
+ // If the strings are valid
+ if ((ftpUrl != null && (ftpUrl.Length == 0 || ftpUrl.Split('.').Length > 2))
+ && (logPath != null && (logPath.Length == 0 || logPath.StartsWith("%")))
+ && (messageFont != null && (messageFont.Length == 0 || !IsTypicalControlCode(messageFont, strict: true)))
+ && !(ftpUrl.Length == 0 && logPath.Length == 0 && messageFont.Length == 0))
+ {
+ header.Flags = data.ReadByteValue();
+ header.UnknownU16_1 = data.ReadUInt16LittleEndian();
+ header.UnknownU16_2 = data.ReadUInt16LittleEndian();
+ header.SomeOffset1 = data.ReadUInt32LittleEndian();
+ header.SomeOffset2 = data.ReadUInt32LittleEndian();
+ header.UnknownBytes_2 = data.ReadBytes(4);
+ header.DateTime = data.ReadUInt32LittleEndian();
+ header.VariableLengthData = data.ReadBytes(31);
+
+ goto ReadStrings;
+ }
+
+ // Otherwise, assume a standard header (Normal)
+ header.Flags = data.ReadByteValue();
+ header.UnknownU16_1 = data.ReadUInt16LittleEndian();
+ header.UnknownU16_2 = data.ReadUInt16LittleEndian();
+ header.SomeOffset1 = data.ReadUInt32LittleEndian();
+ header.SomeOffset2 = data.ReadUInt32LittleEndian();
+ header.UnknownBytes_2 = data.ReadBytes(4);
+ header.DateTime = data.ReadUInt32LittleEndian();
+ header.VariableLengthData = data.ReadBytes(22);
+
+ ReadStrings:
+ header.FTPURL = data.ReadNullTerminatedAnsiString();
+ header.LogPathname = data.ReadNullTerminatedAnsiString();
+ header.MessageFont = data.ReadNullTerminatedAnsiString();
+ header.FontSize = data.ReadUInt32LittleEndian();
+ header.Unknown_2 = data.ReadBytes(2);
+ header.LanguageCount = data.ReadByteValue();
+
+ List headerStrings = [];
+ while (true)
+ {
+ string? str = data.ReadNullTerminatedAnsiString();
+ if (str == null)
+ break;
+
+ // Try to handle invalid string lengths
+ if (str.Length > 0 && IsTypicalControlCode(str, strict: false))
+ {
+ data.Seek(-str.Length - 1, SeekOrigin.Current);
+ break;
+ }
+
+ // Try to handle InstallFile calls
+ long original = data.Position;
+ if (str.Length == 0)
+ {
+ data.Seek(-1, SeekOrigin.Current);
+
+ // Try to read the next block as an install file call
+ var maybeInstall = ParseInstallFile(data, header.LanguageCount);
+ if (maybeInstall != null
+ && (maybeInstall.DeflateEnd - maybeInstall.DeflateStart) < data.Length
+ && (maybeInstall.DeflateEnd - maybeInstall.DeflateStart) < maybeInstall.InflatedSize)
+ {
+ data.Seek(original - 1, SeekOrigin.Begin);
+ break;
+ }
+
+ // Otherwise, seek back to reading
+ data.Seek(original, SeekOrigin.Begin);
+ }
+
+ headerStrings.Add(str);
+ }
+
+ header.HeaderStrings = [.. headerStrings];
+
+ return header;
+ }
+
+ ///
+ /// Parse a Stream into a state machine
+ ///
+ /// Stream to parse
+ /// Parsed script header for information
+ /// Indicates an old install script
+ /// Filled state machine on success, null on error
+ private static MachineState[]? ParseStateMachine(Stream data, ScriptHeader header)
+ {
+ // Extract required information
+ byte languageCount = header.LanguageCount;
+ bool shortDllCall = header.VariableLengthData?.Length != 22
+ && header.SomeOffset1 == 0x00000000
+ && header.Flags != 0x0008
+ && header.Flags != 0x0014;
+
+ // Initialize important loop information
+ int op0x18skip = -1;
+ bool? registryDll = null;
+ bool switched = false;
+
+ // Store the start of the state machine
+ long machineStart = data.Position;
+
+ // Store all states in order
+ List states = [];
+
+ while (data.Position < data.Length)
+ {
+ var op = (OperationCode)data.ReadByteValue();
+ MachineStateData? stateData = op switch
+ {
+ OperationCode.InstallFile => ParseInstallFile(data, languageCount),
+ OperationCode.Invalid0x01 => ParseInvalidOperation(data),
+ OperationCode.NoOp => ParseNoOp(data),
+ OperationCode.DisplayMessage => ParseDisplayMessage(data, languageCount),
+ OperationCode.UserDefinedActionStep => ParseUserDefinedActionStep(data, languageCount),
+ OperationCode.EditIniFile => ParseEditIniFile(data),
+ OperationCode.DisplayBillboard => ParseDisplayBillboard(data, languageCount),
+ OperationCode.ExecuteProgram => ParseExecuteProgram(data),
+ OperationCode.EndBlock => ParseEndBlockStatement(data),
+ OperationCode.CallDllFunction => ParseCallDllFunction(data, languageCount, shortDllCall),
+ OperationCode.EditRegistry => ParseEditRegistry(data, ref registryDll),
+ OperationCode.DeleteFile => ParseDeleteFile(data),
+ OperationCode.IfWhileStatement => ParseIfWhileStatement(data),
+ OperationCode.ElseStatement => ParseElseStatement(data),
+ OperationCode.Invalid0x0E => ParseInvalidOperation(data),
+ OperationCode.StartUserDefinedAction => ParseStartUserDefinedAction(data),
+ OperationCode.EndUserDefinedAction => ParseEndUserDefinedAction(data),
+ OperationCode.CreateDirectory => ParseCreateDirectory(data),
+ OperationCode.CopyLocalFile => ParseCopyLocalFile(data, languageCount),
+ OperationCode.Invalid0x13 => ParseInvalidOperation(data),
+ OperationCode.CustomDialogSet => ParseCustomDialogSet(data),
+ OperationCode.GetSystemInformation => ParseGetSystemInformation(data),
+ OperationCode.GetTemporaryFilename => ParseGetTemporaryFilename(data),
+ OperationCode.PlayMultimediaFile => ParsePlayMultimediaFile(data),
+ OperationCode.NewEvent => ParseNewEvent(data, languageCount, shortDllCall, ref op0x18skip),
+ OperationCode.InstallODBCDriver => ParseUnknown0x19(data),
+ OperationCode.ConfigODBCDataSource => ParseConfigODBCDataSource(data),
+ OperationCode.IncludeScript => ParseIncludeScript(data),
+ OperationCode.AddTextToInstallLog => ParseAddTextToInstallLog(data),
+ OperationCode.RenameFileDirectory => ParseRenameFileDirectory(data),
+ OperationCode.OpenCloseInstallLog => ParseOpenCloseInstallLog(data),
+ OperationCode.Invalid0x1F => ParseInvalidOperation(data),
+ OperationCode.Invalid0x20 => ParseInvalidOperation(data),
+ OperationCode.Invalid0x21 => ParseInvalidOperation(data),
+ OperationCode.Invalid0x22 => ParseInvalidOperation(data),
+ OperationCode.ElseIfStatement => ParseElseIfStatement(data),
+ OperationCode.Unknown0x24 => ParseUnknown0x24(data),
+ OperationCode.Unknown0x25 => ParseUnknown0x25(data),
+
+ // Handled separately below
+ _ => null,
+ };
+
+ // If an error is detected, try parsing with flipped short DLL call values
+ if (stateData == null)
+ {
+ // If there has already been one switch, don't try again
+ if (switched)
+ throw new IndexOutOfRangeException(nameof(op));
+
+ // Debug statement
+ Console.WriteLine($"Opcode {op} resulted in null data, trying with alternate values");
+
+ // Reset the state
+ switched = true;
+ shortDllCall = !shortDllCall;
+ states.Clear();
+
+ // Seek to the start of the machine and try again
+ data.Seek(machineStart, SeekOrigin.Begin);
+ continue;
+ }
+
+ var state = new MachineState
+ {
+ Op = op,
+ Data = stateData,
+ };
+ states.Add(state);
+ }
+
+ return [.. states];
+ }
+
+ #region State Actions
+
+ ///
+ /// Parse a Stream into a InstallFile
+ ///
+ /// Stream to parse
+ /// Filled InstallFile on success, null on error
+ private static InstallFile ParseInstallFile(Stream data, int languageCount)
+ {
+ var header = new InstallFile();
+
+ header.Flags = data.ReadUInt16LittleEndian();
+ header.DeflateStart = data.ReadUInt32LittleEndian();
+ header.DeflateEnd = data.ReadUInt32LittleEndian();
+ header.Date = data.ReadUInt16LittleEndian();
+ header.Time = data.ReadUInt16LittleEndian();
+ header.InflatedSize = data.ReadUInt32LittleEndian();
+ header.Operand_7 = data.ReadBytes(20);
+ header.Crc32 = data.ReadUInt32LittleEndian();
+ header.DestinationPathname = data.ReadNullTerminatedAnsiString();
+
+ header.Description = new string[languageCount];
+ for (int i = 0; i < header.Description.Length; i++)
+ {
+ header.Description[i] = data.ReadNullTerminatedAnsiString() ?? string.Empty;
+ }
+
+ header.Source = data.ReadNullTerminatedAnsiString();
+
+ return header;
+ }
+
+ ///
+ /// Parse a Stream into a NoOp
+ ///
+ /// Stream to parse
+ /// Filled NoOp on success, null on error
+ private static NoOp ParseNoOp(Stream data)
+ {
+ return new NoOp();
+ }
+
+ ///
+ /// Parse a Stream into a DisplayMessage
+ ///
+ /// Stream to parse
+ /// Language counter from the header
+ /// Filled DisplayMessage on success, null on error
+ private static DisplayMessage ParseDisplayMessage(Stream data, int languageCount)
+ {
+ var obj = new DisplayMessage();
+
+ obj.Flags = data.ReadByteValue();
+ obj.TitleText = new string[languageCount * 2];
+ for (int i = 0; i < obj.TitleText.Length; i++)
+ {
+ obj.TitleText[i] = data.ReadNullTerminatedAnsiString() ?? string.Empty;
+ }
+
+ return obj;
+ }
+
+ ///
+ /// Parse a Stream into a UserDefinedActionStep
+ ///
+ /// Stream to parse
+ /// Language counter from the header
+ /// Filled UserDefinedActionStep on success, null on error
+ private static UserDefinedActionStep ParseUserDefinedActionStep(Stream data, int languageCount)
+ {
+ var obj = new UserDefinedActionStep();
+
+ obj.Flags = data.ReadByteValue();
+ obj.ScriptLines = new string[languageCount];
+ for (int i = 0; i < obj.ScriptLines.Length; i++)
+ {
+ obj.ScriptLines[i] = data.ReadNullTerminatedAnsiString() ?? string.Empty;
+ }
+
+ return obj;
+ }
+
+ ///
+ /// Parse a Stream into an EditIniFile
+ ///
+ /// Stream to parse
+ /// Filled EditIniFile on success, null on error
+ private static EditIniFile ParseEditIniFile(Stream data)
+ {
+ var obj = new EditIniFile();
+
+ obj.Pathname = data.ReadNullTerminatedAnsiString();
+ obj.Section = data.ReadNullTerminatedAnsiString();
+ obj.Values = data.ReadNullTerminatedAnsiString();
+
+ return obj;
+ }
+
+ ///
+ /// Parse a Stream into a DisplayBillboard
+ ///
+ /// Stream to parse
+ /// Language counter from the header
+ /// Filled DisplayBillboard on success, null on error
+ private static DisplayBillboard ParseDisplayBillboard(Stream data, int languageCount)
+ {
+ var obj = new DisplayBillboard();
+
+ obj.Flags = data.ReadUInt16LittleEndian();
+ obj.Operand_2 = data.ReadUInt16LittleEndian();
+ obj.Operand_3 = data.ReadUInt16LittleEndian();
+ obj.DeflateInfo = new DeflateEntry[languageCount];
+ for (int i = 0; i < obj.DeflateInfo.Length; i++)
+ {
+ obj.DeflateInfo[i] = ParseDeflateEntry(data);
+ }
+
+ // Check the terminator byte is 0x00
+ obj.Terminator = data.ReadByteValue();
+ if (obj.Terminator != 0x00)
+ {
+ obj.Terminator = 0x00;
+ data.Seek(-1, SeekOrigin.Current);
+ }
+
+ return obj;
+ }
+
+ ///
+ /// Parse a Stream into a ExecuteProgram
+ ///
+ /// Stream to parse
+ /// Filled ExecuteProgram on success, null on error
+ private static ExecuteProgram ParseExecuteProgram(Stream data)
+ {
+ var obj = new ExecuteProgram();
+
+ obj.Flags = data.ReadByteValue();
+ obj.Pathname = data.ReadNullTerminatedAnsiString();
+ obj.CommandLine = data.ReadNullTerminatedAnsiString();
+ obj.DefaultDirectory = data.ReadNullTerminatedAnsiString();
+
+ return obj;
+ }
+
+ ///
+ /// Parse a Stream into a EndBlockStatement
+ ///
+ /// Stream to parse
+ /// Filled EndBlockStatement on success, null on error
+ private static EndBlockStatement ParseEndBlockStatement(Stream data)
+ {
+ var obj = new EndBlockStatement();
+
+ obj.Operand_1 = data.ReadByteValue();
+
+ return obj;
+ }
+
+ ///
+ /// Parse a Stream into a CallDllFunction
+ ///
+ /// Stream to parse
+ /// Language counter from the header
+ /// Indicates a short DLL call
+ /// Filled CallDllFunction on success, null on error
+ private static CallDllFunction ParseCallDllFunction(Stream data, int languageCount, bool shortDllCall)
+ {
+ var obj = new CallDllFunction();
+
+ obj.Flags = data.ReadByteValue();
+ obj.DllPath = data.ReadNullTerminatedAnsiString();
+ obj.FunctionName = data.ReadNullTerminatedAnsiString();
+ if (!shortDllCall)
+ {
+ obj.Operand_4 = data.ReadNullTerminatedAnsiString();
+ obj.ReturnVariable = data.ReadNullTerminatedAnsiString();
+ }
+
+ obj.Entries = new FunctionData[languageCount];
+ for (int i = 0; i < obj.Entries.Length; i++)
+ {
+ // Switch based on the function
+ string entryString = data.ReadNullTerminatedAnsiString() ?? string.Empty;
+ obj.Entries[i] = obj.FunctionName switch
+ {
+ "f0" => ParseAddDirectoryToPath(entryString),
+ "f1" => ParseAddToAutoexecBat(entryString),
+ "f2" => ParseAddToConfigSys(entryString),
+ "f3" => ParseAddToSystemIni(entryString),
+ "f8" => ParseReadIniValue(entryString),
+ "f9" => ParseGetRegistryKeyValue(entryString),
+ "f10" => ParseRegisterFont(entryString),
+ "f11" => ParseWin32SystemDirectory(entryString),
+ "f12" => ParseCheckConfiguration(entryString),
+ "f13" => ParseSearchForFile(entryString),
+ "f15" => ParseReadWriteBinaryFile(entryString),
+ "f16" => ParseSetVariable(entryString),
+ "f17" => ParseGetEnvironmentVariable(entryString),
+ "f19" => ParseCheckIfFileDirExists(entryString),
+ "f20" => ParseSetFileAttributes(entryString),
+ "f21" => ParseSetFilesBuffers(entryString),
+ "f22" => ParseFindFileInPath(entryString),
+ "f23" => ParseCheckDiskSpace(entryString),
+ "f25" => ParseInsertLineIntoTextFile(entryString),
+ "f27" => ParseParseString(entryString),
+ "f28" => ParseExitInstallation(entryString),
+ "f29" => ParseSelfRegisterOCXsDLLs(entryString),
+ "f30" => ParseInstallDirectXComponents(entryString),
+ "f31" => ParseWizardBlockLoop(entryString),
+ "f33" => ParseReadUpdateTextFile(entryString),
+ "f34" => ParsePostToHttpServer(entryString),
+ "f35" => ParsePromptForFilename(entryString),
+ "f36" => ParseStartStopService(entryString),
+ "f38" => ParseCheckHttpConnection(entryString),
+
+ // External and unrecognized functions
+ _ => ParseExternalDllCall(entryString),
+ };
+
+ // Log if a truely unknown function is found
+ if (obj.Entries[i] is ExternalDllCall edc && string.IsNullOrEmpty(obj.DllPath))
+ Console.WriteLine($"Unrecognized function: {obj.FunctionName} with parts: {string.Join(", ", edc.Args ?? [])}");
+
+ }
+
+ return obj;
+ }
+
+ ///
+ /// Parse a Stream into a EditRegistry
+ ///
+ /// Stream to parse
+ /// Indicates if the longer value set is used
+ /// Filled EditRegistry on success, null on error
+ private static EditRegistry ParseEditRegistry(Stream data, ref bool? registryDll)
+ {
+ var obj = new EditRegistry();
+
+ obj.FlagsAndRoot = data.ReadByteValue();
+
+ // If the fsllib32.dll flag is set
+ if (registryDll == false)
+ {
+ obj.DataType = data.ReadByteValue();
+ obj.Key = data.ReadNullTerminatedAnsiString();
+ obj.NewValue = data.ReadNullTerminatedAnsiString();
+ obj.ValueName = data.ReadNullTerminatedAnsiString();
+ return obj;
+ }
+ else if (registryDll == true)
+ {
+ obj.DataType = data.ReadByteValue();
+ obj.UnknownFsllib = data.ReadNullTerminatedAnsiString();
+ obj.Key = data.ReadNullTerminatedAnsiString();
+ obj.NewValue = data.ReadNullTerminatedAnsiString();
+ obj.ValueName = data.ReadNullTerminatedAnsiString();
+ return obj;
+ }
+
+ // Check for an empty registry call
+ uint possiblyEmpty = data.ReadUInt32LittleEndian();
+ data.Seek(-4, SeekOrigin.Current);
+ if (possiblyEmpty == 0x00000000)
+ {
+ obj.DataType = data.ReadByteValue();
+ obj.Key = data.ReadNullTerminatedAnsiString();
+ obj.NewValue = data.ReadNullTerminatedAnsiString();
+ obj.ValueName = data.ReadNullTerminatedAnsiString();
+
+ registryDll = false;
+ return obj;
+ }
+
+ // Assume use until otherwise determined
+ registryDll = true;
+
+ obj.DataType = data.ReadByteValue();
+ obj.UnknownFsllib = data.ReadNullTerminatedAnsiString();
+ obj.Key = data.ReadNullTerminatedAnsiString();
+ obj.NewValue = data.ReadNullTerminatedAnsiString();
+ obj.ValueName = data.ReadNullTerminatedAnsiString();
+
+ // If the delete pattern is found
+ if (obj.UnknownFsllib != null && obj.UnknownFsllib.Length > 0
+ && obj.Key != null && obj.Key.Length == 0
+ && obj.NewValue != null && obj.NewValue.Length == 0)
+ {
+ data.Seek(-(obj.ValueName?.Length ?? 0) - 1, SeekOrigin.Current);
+ obj.ValueName = obj.NewValue;
+ obj.NewValue = obj.Key;
+ obj.Key = obj.UnknownFsllib;
+ obj.UnknownFsllib = null;
+ registryDll = false;
+ }
+
+ // If the last value is a control
+ else if (obj.ValueName != null && IsTypicalControlCode(obj.ValueName, strict: true))
+ {
+ data.Seek(-obj.ValueName.Length - 1, SeekOrigin.Current);
+ obj.ValueName = obj.NewValue;
+ obj.NewValue = obj.Key;
+ obj.Key = obj.UnknownFsllib;
+ obj.UnknownFsllib = null;
+ registryDll = false;
+ }
+
+ return obj;
+ }
+
+ ///
+ /// Parse a Stream into a DeleteFile
+ ///
+ /// Stream to parse
+ /// Filled DeleteFile on success, null on error
+ private static DeleteFile ParseDeleteFile(Stream data)
+ {
+ var obj = new DeleteFile();
+
+ obj.Flags = data.ReadByteValue();
+ obj.Pathname = data.ReadNullTerminatedAnsiString();
+
+ return obj;
+ }
+
+ ///
+ /// Parse a Stream into a IfWhileStatement
+ ///
+ /// Stream to parse
+ /// Filled IfWhileStatement on success, null on error
+ private static IfWhileStatement ParseIfWhileStatement(Stream data)
+ {
+ var obj = new IfWhileStatement();
+
+ obj.Flags = data.ReadByteValue();
+ obj.Variable = data.ReadNullTerminatedAnsiString();
+ obj.Value = data.ReadNullTerminatedAnsiString();
+
+ return obj;
+ }
+
+ ///
+ /// Parse a Stream into an ElseStatement
+ ///
+ /// Stream to parse
+ /// Filled ElseStatement on success, null on error
+ private static ElseStatement ParseElseStatement(Stream data)
+ {
+ return new ElseStatement();
+ }
+
+ ///
+ /// Parse a Stream into an ElseStatement
+ ///
+ /// Stream to parse
+ /// Filled StartUserDefinedAction on success, null on error
+ private static StartUserDefinedAction ParseStartUserDefinedAction(Stream data)
+ {
+ return new StartUserDefinedAction();
+ }
+
+ ///
+ /// Parse a Stream into an EndUserDefinedAction
+ ///
+ /// Stream to parse
+ /// Filled EndUserDefinedAction on success, null on error
+ private static EndUserDefinedAction ParseEndUserDefinedAction(Stream data)
+ {
+ return new EndUserDefinedAction();
+ }
+
+ ///
+ /// Parse a Stream into a CreateDirectory
+ ///
+ /// Stream to parse
+ /// Filled CreateDirectory on success, null on error
+ private static CreateDirectory ParseCreateDirectory(Stream data)
+ {
+ var obj = new CreateDirectory();
+
+ obj.Pathname = data.ReadNullTerminatedAnsiString();
+
+ return obj;
+ }
+
+ ///
+ /// Parse a Stream into a CopyLocalFile
+ ///
+ /// Stream to parse
+ /// Language counter from the header
+ /// Filled CopyLocalFile on success, null on error
+ private static CopyLocalFile ParseCopyLocalFile(Stream data, int languageCount)
+ {
+ var obj = new CopyLocalFile();
+
+ obj.Flags = data.ReadUInt16LittleEndian();
+ obj.Padding = data.ReadBytes(40);
+ obj.Destination = data.ReadNullTerminatedAnsiString();
+
+ obj.Description = new string[languageCount + 1];
+ for (int i = 0; i < obj.Description.Length; i++)
+ {
+ obj.Description[i] = data.ReadNullTerminatedAnsiString() ?? string.Empty;
+ }
+
+ obj.Source = data.ReadNullTerminatedAnsiString();
+
+ return obj;
+ }
+
+ ///
+ /// Parse a Stream into a CustomDialogSet
+ ///
+ /// Stream to parse
+ /// Filled CustomDialogSet on success, null on error
+ private static CustomDialogSet ParseCustomDialogSet(Stream data)
+ {
+ var obj = new CustomDialogSet();
+
+ obj.DeflateStart = data.ReadUInt32LittleEndian();
+ obj.DeflateEnd = data.ReadUInt32LittleEndian();
+ obj.InflatedSize = data.ReadUInt32LittleEndian();
+ obj.DisplayVariable = data.ReadNullTerminatedAnsiString();
+ obj.Name = data.ReadNullTerminatedAnsiString();
+
+ return obj;
+ }
+
+ ///
+ /// Parse a Stream into a GetSystemInformation
+ ///
+ /// Stream to parse
+ /// Filled GetSystemInformation on success, null on error
+ private static GetSystemInformation ParseGetSystemInformation(Stream data)
+ {
+ var obj = new GetSystemInformation();
+
+ obj.Flags = data.ReadByteValue();
+ obj.Variable = data.ReadNullTerminatedAnsiString();
+ obj.Pathname = data.ReadNullTerminatedAnsiString();
+
+ return obj;
+ }
+
+ ///
+ /// Parse a Stream into a GetTemporaryFilename
+ ///
+ /// Stream to parse
+ /// Filled GetTemporaryFilename on success, null on error
+ private static GetTemporaryFilename ParseGetTemporaryFilename(Stream data)
+ {
+ var obj = new GetTemporaryFilename();
+
+ obj.Variable = data.ReadNullTerminatedAnsiString();
+
+ return obj;
+ }
+
+ ///
+ /// Parse a Stream into a PlayMultimediaFile
+ ///
+ /// Stream to parse
+ /// Filled PlayMultimediaFile on success, null on error
+ private static PlayMultimediaFile ParsePlayMultimediaFile(Stream data)
+ {
+ var obj = new PlayMultimediaFile();
+
+ obj.Flags = data.ReadByteValue();
+ obj.XPosition = data.ReadUInt16LittleEndian();
+ obj.YPosition = data.ReadUInt16LittleEndian();
+ obj.Pathname = data.ReadNullTerminatedAnsiString();
+
+ return obj;
+ }
+
+ ///
+ /// Parse a Stream into a NewEvent
+ ///
+ /// Stream to parse
+ /// Language counter from the header
+ /// Indicates a short DLL call
+ /// Current 0x18 skip value
+ /// Filled NewEvent on success, null on error
+ internal static NewEvent ParseNewEvent(Stream data, int languageCount, bool shortDllCall, ref int op0x18skip)
+ {
+ // If the end of the stream has been reached
+ if (data.Position >= data.Length)
+ return new NewEvent();
+
+ // If the skip amount needs to be determined
+ if (op0x18skip == -1)
+ {
+ long current = data.Position;
+ byte nextByte = data.ReadByteValue();
+ data.Seek(current, SeekOrigin.Begin);
+
+ op0x18skip = nextByte == 0 || nextByte == 0xFF ? 6 : 0;
+ if (nextByte == 0x09)
+ {
+ var possible = ParseCallDllFunction(data, languageCount, shortDllCall);
+ op0x18skip = (possible.FunctionName == null || possible.FunctionName.Length == 0) ? 6 : 0;
+ data.Seek(current, SeekOrigin.Begin);
+ }
+ }
+
+ var obj = new NewEvent();
+
+ // Skip additional bytes
+ if (op0x18skip > 0)
+ obj.Padding = data.ReadBytes(op0x18skip);
+
+ return obj;
+ }
+
+ ///
+ /// Parse a Stream into a Unknown0x19
+ ///
+ /// Stream to parse
+ /// Filled Unknown0x19 on success, null on error
+ private static Unknown0x19 ParseUnknown0x19(Stream data)
+ {
+ var obj = new Unknown0x19();
+
+ obj.Operand_1 = data.ReadByteValue();
+ obj.Operand_2 = data.ReadNullTerminatedAnsiString();
+ obj.Operand_3 = data.ReadNullTerminatedAnsiString();
+ obj.Operand_4 = data.ReadNullTerminatedAnsiString();
+
+ return obj;
+ }
+
+ ///
+ /// Parse a Stream into a ConfigODBCDataSource
+ ///
+ /// Stream to parse
+ /// Filled ConfigODBCDataSource on success, null on error
+ private static ConfigODBCDataSource ParseConfigODBCDataSource(Stream data)
+ {
+ var obj = new ConfigODBCDataSource();
+
+ obj.Flags = data.ReadByteValue();
+ obj.FileFormat = data.ReadNullTerminatedAnsiString();
+ obj.ConnectionString = data.ReadNullTerminatedAnsiString();
+
+ return obj;
+ }
+
+ ///
+ /// Parse a Stream into an IncludeScript
+ ///
+ /// Stream to parse
+ /// Filled IncludeScript on success, null on error
+ private static IncludeScript ParseIncludeScript(Stream data)
+ {
+ var obj = new IncludeScript();
+
+ obj.Count = 0;
+ while (data.Position < data.Length && data.ReadByteValue() == 0x1B)
+ {
+ obj.Count++;
+ }
+
+ // Rewind if one was found
+ if (data.Position < data.Length)
+ data.Seek(-1, SeekOrigin.Current);
+
+ return obj;
+ }
+
+ ///
+ /// Parse a Stream into a AddTextToInstallLog
+ ///
+ /// Stream to parse
+ /// Filled AddTextToInstallLog on success, null on error
+ private static AddTextToInstallLog ParseAddTextToInstallLog(Stream data)
+ {
+ var obj = new AddTextToInstallLog();
+
+ obj.Text = data.ReadNullTerminatedAnsiString();
+
+ return obj;
+ }
+
+ ///
+ /// Parse a Stream into a RenameFileDirectory
+ ///
+ /// Stream to parse
+ /// Filled RenameFileDirectory on success, null on error
+ private static RenameFileDirectory ParseRenameFileDirectory(Stream data)
+ {
+ var obj = new RenameFileDirectory();
+
+ obj.OldPathname = data.ReadNullTerminatedAnsiString();
+ obj.NewFileName = data.ReadNullTerminatedAnsiString();
+
+ return obj;
+ }
+
+ ///
+ /// Parse a Stream into a OpenCloseInstallLog
+ ///
+ /// Stream to parse
+ /// Filled OpenCloseInstallLog on success, null on error
+ private static OpenCloseInstallLog ParseOpenCloseInstallLog(Stream data)
+ {
+ var obj = new OpenCloseInstallLog();
+
+ obj.Flags = data.ReadByteValue();
+ obj.LogName = data.ReadNullTerminatedAnsiString();
+
+ return obj;
+ }
+
+ ///
+ /// Parse a Stream into a ElseIfStatement
+ ///
+ /// Stream to parse
+ /// Filled ElseIfStatement on success, null on error
+ private static ElseIfStatement ParseElseIfStatement(Stream data)
+ {
+ var obj = new ElseIfStatement();
+
+ obj.Operator = data.ReadByteValue();
+ obj.Variable = data.ReadNullTerminatedAnsiString();
+ obj.Value = data.ReadNullTerminatedAnsiString();
+
+ return obj;
+ }
+
+ ///
+ /// Parse a Stream into a Unknown0x24
+ ///
+ /// Stream to parse
+ /// Filled Unknown0x24 on success, null on error
+ private static Unknown0x24 ParseUnknown0x24(Stream data)
+ {
+ return new Unknown0x24();
+ }
+
+ ///
+ /// Parse a Stream into a Unknown0x25
+ ///
+ /// Stream to parse
+ /// Filled Unknown0x25 on success, null on error
+ private static Unknown0x25 ParseUnknown0x25(Stream data)
+ {
+ return new Unknown0x25();
+ }
+
+ ///
+ /// Parse a Stream into a InvalidOperation
+ ///
+ /// Stream to parse
+ /// Filled InvalidOperation on success, null on error
+ ///
+ /// This represents a placeholder. The operations that result in this
+ /// type being parsed should never happen in the state machine.
+ ///
+ private static InvalidOperation ParseInvalidOperation(Stream data)
+ {
+ return new InvalidOperation();
+ }
+
+ #endregion
+
+ #region Function Actions
+
+ ///
+ /// Parse a string into a AddDirectoryToPath
+ ///
+ /// 0x7F-separated string to parse
+ /// Filled AddDirectoryToPath on success, null on error
+ private static AddDirectoryToPath ParseAddDirectoryToPath(string data)
+ {
+ string[] parts = data.Split((char)0x7F);
+
+ var obj = new AddDirectoryToPath();
+
+ if (parts.Length > 0 && byte.TryParse(parts[0], out byte flags))
+ obj.DataFlags = flags;
+
+ if (parts.Length > 1)
+ obj.Directory = parts[1];
+
+ return obj;
+ }
+
+ ///
+ /// Parse a string into a AddToAutoexecBat
+ ///
+ /// 0x7F-separated string to parse
+ /// Filled AddToAutoexecBat on success, null on error
+ private static AddToAutoexecBat ParseAddToAutoexecBat(string data)
+ {
+ string[] parts = data.Split((char)0x7F);
+
+ var obj = new AddToAutoexecBat();
+
+ if (parts.Length > 0 && byte.TryParse(parts[0], out byte flags))
+ obj.DataFlags = flags;
+
+ if (parts.Length > 1)
+ obj.FileToEdit = parts[1];
+
+ if (parts.Length > 2)
+ obj.TextToInsert = parts[2];
+
+ if (parts.Length > 3)
+ obj.SearchForText = parts[3];
+
+ if (parts.Length > 4)
+ obj.CommentText = parts[4];
+
+ if (parts.Length > 5 && int.TryParse(parts[5], out int lineNumber))
+ obj.LineNumber = lineNumber;
+
+ return obj;
+ }
+
+ ///
+ /// Parse a string into a AddToConfigSys
+ ///
+ /// 0x7F-separated string to parse
+ /// Filled AddToConfigSys on success, null on error
+ private static AddToConfigSys ParseAddToConfigSys(string data)
+ {
+ string[] parts = data.Split((char)0x7F);
+
+ var obj = new AddToConfigSys();
+
+ if (parts.Length > 0 && byte.TryParse(parts[0], out byte flags))
+ obj.DataFlags = flags;
+
+ if (parts.Length > 1)
+ obj.FileToEdit = parts[1];
+
+ if (parts.Length > 2)
+ obj.TextToInsert = parts[2];
+
+ if (parts.Length > 3)
+ obj.SearchForText = parts[3];
+
+ if (parts.Length > 4)
+ obj.CommentText = parts[4];
+
+ if (parts.Length > 5 && int.TryParse(parts[5], out int lineNumber))
+ obj.LineNumber = lineNumber;
+
+ return obj;
+ }
+
+ ///
+ /// Parse a string into a AddToSystemIni
+ ///
+ /// 0x7F-separated string to parse
+ /// Filled AddToSystemIni on success, null on error
+ private static AddToSystemIni ParseAddToSystemIni(string data)
+ {
+ string[] parts = data.Split((char)0x7F);
+
+ var obj = new AddToSystemIni();
+
+ if (parts.Length > 0)
+ obj.DeviceName = parts[0];
+
+ return obj;
+ }
+
+ ///
+ /// Parse a string into a ReadIniValue
+ ///
+ /// 0x7F-separated string to parse
+ /// Filled ReadIniValue on success, null on error
+ private static ReadIniValue ParseReadIniValue(string data)
+ {
+ string[] parts = data.Split((char)0x7F);
+
+ var obj = new ReadIniValue();
+
+ if (parts.Length > 0 && byte.TryParse(parts[0], out byte flags))
+ obj.DataFlags = flags;
+
+ if (parts.Length > 1)
+ obj.Variable = parts[1];
+
+ if (parts.Length > 2)
+ obj.Pathname = parts[2];
+
+ if (parts.Length > 3)
+ obj.Section = parts[3];
+
+ if (parts.Length > 4)
+ obj.Item = parts[4];
+
+ if (parts.Length > 5)
+ obj.DefaultValue = parts[5];
+
+ return obj;
+ }
+
+ ///
+ /// Parse a string into a GetRegistryKeyValue
+ ///
+ /// 0x7F-separated string to parse
+ /// Filled GetRegistryKeyValue on success, null on error
+ private static GetRegistryKeyValue ParseGetRegistryKeyValue(string data)
+ {
+ string[] parts = data.Split((char)0x7F);
+
+ var obj = new GetRegistryKeyValue();
+
+ if (parts.Length > 0 && byte.TryParse(parts[0], out byte flags))
+ obj.DataFlags = flags;
+
+ if (parts.Length > 1)
+ obj.Variable = parts[1];
+
+ if (parts.Length > 2)
+ obj.Key = parts[2];
+
+ if (parts.Length > 3)
+ obj.Default = parts[3];
+
+ if (parts.Length > 4)
+ obj.ValueName = parts[4];
+
+ if (parts.Length > 5)
+ obj.Root = parts[5];
+
+ return obj;
+ }
+
+ ///
+ /// Parse a string into a RegisterFont
+ ///
+ /// 0x7F-separated string to parse
+ /// Filled RegisterFont on success, null on error
+ private static RegisterFont ParseRegisterFont(string data)
+ {
+ string[] parts = data.Split((char)0x7F);
+
+ var obj = new RegisterFont();
+
+ if (parts.Length > 0)
+ obj.FontFileName = parts[0];
+
+ if (parts.Length > 1)
+ obj.FontName = parts[1];
+
+ return obj;
+ }
+
+ ///
+ /// Parse a string into a Win32SystemDirectory
+ ///
+ /// 0x7F-separated string to parse
+ /// Filled Win32SystemDirectory on success, null on error
+ private static Win32SystemDirectory ParseWin32SystemDirectory(string data)
+ {
+ string[] parts = data.Split((char)0x7F);
+
+ var obj = new Win32SystemDirectory();
+
+ if (parts.Length > 0)
+ obj.VariableName = parts[0];
+
+ return obj;
+ }
+
+ ///
+ /// Parse a string into a CheckConfiguration
+ ///
+ /// 0x7F-separated string to parse
+ /// Filled CheckConfiguration on success, null on error
+ private static CheckConfiguration ParseCheckConfiguration(string data)
+ {
+ string[] parts = data.Split((char)0x7F);
+
+ var obj = new CheckConfiguration();
+
+ if (parts.Length > 0 && byte.TryParse(parts[0], out byte flags))
+ obj.DataFlags = flags;
+
+ if (parts.Length > 1)
+ obj.Message = parts[1];
+
+ if (parts.Length > 2)
+ obj.Title = parts[2];
+
+ return obj;
+ }
+
+ ///
+ /// Parse a string into a SearchForFile
+ ///
+ /// 0x7F-separated string to parse
+ /// Filled SearchForFile on success, null on error
+ private static SearchForFile ParseSearchForFile(string data)
+ {
+ string[] parts = data.Split((char)0x7F);
+
+ var obj = new SearchForFile();
+
+ if (parts.Length > 0 && byte.TryParse(parts[0], out byte flags))
+ obj.DataFlags = flags;
+
+ if (parts.Length > 1)
+ obj.Variable = parts[1];
+
+ if (parts.Length > 2)
+ obj.FileName = parts[2];
+
+ if (parts.Length > 3)
+ obj.FileName = parts[3];
+
+ if (parts.Length > 4)
+ obj.MessageText = parts[4];
+
+ return obj;
+ }
+
+ ///
+ /// Parse a string into a ReadWriteBinaryFile
+ ///
+ /// 0x7F-separated string to parse
+ /// Filled ReadWriteBinaryFile on success, null on error
+ private static ReadWriteBinaryFile ParseReadWriteBinaryFile(string data)
+ {
+ string[] parts = data.Split((char)0x7F);
+
+ var obj = new ReadWriteBinaryFile();
+
+ if (parts.Length > 0 && byte.TryParse(parts[0], out byte flags))
+ obj.DataFlags = flags;
+
+ if (parts.Length > 1)
+ obj.FilePathname = parts[1];
+
+ if (parts.Length > 2)
+ obj.VariableName = parts[2];
+
+ if (parts.Length > 3 && int.TryParse(parts[3], out int fileOffset))
+ obj.FileOffset = fileOffset;
+
+ if (parts.Length > 4 && int.TryParse(parts[4], out int maxLength))
+ obj.MaxLength = maxLength;
+
+ return obj;
+ }
+
+ ///
+ /// Parse a string into a SetVariable
+ ///
+ /// 0x7F-separated string to parse
+ /// Filled SetVariable on success, null on error
+ private static SetVariable ParseSetVariable(string data)
+ {
+ string[] parts = data.Split((char)0x7F);
+
+ var obj = new SetVariable();
+
+ if (parts.Length > 0 && byte.TryParse(parts[0], out byte flags))
+ obj.DataFlags = flags;
+
+ if (parts.Length > 1)
+ obj.Variable = parts[1];
+
+ if (parts.Length > 2)
+ obj.Value = parts[2];
+
+ return obj;
+ }
+
+ ///
+ /// Parse a string into a GetEnvironmentVariable
+ ///
+ /// 0x7F-separated string to parse
+ /// Filled GetEnvironmentVariable on success, null on error
+ private static GetEnvironmentVariable ParseGetEnvironmentVariable(string data)
+ {
+ string[] parts = data.Split((char)0x7F);
+
+ var obj = new GetEnvironmentVariable();
+
+ if (parts.Length > 0 && byte.TryParse(parts[0], out byte flags))
+ obj.DataFlags = flags;
+
+ if (parts.Length > 1)
+ obj.Variable = parts[1];
+
+ if (parts.Length > 2)
+ obj.Environment = parts[2];
+
+ if (parts.Length > 3)
+ obj.DefaultValue = parts[3];
+
+ return obj;
+ }
+
+ ///
+ /// Parse a string into a CheckIfFileDirExists
+ ///
+ /// 0x7F-separated string to parse
+ /// Filled CheckIfFileDirExists on success, null on error
+ private static CheckIfFileDirExists ParseCheckIfFileDirExists(string data)
+ {
+ string[] parts = data.Split((char)0x7F);
+
+ var obj = new CheckIfFileDirExists();
+
+ if (parts.Length > 0 && byte.TryParse(parts[0], out byte flags))
+ obj.DataFlags = flags;
+
+ if (parts.Length > 1)
+ obj.Pathname = parts[1];
+
+ if (parts.Length > 2)
+ obj.Message = parts[2];
+
+ if (parts.Length > 3)
+ obj.Title = parts[3];
+
+ return obj;
+ }
+
+ ///
+ /// Parse a string into a SetFileAttributes
+ ///
+ /// 0x7F-separated string to parse
+ /// Filled SetFileAttributes on success, null on error
+ private static SetFileAttributes ParseSetFileAttributes(string data)
+ {
+ string[] parts = data.Split((char)0x7F);
+
+ var obj = new SetFileAttributes();
+
+ if (parts.Length > 0 && byte.TryParse(parts[0], out byte flags))
+ obj.DataFlags = flags;
+
+ if (parts.Length > 1)
+ obj.FilePathname = parts[1];
+
+ return obj;
+ }
+
+ ///
+ /// Parse a string into a SetFilesBuffers
+ ///
+ /// 0x7F-separated string to parse
+ /// Filled SetFilesBuffers on success, null on error
+ private static SetFilesBuffers ParseSetFilesBuffers(string data)
+ {
+ string[] parts = data.Split((char)0x7F);
+
+ var obj = new SetFilesBuffers();
+
+ if (parts.Length > 0)
+ obj.MinimumFiles = parts[0];
+
+ if (parts.Length > 1)
+ obj.MinimumBuffers = parts[1];
+
+ return obj;
+ }
+
+ ///
+ /// Parse a string into a FindFileInPath
+ ///
+ /// 0x7F-separated string to parse
+ /// Filled FindFileInPath on success, null on error
+ private static FindFileInPath ParseFindFileInPath(string data)
+ {
+ string[] parts = data.Split((char)0x7F);
+
+ var obj = new FindFileInPath();
+
+ if (parts.Length > 0 && byte.TryParse(parts[0], out byte flags))
+ obj.DataFlags = flags;
+
+ if (parts.Length > 1)
+ obj.VariableName = parts[1];
+
+ if (parts.Length > 2)
+ obj.FileName = parts[2];
+
+ if (parts.Length > 3)
+ obj.DefaultValue = parts[3];
+
+ if (parts.Length > 4)
+ obj.SearchDirectories = parts[4];
+
+ if (parts.Length > 5)
+ obj.Description = parts[5];
+
+ return obj;
+ }
+
+ ///
+ /// Parse a string into a CheckDiskSpace
+ ///
+ /// 0x7F-separated string to parse
+ /// Filled CheckDiskSpace on success, null on error
+ private static CheckDiskSpace ParseCheckDiskSpace(string data)
+ {
+ string[] parts = data.Split((char)0x7F);
+
+ var obj = new CheckDiskSpace();
+
+ if (parts.Length > 0 && byte.TryParse(parts[0], out byte flags))
+ obj.DataFlags = flags;
+
+ if (parts.Length > 1)
+ obj.ReserveSpace = parts[1];
+
+ if (parts.Length > 2)
+ obj.StatusVariable = parts[2];
+
+ if (parts.Length > 3)
+ obj.ComponentVariables = parts[3];
+
+ return obj;
+ }
+
+ ///
+ /// Parse a string into a InsertLineIntoTextFile
+ ///
+ /// 0x7F-separated string to parse
+ /// Filled InsertLineIntoTextFile on success, null on error
+ private static InsertLineIntoTextFile ParseInsertLineIntoTextFile(string data)
+ {
+ string[] parts = data.Split((char)0x7F);
+
+ var obj = new InsertLineIntoTextFile();
+
+ if (parts.Length > 0 && byte.TryParse(parts[0], out byte flags))
+ obj.DataFlags = flags;
+
+ if (parts.Length > 1)
+ obj.FileToEdit = parts[1];
+
+ if (parts.Length > 2)
+ obj.TextToInsert = parts[2];
+
+ if (parts.Length > 3)
+ obj.SearchForText = parts[3];
+
+ if (parts.Length > 4)
+ obj.CommentText = parts[4];
+
+ if (parts.Length > 5 && int.TryParse(parts[5], out int lineNumber))
+ obj.LineNumber = lineNumber;
+
+ return obj;
+ }
+
+ ///
+ /// Parse a string into a ParseString
+ ///
+ /// 0x7F-separated string to parse
+ /// Filled ParseString on success, null on error
+ private static ParseString ParseParseString(string data)
+ {
+ string[] parts = data.Split((char)0x7F);
+
+ var obj = new ParseString();
+
+ if (parts.Length > 0 && byte.TryParse(parts[0], out byte flags))
+ obj.DataFlags = flags;
+
+ if (parts.Length > 1)
+ obj.Source = parts[1];
+
+ if (parts.Length > 2)
+ obj.PatternPosition = parts[2];
+
+ if (parts.Length > 3)
+ obj.DestinationVariable1 = parts[3];
+
+ if (parts.Length > 4)
+ obj.DestinationVariable2 = parts[4];
+
+ return obj;
+ }
+
+ ///
+ /// Parse a string into a ExitInstallation
+ ///
+ /// 0x7F-separated string to parse
+ /// Filled ExitInstallation on success, null on error
+ private static ExitInstallation ParseExitInstallation(string data)
+ {
+ return new ExitInstallation();
+ }
+
+ ///
+ /// Parse a string into a SelfRegisterOCXsDLLs
+ ///
+ /// 0x7F-separated string to parse
+ /// Filled SelfRegisterOCXsDLLs on success, null on error
+ private static SelfRegisterOCXsDLLs ParseSelfRegisterOCXsDLLs(string data)
+ {
+ string[] parts = data.Split((char)0x7F);
+
+ var obj = new SelfRegisterOCXsDLLs();
+
+ if (parts.Length > 0 && byte.TryParse(parts[0], out byte flags))
+ obj.DataFlags = flags;
+
+ if (parts.Length > 1)
+ obj.Description = parts[1];
+
+ return obj;
+ }
+
+ ///
+ /// Parse a string into a InstallDirectXComponents
+ ///
+ /// 0x7F-separated string to parse
+ /// Filled InstallDirectXComponents on success, null on error
+ private static InstallDirectXComponents ParseInstallDirectXComponents(string data)
+ {
+ string[] parts = data.Split((char)0x7F);
+
+ var obj = new InstallDirectXComponents();
+
+ if (parts.Length > 0 && byte.TryParse(parts[0], out byte flags))
+ obj.DataFlags = flags;
+
+ if (parts.Length > 1)
+ obj.RootPath = parts[1];
+
+ if (parts.Length > 2)
+ obj.LibraryPath = parts[2];
+
+ if (parts.Length > 3 && int.TryParse(parts[3], out int sizeOrOffsetOrFlag))
+ obj.SizeOrOffsetOrFlag = sizeOrOffsetOrFlag;
+
+ return obj;
+ }
+
+ ///
+ /// Parse a string into a WizardBlockLoop
+ ///
+ /// 0x7F-separated string to parse
+ /// Filled WizardBlockLoop on success, null on error
+ private static WizardBlockLoop ParseWizardBlockLoop(string data)
+ {
+ string[] parts = data.Split((char)0x7F);
+
+ var obj = new WizardBlockLoop();
+
+ if (parts.Length > 0 && byte.TryParse(parts[0], out byte flags))
+ obj.DataFlags = flags;
+
+ // TODO: This needs to be fixed when the model is updated
+
+ if (parts.Length > 1)
+ obj.DirectionVariable = parts[1];
+
+ if (parts.Length > 2)
+ obj.DisplayVariable = parts[2];
+
+ if (parts.Length > 3 && int.TryParse(parts[3], out int xPosition))
+ obj.XPosition = xPosition;
+
+ if (parts.Length > 4 && int.TryParse(parts[4], out int yPosition))
+ obj.YPosition = yPosition;
+
+ if (parts.Length > 5 && int.TryParse(parts[5], out int fillerColor))
+ obj.FillerColor = fillerColor;
+
+ if (parts.Length > 6)
+ obj.Operand_6 = parts[6];
+
+ if (parts.Length > 7)
+ obj.Operand_7 = parts[7];
+
+ if (parts.Length > 8)
+ obj.Operand_8 = parts[8];
+
+ if (parts.Length > 9)
+ obj.DialogVariableValueCompare = parts[9];
+
+ return obj;
+ }
+
+ ///
+ /// Parse a string into a ReadUpdateTextFile
+ ///
+ /// 0x7F-separated string to parse
+ /// Filled ReadUpdateTextFile on success, null on error
+ private static ReadUpdateTextFile ParseReadUpdateTextFile(string data)
+ {
+ string[] parts = data.Split((char)0x7F);
+
+ var obj = new ReadUpdateTextFile();
+
+ if (parts.Length > 0 && byte.TryParse(parts[0], out byte flags))
+ obj.DataFlags = flags;
+
+ if (parts.Length > 1)
+ obj.Variable = parts[1];
+
+ if (parts.Length > 2)
+ obj.Pathname = parts[2];
+
+ if (parts.Length > 3)
+ obj.LanguageStrings = parts[3];
+
+ return obj;
+ }
+
+ ///
+ /// Parse a string into a PostToHttpServer
+ ///
+ /// 0x7F-separated string to parse
+ /// Filled PostToHttpServer on success, null on error
+ private static PostToHttpServer ParsePostToHttpServer(string data)
+ {
+ string[] parts = data.Split((char)0x7F);
+
+ var obj = new PostToHttpServer();
+
+ if (parts.Length > 0 && byte.TryParse(parts[0], out byte flags))
+ obj.DataFlags = flags;
+
+ if (parts.Length > 1)
+ obj.URL = parts[1];
+
+ if (parts.Length > 2)
+ obj.PostData = parts[2];
+
+ return obj;
+ }
+
+ ///
+ /// Parse a string into a PromptForFilename
+ ///
+ /// 0x7F-separated string to parse
+ /// Filled PromptForFilename on success, null on error
+ private static PromptForFilename ParsePromptForFilename(string data)
+ {
+ string[] parts = data.Split((char)0x7F);
+
+ var obj = new PromptForFilename();
+
+ if (parts.Length > 0 && byte.TryParse(parts[0], out byte flags))
+ obj.DataFlags = flags;
+
+ if (parts.Length > 1)
+ obj.DestinationVariable = parts[1];
+
+ if (parts.Length > 2)
+ obj.DefaultExtension = parts[2];
+
+ if (parts.Length > 3)
+ obj.DialogTitle = parts[3];
+
+ if (parts.Length > 4)
+ obj.FilterList = parts[4];
+
+ return obj;
+ }
+
+ ///
+ /// Parse a string into a StartStopService
+ ///
+ /// 0x7F-separated string to parse
+ /// Filled StartStopService on success, null on error
+ private static StartStopService ParseStartStopService(string data)
+ {
+ string[] parts = data.Split((char)0x7F);
+
+ var obj = new StartStopService();
+
+ if (parts.Length > 0 && byte.TryParse(parts[0], out byte operation))
+ obj.Operation = operation;
+
+ if (parts.Length > 1)
+ obj.ServiceName = parts[1];
+
+ return obj;
+ }
+
+ ///
+ /// Parse a string into a CheckHttpConnection
+ ///
+ /// 0x7F-separated string to parse
+ /// Filled CheckHttpConnection on success, null on error
+ private static CheckHttpConnection ParseCheckHttpConnection(string data)
+ {
+ string[] parts = data.Split((char)0x7F);
+
+ var obj = new CheckHttpConnection();
+
+ if (parts.Length > 0)
+ obj.UrlToCheck = parts[0];
+
+ if (parts.Length > 1)
+ obj.Win32ErrorTextVariable = parts[1];
+
+ if (parts.Length > 2)
+ obj.Win32ErrorNumberVariable = parts[2];
+
+ if (parts.Length > 3)
+ obj.Win16ErrorTextVariable = parts[3];
+
+ if (parts.Length > 4)
+ obj.Win16ErrorNumberVariable = parts[4];
+
+ return obj;
+ }
+
+ ///
+ /// Parse a string into a ExternalDllCall
+ ///
+ /// 0x7F-separated string to parse
+ /// Filled ExternalDllCall on success, null on error
+ private static ExternalDllCall ParseExternalDllCall(string data)
+ {
+ var obj = new ExternalDllCall();
+
+ obj.Args = data.Split((char)0x7F);
+
+ return obj;
+ }
+
+ #endregion
+
+ #region Helpers
+
+ ///
+ /// Returns if a string may be a typical control code
+ ///
+ /// String to check
+ /// Indicates if control codes should always be checked
+ /// True if the string probably represents a control code, false otherwise
+ private static bool IsTypicalControlCode(string str, bool strict)
+ {
+ // Safeguard against odd cases
+ if (str.Length == 0)
+ return strict;
+
+ char firstChar = str[0];
+
+ // If there is no worry about newline trickery
+ if (strict)
+ return firstChar >= (char)0x00 && firstChar <= (char)0x25;
+
+ if (firstChar < (char)0x0A)
+ return true;
+ else if (firstChar == (char)0x0A && str.Length == 1)
+ return true;
+ else if (firstChar > (char)0x0A && firstChar < (char)0x0D)
+ return true;
+ else if (firstChar == (char)0x0D && str.Length == 1)
+ return true;
+ else if (firstChar > (char)0x0D && firstChar < (char)0x20)
+ return true;
+ else if (firstChar > (char)0x20 && firstChar <= (char)0x25 && str.Length == 1)
+ return true;
+
+ return false;
+ }
+
+ ///
+ /// Parse a Stream into a DeflateEntry
+ ///
+ /// Stream to parse
+ /// Filled DeflateEntry on success, null on error
+ private static DeflateEntry ParseDeflateEntry(Stream data)
+ {
+ var obj = new DeflateEntry();
+
+ obj.DeflateStart = data.ReadUInt32LittleEndian();
+ obj.DeflateEnd = data.ReadUInt32LittleEndian();
+ obj.InflatedSize = data.ReadUInt32LittleEndian();
+
+ return obj;
+ }
+
+ #endregion
+ }
+}
diff --git a/SabreTools.Serialization/Deserializers/WiseSectionHeader.cs b/SabreTools.Serialization/Deserializers/WiseSectionHeader.cs
new file mode 100644
index 00000000..bf94755b
--- /dev/null
+++ b/SabreTools.Serialization/Deserializers/WiseSectionHeader.cs
@@ -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
+ {
+ ///
+ 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;
+ }
+ }
+
+ ///
+ /// Parse a Stream into a WiseSectionHeader
+ ///
+ /// Stream to parse
+ /// Initial offset to use in address comparisons
+ /// Filled WiseSectionHeader on success, null on error
+ 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;
+ }
+
+ ///
+ /// Get the pre-string bytes size, if possible
+ ///
+ /// Stream to parse
+ /// Section header to get information from
+ /// Offset to the WIS string relative to the start of the header
+ /// The size of the pre-string section
+ ///
+ /// This method also sets and
+ /// , if possible.
+ ///
+ 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;
+ }
+
+ ///
+ /// Parse the string table, if possible
+ ///
+ ///
+ ///
+ ///
+ /// The filled string table on success, false otherwise
+ private static byte[][]? ParseStringTable(Stream data, byte[] preStringValues)
+ {
+ // Setup the loop variables
+ List 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];
+ }
+ }
+}
\ No newline at end of file
diff --git a/SabreTools.Serialization/Extensions.PortableExecutable.cs b/SabreTools.Serialization/Extensions.PortableExecutable.cs
index 3b81c1fd..e37abf0b 100644
--- a/SabreTools.Serialization/Extensions.PortableExecutable.cs
+++ b/SabreTools.Serialization/Extensions.PortableExecutable.cs
@@ -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
{
diff --git a/SabreTools.Serialization/Extensions.WiseScript.cs b/SabreTools.Serialization/Extensions.WiseScript.cs
new file mode 100644
index 00000000..e38fb09f
--- /dev/null
+++ b/SabreTools.Serialization/Extensions.WiseScript.cs
@@ -0,0 +1,64 @@
+using System.Text.RegularExpressions;
+
+namespace SabreTools.Serialization
+{
+ public static partial class Extensions
+ {
+ ///
+ /// Convert a Wise function ID to the formal action name
+ ///
+ /// Function ID to convert
+ /// The formal action name on success, null otherwise
+ 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}",
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/SabreTools.Serialization/Printer.cs b/SabreTools.Serialization/Printer.cs
index 0de809b8..9b366cf9 100644
--- a/SabreTools.Serialization/Printer.cs
+++ b/SabreTools.Serialization/Printer.cs
@@ -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;
}
+ ///
+ /// Export the item information as pretty-printed text
+ ///
+ private static StringBuilder PrettyPrint(this Wrapper.WiseOverlayHeader item)
+ {
+ var builder = new StringBuilder();
+ WiseOverlayHeader.Print(builder, item.Model);
+ return builder;
+ }
+
+ ///
+ /// Export the item information as pretty-printed text
+ ///
+ private static StringBuilder PrettyPrint(this Wrapper.WiseScript item)
+ {
+ var builder = new StringBuilder();
+ WiseScript.Print(builder, item.Model);
+ return builder;
+ }
+
///
/// Export the item information as pretty-printed text
///
diff --git a/SabreTools.Serialization/Printers/GZip.cs b/SabreTools.Serialization/Printers/GZip.cs
index 6c5cf076..7d297ad5 100644
--- a/SabreTools.Serialization/Printers/GZip.cs
+++ b/SabreTools.Serialization/Printers/GZip.cs
@@ -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();
}
}
diff --git a/SabreTools.Serialization/Printers/MoPaQ.cs b/SabreTools.Serialization/Printers/MoPaQ.cs
index 30003890..d6efb61e 100644
--- a/SabreTools.Serialization/Printers/MoPaQ.cs
+++ b/SabreTools.Serialization/Printers/MoPaQ.cs
@@ -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");
diff --git a/SabreTools.Serialization/Printers/PKZIP.cs b/SabreTools.Serialization/Printers/PKZIP.cs
index 1a58b449..84636ad7 100644
--- a/SabreTools.Serialization/Printers/PKZIP.cs
+++ b/SabreTools.Serialization/Printers/PKZIP.cs
@@ -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
diff --git a/SabreTools.Serialization/Printers/PortableExecutable.cs b/SabreTools.Serialization/Printers/PortableExecutable.cs
index 173dc387..a7f7916b 100644
--- a/SabreTools.Serialization/Printers/PortableExecutable.cs
+++ b/SabreTools.Serialization/Printers/PortableExecutable.cs
@@ -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:");
diff --git a/SabreTools.Serialization/Printers/TapeArchive.cs b/SabreTools.Serialization/Printers/TapeArchive.cs
index 32ef3839..b111a604 100644
--- a/SabreTools.Serialization/Printers/TapeArchive.cs
+++ b/SabreTools.Serialization/Printers/TapeArchive.cs
@@ -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? 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? 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)
diff --git a/SabreTools.Serialization/Printers/WiseOverlayHeader.cs b/SabreTools.Serialization/Printers/WiseOverlayHeader.cs
new file mode 100644
index 00000000..d2d00168
--- /dev/null
+++ b/SabreTools.Serialization/Printers/WiseOverlayHeader.cs
@@ -0,0 +1,58 @@
+using System.Text;
+using SabreTools.Models.WiseInstaller;
+using SabreTools.Serialization.Interfaces;
+
+namespace SabreTools.Serialization.Printers
+{
+ public class WiseOverlayHeader : IPrinter
+ {
+ ///
+ 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();
+ }
+ }
+}
\ No newline at end of file
diff --git a/SabreTools.Serialization/Printers/WiseScript.cs b/SabreTools.Serialization/Printers/WiseScript.cs
new file mode 100644
index 00000000..4d3764f0
--- /dev/null
+++ b/SabreTools.Serialization/Printers/WiseScript.cs
@@ -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
+ {
+ ///
+ 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
+ }
+}
\ No newline at end of file
diff --git a/SabreTools.Serialization/SabreTools.Serialization.csproj b/SabreTools.Serialization/SabreTools.Serialization.csproj
index c526a3fa..2944ac25 100644
--- a/SabreTools.Serialization/SabreTools.Serialization.csproj
+++ b/SabreTools.Serialization/SabreTools.Serialization.csproj
@@ -65,8 +65,8 @@
-
-
+
+
diff --git a/SabreTools.Serialization/Wrappers/GZip.cs b/SabreTools.Serialization/Wrappers/GZip.cs
index d7eb8874..1294a2e5 100644
--- a/SabreTools.Serialization/Wrappers/GZip.cs
+++ b/SabreTools.Serialization/Wrappers/GZip.cs
@@ -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;
}
diff --git a/SabreTools.Serialization/Wrappers/PKZIP.cs b/SabreTools.Serialization/Wrappers/PKZIP.cs
index 16dd96ea..6b0daadd 100644
--- a/SabreTools.Serialization/Wrappers/PKZIP.cs
+++ b/SabreTools.Serialization/Wrappers/PKZIP.cs
@@ -35,9 +35,8 @@ namespace SabreTools.Serialization.Wrappers
///
public EndOfCentralDirectoryRecord64? ZIP64EndOfCentralDirectoryRecord => Model.ZIP64EndOfCentralDirectoryRecord;
- ///
- /// TODO: Use LocalFiles when Models is updated
- public LocalFileHeader[]? LocalFiles => Model.LocalFileHeaders;
+ ///
+ public LocalFile[]? LocalFiles => Model.LocalFiles;
#endregion
diff --git a/SabreTools.Serialization/Wrappers/PortableExecutable.cs b/SabreTools.Serialization/Wrappers/PortableExecutable.cs
index 8e647967..60ea9814 100644
--- a/SabreTools.Serialization/Wrappers/PortableExecutable.cs
+++ b/SabreTools.Serialization/Wrappers/PortableExecutable.cs
@@ -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
///
/// Cached version info data
///
- private Models.PortableExecutable.VersionInfo? _versionInfo = null;
+ private VersionInfo? _versionInfo = null;
///
/// Cached assembly manifest data
///
- private Models.PortableExecutable.AssemblyManifest? _assemblyManifest = null;
+ private AssemblyManifest? _assemblyManifest = null;
///
/// 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
///
/// Assembly manifest object, null on error
- 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
///
/// Dialog box title to check for
/// List of matching resources
- public List FindDialogByTitle(string title)
+ public List FindDialogByTitle(string title)
{
// Ensure that we have the resource data cached
if (ResourceData == null)
return [];
- var resources = new List();
+ var resources = new List();
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
///
/// Dialog box item title to check for
/// List of matching resources
- public List FindDialogBoxByItemTitle(string title)
+ public List FindDialogBoxByItemTitle(string title)
{
// Ensure that we have the resource data cached
if (ResourceData == null)
return [];
- var resources = new List();
+ var resources = new List();
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)
diff --git a/SabreTools.Serialization/Wrappers/TapeArchive.cs b/SabreTools.Serialization/Wrappers/TapeArchive.cs
index 73babb46..559dc398 100644
--- a/SabreTools.Serialization/Wrappers/TapeArchive.cs
+++ b/SabreTools.Serialization/Wrappers/TapeArchive.cs
@@ -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;
}
diff --git a/SabreTools.Serialization/Wrappers/ViewStream.cs b/SabreTools.Serialization/Wrappers/ViewStream.cs
deleted file mode 100644
index 2c38aff8..00000000
--- a/SabreTools.Serialization/Wrappers/ViewStream.cs
+++ /dev/null
@@ -1,255 +0,0 @@
-using System;
-using System.IO;
-
-// TODO: Remove when IO is updated
-namespace SabreTools.IO.Streams
-{
- ///
- /// Stream representing a view into a source
- ///
- public class ViewStream : Stream
- {
- #region Properties
-
- ///
- public override bool CanRead => true;
-
- ///
- public override bool CanSeek => _source.CanSeek;
-
- ///
- public override bool CanWrite => false;
-
- ///
- /// Filename from the source, if possible
- ///
- 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;
- }
- }
-
- ///
- public override long Length => _length;
-
- ///
- 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
-
- ///
- /// Initial position within the underlying data
- ///
- protected long _initialPosition;
-
- ///
- /// Usable length in the underlying data
- ///
- protected long _length;
-
- ///
- /// Source data
- ///
- protected Stream _source;
-
- ///
- /// Lock object for reading from the source
- ///
- private readonly object _sourceLock = new();
-
- #endregion
-
- #region Constructors
-
- ///
- /// Construct a new ViewStream from a Stream
- ///
- 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);
- }
-
- ///
- /// Construct a new ViewStream from a Stream
- ///
- 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);
- }
-
- ///
- /// Construct a new ViewStream from a byte array
- ///
- 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);
- }
-
- ///
- /// Construct a new ViewStream from a byte array
- ///
- 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
-
- ///
- /// Check if a data segment is valid in the data source
- ///
- /// Position in the source
- /// Length of the data to check
- /// True if the positional data is valid, false otherwise
- 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
-
- ///
- public override void Flush()
- => throw new NotImplementedException();
-
- ///
- 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;
- }
- }
-
- ///
- 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;
- }
-
- ///
- public override void SetLength(long value)
- => throw new NotImplementedException();
-
- ///
- public override void Write(byte[] buffer, int offset, int count)
- => throw new NotImplementedException();
-
- #endregion
- }
-}
\ No newline at end of file
diff --git a/SabreTools.Serialization/Wrappers/WiseOverlayHeader.cs b/SabreTools.Serialization/Wrappers/WiseOverlayHeader.cs
new file mode 100644
index 00000000..f035ef64
--- /dev/null
+++ b/SabreTools.Serialization/Wrappers/WiseOverlayHeader.cs
@@ -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
+ {
+ #region Descriptive Properties
+
+ ///
+ public override string DescriptionString => "Wise Installer Overlay Header";
+
+ #endregion
+
+ #region Extension Properties
+
+ ///
+ public uint Ctl3d32DeflatedSize => Model.Ctl3d32DeflatedSize;
+
+ ///
+ public uint DibDeflatedSize => Model.DibDeflatedSize;
+
+ ///
+ public uint DibInflatedSize => Model.DibInflatedSize;
+
+ ///
+ public uint FinalFileDeflatedSize => Model.FinalFileDeflatedSize;
+
+ ///
+ public uint FinalFileInflatedSize => Model.FinalFileInflatedSize;
+
+ ///
+ public OverlayHeaderFlags Flags => Model.Flags;
+
+ ///
+ public uint InstallScriptDeflatedSize => Model.InstallScriptDeflatedSize ?? 0;
+
+ ///
+ /// Indicates if data is packed in PKZIP containers
+ ///
+ 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
+ }
+ }
+
+ ///
+ public uint ProgressDllDeflatedSize => Model.ProgressDllDeflatedSize;
+
+ ///
+ public uint SomeData4DeflatedSize => Model.SomeData4DeflatedSize;
+
+ ///
+ public uint SomeData7DeflatedSize => Model.SomeData7DeflatedSize;
+
+ ///
+ public uint SomeData8DeflatedSize => Model.SomeData8DeflatedSize;
+
+ ///
+ public uint SomeData9DeflatedSize => Model.SomeData9DeflatedSize;
+
+ ///
+ public uint SomeData10DeflatedSize => Model.SomeData10DeflatedSize;
+
+ ///
+ public uint RegToolDeflatedSize => Model.RegToolDeflatedSize;
+
+ ///
+ public uint WiseDllDeflatedSize => Model.WiseDllDeflatedSize;
+
+ ///
+ public uint WiseScriptDeflatedSize => Model.WiseScriptDeflatedSize;
+
+ ///
+ public uint WiseScriptInflatedSize => Model.WiseScriptInflatedSize;
+
+ #endregion
+
+ #region Constructors
+
+ ///
+ public WiseOverlayHeader(OverlayHeader? model, byte[]? data, int offset)
+ : base(model, data, offset)
+ {
+ // All logic is handled by the base class
+ }
+
+ ///
+ public WiseOverlayHeader(OverlayHeader? model, Stream? data)
+ : base(model, data)
+ {
+ // All logic is handled by the base class
+ }
+
+ ///
+ /// Create a Wise installer overlay header from a byte array and offset
+ ///
+ /// Byte array representing the header
+ /// Offset within the array to parse
+ /// A Wise installer overlay header wrapper on success, null on failure
+ 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);
+ }
+
+ ///
+ /// Create a Wise installer overlay header from a Stream
+ ///
+ /// Stream representing the header
+ /// A Wise installer overlay header wrapper on success, null on failure
+ 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
+
+ ///
+ /// Extract all files from a Wise installer to an output directory
+ ///
+ /// Input filename to read from
+ /// Output directory to write to
+ /// True to include debug data, false otherwise
+ /// True if all files extracted, false otherwise
+ 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);
+ }
+
+ ///
+ /// Extract all files from a Wise installer to an output directory
+ ///
+ /// Stream representing the Wise installer
+ /// Output directory to write to
+ /// True to include debug data, false otherwise
+ /// True if all files extracted, false otherwise
+ public static bool ExtractAll(Stream? data, string outputDirectory, bool includeDebug)
+ => ExtractAll(data, sourceDirectory: null, outputDirectory, includeDebug);
+
+ ///
+ /// Extract all files from a Wise installer to an output directory
+ ///
+ /// Stream representing the Wise installer
+ /// Directory where installer files live, if possible
+ /// Output directory to write to
+ /// True to include debug data, false otherwise
+ /// True if all files extracted, false otherwise
+ 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);
+ }
+
+ ///
+ /// Find the overlay header from the Wise installer, if possible
+ ///
+ /// Stream representing the Wise installer
+ /// True to include debug data, false otherwise
+ /// The found overlay header on success, null otherwise
+ /// True if the header was found and valid, false otherwise
+ 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;
+ }
+ }
+
+ ///
+ /// Find the overlay header from a NE Wise installer, if possible
+ ///
+ /// Stream representing the Wise installer
+ /// Wrapper representing the NE
+ /// True to include debug data, false otherwise
+ /// The found overlay header on success, null otherwise
+ /// True if the header was found and valid, false otherwise
+ 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;
+ }
+
+ ///
+ /// Find the overlay header from a PE Wise installer, if possible
+ ///
+ /// Stream representing the Wise installer
+ /// Wrapper representing the PE
+ /// True to include debug data, false otherwise
+ /// The found overlay header on success, null otherwise
+ /// True if the header was found and valid, false otherwise
+ 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);
+ }
+
+ ///
+ /// Open a potential WISE installer file and any additional files
+ ///
+ /// Input filename or base name to read from
+ /// True to include debug data, false otherwise
+ /// True if the file could be opened, false otherwise
+ 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;
+ }
+
+ ///
+ /// Extract the predefined, static files defined in the header
+ ///
+ /// Stream representing the Wise installer
+ /// Output directory to write to
+ /// True to include debug data, false otherwise
+ /// True if the files extracted successfully, false otherwise
+ 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
+ }
+}
diff --git a/SabreTools.Serialization/Wrappers/WiseScript.cs b/SabreTools.Serialization/Wrappers/WiseScript.cs
new file mode 100644
index 00000000..3f2e06a4
--- /dev/null
+++ b/SabreTools.Serialization/Wrappers/WiseScript.cs
@@ -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
+ {
+ #region Descriptive Properties
+
+ ///
+ public override string DescriptionString => "Wise Installer Script File";
+
+ #endregion
+
+ #region Extension Properties
+
+ ///
+ public uint DateTime => Model.Header?.DateTime ?? 0;
+
+ ///
+ public ushort Flags => Model.Header?.Flags ?? 0;
+
+ ///
+ public uint FontSize => Model.Header?.FontSize ?? 0;
+
+ ///
+ public string? FTPURL => Model.Header?.FTPURL;
+
+ ///
+ public string[]? HeaderStrings => Model.Header?.HeaderStrings;
+
+ ///
+ public byte LanguageCount => Model.Header?.LanguageCount ?? 0;
+
+ ///
+ public string? LogPathname => Model.Header?.LogPathname;
+
+ ///
+ public string? MessageFont => Model.Header?.MessageFont;
+
+ ///
+ public MachineState[]? States => Model.States;
+
+ ///
+ public ushort UnknownU16_1 => Model.Header?.UnknownU16_1 ?? 0;
+
+ ///
+ public ushort UnknownU16_2 => Model.Header?.UnknownU16_2 ?? 0;
+
+ ///
+ public byte[]? VariableLengthData => Model.Header?.VariableLengthData;
+
+ #endregion
+
+ #region Constructors
+
+ ///
+ public WiseScript(ScriptFile? model, byte[]? data, int offset)
+ : base(model, data, offset)
+ {
+ // All logic is handled by the base class
+ }
+
+ ///
+ public WiseScript(ScriptFile? model, Stream? data)
+ : base(model, data)
+ {
+ // All logic is handled by the base class
+ }
+
+ ///
+ /// Create a Wise installer script file from a byte array and offset
+ ///
+ /// Byte array representing the script
+ /// Offset within the array to parse
+ /// A Wise installer script file wrapper on success, null on failure
+ 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);
+ }
+
+ ///
+ /// Create a Wise installer script file from a Stream
+ ///
+ /// Stream representing the script
+ /// A Wise installer script file wrapper on success, null on failure
+ 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
+
+ ///
+ /// Process the state machine and perform all required actions
+ ///
+ /// Stream representing the Wise installer
+ /// Directory where installer files live, if possible
+ /// Parsed script to retrieve information from
+ /// Start of the deflated data
+ /// Output directory to write to
+ /// Indicates if PKZIP containers are used
+ /// True to include debug data, false otherwise
+ /// True if there were no errors during processing, false otherwise
+ 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 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
+
+ ///
+ /// Attempt to extract a file defined by a file header
+ ///
+ /// Stream representing the Wise installer
+ /// Start of the deflated data
+ /// Deflate information
+ /// File index for automatic naming
+ /// Output directory to write to
+ /// Indicates if PKZIP containers are used
+ /// True to include debug data, false otherwise
+ /// True if the file extracted successfully, false otherwise
+ 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);
+ }
+
+ ///
+ /// Attempt to extract a file defined by a file header
+ ///
+ /// Stream representing the Wise installer
+ /// Start of the deflated data
+ /// Deflate information
+ /// File index for automatic naming
+ /// Output directory to write to
+ /// Indicates if PKZIP containers are used
+ /// True to include debug data, false otherwise
+ /// True if the file extracted successfully, false otherwise
+ 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;
+ }
+
+ ///
+ /// Attempt to extract a file defined by a file header
+ ///
+ /// Stream representing the Wise installer
+ /// Start of the deflated data
+ /// Deflate information
+ /// Output directory to write to
+ /// Indicates if PKZIP containers are used
+ /// True to include debug data, false otherwise
+ /// True if the file extracted successfully, false otherwise
+ 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);
+ }
+
+ ///
+ /// Attempt to write INI data to the correct file
+ ///
+ /// INI information
+ /// Output directory to write to
+ /// File index for automatic naming
+ 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
+ }
+}
diff --git a/SabreTools.Serialization/Wrappers/WiseSectionHeader.cs b/SabreTools.Serialization/Wrappers/WiseSectionHeader.cs
new file mode 100644
index 00000000..946e3d54
--- /dev/null
+++ b/SabreTools.Serialization/Wrappers/WiseSectionHeader.cs
@@ -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, IExtractable
+ {
+ #region Descriptive Properties
+
+ ///
+ public override string DescriptionString => "Self-Extracting Wise Installer Header";
+
+ #endregion
+
+ #region Extension Properties
+
+ ///
+ public uint UnknownDataSize => Model.UnknownDataSize;
+
+ /// // TODO: VERIFY ON CHANGE
+ public uint SecondExecutableFileEntryLength => Model.SecondExecutableFileEntryLength;
+
+ ///
+ public uint UnknownValue2 => Model.UnknownValue2;
+
+ ///
+ public uint UnknownValue3 => Model.UnknownValue3;
+
+ ///
+ public uint UnknownValue4 => Model.UnknownValue4;
+
+ ///
+ public uint FirstExecutableFileEntryLength => Model.FirstExecutableFileEntryLength; // TODO: VERIFY ON CHANGE
+
+ ///
+ public uint MsiFileEntryLength => Model.MsiFileEntryLength;
+
+ ///
+ public uint UnknownValue7 => Model.UnknownValue7;
+
+ ///
+ public uint UnknownValue8 => Model.UnknownValue8;
+
+ ///
+ public uint ThirdExecutableFileEntryLength => Model.ThirdExecutableFileEntryLength;
+
+ ///
+ public uint UnknownValue10 => Model.UnknownValue10;
+
+ ///
+ public uint UnknownValue11 => Model.UnknownValue11;
+
+ ///
+ public uint UnknownValue12 => Model.UnknownValue12;
+
+ ///
+ public uint UnknownValue13 => Model.UnknownValue13;
+
+ ///
+ public uint UnknownValue14 => Model.UnknownValue14;
+
+ ///
+ public uint UnknownValue15 => Model.UnknownValue15;
+
+ ///
+ public uint UnknownValue16 => Model.UnknownValue16;
+
+ ///
+ public uint UnknownValue17 => Model.UnknownValue17;
+
+ ///
+ public uint UnknownValue18 => Model.UnknownValue18;
+
+ ///
+ public byte[]? Version => Model.Version;
+
+ ///
+ public byte[]? PreStringValues => Model.PreStringValues;
+
+ ///
+ public byte[][]? Strings => Model.Strings;
+
+ #endregion
+
+ #region Constructors
+
+ ///
+ public WiseSectionHeader(SectionHeader? model, byte[]? data, int offset)
+ : base(model, data, offset)
+ {
+ // All logic is handled by the base class
+ }
+
+ ///
+ public WiseSectionHeader(SectionHeader? model, Stream? data)
+ : base(model, data)
+ {
+ // All logic is handled by the base class
+ }
+
+ ///
+ /// Create a Wise Self-Extracting installer .WISE section from a byte array and offset
+ ///
+ /// Byte array representing the section
+ /// Offset within the array to parse
+ /// A Wise Self-Extracting installer .WISE section wrapper on success, null on failure
+ 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);
+ }
+
+ ///
+ /// Create a Wise Self-Extracting installer .WISE section from a Stream
+ ///
+ /// Stream representing the section
+ /// A Wise Self-Extracting installer .WISE section wrapper on success, null on failure
+ 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
+
+ ///
+ 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.
+
+ ///
+ /// Extract the predefined, static files defined in the header
+ ///
+ /// Output directory to write to
+ /// True to include debug data, false otherwise
+ /// True if the files extracted successfully, false otherwise
+ 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;
+ }
+
+ ///
+ /// Attempt to extract a file defined by a filename
+ ///
+ /// Output filename, null to auto-generate
+ /// Output directory to write to
+ /// Expected size of the file plus crc32
+ /// True to include debug data, false otherwise
+ /// Extraction status representing the final state
+ /// Assumes that the current stream position is the end of where the data lives
+ 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;
+ }
+
+ ///
+ /// Extract source data with a trailing CRC-32 checksum
+ ///
+ /// Stream where the file data will be written
+ /// Expected size of the file plus crc32
+ /// True to include debug data, false otherwise
+ ///
+ 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;
+ }
+ }
+
+ ///
+ /// Extract source data without a trailing CRC-32 checksum
+ ///
+ /// Stream where the file data will be written
+ /// Expected size of the file
+ /// True to include debug data, false otherwise
+ ///
+ 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
+ }
+}