 |
Cheat Engine The Official Site of Cheat Engine
|
| View previous topic :: View next topic |
| Author |
Message |
AJMansfield How do I cheat?
Reputation: 0
Joined: 24 Jun 2026 Posts: 2
|
Posted: Wed Jun 24, 2026 7:05 am Post subject: How to trigger Branch Mapper from Lua? |
|
|
How can I trigger Branch Mapper to start from a Lua script?
Background: I'm currently developing cheats for a game that seems to perform a sort of 'normalization' each time it loads saves from disk, that wipes away some types of previously-cheated progress and makes everything more complicated. There's a lot of other things that it also does during save load, though, and trying to locate this specific normalization routine in order to bypass it is as yet an unsolved problem.
I can't trigger Branch Mapper before I start the save load, as the single-stepping causes the UI to chug to the point that I'm then literally unable to even click the 'load' button, but the load process itself is so fast on modern hardware that it's impossible for me to trigger Branch Mapper on a normal save file.
I've managed to get a bit of success by synthesizing a save files that includes a bunch of extra junk data in 'comment' directives to slow down the save load process, hitting 'load', starting Branch Mapper, and then letting it run overnight. I messed up the DLL load order that time, though, and a save with this junk data is always going to trigger this normalizer, so I can't map out the alternative path that bypasses this normalizer using this approach.
Therefore, I want to trigger Branch Mapper to start directly from a lua-scripted breakpoint directly at the underlying windows API call that opens the save file for reading.
So far, I've managed to cobble together a function for setting a single-shot breakpoint when opening a particular named file. However, I'm at a loss for how to actually trigger Branch Mapper to start mapping branches from this breakpoint:
| Code: |
function startBranchMapperOnCreateFileA(targetFile)
local createFileA = getAddress("kernel32.CreateFileA")
local function onCreateFileA()
if EIP ~= createFileA then
return 0
end
local filenamePtr = readInteger(ESP + 4)
local filename = readString(filenamePtr)
if filename then
print("CreateFileA: "..filename)
if string.find(string.upper(filename), string.upper(targetFile), 1, true)
then
print("Target file opened")
debug_removeBreakpoint(createFileA)
-- How to trigger branch mapper to start?
return 1
end
end
return 1
end
debug_setBreakpoint(createFileA, onCreateFileA)
end
|
As I rubber-duck this problem out into this post, it becomes apparent that it's actually adequate for me to just "return 0" when the filename matches to get the debugger to just not automatically resume execution; then I can manually start Branch Mapper while the process is paused in the debugger.
Either way, though, the question still occurred to me. Hopefully I'll not need its answer, when it turns out to be even more complicated than I currently imagine and I do actually end up needing to script this so I can run this mapping process a dozen times to gather more data. But perhaps some future user will be helped, at least by finding this question and the do-B-not-A answer I found.
How can I trigger Branch Mapper to start from a Lua script?
|
|
| Back to top |
|
 |
Dark Byte Site Admin
Reputation: 474
Joined: 09 May 2003 Posts: 25952 Location: The netherlands
|
Posted: Wed Jun 24, 2026 2:29 pm Post subject: |
|
|
hmm, not very easily, but theoretically it's possible.
There's no build in lua code that does this automatically for you, but you 'could' execute the GUI controls that lead to the branch mapper dialog, parse the threadlist for the thread you're interested in, uncheck all other checkboxes, and execute the OnClick code of the start button. (requires form create notifications and timers)
but yeah, it's not really a neat solution and you'd first have to resume the current breakpoint before it'd work. So you'll end up placing an infinite loop at your breakpoint location (eb fe), start a timer that does all this above, resume from the current breakpoint, and let the timer run, in which, after the branch mapper has started, you restore the original code so it the thread you broke on continues without interfering with the debugger.
And one more annoying thing, because why not, the infinite loop you first placed will be stored in the branch mapper results, so you'll have to remove that one entry. (maybe lua command 'pause/unpause' might work instead of a infinite loop)
_________________
Tools give you results. Knowledge gives you control.
Like my help? Join me on Patreon so i can keep helping |
|
| Back to top |
|
 |
AJMansfield How do I cheat?
Reputation: 0
Joined: 24 Jun 2026 Posts: 2
|
Posted: Thu Jun 25, 2026 7:03 am Post subject: |
|
|
| Quote: | | (maybe lua command 'pause/unpause' might work instead of a infinite loop) |
Ok thank you so much for this! That's exactly the insight I needed to make this work.
As I suggested I'd try in my original post, I tried configuring my file-open breakpoint to just break into the debugger (with a `return 1`), where I'd then manually start Branch Mapper and then resume from that breakpoint. But resuming the breakpoint interfered with Branch Mapper's single-stepping.
Having my breakpoint issue a `pause()` after the filename match worked perfectly, though! Allowing me to start Branch Mapper and then manually run `unpause()` in the console.
Also needed to make one other update, since it turns out the game uses the raw ntdll.NtCreateFile function directly rather than kernel32.CreateFileA. Which in theory should make this script even more universally-applicable as AFAIK the kernel32 file functions are just wrappers for the ntdll functions anyway.
Here's the script I ended up with:
| Code: | NtCreateFileA = getAddress("ntdll.NtCreateFile")
OnCreateFilePatterns = {
"%.dsn$",
"%.fst$",
}
function OnNtCreateFile()
if EIP ~= NtCreateFileA then
return 0
end
local objectAttributes = readInteger(ESP + 0x0C)
if objectAttributes == 0 then
return 0
end
local objectName = readInteger(objectAttributes + 8)
if objectName == 0 then
return 0
end
local length = readSmallInteger(objectName)
local buffer = readInteger(objectName + 4)
if buffer == 0 then
return 0
end
local filename = readString(buffer, length, true)
print("Opening:", filename)
for i, p in ipairs(OnCreateFilePatterns) do
if string.find(filename, p) then
print("Paused:", getOpenedProcessID())
pause()
print("Resume with `unpause()`.")
return 0
end
end
return 0
end
function SetOnNtCreateFile()
debug_setBreakpoint(NtCreateFileA, OnNtCreateFile)
end
|
BTW, is there a set of language server annotation stubs somewhere for providing in-IDE documentation for CheatEngine's lua API? I've done some work before with creating annotation stubs to help with authoring lua mods for Baba Is You, so if nobody's created such a thing yet I might see about contributing a set of LSP stubs upstream (or, some scripts for generating them from the underlying source).
EDIT: I found a set of LSP stubs: github dungbeest/CheatEngine-LuaEnvironment (can't post URLs yet...)
|
|
| Back to top |
|
 |
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum You cannot attach files in this forum You can download files in this forum
|
|