mirror of
https://github.com/SabreTools/SabreTools.Serialization.git
synced 2026-04-05 22:01:33 +00:00
1791 lines
65 KiB
C#
1791 lines
65 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using SabreTools.Data.Models.WiseInstaller;
|
|
using SabreTools.Data.Models.WiseInstaller.Actions;
|
|
using SabreTools.IO.Extensions;
|
|
using SabreTools.Numerics.Extensions;
|
|
using SabreTools.Text.Extensions;
|
|
|
|
#pragma warning disable CA1866 // Use char overload
|
|
#pragma warning disable IDE0017 // Simplify object initialization
|
|
#pragma warning disable IDE0060 // Remove unused parameter
|
|
namespace SabreTools.Serialization.Readers
|
|
{
|
|
public class WiseScript : BaseBinaryReader<ScriptFile>
|
|
{
|
|
/// <inheritdoc/>
|
|
public override ScriptFile? 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 script = new ScriptFile();
|
|
|
|
#region Header
|
|
|
|
var header = ParseScriptHeader(data);
|
|
script.Header = header;
|
|
|
|
#endregion
|
|
|
|
#region State Machine
|
|
|
|
var states = ParseStateMachine(data, header);
|
|
if (states is null)
|
|
return null;
|
|
|
|
script.States = states;
|
|
|
|
#endregion
|
|
|
|
return script;
|
|
}
|
|
catch
|
|
{
|
|
// Ignore the actual error
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a ScriptHeader
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <returns>Filled ScriptHeader on success, null on error</returns>
|
|
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.SeekIfPossible(current + 0x12, SeekOrigin.Begin);
|
|
string? ftpUrl = data.ReadNullTerminatedAnsiString();
|
|
string? logPath = data.ReadNullTerminatedAnsiString();
|
|
string? messageFont = data.ReadNullTerminatedAnsiString();
|
|
data.SeekIfPossible(current, SeekOrigin.Begin);
|
|
|
|
// If the strings are valid
|
|
if (ftpUrl is not null && (ftpUrl.Length == 0 || ftpUrl.Split('.').Length > 2)
|
|
&& logPath is not null && (logPath.Length == 0 || logPath.StartsWith("%"))
|
|
&& messageFont is not 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.SeekIfPossible(current + 0x26, SeekOrigin.Begin);
|
|
ftpUrl = data.ReadNullTerminatedAnsiString();
|
|
logPath = data.ReadNullTerminatedAnsiString();
|
|
messageFont = data.ReadNullTerminatedAnsiString();
|
|
data.SeekIfPossible(current, SeekOrigin.Begin);
|
|
|
|
// If the strings are valid
|
|
if (ftpUrl is not null && (ftpUrl.Length == 0 || ftpUrl.Split('.').Length > 2)
|
|
&& logPath is not null && (logPath.Length == 0 || logPath.StartsWith("%"))
|
|
&& messageFont is not 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.SeekIfPossible(current + 0x34, SeekOrigin.Begin);
|
|
ftpUrl = data.ReadNullTerminatedAnsiString();
|
|
logPath = data.ReadNullTerminatedAnsiString();
|
|
messageFont = data.ReadNullTerminatedAnsiString();
|
|
data.SeekIfPossible(current, SeekOrigin.Begin);
|
|
|
|
// If the strings are valid
|
|
if (ftpUrl is not null && (ftpUrl.Length == 0 || ftpUrl.Split('.').Length > 2)
|
|
&& logPath is not null && (logPath.Length == 0 || logPath.StartsWith("%"))
|
|
&& messageFont is not 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<string> headerStrings = [];
|
|
while (true)
|
|
{
|
|
string? str = data.ReadNullTerminatedAnsiString();
|
|
if (str is null)
|
|
break;
|
|
|
|
// Try to handle invalid string lengths
|
|
if (str.Length > 0 && IsTypicalControlCode(str, strict: false))
|
|
{
|
|
data.SeekIfPossible(-str.Length - 1, SeekOrigin.Current);
|
|
break;
|
|
}
|
|
|
|
// Try to handle InstallFile calls
|
|
long original = data.Position;
|
|
if (str.Length == 0)
|
|
{
|
|
data.SeekIfPossible(-1, SeekOrigin.Current);
|
|
|
|
// Try to read the next block as an install file call
|
|
var maybeInstall = ParseInstallFile(data, header.LanguageCount);
|
|
if (maybeInstall is not null
|
|
&& (maybeInstall.DeflateEnd - maybeInstall.DeflateStart) < data.Length
|
|
&& (maybeInstall.DeflateEnd - maybeInstall.DeflateStart) < maybeInstall.InflatedSize)
|
|
{
|
|
data.SeekIfPossible(original - 1, SeekOrigin.Begin);
|
|
break;
|
|
}
|
|
|
|
// Otherwise, seek back to reading
|
|
data.SeekIfPossible(original, SeekOrigin.Begin);
|
|
}
|
|
|
|
headerStrings.Add(str);
|
|
}
|
|
|
|
header.HeaderStrings = [.. headerStrings];
|
|
|
|
return header;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a state machine
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <param name="header">Parsed script header for information</param>
|
|
/// <param name="old">Indicates an old install script</param>
|
|
/// <returns>Filled state machine on success, null on error</returns>
|
|
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<MachineState> 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 is 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.SeekIfPossible(machineStart, SeekOrigin.Begin);
|
|
continue;
|
|
}
|
|
|
|
var state = new MachineState
|
|
{
|
|
Op = op,
|
|
Data = stateData,
|
|
};
|
|
states.Add(state);
|
|
}
|
|
|
|
return [.. states];
|
|
}
|
|
|
|
#region State Actions
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a InstallFile
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <returns>Filled InstallFile on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a NoOp
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <returns>Filled NoOp on success, null on error</returns>
|
|
private static NoOp ParseNoOp(Stream data)
|
|
{
|
|
return new NoOp();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a DisplayMessage
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <param name="languageCount">Language counter from the header</param>
|
|
/// <returns>Filled DisplayMessage on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a UserDefinedActionStep
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <param name="languageCount">Language counter from the header</param>
|
|
/// <returns>Filled UserDefinedActionStep on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into an EditIniFile
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <returns>Filled EditIniFile on success, null on error</returns>
|
|
private static EditIniFile ParseEditIniFile(Stream data)
|
|
{
|
|
var obj = new EditIniFile();
|
|
|
|
obj.Pathname = data.ReadNullTerminatedAnsiString();
|
|
obj.Section = data.ReadNullTerminatedAnsiString();
|
|
obj.Values = data.ReadNullTerminatedAnsiString();
|
|
|
|
return obj;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a DisplayBillboard
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <param name="languageCount">Language counter from the header</param>
|
|
/// <returns>Filled DisplayBillboard on success, null on error</returns>
|
|
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.SeekIfPossible(-1, SeekOrigin.Current);
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a ExecuteProgram
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <returns>Filled ExecuteProgram on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a EndBlockStatement
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <returns>Filled EndBlockStatement on success, null on error</returns>
|
|
private static EndBlockStatement ParseEndBlockStatement(Stream data)
|
|
{
|
|
var obj = new EndBlockStatement();
|
|
|
|
obj.Operand_1 = data.ReadByteValue();
|
|
|
|
return obj;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a CallDllFunction
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <param name="languageCount">Language counter from the header</param>
|
|
/// <param name="shortDllCall">Indicates a short DLL call</param>
|
|
/// <returns>Filled CallDllFunction on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a EditRegistry
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <param name="registryDll">Indicates if the longer value set is used</param>
|
|
/// <returns>Filled EditRegistry on success, null on error</returns>
|
|
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.PeekUInt32LittleEndian();
|
|
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 is not null && obj.UnknownFsllib.Length > 0
|
|
&& obj.Key is not null && obj.Key.Length == 0
|
|
&& obj.NewValue is not null && obj.NewValue.Length == 0)
|
|
{
|
|
data.SeekIfPossible(-(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 is not null && IsTypicalControlCode(obj.ValueName, strict: true))
|
|
{
|
|
data.SeekIfPossible(-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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a DeleteFile
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <returns>Filled DeleteFile on success, null on error</returns>
|
|
private static DeleteFile ParseDeleteFile(Stream data)
|
|
{
|
|
var obj = new DeleteFile();
|
|
|
|
obj.Flags = data.ReadByteValue();
|
|
obj.Pathname = data.ReadNullTerminatedAnsiString();
|
|
|
|
return obj;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a IfWhileStatement
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <returns>Filled IfWhileStatement on success, null on error</returns>
|
|
private static IfWhileStatement ParseIfWhileStatement(Stream data)
|
|
{
|
|
var obj = new IfWhileStatement();
|
|
|
|
obj.Flags = data.ReadByteValue();
|
|
obj.Variable = data.ReadNullTerminatedAnsiString();
|
|
obj.Value = data.ReadNullTerminatedAnsiString();
|
|
|
|
return obj;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into an ElseStatement
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <returns>Filled ElseStatement on success, null on error</returns>
|
|
private static ElseStatement ParseElseStatement(Stream data)
|
|
{
|
|
return new ElseStatement();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into an ElseStatement
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <returns>Filled StartUserDefinedAction on success, null on error</returns>
|
|
private static StartUserDefinedAction ParseStartUserDefinedAction(Stream data)
|
|
{
|
|
return new StartUserDefinedAction();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into an EndUserDefinedAction
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <returns>Filled EndUserDefinedAction on success, null on error</returns>
|
|
private static EndUserDefinedAction ParseEndUserDefinedAction(Stream data)
|
|
{
|
|
return new EndUserDefinedAction();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a CreateDirectory
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <returns>Filled CreateDirectory on success, null on error</returns>
|
|
private static CreateDirectory ParseCreateDirectory(Stream data)
|
|
{
|
|
var obj = new CreateDirectory();
|
|
|
|
obj.Pathname = data.ReadNullTerminatedAnsiString();
|
|
|
|
return obj;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a CopyLocalFile
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <param name="languageCount">Language counter from the header</param>
|
|
/// <returns>Filled CopyLocalFile on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a CustomDialogSet
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <returns>Filled CustomDialogSet on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a GetSystemInformation
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <returns>Filled GetSystemInformation on success, null on error</returns>
|
|
private static GetSystemInformation ParseGetSystemInformation(Stream data)
|
|
{
|
|
var obj = new GetSystemInformation();
|
|
|
|
obj.Flags = data.ReadByteValue();
|
|
obj.Variable = data.ReadNullTerminatedAnsiString();
|
|
obj.Pathname = data.ReadNullTerminatedAnsiString();
|
|
|
|
return obj;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a GetTemporaryFilename
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <returns>Filled GetTemporaryFilename on success, null on error</returns>
|
|
private static GetTemporaryFilename ParseGetTemporaryFilename(Stream data)
|
|
{
|
|
var obj = new GetTemporaryFilename();
|
|
|
|
obj.Variable = data.ReadNullTerminatedAnsiString();
|
|
|
|
return obj;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a PlayMultimediaFile
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <returns>Filled PlayMultimediaFile on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a NewEvent
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <param name="languageCount">Language counter from the header</param>
|
|
/// <param name="shortDllCall">Indicates a short DLL call</param>
|
|
/// <param name="op0x18skip">Current 0x18 skip value</param>
|
|
/// <returns>Filled NewEvent on success, null on error</returns>
|
|
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.SeekIfPossible(current, SeekOrigin.Begin);
|
|
|
|
op0x18skip = nextByte == 0 || nextByte == 0xFF ? 6 : 0;
|
|
if (nextByte == 0x09)
|
|
{
|
|
var possible = ParseCallDllFunction(data, languageCount, shortDllCall);
|
|
op0x18skip = (possible.FunctionName is null || possible.FunctionName.Length == 0) ? 6 : 0;
|
|
data.SeekIfPossible(current, SeekOrigin.Begin);
|
|
}
|
|
}
|
|
|
|
var obj = new NewEvent();
|
|
|
|
// Skip additional bytes
|
|
if (op0x18skip > 0)
|
|
obj.Padding = data.ReadBytes(op0x18skip);
|
|
|
|
return obj;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a Unknown0x19
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <returns>Filled Unknown0x19 on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a ConfigODBCDataSource
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <returns>Filled ConfigODBCDataSource on success, null on error</returns>
|
|
private static ConfigODBCDataSource ParseConfigODBCDataSource(Stream data)
|
|
{
|
|
var obj = new ConfigODBCDataSource();
|
|
|
|
obj.Flags = data.ReadByteValue();
|
|
obj.FileFormat = data.ReadNullTerminatedAnsiString();
|
|
obj.ConnectionString = data.ReadNullTerminatedAnsiString();
|
|
|
|
return obj;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into an IncludeScript
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <returns>Filled IncludeScript on success, null on error</returns>
|
|
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.SeekIfPossible(-1, SeekOrigin.Current);
|
|
|
|
return obj;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a AddTextToInstallLog
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <returns>Filled AddTextToInstallLog on success, null on error</returns>
|
|
private static AddTextToInstallLog ParseAddTextToInstallLog(Stream data)
|
|
{
|
|
var obj = new AddTextToInstallLog();
|
|
|
|
obj.Text = data.ReadNullTerminatedAnsiString();
|
|
|
|
return obj;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a RenameFileDirectory
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <returns>Filled RenameFileDirectory on success, null on error</returns>
|
|
private static RenameFileDirectory ParseRenameFileDirectory(Stream data)
|
|
{
|
|
var obj = new RenameFileDirectory();
|
|
|
|
obj.OldPathname = data.ReadNullTerminatedAnsiString();
|
|
obj.NewFileName = data.ReadNullTerminatedAnsiString();
|
|
|
|
return obj;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a OpenCloseInstallLog
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <returns>Filled OpenCloseInstallLog on success, null on error</returns>
|
|
private static OpenCloseInstallLog ParseOpenCloseInstallLog(Stream data)
|
|
{
|
|
var obj = new OpenCloseInstallLog();
|
|
|
|
obj.Flags = data.ReadByteValue();
|
|
obj.LogName = data.ReadNullTerminatedAnsiString();
|
|
|
|
return obj;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a ElseIfStatement
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <returns>Filled ElseIfStatement on success, null on error</returns>
|
|
private static ElseIfStatement ParseElseIfStatement(Stream data)
|
|
{
|
|
var obj = new ElseIfStatement();
|
|
|
|
obj.Operator = data.ReadByteValue();
|
|
obj.Variable = data.ReadNullTerminatedAnsiString();
|
|
obj.Value = data.ReadNullTerminatedAnsiString();
|
|
|
|
return obj;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a Unknown0x24
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <returns>Filled Unknown0x24 on success, null on error</returns>
|
|
private static Unknown0x24 ParseUnknown0x24(Stream data)
|
|
{
|
|
return new Unknown0x24();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a Unknown0x25
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <returns>Filled Unknown0x25 on success, null on error</returns>
|
|
private static Unknown0x25 ParseUnknown0x25(Stream data)
|
|
{
|
|
return new Unknown0x25();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a InvalidOperation
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <returns>Filled InvalidOperation on success, null on error</returns>
|
|
/// <remarks>
|
|
/// This represents a placeholder. The operations that result in this
|
|
/// type being parsed should never happen in the state machine.
|
|
/// </remarks>
|
|
private static InvalidOperation ParseInvalidOperation(Stream data)
|
|
{
|
|
return new InvalidOperation();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Function Actions
|
|
|
|
/// <summary>
|
|
/// Parse a string into a AddDirectoryToPath
|
|
/// </summary>
|
|
/// <param name="data">0x7F-separated string to parse</param>
|
|
/// <returns>Filled AddDirectoryToPath on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a string into a AddToAutoexecBat
|
|
/// </summary>
|
|
/// <param name="data">0x7F-separated string to parse</param>
|
|
/// <returns>Filled AddToAutoexecBat on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a string into a AddToConfigSys
|
|
/// </summary>
|
|
/// <param name="data">0x7F-separated string to parse</param>
|
|
/// <returns>Filled AddToConfigSys on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a string into a AddToSystemIni
|
|
/// </summary>
|
|
/// <param name="data">0x7F-separated string to parse</param>
|
|
/// <returns>Filled AddToSystemIni on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a string into a ReadIniValue
|
|
/// </summary>
|
|
/// <param name="data">0x7F-separated string to parse</param>
|
|
/// <returns>Filled ReadIniValue on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a string into a GetRegistryKeyValue
|
|
/// </summary>
|
|
/// <param name="data">0x7F-separated string to parse</param>
|
|
/// <returns>Filled GetRegistryKeyValue on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a string into a RegisterFont
|
|
/// </summary>
|
|
/// <param name="data">0x7F-separated string to parse</param>
|
|
/// <returns>Filled RegisterFont on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a string into a Win32SystemDirectory
|
|
/// </summary>
|
|
/// <param name="data">0x7F-separated string to parse</param>
|
|
/// <returns>Filled Win32SystemDirectory on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a string into a CheckConfiguration
|
|
/// </summary>
|
|
/// <param name="data">0x7F-separated string to parse</param>
|
|
/// <returns>Filled CheckConfiguration on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a string into a SearchForFile
|
|
/// </summary>
|
|
/// <param name="data">0x7F-separated string to parse</param>
|
|
/// <returns>Filled SearchForFile on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a string into a ReadWriteBinaryFile
|
|
/// </summary>
|
|
/// <param name="data">0x7F-separated string to parse</param>
|
|
/// <returns>Filled ReadWriteBinaryFile on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a string into a SetVariable
|
|
/// </summary>
|
|
/// <param name="data">0x7F-separated string to parse</param>
|
|
/// <returns>Filled SetVariable on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a string into a GetEnvironmentVariable
|
|
/// </summary>
|
|
/// <param name="data">0x7F-separated string to parse</param>
|
|
/// <returns>Filled GetEnvironmentVariable on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a string into a CheckIfFileDirExists
|
|
/// </summary>
|
|
/// <param name="data">0x7F-separated string to parse</param>
|
|
/// <returns>Filled CheckIfFileDirExists on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a string into a SetFileAttributes
|
|
/// </summary>
|
|
/// <param name="data">0x7F-separated string to parse</param>
|
|
/// <returns>Filled SetFileAttributes on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a string into a SetFilesBuffers
|
|
/// </summary>
|
|
/// <param name="data">0x7F-separated string to parse</param>
|
|
/// <returns>Filled SetFilesBuffers on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a string into a FindFileInPath
|
|
/// </summary>
|
|
/// <param name="data">0x7F-separated string to parse</param>
|
|
/// <returns>Filled FindFileInPath on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a string into a CheckDiskSpace
|
|
/// </summary>
|
|
/// <param name="data">0x7F-separated string to parse</param>
|
|
/// <returns>Filled CheckDiskSpace on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a string into a InsertLineIntoTextFile
|
|
/// </summary>
|
|
/// <param name="data">0x7F-separated string to parse</param>
|
|
/// <returns>Filled InsertLineIntoTextFile on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a string into a ParseString
|
|
/// </summary>
|
|
/// <param name="data">0x7F-separated string to parse</param>
|
|
/// <returns>Filled ParseString on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a string into a ExitInstallation
|
|
/// </summary>
|
|
/// <param name="data">0x7F-separated string to parse</param>
|
|
/// <returns>Filled ExitInstallation on success, null on error</returns>
|
|
private static ExitInstallation ParseExitInstallation(string data)
|
|
{
|
|
return new ExitInstallation();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a string into a SelfRegisterOCXsDLLs
|
|
/// </summary>
|
|
/// <param name="data">0x7F-separated string to parse</param>
|
|
/// <returns>Filled SelfRegisterOCXsDLLs on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a string into a InstallDirectXComponents
|
|
/// </summary>
|
|
/// <param name="data">0x7F-separated string to parse</param>
|
|
/// <returns>Filled InstallDirectXComponents on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a string into a WizardBlockLoop
|
|
/// </summary>
|
|
/// <param name="data">0x7F-separated string to parse</param>
|
|
/// <returns>Filled WizardBlockLoop on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a string into a ReadUpdateTextFile
|
|
/// </summary>
|
|
/// <param name="data">0x7F-separated string to parse</param>
|
|
/// <returns>Filled ReadUpdateTextFile on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a string into a PostToHttpServer
|
|
/// </summary>
|
|
/// <param name="data">0x7F-separated string to parse</param>
|
|
/// <returns>Filled PostToHttpServer on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a string into a PromptForFilename
|
|
/// </summary>
|
|
/// <param name="data">0x7F-separated string to parse</param>
|
|
/// <returns>Filled PromptForFilename on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a string into a StartStopService
|
|
/// </summary>
|
|
/// <param name="data">0x7F-separated string to parse</param>
|
|
/// <returns>Filled StartStopService on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a string into a CheckHttpConnection
|
|
/// </summary>
|
|
/// <param name="data">0x7F-separated string to parse</param>
|
|
/// <returns>Filled CheckHttpConnection on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a string into a ExternalDllCall
|
|
/// </summary>
|
|
/// <param name="data">0x7F-separated string to parse</param>
|
|
/// <returns>Filled ExternalDllCall on success, null on error</returns>
|
|
private static ExternalDllCall ParseExternalDllCall(string data)
|
|
{
|
|
var obj = new ExternalDllCall();
|
|
|
|
obj.Args = data.Split((char)0x7F);
|
|
|
|
return obj;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Helpers
|
|
|
|
/// <summary>
|
|
/// Returns if a string may be a typical control code
|
|
/// </summary>
|
|
/// <param name="str">String to check</param>
|
|
/// <param name="strict">Indicates if control codes should always be checked</param>
|
|
/// <returns>True if the string probably represents a control code, false otherwise</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a DeflateEntry
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <returns>Filled DeflateEntry on success, null on error</returns>
|
|
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
|
|
}
|
|
}
|