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 + } +}