This LUA function will fetch you the address to a static pointer from an AOB search for code that reads from it.
You can pass it a string of bytes to AOB search, or you can pass it a whole table of multiple AOB strings to search for. You can optionally provide a table of names for the pointers, for example to automatically register a symbol for each address found. This way, you don't need to tediously update all base pointers for your table by hand when an update changes those; you only need to check the AOB searches once.
But what does this do exactly?
Let's say a game fetches the base pointer for the player from a static location with code like this:
Code: Select all
...
48 8B 05 57E2A90D - mov rax,[Game.exe+F6FA348]
48 8B 48 68 - mov rcx,[rax+68]
8B 46 20 - mov eax,[rsi+20]
39 41 18 - cmp [rcx+18],eax
...
Tell the function that the address comes at three bytes after the start of the instruction, and pass an AOB string to search for like this:
playerPointer = getPointerFromCodeAOB( 3, "48 8B 05 ?? ?? ?? ?? 48 8B 48 68 8B 46 20" )
The function will return the address Game.exe+F6FA348 as a string and also print it to LUA console.
Or maybe an AOB search is easier when the instruction with the pointer isn't the first one? Consider a case like this:
Code: Select all
...
66 0F5A C8 - cvtpd2ps xmm1,xmm0
0F57 C0 - xorps xmm0,xmm0
F3 0F5D 0D BC6E8005 - minss xmm1,[Game.exe+75DCC44]
...
local aob = "66 0F 5A C8 0F 57 C0 F3 0F 5D 0D ?? ?? ?? ??"
getPointerFromCodeAOB( 4, aob, "Health Cap", 7, true, true )
The minss instruction is 4 bytes before the pointer this time, then we search for an AOB with the name "Health Cap" and tell the function to start looking for the pointer instruction 7 bytes from the start of the search to skip the first two instructions.
We set isSilent to true and registerSymbol to true, so the result isn't posted to the console but is immediately registered as a symbol called "Health Cap" in the table.
You can even search for multiple pointers in one go by passing it tables like this:
Code: Select all
local aobList = {
"48 8B 15 ?? ?? ?? ?? 48 85 D2 74",
"48 8B 05 ?? ?? ?? ?? 80 78 62 00 75",
"48 8B 05 ?? ?? ?? ?? 48 85 C0 74",
"48 8B 15 ?? ?? ?? ?? E9 ?? ?? ?? ?? 48 8B 15"
}
local aobNames = {
"Player",
"Inventory",
"Weapons",
"Quests"
}
I tried to make it as robust as possible, so the function throws errors if something goes wrong. It will by default scan the currently attached process module, but you can specify a different one too as the processname argument.
How to use or install
Either:
- Create a .lua file in the ..\Cheat Engine\autorun\ folder, then copy & paste the function into it for local use
- Copy & paste the function into the LUA script of a certain table if you want to distribute a table that uses the function for pointer updates.
Code: Select all
--- Get a static pointer from an executable code AOB,
-- for example a "mov rax, [pointer]" instruction
-- can take single AOB strings or an array of AOBs
-- getPointerFromCodeAOB( ins_offset, aobList, OPTIONAL aobNames, OPTIONAL aob_offset, OPTIONAL isSilent, OPTIONAL registerSymbol, OPTIONAL modulename )
-- @param ins_offset: number of bytes from the start of the instruction to the pointer, e.g. for mov rax,[pointer] it's 3 bytes
-- @param aobList: one string of AOB bytes or an array of multiple strings
-- @param aobNames: string or array of names for the AOB pointers
-- @param aob_offset: number of bytes from start of AOB to start of pointer instruction, default 0
-- @param isSilent: suppresses non-error prints to console if true, default false
-- @param registerSymbol: registers a symbol for all AOBs found if true, default false
-- @param modulename: name of the module to search in, formatted like "process.exe"; default is currently attached process module
-- @return string or array of strings with module pointers, returns empty string when not found and false on error
function getPointerFromCodeAOB(...)
if getOpenedProcessID() == 0 then print( "ERROR: Not attached to process!\n" ) return false end
local args = {...}
if #args < 2 then print( "ERROR: getPointerFromCodeAOB needs at least 2 arguments!\n" ) return false end
local aobList, result = {}, {}
if (type(args[2]) == "table") then
aobList = args[2]
else
aobList = {args[2]}
end
local aobNames = args[3]
if (type(args[3]) ~= "table") then
aobNames = {aobNames}
end
for i=1,#aobList do
aobNames[i] = aobNames[i] or 'Pointer '..i
end
local offins = args[1]
local offaob = args[4] or 0
local isSilent = args[5] or false
local doSymbol = args[6] or false
local procname = ( type(args[7])~='string' or (args[7] or '')=='' ) and process or args[7]
if args[7] then
local moduleList = enumModules()
local moduleFound
for _, v in pairs(moduleList) do
if v.Name == procname then moduleFound = true break end
end
if not moduleFound then print(string.format( "ERROR: Specified modulename %s not found!\n",procname )) return false end
end
local pbase = readInteger(procname) and getAddress(procname)
local pend = pbase + getModuleSize(procname)
if type(offins) ~= "number" or type(offaob) ~= "number" then print ( "ERROR: offsets for getPointerFromCodeAOB must be a number! \n" ) return false end
if not isSilent then print(string.format( "Fetching %d pointer(s) for %s ...\n",#aobList,procname )) end
for i = 1, #aobList do
local aob = AOBScan(aobList[i],"+X*C*W")
if aob then
local j = 0
while j < aob.Count do
local adr = getAddressSafe(aob[j])
if adr < pbase or adr >= pend then aob.delete(j) else j = j + 1 end
end
end
if not aob or aob.Count < 1 then
result[i] = ""
if not isSilent then print(string.format( "AOB #%d (%s) not found!",i,aobNames[i] )) end
else
if (aob.Count > 1) and not isSilent then print(string.format("WARNING: %d matches for AOB #%d (%s)! Using first result.",aob.Count,i,aobNames[i])) end
local instruct = getAddressSafe(aob[0])+offaob
local instructSize = getInstructionSize(instruct)
local distance = readInteger( getAddressSafe(instruct)+offins )
local address = ( instruct + instructSize + distance ) - pbase
result[i] = string.format("%s+%X",procname,address)
if not isSilent then print(string.format( "%s: %s+%X",aobNames[i],procname,address) ) end
if doSymbol then registerSymbol( aobNames[i], string.format("%s+%X",procname,address) ) end
end
end
if #aobList == 1 then result = result[1] end
return result
end
edit 20240920: renamed processname parameter to modulename for clarification, added some sanity checking to that parameter to make sure you pass a valid module name.
edit 20240920-2: Fixed an error in module scanning: changed for-loop to a while-loop, since LUA has static for-loops that don't handle shrinking tables well.