using System.Collections.Generic; using System.IO; using System.Text; using SabreTools.Data.Models.WiseInstaller; using SabreTools.IO.Extensions; using SabreTools.Matching; using SabreTools.Numerics.Extensions; using SabreTools.Text.Extensions; using static SabreTools.Data.Models.WiseInstaller.Constants; namespace SabreTools.Serialization.Readers { public class WiseSectionHeader : BaseBinaryReader { /// public override SectionHeader? Deserialize(Stream? data) { // If the data is invalid if (data is null || !data.CanRead) return null; try { // Cache the current offset long initialOffset = data.Position; var header = ParseSectionHeader(data, initialOffset); if (header is 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 obj = 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.SeekIfPossible(initialOffset + offset, 0); byte[] checkBytes = data.ReadBytes(3); if (!checkBytes.EqualsExactly(WisString)) continue; headerLength = WiseSectionHeaderLengthDictionary[offset]; int versionOffset = WiseSectionVersionOffsetDictionary[offset]; data.SeekIfPossible(initialOffset + offset - versionOffset, 0); obj.Version = data.ReadBytes(versionOffset); wisOffset = offset; break; } //Seek back to the beginning of the section data.SeekIfPossible(initialOffset, 0); // Read common values obj.UnknownDataSize = data.ReadUInt32LittleEndian(); obj.SecondExecutableFileEntryLength = data.ReadUInt32LittleEndian(); obj.UnknownValue2 = data.ReadUInt32LittleEndian(); obj.UnknownValue3 = data.ReadUInt32LittleEndian(); obj.UnknownValue4 = data.ReadUInt32LittleEndian(); obj.FirstExecutableFileEntryLength = data.ReadUInt32LittleEndian(); obj.MsiFileEntryLength = data.ReadUInt32LittleEndian(); // If the reported header information is invalid if (obj.Version is null) return obj; if (wisOffset < 0) return obj; if (headerLength < 0) return obj; if (headerLength > 6) { obj.UnknownValue7 = data.ReadUInt32LittleEndian(); obj.UnknownValue8 = data.ReadUInt32LittleEndian(); } if (headerLength > 8) { obj.ThirdExecutableFileEntryLength = data.ReadUInt32LittleEndian(); obj.UnknownValue10 = data.ReadUInt32LittleEndian(); obj.UnknownValue11 = data.ReadUInt32LittleEndian(); obj.UnknownValue12 = data.ReadUInt32LittleEndian(); obj.UnknownValue13 = data.ReadUInt32LittleEndian(); obj.UnknownValue14 = data.ReadUInt32LittleEndian(); obj.UnknownValue15 = data.ReadUInt32LittleEndian(); obj.UnknownValue16 = data.ReadUInt32LittleEndian(); obj.UnknownValue17 = data.ReadUInt32LittleEndian(); } if (headerLength > 17) { obj.UnknownValue18 = data.ReadUInt32LittleEndian(); } // If the WIS string has not been hit, read the padding bytes if (data.Position < initialOffset + wisOffset) { int paddingLength = (int)(initialOffset + wisOffset - data.Position); _ = data.ReadBytes(paddingLength); } // Read the consistent strings obj.TmpString = data.ReadNullTerminatedAnsiString() ?? string.Empty; obj.GuidString = data.ReadNullTerminatedAnsiString() ?? string.Empty; // Parse the pre-string section int preStringBytesSize = GetPreStringBytesSize(data, obj, wisOffset); if (preStringBytesSize <= 0) return obj; // Read the pre-string bytes obj.PreStringValues = data.ReadBytes(preStringBytesSize); // Try to read the string arrays // TODO: Count size of string section for later size verification byte[][]? stringArrays = ParseStringTable(data, obj.PreStringValues); if (stringArrays is null) return obj; // Set the string arrays obj.Strings = stringArrays; // Not sure what this data is. Might be a wisescript? if (obj.UnknownDataSize != 0) data.SeekIfPossible(obj.UnknownDataSize, SeekOrigin.Current); return obj; } /// /// 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 is null) return 0; // TODO: better way to figure out how far it's needed to advance? int versionSize; #if NETCOREAPP || NETSTANDARD2_1_OR_GREATER if (header.Version[^1] == 0x02) versionSize = header.Version[^3]; else versionSize = header.Version[^2]; #else if (header.Version[header.Version.Length - 1] == 0x02) versionSize = header.Version[header.Version.Length - 3]; else versionSize = header.Version[header.Version.Length - 2]; #endif // 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 /// /// Stream to parse /// Pre-string byte array containing string lengths /// 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 is 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.SeekIfPossible(-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.SeekIfPossible(-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]; } } }