Files
Matt Nadareski 8f49e190d8 Fix everything
2026-03-24 19:17:25 -04:00

340 lines
13 KiB
C#

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<SectionHeader>
{
/// <inheritdoc/>
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;
}
}
/// <summary>
/// Parse a Stream into a WiseSectionHeader
/// </summary>
/// <param name="data">Stream to parse</param>
/// <param name="initialOffset">Initial offset to use in address comparisons</param>
/// <returns>Filled WiseSectionHeader on success, null on error</returns>
public static SectionHeader? ParseSectionHeader(Stream data, long initialOffset)
{
var 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;
}
/// <summary>
/// Get the pre-string bytes size, if possible
/// </summary>
/// <param name="data">Stream to parse</param>
/// <param name="header">Section header to get information from</param>
/// <param name="wisOffset">Offset to the WIS string relative to the start of the header</param>
/// <returns>The size of the pre-string section</returns>
/// <remarks>
/// This method also sets <see cref="SectionHeader.NonWiseVersion"/> and
/// <see cref="SectionHeader.PreFontValue"/>, if possible.
/// </remarks>
private static int GetPreStringBytesSize(Stream data, SectionHeader header, int wisOffset)
{
// Handle a case that shouldn't happen
if (header.Version 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;
}
/// <summary>
/// Parse the string table, if possible
/// </summary>
/// <param name="data">Stream to parse</param>
/// <param name="preStringValues">Pre-string byte array containing string lengths</param>
/// <returns>The filled string table on success, false otherwise</returns>
private static byte[][]? ParseStringTable(Stream data, byte[] preStringValues)
{
// Setup the loop variables
List<byte[]> stringList = [];
int counter = 0;
bool endNow = false;
bool languageSection = false;
int languageSectionCounter = 0;
// Iterate pre-string byte array
while (counter < preStringValues.Length)
{
// Read the next byte value
byte currentByte = preStringValues[counter];
if (currentByte == 0x00)
break;
// Now doing third byte after language section begins
if (currentByte == 0x01 && languageSectionCounter == 2)
{
int extraLanguages = preStringValues[counter + 1];
for (int i = 0; i < extraLanguages; i++)
{
byte[]? incrementBytes = data.ReadBytes(2);
string? extraLanguageString = data.ReadNullTerminatedAnsiString();
if (extraLanguageString 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];
}
}
}