This is a part 1 meant for dealing with CScripts in GameMaker Studio v3.1.4 specifically though the concepts can be carried over to other versions.
No doubt many of you have had issues with finding good compare values or tried backtracing to find a function start with no such luck. Well that is due to the game using these CScript objects to determine the function structure.
What I have found is these CScript instances are stored at a location and indexed depending on which has been called.
To start here are some useful functions that you can use once you find the CScript table base address:
Code: Select all
function findCScript(scriptname)
local base = getAddressSafe('[baseCodes]')
local numCodes = readInteger(getAddressSafe('baseCodes+8'))-1
for i = 0,numCodes do
local name = readString(readPointer(readPointer(readPointer(base+i*4)+0x8)+0x5C),100)
if scriptname == name then return i end
end
return nil
end
function registerCScripts()
local base = getAddressSafe('[baseCodes]')
local numCodes = readInteger(getAddressSafe('baseCodes+8'))-1
for i = 0,numCodes do
local name = readString(readPointer(readPointer(readPointer(base+i*4)+0x8)+0x5C),100)
_G[name]=i
end
end
function dumpCScripts()
local base = getAddressSafe('[baseCodes]')
local numCodes = readInteger(getAddressSafe('baseCodes+8'))-1
for i = 0,numCodes do
local name = readString(readPointer(readPointer(readPointer(base+i*4)+0x8)+0x5C),100)
print(name)
end
end
You can register the CScript table base address (what is being moved into ECX) as baseCodes and run the dumpCScript()
function included at the top of this post which will print all of the gml_Script_... names that are currently in use.
Do a short break and trace from here to get back to the main CScript Calling function:
Use registerCScripts() and print the hex notation of the script instance you are looking for:
Set the breakpoint as shown below:
After it breaks on your condition, set another breakpoint at the location shown (the first push 00) and run until that point, take the contents of EDX (or whatever other register is used) and do a structure dissect:
In this case (keep in mind I made the structure elements manually so you won't see the names like this) we can see that EDX contains a pointer to the function arguments where the damage number is at EDX+10 and the ID is at EDX+20. Using that info you can make a script to check the sign of EDX+20 and if not signed (as that is the indicator of whether it is enemy damage or not) you modify your damage.
Another example would be for spending gold where if you encounter the spend gold script, you set the first argument (gold cost) to 0. Basically, at this point you are doing a compare with which script is being called and then modify its input parameters.
In other cases, you may not want the function to run at all in which case you can set the vmBuffer value to 0, just be careful that the script you want to stop running does not return any important information. Here is an example of doing that:
Code: Select all
{$lua}
if syntaxcheck then return end
local off = findCScript('gml_Script_scr_mana_withdraw')
if off == nil then error("Script not found") end
off = string.format('%X',off)
local vmBufferBase = getAddressSafe('[[[baseCodes]+'..off..'*4]+8]+50')
[ENABLE]
origUseMana = readInteger(vmBufferBase)
writeInteger(vmBufferBase,0)
[DISABLE]
writeInteger(vmBufferBase,origUseMana)
I hope you find this information helpful.
Happy hacking,
aSwedishMagyar