 |
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: 3
|
Posted: Tue Jun 30, 2026 7:14 am Post subject: Lua Breakpoints for Pausing on File Open/Close |
|
|
Ended up developing out the script in my previous post into a more polished and versatile form, and wanted to share with the community in case someone else ever has a similar need.
The purpose of this script is to log and optionally pause execution when the attached game opens or closes a file with a filename that matches a relevant pattern, e.g. so that you can trigger something like Branch Mapper, without having it slow down the UI interaction needed to actually get a game to start loading things --- to make it easier to troubleshoot behaviors that occur while loading a save file or modded asset.
This works by attaching a scripted breakpoint to the underlying ntdll NtCreateFile and NtClose low-level API functions; with a bit of hacked-together data structure traversal and bookkeeping to capture both the relevant arguments and the return values. This should in theory also collect usages of the higher level kernel32 API functions, though I've not tested this. With some modification this could probably also be made to collect calls to the equivalent 64-bit API; that's left as an exercise for the viewer.
Here's the code, hopefully someone finds this useful:
| Code: | --- Copyright (C) 2026 Anson Mansfield
--- Released under the MIT License.
--- SPDX-License-Identifier: MIT
---@class FileTracker
FileTracker = {}
--- Filename patterns to track, and their logging/pause behaviors.
--- @type {pattern: string, on_open: integer?, on_opened: integer?, on_close: integer?}[]
FileTracker.track = {
{pattern = "%.png$", on_open = 1, on_close = 0}, --- log open for PNG images
{pattern = "%.vmf$", on_open = 2, on_close = 1}, --- pause on open / log close for valve map files
{pattern = "%.ogg$", on_open = 1, on_close = 1}, --- log open/close for OGG sound effects
{pattern = "$", on_open = 1, on_close = 0}, -- log every file opened
}
--- Table from currently-open file handles to their filenames.
--- @type {[integer]: string}
FileTracker.open = {}
--- Table from pending return breakpoint addresses to the corresponding captured arguments from the call.
--- @type {[integer]: {phandle: integer, filename: string}}
FileTracker.pendingOpen = {}
--- @protected
function FileTracker.onNtCreateFileCall()
local returnAddress = readInteger(ESP)
--- arg 1, out pointer to handle
local phandle = readInteger(ESP + 0x04)
if phandle == 0 then
return 0
end
--- arg 3, in pointer to object attribute struct
local objectAttributes = readInteger(ESP + 0x0C)
if objectAttributes == 0 then
return 0
end
local objectName = readInteger(objectAttributes + 0x08)
if objectName == 0 then
return 0
end
local length = readSmallInteger(objectName)
local buffer = readInteger(objectName + 0x04)
if buffer == 0 then
return 0
end
local filename = readString(buffer, length, true)
for _, entry in ipairs(FileTracker.track) do
if string.find(filename, entry.pattern) then
FileTracker.pendingOpen[returnAddress] = {
phandle = phandle,
filename = filename,
}
debug_setBreakpoint(returnAddress, FileTracker.onNtCreateFileReturn)
break
end
end
return 0
end
--- @protected
function FileTracker.onNtCreateFileReturn()
local pending = FileTracker.pendingOpen[EIP]
if pending == nil then
return 0
end
FileTracker.pendingOpen[EIP] = nil
debug_removeBreakpoint(EIP)
local filename = pending.filename
local phandle = pending.phandle
local result
if EAX ~= 0 then
result = string.format("<errno %d>", EAX)
else
local handle = readInteger(phandle)
FileTracker.open[handle] = filename
result = string.format("%d", handle)
end
for _, entry in ipairs(FileTracker.track) do
if string.find(filename, entry.pattern) then
FileTracker.modeBehavior(
entry.on_open,
string.format("open(\"%s\") -> %s", filename, result)
)
break
end
end
return 0
end
--- @protected
function FileTracker.onNtCloseCall()
local handle = readInteger(ESP + 0x04)
local filename = FileTracker.open[handle]
if filename == nil then
return 0
end
FileTracker.open[handle] = nil
for _, entry in ipairs(FileTracker.track) do
if string.find(filename, entry.pattern) then
FileTracker.modeBehavior(
entry.on_close,
string.format("close(%d) -- \"%s\"", handle, filename)
)
break
end
end
return 0
end
--- @protected
function FileTracker.modeBehavior(modeValue, message)
if modeValue >= 1 then
print(message)
end
if modeValue >= 2 then
pause()
print("pause() -- resume with unpause()")
end
end
--- @public
function FileTracker.begin()
debug_setBreakpoint(getAddress("ntdll.NtClose"), FileTracker.onNtCloseCall)
debug_setBreakpoint(getAddress("ntdll.NtCreateFile"), FileTracker.onNtCreateFileCall)
end
FileTracker.begin()
|
|
|
| Back to top |
|
 |
Corroder Grandmaster Cheater Supreme
Reputation: 75
Joined: 10 Apr 2015 Posts: 1672
|
Posted: Wed Jul 01, 2026 9:58 pm Post subject: |
|
|
Thread : Combining File Tracking (CCTV) with AOB Injection for Maximum One-Click Trainer Efficiency
Hey everyone,
I wanted to share an interesting perspective on optimization. Most of us are used to the standard, tedious process of finding memory addresses (like character HP) manually—doing a First Scan, changing the value in-game, and spamming Next Scan. It honestly feels like looking for a needle in a haystack every single time the game restarts.
However, if we hook into the file-tracking logic (like utilizing a "CCTV" script on the ntdll API) and combine it with AOB Injection, we can instantly streamline that whole process into a single click. It's pretty much like hitting F12 (Inspect Element) on a web browser, but for games.
The workflow is simple: once the tracker script intercepts the exact "gateway" instruction where assets are loaded or character states are handled, we can instantly grab the unique byte signature (Array of Bytes) of that instruction. This allows us to write a quick, one-click automated script that bypasses manual scanning entirely. Every time the game launches, the script dynamically hooks the right function out of the box.
I think combining these two approaches makes trainer development way more efficient and dynamic.
What are your thoughts on this?
_________________
Stealing Code From Stolen Code...
And Admit It.. Hmmm....Typically LOL |
|
| 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
|
|