state("kuso") {} startup { settings.Add("gameTime", true, "Automatically change timing method to Game Time"); settings.Add("patchLowFPS", false, "Make the game run at full intended FPS"); settings.Add("patchTempBug", false, "Stop the game from deleting %TEMP% folder"); settings.SetToolTip("patchLowFPS", "Affects all versions of kuso Demo, and old versions of kuso Full"); settings.SetToolTip("patchTempBug", "Affects all versions of kuso Demo, and the first version of kuso Full"); vars.ActionRooms = new List { "room_2p_select", "room_gameselect", "room_levelselect", "room_levelselect_kuso", "room_levelselect_love", "room_levelselect_other", "room_levelsetselect", "room_mainmenu", "room_startup", "room_titlescreen" }; } init { try { vars.GameExe = modules.First().ModuleName; if (!vars.GameExe.ToLower().EndsWith(".exe")) { throw new Exception("Game not loaded yet."); } vars.PatchedLowFPS = false; vars.PatchedTempBug = false; vars.Demo = false; vars.Ready = false; } catch { throw; } bool is64bit = game.Is64Bit(); int pointerSize = is64bit ? 8 : 4; string exePath = modules.First().FileName; string winPath = new FileInfo(exePath).DirectoryName + @"\data.win"; long exeSize = new FileInfo(exePath).Length; long winSize = new FileInfo(winPath).Exists ? new FileInfo(winPath).Length : 0; var log = vars.Log = (Action)(input => { print("[" + vars.GameExe + "] " + input); }); var qt = vars.Qt = (Func)(input => { input = input != null ? input.ToString().Split('\0')[0] : ""; return "\"" + input + "\""; }); var hex = vars.Hex = (Func)(input => { if (input != null) { long number; bool success = long.TryParse(input.ToString(), out number); if (success) { return "0x" + number.ToString("X"); } } return "0"; }); vars.CancelSource = new CancellationTokenSource(); CancellationToken token = vars.CancelSource.Token; System.Threading.Tasks.Task.Run(async () => { // Resolves global GameMaker variable names to data addresses. Finds the current room name. // For games made with GameMaker: Studio IDE 1.4.1760 (30-Aug-2016) and newer, up until and including the latest GameMaker Studio 2/GameMaker (Zeus) runtime. // Does not work with 1.4.1760 YYC or 1.4.1763 YYC (06-Oct-2016). // Tested on stable/LTS, VM/YYC 32-bit/64-bit (Windows only), excluding the earliest GameMaker Studio 2 runtimes. // Oldest GMS2 runtime I have found/tested is 2.0.6.96 (16-May-2017), but the first stable GMS2 IDE is 2.0.5.76 (07-Mar-2017). string[] variableTargets = { "playerFrames", /* "playerSpawns", "gameMode" */ }; var signatureTargets = new Dictionary(); if (is64bit) { signatureTargets.Add("RoomNumber", new SigScanTarget(6, "48 ?? ?? ?? 3B 35 ?? ?? ?? ?? 41 ?? ?? ?? 49 ?? ?? E8 ?? ?? ?? ?? FF")); signatureTargets.Add("RoomBase", new SigScanTarget(18, "84 C9 ?? F2 ?? ?? ?? ?? 8D 1C C5 00 00 00 00 ?? 8B")); signatureTargets.Add("VariableNames", new SigScanTarget(15, "3B 35 ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 8B 1C E8")); signatureTargets.Add("GlobalData", new SigScanTarget(13, "BA FF FF FF 00 E8 ?? ?? ?? ?? 48 89 05 ?? ?? ?? ?? 48")); signatureTargets.Add("SleepMargin", new SigScanTarget(10, "48 8B CE E8 ?? ?? ?? ?? 89 05 ?? ?? ?? ?? EB 2C 48 8D 15 ?? ?? ?? ?? 48 8B ?? E8")); } else { signatureTargets.Add("RoomNumber", new SigScanTarget(7, "E8 ?? ?? ?? ?? 8B 0D ?? ?? ?? ?? 3B C8 75 1A 6A 01 68 ?? ?? ?? ?? E8")); signatureTargets.Add("RoomBase", new SigScanTarget(11, "4A 8B 41 FC 89 01 ?? ?? ?? ?? A1 ?? ?? ?? ?? 68 ?? ?? ?? ?? ?? ?? ?? E8")); signatureTargets.Add("RoomBaseOld", new SigScanTarget(13, "90 8A ?? 88 ?? ?? 40 84 C9 75 F6 8B 0D ?? ?? ?? ?? 8B ?? ?? 85 C0")); // GMS IDE 1.4.1760 ... 1.4.9999 VM/YYC (04-Oct-2018) signatureTargets.Add("VariableNames", new SigScanTarget(1, "A3 ?? ?? ?? ?? C7 05 ?? ?? ?? ?? 08 00 00 00 E8 ?? ?? ?? ?? 83 C4 18 C3 CC")); signatureTargets.Add("VariableNamesOld", new SigScanTarget(14, "68 ?? ?? ?? ?? 8D 04 ?? 00 00 00 00 50 68 ?? ?? ?? ?? E8 ?? ?? ?? ?? 89 35")); // GMS IDE 1.4.1760, 1.4.1763 VM signatureTargets.Add("GlobalData", new SigScanTarget(4, "55 56 8B 35 ?? ?? ?? ?? ?? ?? ?? 85 F6 0F 84 ?? 00 00 00")); signatureTargets.Add("SleepMargin", new SigScanTarget(11, "8B ?? E8 ?? ?? ?? ?? 83 C4 0C A3 ?? ?? ?? ?? C6 05 ?? ?? ?? ?? 01 E9 ?? ?? ?? ?? B9")); } foreach (KeyValuePair target in signatureTargets.Where(x => !x.Key.StartsWith("VariableNames") && x.Key != "GlobalData")) { target.Value.OnFound = (proc, scan, result) => is64bit ? result + 0x4 + proc.ReadValue(result) : proc.ReadPointer(result); } scan_start:; IntPtr exeBaseAddress = modules.First().BaseAddress; int exeMemorySize = modules.First().ModuleMemorySize; int variableTargetsCount = variableTargets.Distinct().Count(); var scanner = new SignatureScanner(game, exeBaseAddress, exeMemorySize); log(qt(exePath)); log("exeSize: " + exeSize + ", winSize: " + winSize + ", exeMemorySize: " + hex(exeMemorySize) + ", exeBaseAddress: " + hex(exeBaseAddress) + ", is64bit: " + is64bit); log("Scanning for current room name.."); var roomBasePointers = new List>(); foreach (IntPtr pointer in scanner.ScanAll(signatureTargets["RoomBase"]).Distinct()) { roomBasePointers.Add(Tuple.Create("RoomBase", pointer)); } if (!is64bit) { foreach (IntPtr pointer in scanner.ScanAll(signatureTargets["RoomBaseOld"]).Distinct()) { roomBasePointers.Add(Tuple.Create("RoomBaseOld", pointer)); } } IEnumerable roomNumberAddresses = scanner.ScanAll(signatureTargets["RoomNumber"]).Distinct(); foreach (var element in roomBasePointers) { string signatureTargetName = element.Item1; IntPtr roomBasePointer = element.Item2; foreach (IntPtr roomNumberAddress in roomNumberAddresses) { vars.RoomName = (Action)(() => { try { current.RoomName = ""; IntPtr roomBaseAddress = game.ReadPointer(roomBasePointer); if (roomBaseAddress != IntPtr.Zero) { byte[] number = game.ReadBytes(roomNumberAddress, 4); if (number != null) { int roomNumber = BitConverter.ToInt32(number, 0); IntPtr roomNameAddress = game.ReadPointer(roomBaseAddress + (roomNumber * pointerSize)); string roomName = game.ReadString(roomNameAddress, 256) ?? ""; if (System.Text.RegularExpressions.Regex.IsMatch(roomName, @"^[a-zA-Z_]+[a-zA-Z0-9_]*$")) { current.RoomName = roomName.ToLower(); if (!vars.Ready) { log("RoomNumber: " + hex(roomNumberAddress) + " = " + hex(roomNumber)); log(signatureTargetName + ": " + hex(roomBasePointer) + " = " + hex(roomBaseAddress)); log(hex(roomBaseAddress) + " + (" + hex(roomNumber) + " * " + hex(pointerSize) + ") -> " + hex(roomNameAddress) + " = " + qt(current.RoomName)); } } } } } catch { } }); vars.RoomName(); if (current.RoomName != "") { vars.RoomNumberAddress = roomNumberAddress; goto room_name_found; } } } log("Room name not found."); await System.Threading.Tasks.Task.Delay(2000, token); goto scan_start; room_name_found:; log("Scanning for global variable names.."); var variableNamesBasesFound = new List>(); foreach (IntPtr result in scanner.ScanAll(signatureTargets["VariableNames"])) { IntPtr variableNamesBasePointer, variableNamesCountAddress; if (is64bit) { variableNamesBasePointer = result + 0x4 + game.ReadValue(result); variableNamesCountAddress = (result - 0xD) + 0x4 + game.ReadValue(result - 0xD); } else { variableNamesBasePointer = game.ReadPointer(result); variableNamesCountAddress = game.ReadPointer(result + 0x6); } int variableNamesCount = game.ReadValue(variableNamesCountAddress); long offset = (long)variableNamesBasePointer - (long)variableNamesCountAddress; if (variableNamesCount > 0 && variableNamesCount <= 0xFFFF && (is64bit && (offset == 0x8 || offset == 0x10) || !is64bit && offset == 0xC)) { if (!variableNamesBasesFound.Any(x => x.Item1 == "VariableNames" && x.Item2 == variableNamesBasePointer)) { variableNamesBasesFound.Add(Tuple.Create("VariableNames", variableNamesBasePointer, variableNamesCountAddress, variableNamesCount)); } } } if (!is64bit) { foreach (IntPtr result in scanner.ScanAll(signatureTargets["VariableNamesOld"])) { IntPtr variableNamesBasePointer = game.ReadPointer(result); IntPtr variableNamesCountAddress = game.ReadPointer(result + 0xB); int variableNamesCount = game.ReadValue(variableNamesCountAddress); long offset = (long)variableNamesCountAddress - (long)variableNamesBasePointer; if (variableNamesCount > 0 && variableNamesCount <= 0xFFFF && offset == 0x4) { if (!variableNamesBasesFound.Any(x => x.Item1 == "VariableNamesOld" && x.Item2 == variableNamesBasePointer)) { variableNamesBasesFound.Add(Tuple.Create("VariableNamesOld", variableNamesBasePointer, variableNamesCountAddress, variableNamesCount)); } } } } if (variableNamesBasesFound.Count == 0) { log("Name base not found."); await System.Threading.Tasks.Task.Delay(2000, token); goto scan_start; } var variableNamesFoundLists = new List>>(); foreach (var element in variableNamesBasesFound) { string signatureTargetName = element.Item1; IntPtr variableNamesBasePointer = element.Item2; IntPtr variableNamesCountAddress = element.Item3; int variableNamesCount = element.Item4; IntPtr variableNamesBaseAddress = game.ReadPointer(variableNamesBasePointer); int variableNameIndex = variableNamesBaseAddress == IntPtr.Zero ? int.MaxValue : 0; log(signatureTargetName + ": " + hex(variableNamesBasePointer) + " = " + hex(variableNamesBaseAddress) + ", " + hex(variableNamesCountAddress) + " = " + hex(variableNamesCount)); if (variableTargetsCount > 0) { var variableNamesFound = new List>(); while (variableNameIndex < variableNamesCount) { IntPtr variableNameAddress = game.ReadPointer(variableNamesBaseAddress + (variableNameIndex * pointerSize)); string variableName = game.ReadString(variableNameAddress, 256); if (!string.IsNullOrWhiteSpace(variableName) && variableTargets.Contains(variableName)) { variableNamesFound.Add(Tuple.Create(variableNamesBaseAddress, variableNameIndex, variableNameAddress, variableName)); log(hex(variableNamesBaseAddress) + " + (" + hex(variableNameIndex) + " * " + hex(pointerSize) + ") -> " + hex(variableNameAddress) + " = " + qt(variableName)); } variableNameIndex++; } int variableNamesFoundCount = variableNamesFound.Select(x => x.Item4).Distinct().Count(); if (!variableNamesFoundLists.Any(x => x.SequenceEqual(variableNamesFound)) && variableNamesFoundCount == variableTargetsCount) { variableNamesFoundLists.Add(variableNamesFound); } log("variableNamesFound: " + variableNamesFoundCount + "/" + variableTargetsCount); } } log("Scanning for global variable addresses.."); var pointerBasesFound = new List>(); foreach (IntPtr result in scanner.ScanAll(signatureTargets["GlobalData"])) { IntPtr searchBaseFirstPointer, searchBaseFirstAddress, searchBaseSecondPointer, searchBaseSecondAddress; if (is64bit) { searchBaseFirstPointer = result + 0x4 + game.ReadValue(result); searchBaseFirstAddress = game.ReadPointer(searchBaseFirstPointer); searchBaseSecondPointer = (result + 0x7) + 0x4 + game.ReadValue(result + 0x7); searchBaseSecondAddress = game.ReadPointer(searchBaseSecondPointer); } else { searchBaseFirstPointer = game.ReadPointer(result); searchBaseFirstAddress = game.ReadPointer(searchBaseFirstPointer); searchBaseSecondPointer = IntPtr.Zero; searchBaseSecondAddress = IntPtr.Zero; } var searchBasesFound = new List>(); if (searchBaseFirstAddress != IntPtr.Zero && !searchBasesFound.Any(x => x.Item2 == searchBaseFirstAddress)) { searchBasesFound.Add(Tuple.Create(searchBaseFirstPointer, searchBaseFirstAddress)); } if (searchBaseSecondAddress != IntPtr.Zero && !searchBasesFound.Any(x => x.Item2 == searchBaseSecondAddress)) { searchBasesFound.Add(Tuple.Create(searchBaseSecondPointer, searchBaseSecondAddress)); } foreach (var element in searchBasesFound) { IntPtr searchBasePointer = element.Item1; IntPtr searchBaseAddress = element.Item2; int searchBaseOffset = pointerSize; while (pointerBasesFound.Where(x => x.Item2 == searchBaseAddress).Count() < 4 && searchBaseOffset <= 0x3FF) { IntPtr pointerBasePointer = game.ReadPointer(searchBaseAddress + searchBaseOffset); if (pointerBasePointer != IntPtr.Zero) { IntPtr pointerBaseAddress = game.ReadPointer(pointerBasePointer + 0x10); int andOperand = game.ReadBytes(pointerBaseAddress, pointerSize) != null ? game.ReadValue(pointerBasePointer + 0x8) : 0; if (andOperand > 0 && andOperand <= 0xFFFF && !pointerBasesFound.Any(x => x.Item5 == pointerBaseAddress && x.Item6 == andOperand)) { pointerBasesFound.Add(Tuple.Create(searchBasePointer, searchBaseAddress, searchBaseOffset, pointerBasePointer, pointerBaseAddress, andOperand)); } } searchBaseOffset += pointerSize; } } } if (pointerBasesFound.Count == 0) { log("Pointer base not found."); await System.Threading.Tasks.Task.Delay(2000, token); goto scan_start; } foreach (var element in pointerBasesFound) { IntPtr searchBasePointer = element.Item1; IntPtr searchBaseAddress = element.Item2; int searchBaseOffset = element.Item3; IntPtr pointerBasePointer = element.Item4; IntPtr pointerBaseAddress = element.Item5; int andOperand = element.Item6; log("GlobalData: " + hex(searchBasePointer) + " -> " + hex(searchBaseAddress) + " + " + hex(searchBaseOffset) + " -> " + hex(pointerBasePointer) + " + 0x10 = " + hex(pointerBaseAddress) + ", " + hex(pointerBasePointer) + " + 0x8 = " + hex(andOperand)); foreach (var list in variableNamesFoundLists) { var variableAddressesFound = new List>(); foreach (var variableTarget in list) { IntPtr variableNamesBaseAddress = vars.VariableNamesBaseAddress = variableTarget.Item1; int variableNameIndex = variableTarget.Item2; IntPtr variableNameAddress = variableTarget.Item3; string variableName = variableTarget.Item4; try { // GM runtime 2023.11.0.157 (04-Dec-2023) ... int variableIdentifierD = variableNameIndex + 0x186A0; int identifierExtensionD = variableIdentifierD + 0x1; int resultD = identifierExtensionD & andOperand; int offsetD = checked(resultD * (pointerSize + 0x8)); IntPtr variablePointerD = checked(pointerBaseAddress + offsetD); int identifierD = game.ReadValue(variablePointerD + pointerSize); int extensionD = game.ReadValue(variablePointerD + pointerSize + 0x4); // GMS2 runtime 2.3.0.401 (14-Aug-2020) ... GM runtime 2023.8.2.152 (06-Oct-2023) int variableIdentifierC = variableNameIndex + 0x186A0; int identifierExtensionC = (0x1 - (0x61C8864F * variableIdentifierC)) & 0x7FFFFFFF; int resultC = identifierExtensionC & andOperand; int offsetC = checked(resultC * (pointerSize + 0x8)); IntPtr variablePointerC = checked(pointerBaseAddress + offsetC); int identifierC = game.ReadValue(variablePointerC + pointerSize); int extensionC = game.ReadValue(variablePointerC + pointerSize + 0x4); // GMS2 runtime 2.2.1.287 (05-Dec-2018) ... 2.2.5.378 (18-Dec-2019) int variableIdentifierB = variableNameIndex; int identifierExtensionB = (0x1 - (0x61C8864F * variableIdentifierB)) & 0x7FFFFFFF; int resultB = identifierExtensionB & andOperand; int offsetB = checked(resultB * (pointerSize + 0x8)); IntPtr variablePointerB = checked(pointerBaseAddress + offsetB); int identifierB = game.ReadValue(variablePointerB + pointerSize); int extensionB = game.ReadValue(variablePointerB + pointerSize + 0x4); // GMS IDE 1.4.1760 (30-Aug-2016) ... GMS2 runtime 2.2.0.261 (09-Oct-2018) int variableIdentifierA = variableNameIndex; int identifierExtensionA = variableIdentifierA + 0x1; int resultA = identifierExtensionA & andOperand; int offsetA = checked((resultA * (pointerSize + 0x8)) + 0x4); IntPtr variablePointerA = checked(pointerBaseAddress + offsetA); int identifierA = game.ReadValue(variablePointerA - pointerSize); int extensionA = game.ReadValue(variablePointerA + pointerSize); string gameMakerGroup = ""; IntPtr variablePointer = IntPtr.Zero; IntPtr variableAddress = IntPtr.Zero; if (variableIdentifierD == identifierD && identifierExtensionD == extensionD) { gameMakerGroup = " (D)"; variablePointer = variablePointerD; variableAddress = game.ReadPointer(variablePointerD); } else if (variableIdentifierC == identifierC && identifierExtensionC == extensionC) { gameMakerGroup = " (C)"; variablePointer = variablePointerC; variableAddress = game.ReadPointer(variablePointerC); } else if (variableIdentifierB == identifierB && identifierExtensionB == extensionB) { gameMakerGroup = " (B)"; variablePointer = variablePointerB; variableAddress = game.ReadPointer(variablePointerB); } else if (variableIdentifierA == identifierA && identifierExtensionA == extensionA) { gameMakerGroup = " (A)"; variablePointer = variablePointerA; variableAddress = game.ReadPointer(variablePointerA); } if (variableAddress != IntPtr.Zero) { Tuple variable = Tuple.Create(variableName, variableAddress); string ifDuplicateFound = ""; if (variableAddressesFound.Contains(variable)) { ifDuplicateFound = " (duplicate, ignored)"; } else { variableAddressesFound.Add(variable); } // Values are either variableAddress = double, or variableAddress -> stringPointer -> stringAddress = string. // Note that stringPointer and stringAddress may change while the game is running, variableAddress does not. double value = game.ReadValue(variableAddress); if (!value.ToString().Any(char.IsLetter) && value.ToString().Length <= 12) { log(hex(variableNameAddress) + " = " + qt(variableName) + " -> " + hex(variablePointer) + " -> " + hex(variableAddress) + " = " + value + gameMakerGroup + ifDuplicateFound); } else { IntPtr stringPointer = game.ReadPointer(variableAddress); IntPtr stringAddress = game.ReadPointer(stringPointer); string stringValue = game.ReadString(stringAddress, 256); log(hex(variableNameAddress) + " = " + qt(variableName) + " -> " + hex(variablePointer) + " -> " + hex(variableAddress) + " -> " + hex(stringPointer) + " -> " + hex(stringAddress) + " = " + qt(stringValue) + gameMakerGroup + ifDuplicateFound); } } } catch { } } int variableAddressesFoundCount = variableAddressesFound.Distinct().Count(); string ifZeroFound = variableAddressesFoundCount == 0 ? " (" + hex(vars.VariableNamesBaseAddress) + ")" : ""; log("variableAddressesFound: " + variableAddressesFoundCount + "/" + variableTargetsCount + ifZeroFound); if (variableAddressesFoundCount == variableTargetsCount) { if (token.IsCancellationRequested) { goto task_end; } IntPtr frameCountAddress = variableAddressesFound.Where(x => x.Item1 == "playerFrames").Select(x => x.Item2).FirstOrDefault(); vars.RoomNumber = new MemoryWatcher(vars.RoomNumberAddress); vars.FrameCount = new MemoryWatcher(frameCountAddress); goto ready; } } } await System.Threading.Tasks.Task.Delay(2000, token); goto scan_start; ready:; vars.PatchLowFPS = (Action)(() => { // Makes a GameMaker game run at full intended frame rate regardless of display refresh rate or Windows version. // Increases sleep margin value. This doesn't fix the actual underlying issue, // but achieves the desired result (full FPS) at the cost of increased CPU usage. // Affects all (?) versions up until and including GMS2 runtime 2.3.1.409 (16-Dec-2020). // This GameMaker issue was fixed in GMS2 runtime 2.3.2.420 (30-Mar-2021). try { game.Suspend(); IntPtr sleepMarginAddress = scanner.Scan(signatureTargets["SleepMargin"]); byte[] value = game.ReadBytes(sleepMarginAddress, 4); if (value != null) { int sleepMarginNewValue = 200; int sleepMarginOldValue = BitConverter.ToInt32(value, 0); game.WriteBytes(sleepMarginAddress, BitConverter.GetBytes(sleepMarginNewValue)); log("Sleep margin patched. " + hex(sleepMarginAddress) + " = " + sleepMarginOldValue + " -> " + sleepMarginNewValue); } else { log("Sleep margin not found."); } } finally { vars.PatchedLowFPS = true; game.Resume(); } }); vars.PatchTempBug = (Action)(() => { // Stops a GameMaker game from attempting to delete %TEMP% folder. More info at https://github.com/neesi/autosplitters/tree/main/LOVE_2_kuso // Fills the offending function with NOPs. This is essentially how the bug was fixed in official GameMaker releases. // Affects GMS IDE 1.4.1760 (30-Aug-2016) ... 1.4.1773 (14-Sep-2017) and all (?) GMS2 versions up until and including GMS2 runtime 2.0.7.110 (21-Jun-2017). // This GameMaker bug was fixed in GMS2 runtime 2.1.0.135 (23-Aug-2017) and later in GMS IDE 1.4.1788 (08-Feb-2018). try { if (!is64bit) { game.Suspend(); SigScanTarget tempPathGameMaker14 = new SigScanTarget(2, "CC A1 ?? ?? ?? ?? 50 E8 ?? ?? FF FF 59 C3 CC"); SigScanTarget tempPathGameMaker2 = new SigScanTarget(19, "C3 5F 5E 5B C7 44 24 04 ?? ?? ?? ?? E9 ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? E8 ?? ?? ?? ?? ?? C3"); SigScanTarget functionCalls = new SigScanTarget(21, "E8 ?? ?? ?? ?? E8 ?? ?? ?? ?? E8 ?? ?? ?? ?? E8 ?? ?? ?? ?? E8 ?? ?? ?? ?? E8 ?? ?? ?? ?? E8"); var tempPathResults = new List>(); foreach (IntPtr result in scanner.ScanAll(tempPathGameMaker14)) { tempPathResults.Add(Tuple.Create(result, 0x1)); } foreach (IntPtr result in scanner.ScanAll(tempPathGameMaker2)) { tempPathResults.Add(Tuple.Create(result, 0x2)); } IEnumerable functionCallsResults = scanner.ScanAll(functionCalls); foreach (var element in tempPathResults) { IntPtr tempPathResult = element.Item1; int functionStartOffset = element.Item2; string tempPath = new DeepPointer(tempPathResult, 0x0, 0x0).DerefString(game, 4); if (!string.IsNullOrWhiteSpace(tempPath) && tempPath.Length == 4) { foreach (IntPtr result in functionCallsResults) { IntPtr calleeAddress = result + 0x4 + game.ReadValue(result); IntPtr functionAddress = tempPathResult - functionStartOffset; if (calleeAddress.Equals(functionAddress)) { byte[] nops = new byte[] { 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 }; game.WriteBytes(functionAddress, nops); log("%TEMP% bug patched. Call: " + hex(result - 0x1)); goto end; } } } } } log("%TEMP% bug not found."); end:; } finally { vars.PatchedTempBug = true; game.Resume(); } }); if (settings["gameTime"]) { timer.CurrentTimingMethod = TimingMethod.GameTime; } if (exeSize == 4270592 && winSize > 0 && winSize < 3000000 && !is64bit) { vars.Demo = true; } vars.SubtractFrames = 0; vars.SubtractFramesCache = 0; current.RoomName = ""; vars.InitialUpdate = true; vars.Ready = true; task_end:; log("Task end."); }); } update { if (!vars.Ready) { return false; } vars.RoomNumber.Update(game); vars.FrameCount.Update(game); if (vars.RoomNumber.Changed || vars.InitialUpdate) { vars.InitialUpdate = false; vars.RoomName(); vars.Log("current.RoomName: " + vars.Qt(current.RoomName) + " [" + vars.RoomNumber.Current + "]"); if (current.RoomName != old.RoomName && old.RoomName == "room_levelselect") { vars.SubtractFrames = vars.SubtractFramesCache; } } if (vars.Demo) { if (vars.FrameCount.Current < vars.SubtractFrames) { vars.SubtractFrames = 0; vars.SubtractFramesCache = 0; } if (current.RoomName == "room_levelselect" && vars.FrameCount.Current > 90) { vars.SubtractFramesCache = vars.FrameCount.Current; } } if (!vars.PatchedLowFPS && settings["patchLowFPS"]) { vars.PatchLowFPS(); } if (!vars.PatchedTempBug && settings["patchTempBug"]) { vars.PatchTempBug(); } } start { return !vars.ActionRooms.Contains(current.RoomName) && vars.FrameCount.Current == vars.FrameCount.Old + 1; } split { return vars.RoomNumber.Changed && vars.FrameCount.Current > 90; } reset { return vars.FrameCount.Current < vars.FrameCount.Old || vars.ActionRooms.Contains(current.RoomName) && (!vars.Demo || (vars.Demo && current.RoomName != "room_levelselect")) || vars.Demo && current.RoomName != old.RoomName && old.RoomName == "room_levelselect"; } gameTime { return TimeSpan.FromSeconds((vars.FrameCount.Current - vars.SubtractFrames) / 60f); } isLoading { return true; } exit { vars.CancelSource.Cancel(); } shutdown { vars.CancelSource.Cancel(); } // v1.0.1 21-Mar-2024