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 { /// 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; } } /// /// 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.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 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; } /// /// 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 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 /// /// 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.SeekIfPossible(-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.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; } /// /// 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.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; } /// /// 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.SeekIfPossible(-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 } }