mirror of
https://github.com/SabreTools/SabreTools.Serialization.git
synced 2026-04-05 22:01:33 +00:00
340 lines
13 KiB
C#
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];
|
|
}
|
|
}
|
|
}
|