|
Cheat Engine The Official Site of Cheat Engine
|
View previous topic :: View next topic |
Author |
Message |
gibberishh Cheater Reputation: 1
Joined: 30 Aug 2021 Posts: 37
|
Posted: Wed Jul 20, 2022 11:15 pm Post subject: Basic 32-bit Lua Function Call from Assembly |
|
|
I've recently finished wrestling with Lua function calls in a 32-bit environment and the biggest part of the struggle for me was a lack of detailed documentation. So maybe this tutorial (or rather discussion) will be useful to some. I intend to be as detailed as possible and will also describe some mistakes I made or wrong turns I took so you can avoid the same. I'll try and take you on the whole journey so maybe you can learn from the evolution of the code. Note that for now this only addresses passing of a single parameter to Lua from asm.
Game: This was implemented on a version of The Hell 2.
Goal
The game is broken up into maps. Every time a map loads, the game loads a list of all the monsters that inhabit that map. My player has 3 Summon spells which summon 3 generic creatures (let's call them Dog, Cat, Pig). However, if I can get access to the list of monsters, I can replace the creature Id of my sidekick with the monster Id, thus summoning a copy of The Boss to fight for me.
- Minor Goal 1: Get assembly to pass the count of monsters into Lua and update a memrec-dropdown list with generic values (Monster 1, Monster 2, etc.). The list has to be updated on every map load.
- Minor Goal 2: Map 1 has 15 monster Ids and I set Id 14 to be my sidekick. I proceed to Map 2. Map 2 offers up only 10 creatures. In this case I need to reset my memrec value to a safe number because 14 will cause the game to crash.
- Minor Goal 3: Change the dropdown list to display actual names of monsters.
- Minor Goal 4: Summoning certain monsters causes the game to crash -- they are in perpetual development and haven't implemented all creatures properly. So I want to filter out buggy creatures.
Game Data & Code Structure
- I found the function where the game enumerates the list of monsters. This is where I will inject code for our Lua function call.
- Within this function is a loop that goes through all the creature ids (my generic friendly creatures as well as the monsters).
- The game's summon mechanism is not simply calling the monster id. It is instead calling creatures based on an offset from an address. This is how the creature data is stored:
Code: | TH2.exe+Address+($150*0) = DogId
TH2.exe+Address+($150*1) = CatId
TH2.exe+Address+($150*2) = PigId
TH2.exe+Address+($150*3) = Monster1Id
TH2.exe+Address+($150*4) = Monster2Id |
So my memrec needs to hold the counter values (0,1,2,3,4), not the monster ids. The game's code itself works with the counter values and handles the *$150 part on its own. This is super for Minor Goal 1. Howver, by the time we get to Minor Goal 3, it doesn't matter whether we need counter or id, both are possible to get.
Code for Minor Goal 1:
The first thing I want to do is make sure I get the most basic functionality correct:
- Write basic asm to aobscanmodule() for the counter, and register this as a symbol that can be loaded into a memrec.
- Use this counter to populate a dropdownlist.
- Include a 5-second timer to continuously update the list while the cheat is active. That way I can make sure that the list is updating properly on map loads.
I'm not providing code for this basic setup because this much should be straightforward to anyone reading this.
Now to remove the timer and pass the counter to my Lua function as a parameter:
Code: | {$lua}
function PopulateCreatureList(CreatureCount)
local friendly=[[0:Dog
1:Cat
2:Pig]] -- the list of my generic friendly summons which are always safe to summon and always available.
local clist = getAddressList().getMemoryRecordByDescription("Creature")
local creatures = friendly
for i=3,CreatureCount-1 do -- starting loop at 3 because I don't want friendlies to be named as Monster X
creatures = creatures .. "\n" .. i .. ":Monster " .. (i-3)
end
clist.DropDownList.Text = creatures
clist.DropDownReadOnly = true
clist.DropDownDescriptionOnly = true
clist.DisplayAsDropDownListItem = true
end
{$asm}
[ENABLE]
LuaCall(PopulateCreatureList(0)) // Initialize the dropdown list. Only the friendlies will be displayed until map load.
LoadLibrary(luaclient-i386.dll) // This library is required for Lua to be able to interface with asm
LuaCall(openLuaServer('CELUASERVER')) // Necessary
CELUA_ServerName: // Necessary
db 'CELUASERVER',0 // Any name you wish, doesn't matter, but some server name is necessary
aobscanmodule(sumcount,$process,39 35 * * * * 0F 8E * * * * 6B CB 31) // injection point
alloc(orisumcount,6) // holds the first 6 bytes for the DISABLE script
label(CreatureCount) // WANT TO PASS THIS TO LUA
registersymbol(sumcount,orisumcount)
alloc(newmem,500)
label(code)
label(return)
alloc(luaFuncName,256) // holds the function name that you want to call
alloc(luaFuncId,4) // Lua generates an id for the function, which we will require later to call it
label(hasrefid) // code executed only if Lua has assigned an Id to the function
luaFuncName:
db 'PopulateCreatureList',0 // set the name of the function we want to call
orisumcount:
readmem(sumcount,6) // remmber the first 6 bytes of the scan for the DISABLE script
newmem:
mov [CreatureCount],bl // bl holds the counter. Move it to the CreatureCount label so we can access it for the Lua function
push edx // The Lua function caller functions overwrite several registers
push ecx // Always push all required registers so that they can be restored
push eax // once you are done with the Lua part, or your game could crash.
mov eax,[luaFuncId]
test eax,eax // Checks if the function has been assigned an Id
jne hasrefid // If Id not 0, go and call the Lua function
push luaFuncName // Else, generate and assign an Id
// In 32-bit, you need to push the value on to the stack for the CELUA function to be able to access it.
call CELUA_GetFunctionReferenceFromName // This assigns an Id and stores it in eax
mov [luaFuncId],eax // and this copies over the Id to our allocated memory region so that we can use it again and again
hasrefid:
// According to celua.txt, CELUA_ExecuteFunctionByReference expects 4 parameters:
// int refid, int paramcount, PVOID *parameters, BOOL async
// Note that these need to be fed in the REVERSE order
// Like with the Id generator, this function also works with values from the stack, so let's push them:
push 0 // Synchronous = 0, Async = 1. If you use 1, the UI probably won't update
push CreatureCount // The parameter we want to pass to our function
push 1 // The number of parameters we are passing
push eax // The Id of the Lua function. It could've been copied to eax from [luaFuncId] OR from the Id generator
call CELUA_ExecuteFunctionByReference // This will call the function and (in our case) update the dropdown list
pop eax // Several registers are overwritten at this point
pop ecx // Remember to pop back the values stored earlier
pop edx // Always leave your stack (and preferably registers and flags) in the state in which you entered newmem
code:
reassemble(sumcount) // The actual code that was supposed to execute at our injection point
jmp return
CreatureCount:
db #2 // The default (friendly) number of creatures we want in our list (zero-based in our case)
sumcount:
jmp newmem
nop
return:
[DISABLE]
sumcount:
readmem(orisumcount,6) // Restore the original code and clean up
unregistersymbol(sumcount,orisumcount)
dealloc(orisumcount,newmem,luaFuncName,luaFuncId) |
We now have a list that updates on every map load without a timer! Hooray.
Minor Goal 2:
Ensure that a safe creature is always set for summoning. If the memrec value is out of bounds of the map's list of creatures, the game will crash. For this, I'm going to slightly modify the Lua function:
Code: | {$lua}
function PopulateCreatureList(CreatureCount)
local friendly=[[0:Dog
1:Cat
2:Pig]]
local clist = getAddressList().getMemoryRecordByDescription("Creature")
local creatures = friendly
for i=3,CreatureCount-1 do creatures = creatures .. "\n" .. i .. ":Monster " .. (i-3)
end
-- CODE FOR SAFE CREATURE SELECTION
if tonumber(clist.Value) > CreatureCount then
clist.Value = 2 -- A safe creature to default to if current value is out of bounds on a new map
end
-- END CODE FOR SAFE CREATURE SELECTION
clist.DropDownList.Text = creatures
clist.DropDownReadOnly = true
clist.DropDownDescriptionOnly = true
clist.DisplayAsDropDownListItem = true
end |
Lessons for me:
So far, everything was breezy. Okay, I struggled because it was my first time, but I needed to pass only a single value to the Lua function.
- It took me a while (and some help from this forum) to figure out that the 32-bit dll functions require parameters to be pushed on to the stack.
- It took me even more time to figure out that the parameters need to be supplied to the function caller in reverse order.
- Finally, I had to figure out which registers were being overwritten by the various functions so that I could restore them before returning to the game code.
Once these were taken care of, everything worked like a charm.
Minor Goal 3:
Now this was the big one. How could I pass a list of parameters (monster ids) to my Lua function so that I might properly name the creatures in the dropdown list? Frankly, Gocl knows, ParkourPenguin knows and DarkByte knows. I'll be damned if I understand it. You are supposed to pass an address which holds a list of your parameters and then parse that within Lua. Which is all Greek to me.
I got extremely lucky instead. The game's code already has a loop which goes through each monster id. And it's not very far from my current injection point. So I'm going to use that loop to pass one monster at a time to my function. Sucks to be a newb, but that's me.
Here's the code that does this. I'll only point out the major differences:
Code: | {$lua}
local creaturecount = 0 -- Maintain a counter in Lua instead of having asm pass it
local lastCreature = 2 -- This will store the value of the last-selected creature. Here it's defaulted to 2
-- Remember that now the function is called in a loop with the counter starting from 0.
-- Checking for out-of-bounds against every tick will always reset the selection.
-- But we want to reset it only if it is out of bounds.
function PopulateCreatureList(cid)
local clist = getAddressList().getMemoryRecordByDescription("Summon Creature")
local creatures = clist.DropDownList.Text -- I also hard-set the friendly creatures as the ddlist's options
if(cid == 109) then -- Dog's MonsterId (or CreatureId). We are checking if this is the first creature being passed in
lastCreature = tonumber(clist.Value) -- Store the value of the currently selected creature
creaturecount = 0 -- Reset the counter
clist.DropDownList.Text = ''
creatures = clist.DropDownList.Text -- Reset the ddlist options
else
creaturecount = creaturecount + 1 -- Increment the counter
creatures = creatures .. creaturecount .. ":" .. monsters[cid] -- monsters[] is an array (table) that stores the Id:Name of monsters
end
clist.Value = (lastCreature < creaturecount) and lastCreature or 2 -- Check if current value out of map bounds
clist.DropDownList.Text = creatures
clist.DropDownReadOnly = true
clist.DropDownDescriptionOnly = true
clist.DisplayAsDropDownListItem = true
end
{$asm}
[ENABLE] // Removed the Lua call to initialize the dropdown since default values are stored in the control's properties.
LoadLibrary(luaclient-i386.dll)
LuaCall(openLuaServer('CELUASERVER'))
CELUA_ServerName:
db 'CELUASERVER',0
aobscanmodule(sumcount,$process,8B 45 FC 89 7D F8)
label(CreatureCount)
registersymbol(sumcount)
alloc(newmem,500)
label(code)
label(return)
alloc(luaFuncName,256)
alloc(luaFuncId,4)
label(hasrefid)
luaFuncName:
db 'PopulateCreatureList',0
newmem:
push ebx // Note that I'm pushing more registers on to the stack
push edi // Learnt the hard way to push everything to be safe
push edx // And even now I'm not pushing a couple of registers
push ecx // Guess I'm living on the edge
push eax
mov [CreatureCount],ecx // ecx holds the MonsterId. We no longer need bl
// I got lucky here, but it was just as simple to write code to find
// the monster id because it just so happens that edi holds the base
// address and bl still holds the counter. so base+bl*$150 would have
// given me the monster Id.
// I know because that's what I did first, until I noticed that ecx
// actually already held the value I needed!! (sheepish grin)
// Damn, I should've renamed the label to CreatureId. Grrr
mov eax,[luaFuncId]
test eax,eax
jne hasrefid
push luaFuncName
call CELUA_GetFunctionReferenceFromName
mov [luaFuncId],eax
hasrefid:
push 0
push CreatureCount
push 1
push eax
call CELUA_ExecuteFunctionByReference
pop eax
pop ecx
pop edx
pop edi
pop ebx // Reset all registers
code:
mov eax,[ebp-04] // This was the code I injected at
mov [ebp-08],edi // Just spitting it back out here
jmp return
CreatureCount:
db #109 // Dog's CreatureId
sumcount:
jmp newmem
nop
return:
[DISABLE]
sumcount:
db 8B 45 FC 89 7D F8
unregistersymbol(sumcount)
dealloc(newmem,luaFuncName,luaFuncId)
{$lua}
-- On disable, reset the dropdown list options to only the friendlies.
getAddressList().getMemoryRecordByDescription("Summon Creature").DropDownList.Text = [[0:Dog
1:Cat
2:Pig]] |
Minor Goal 4:
I know which creatures are safe to call. I have a list (actually an array, which in Lua is actually a table). From Minor Goal 3 you can see that monsters[] holds a list of all the Id:Name of monsters I am comfortable calling to do my dirty work. I have deleted the buggy creatures from that list. So here's how I modified my Lua function:
Code: | function PopulateCreatureList(cid)
...
if(cid == 109) then
...
else
creaturecount = creaturecount + 1
if monsters[cid] then -- if the creatureid exists in my list
creatures = creatures .. creaturecount .. ":" .. monsters[cid] -- then append it to the ddlist options
end
end
...
end |
There's probably a much more efficient way of getting the monster ids in one go from assembly into Lua, but until I properly learn and understand how to pass a reference to a list of parameters, this will have to do. Luckily the loop usually runs only 10-20 times per map load or this would be quite bad.
This tutorial illustrates how to call a Lua function with a single parameter in a 32-bit environment and what pitfalls to avoid -- because that was quite confusing for me in the first go. Hope someone finds it useful.
_________________
It's not cheating. It's playing by my rules. |
|
Back to top |
|
|
Dark Byte Site Admin Reputation: 460
Joined: 09 May 2003 Posts: 25333 Location: The netherlands
|
Posted: Thu Jul 21, 2022 4:46 am Post subject: |
|
|
If you wanted to pass a list of parameters you could allocate some space on the stack (sub esp,paramcount*4) , then fill in [esp+0, esp+4, esp+8,... the parameters
and then give as parameter the address where the parameters are stored. Of course, the pushing of parameters is going to mess up esp, so you may want to copy the value of esp to a register before you start pushing the parameters
don't forget to add esp,paramcount*4 else you're going to have issues
For those who aren't confident in their assembly skills, (registers and stack can be difficult) then you can alternatively opt for {$luacode} which will take most of the headache away from you. (At the cost of a slightly less efficient transition between code and lua, as it saves everything before and restores everything after)
e.g the final script I think I can rewrite into:
Code: |
{$lua}
local creaturecount = 0 -- Maintain a counter in Lua instead of having asm pass it
local lastCreature = 2 -- This will store the value of the last-selected creature. Here it's defaulted to 2
-- Remember that now the function is called in a loop with the counter starting from 0.
-- Checking for out-of-bounds against every tick will always reset the selection.
-- But we want to reset it only if it is out of bounds.
function PopulateCreatureList(cid)
local clist = getAddressList().getMemoryRecordByDescription("Summon Creature")
local creatures = clist.DropDownList.Text -- I also hard-set the friendly creatures as the ddlist's options
if(cid == 109) then -- Dog's MonsterId (or CreatureId). We are checking if this is the first creature being passed in
lastCreature = tonumber(clist.Value) -- Store the value of the currently selected creature
creaturecount = 0 -- Reset the counter
clist.DropDownList.Text = ''
creatures = clist.DropDownList.Text -- Reset the ddlist options
else
creaturecount = creaturecount + 1 -- Increment the counter
--creatures = creatures .. creaturecount .. ":" .. monsters[cid] -- monsters[] is an array (table) that stores the Id:Name of monsters
if monsters[cid] then -- if the creatureid exists in my list
creatures = creatures .. creaturecount .. ":" .. monsters[cid] -- then append it to the ddlist options
end
end
clist.Value = (lastCreature < creaturecount) and lastCreature or 2 -- Check if current value out of map bounds
clist.DropDownList.Text = creatures
clist.DropDownReadOnly = true
clist.DropDownDescriptionOnly = true
clist.DisplayAsDropDownListItem = true
end
{$asm}
[ENABLE]
aobscanmodule(sumcount,$process,8B 45 FC 89 7D F8)
registersymbol(sumcount)
alloc(newmem,500)
label(code)
label(return)
newmem:
{$luacode cid=ECX}
synchronize(function() --use synchronize as GUI controls are extensively accessed by PopulateCreatureList
PopulateCreatureList(cid)
end)
{$asm}
code:
mov eax,[ebp-04] // This was the code I injected at
mov [ebp-08],edi // Just spitting it back out here
jmp return
sumcount:
jmp newmem
nop
return:
[DISABLE]
sumcount:
db 8B 45 FC 89 7D F8
unregistersymbol(sumcount)
dealloc(newmem,luaFuncName,luaFuncId)
{$lua}
-- On disable, reset the dropdown list options to only the friendlies.
getAddressList().getMemoryRecordByDescription("Summon Creature").DropDownList.Text = [[0:Dog
1:Cat
2:Pig]]
|
_________________
Do not ask me about online cheats. I don't know any and wont help finding them.
Like my help? Join me on Patreon so i can keep helping |
|
Back to top |
|
|
gibberishh Cheater Reputation: 1
Joined: 30 Aug 2021 Posts: 37
|
Posted: Thu Jul 21, 2022 9:04 pm Post subject: |
|
|
Dark Byte wrote: | If you wanted to pass a list of parameters you could allocate some space on the stack (sub esp,paramcount*4) , then fill in [esp+0, esp+4, esp+8,... the parameters
and then give as parameter the address where the parameters are stored. Of course, the pushing of parameters is going to mess up esp, so you may want to copy the value of esp to a register before you start pushing the parameters
don't forget to add esp,paramcount*4 else you're going to have issues |
Right now, my biggest confusion is how to define my Lua function, and how to access the passed parameters within it. From everything I've read online, I can't find a Lua function to take multiple parameters from asm. Maybe I suck at searching.
1. Does my function need to be defined as:
- function tester(vara, varb, varc)
- function tester(paramaddress) -- maybe the first param at that address is the count?
- function tester(paramcount, paramaddress)
- function tester(...)
2. And then how do I access the passed parameters (a variable number of them) within the function? Is it through args[#], paramaddress+(paramcount*4), or some other method?
A sample script for 32-bit would be tremendously helpful -- the Lua function can just print() the param values as integers in a loop, no need to use my ddlist code as the starting point.
* I can modify my asm to store ecx at any location required. That part is easier. This is how I'd do it:
Code: | push edx
push eax
mov edx,MonsterIds // address of label or allocated memory region
movzx eax,bl // counter
dec eax // bl starts from 1, this ensures 0 base
mov [edx+eax*4],ecx // ecx holds 1 monster id, I can spit it out every 4 bytes
pop eax
pop edx | In my example, MonsterIds would be 'filled' by code injected within the asm loop (Minor Goal 3). The Lua function would then be called outside the loop (Minor Goal 1).
3. Do I need to mov [MonsterIds] to [esp+something]? I don't quite understand that part, but if I have working code to read, I'm confident I'll get to understanding it eventually.
Once I've built MonsterIds, I can push the value of bl (movzx-ed into a full register) as my paramcount and MonsterIds as my paramaddress before calling the Lua function. But what to do in the Lua part?
Thanks.
_________________
It's not cheating. It's playing by my rules. |
|
Back to top |
|
|
Dark Byte Site Admin Reputation: 460
Joined: 09 May 2003 Posts: 25333 Location: The netherlands
|
Posted: Fri Jul 22, 2022 10:30 am Post subject: |
|
|
the function(...) methid works and then reference the .n field of ... for the count
though it may be easier to just give one address as parameter and then use readInteger/related to parse the data you have formatted in the way you like
_________________
Do not ask me about online cheats. I don't know any and wont help finding them.
Like my help? Join me on Patreon so i can keep helping |
|
Back to top |
|
|
gibberishh Cheater Reputation: 1
Joined: 30 Aug 2021 Posts: 37
|
|
Back to top |
|
|
Dark Byte Site Admin Reputation: 460
Joined: 09 May 2003 Posts: 25333 Location: The netherlands
|
Posted: Fri Jul 22, 2022 1:42 pm Post subject: |
|
|
the parameter list is a list of integers.
If parametercount is 5, then it expects a list of 5 integers at the parameter address (5 times 4 byte) if you wish to use a different format, then as I mentioned, use 1 single parameter holding the address of your actual data, and then in lua use readShortInteger(address+index*2) to read through it
You are responsible of allocating the list and filling it in your assembler code, so it should never be an unpredicted amount.
(Just keep it less than 20, as lua isn't configured to handle more than 20 parameters)
_________________
Do not ask me about online cheats. I don't know any and wont help finding them.
Like my help? Join me on Patreon so i can keep helping |
|
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
|
|