|
Cheat Engine The Official Site of Cheat Engine
|
View previous topic :: View next topic |
Author |
Message |
tuxutat How do I cheat? Reputation: 0
Joined: 15 Mar 2013 Posts: 6
|
Posted: Wed May 01, 2013 2:46 pm Post subject: How to write scripts that work with multiple game versions |
|
|
Normally, when a new patch is released, you have to update your AA scripts even if you use aobscan to find your hacking points. There is, however, a way to generalize your scripts so they will still work for new versions as long as the code at your hacking points remains the same (if only addresses and offsets change).
The cleanest way to do this is to have one (or more) initialization script that everything (other scripts, memory values) depends on. The init script could look something like this:
Code: |
[enable]
// Find hacking point 1
aobscan(aob_hp1,AB CD EF 01 02 03 04 05 06 07 08 09 0A)
globalalloc(org_hp1,6)
label(r_hp1)
registersymbol(r_hp1)
// Find hacking point 2
// Always use ?? for things representing addresses or fixed offsets
aobscan(aob_hp2,C7 40 ?? 00 00 00 00 FF FE ?? ?? ?? ?? F3 F4)
globalalloc(org_hp2,8)
label(r_hp2)
registersymbol(r_hp2)
aob_hp1+2:
r_hp1:
aob_hp2:
r_hp2:
// Use createthread to make a backup of the original game code
// You can't use readmem for this
alloc(backupCode,128)
createthread(backupCode)
backupCode:
cld
mov esi,r_hp1
mov edi,org_hp1
mov ecx,#6
rep movsb // copy 6 bytes from esi to edi
mov esi,r_hp2
mov edi,org_hp2
mov ecx,#8
rep movsb
ret
[disable]
dealloc(org_hp1)
unregistersymbol(org_hp1)
dealloc(org_hp2)
unregistersymbol(org_hp2)
unregistersymbol(r_hp1)
unregistersymbol(r_hp2)
|
Having an init script like this is mostly necessary because readmem() can't be used otherwise in dependent scripts.
A dependent script that actually does something could look like this:
Code: |
// Note: The init script must be active to be able to save this dependent script to your table.
[enable]
alloc(myCode1, 256)
label(ret_myCode1)
label(myCode2)
label(ret_myCode2)
r_hp1:
jmp myCode1
nop
ret_myCode1:
r_hp2:
jmp myCode2
nop
nop
nop
ret_myCode2:
myCode1:
// do whatever we like
xor eax,eax
xor ebx,ebx
// use the original code from our backup:
readmem(org_hp1,6)
jmp ret_myCode1
myCode2:
// Let's assume this is the original code:
// test.exe+2898C7 - C7 40 0C 00000000 - mov [eax+0C],00000000
// and we want to change it to this instead:
// test.exe+2898C7 - C7 40 0C 01000000 - mov [eax+0C],00000001
// We could do it like this:
readmem(org_hp2,3) // the offset +0C can change in a new game version, but no problem with readmem!
dd 1
{ // Alternatively, we could do this:
push eax
xor eax,eax // eax=0
mov al,byte ptr [org_hp2+2]
add eax,[esp]
mov [eax],1
pop eax
} //------------------------
readmem(org_hp2+7,1) // copy the last missing byte
// Note: The last NOP at the HP and this readmem are redundant and only here for demonstration purposes
jmp ret_myCode2
[disable]
r_hp1:
readmem(org_hp1,6)
r_hp2:
readmem(org_hp2,8)
dealloc(myCode1)
|
One thing to watch out for is if the code you overwrite with your injection contains a jump short instruction. When it's something like "je +02" (Bytes: 74 02), that's of course short enough to still point inside your code. If it's too long, then you'll have to split your readmem() code and insert a long jump instead (e.g. something like "je r_hp1+2C").
That's it, basically. If you are careful with absolute addresses, offsets and short jumps, your scripts have a good chance to survive game updates. It's still a good idea to save the original assembly code at the hacking points somewhere, so you can quickly check if the update breaks anything.
|
|
Back to top |
|
|
Doctor Death Cheater Reputation: 1
Joined: 26 Apr 2014 Posts: 42 Location: Breaking Code
|
Posted: Thu Feb 19, 2015 5:35 pm Post subject: Re: How to write scripts that work with multiple game versio |
|
|
tuxutat wrote: | Normally, when a new patch is released, you have to update your AA scripts even if you use aobscan to find your hacking points. There is, however, a way to generalize your scripts so they will still work for new versions as long as the code at your hacking points remains the same (if only addresses and offsets change).
The cleanest way to do this is to have one (or more) initialization script that everything (other scripts, memory values) depends on. The init script could look something like this:
Code: |
[enable]
// Find hacking point 1
aobscan(aob_hp1,AB CD EF 01 02 03 04 05 06 07 08 09 0A)
globalalloc(org_hp1,6)
label(r_hp1)
registersymbol(r_hp1)
// Find hacking point 2
// Always use ?? for things representing addresses or fixed offsets
aobscan(aob_hp2,C7 40 ?? 00 00 00 00 FF FE ?? ?? ?? ?? F3 F4)
globalalloc(org_hp2,8)
label(r_hp2)
registersymbol(r_hp2)
aob_hp1+2:
r_hp1:
aob_hp2:
r_hp2:
// Use createthread to make a backup of the original game code
// You can't use readmem for this
alloc(backupCode,128)
createthread(backupCode)
backupCode:
cld
mov esi,r_hp1
mov edi,org_hp1
mov ecx,#6
rep movsb // copy 6 bytes from esi to edi
mov esi,r_hp2
mov edi,org_hp2
mov ecx,#8
rep movsb
ret
[disable]
dealloc(org_hp1)
unregistersymbol(org_hp1)
dealloc(org_hp2)
unregistersymbol(org_hp2)
unregistersymbol(r_hp1)
unregistersymbol(r_hp2)
|
Having an init script like this is mostly necessary because readmem() can't be used otherwise in dependent scripts.
A dependent script that actually does something could look like this:
Code: |
// Note: The init script must be active to be able to save this dependent script to your table.
[enable]
alloc(myCode1, 256)
label(ret_myCode1)
label(myCode2)
label(ret_myCode2)
r_hp1:
jmp myCode1
nop
ret_myCode1:
r_hp2:
jmp myCode2
nop
nop
nop
ret_myCode2:
myCode1:
// do whatever we like
xor eax,eax
xor ebx,ebx
// use the original code from our backup:
readmem(org_hp1,6)
jmp ret_myCode1
myCode2:
// Let's assume this is the original code:
// test.exe+2898C7 - C7 40 0C 00000000 - mov [eax+0C],00000000
// and we want to change it to this instead:
// test.exe+2898C7 - C7 40 0C 01000000 - mov [eax+0C],00000001
// We could do it like this:
readmem(org_hp2,3) // the offset +0C can change in a new game version, but no problem with readmem!
dd 1
{ // Alternatively, we could do this:
push eax
xor eax,eax // eax=0
mov al,byte ptr [org_hp2+2]
add eax,[esp]
mov [eax],1
pop eax
} //------------------------
readmem(org_hp2+7,1) // copy the last missing byte
// Note: The last NOP at the HP and this readmem are redundant and only here for demonstration purposes
jmp ret_myCode2
[disable]
r_hp1:
readmem(org_hp1,6)
r_hp2:
readmem(org_hp2,8)
dealloc(myCode1)
|
One thing to watch out for is if the code you overwrite with your injection contains a jump short instruction. When it's something like "je +02" (Bytes: 74 02), that's of course short enough to still point inside your code. If it's too long, then you'll have to split your readmem() code and insert a long jump instead (e.g. something like "je r_hp1+2C").
That's it, basically. If you are careful with absolute addresses, offsets and short jumps, your scripts have a good chance to survive game updates. It's still a good idea to save the original assembly code at the hacking points somewhere, so you can quickly check if the update breaks anything. |
I'm seeing a lot of new functions that I've never seen before...
What is "globalalloc"?
What is going on here:
Code: |
aob_hp1+2:
r_hp1:
aob_hp2:
r_hp2:
|
And finally, what is "readmem"? I've never heard of that before.
|
|
Back to top |
|
|
Zanzer I post too much Reputation: 126
Joined: 09 Jun 2013 Posts: 3278
|
Posted: Sun Mar 15, 2015 11:48 pm Post subject: |
|
|
During script execution, READMEM directly copies the bytes at the specified location into the script.
tuxutat, you can use READMEM within the same script that contains the AOB.
You just need to make sure you use it before the code which changes the found AOB.
Code: | [ENABLE]
aobscan(aob_hp1,AB CD EF 01 02 03 04 05 06 07 08 09 0A)
alloc(myCode1,256)
alloc(org_hp1,6)
registersymbol(aob_hp1)
registersymbol(org_hp1)
label(ret_myCode1)
org_hp1:
readmem(aob_hp1,6)
myCode1:
// blah blah blah
jmp ret_myCode1
aob_hp1:
jmp myCode1
nop
ret_myCode1:
[DISABLE]
aob_hp1:
readmem(org_hp1,6)
unregistersymbol(aob_hp1)
unregistersymbol(org_hp1)
dealloc(myCode1)
dealloc(org_hp1) |
|
|
Back to top |
|
|
TheByteSize Advanced Cheater Reputation: 0
Joined: 06 Aug 2015 Posts: 62
|
Posted: Fri Sep 04, 2015 5:41 pm Post subject: |
|
|
Zanzer wrote: | During script execution, READMEM directly copies the bytes at the specified location into the script.
tuxutat, you can use READMEM within the same script that contains the AOB.
You just need to make sure you use it before the code which changes the found AOB.
|
I just want to revive this old post for a quick question. I'm still new and I would like a clarification.
OP said we need to do a backup but Zanzer said READMEM can be use without backup. So, when do we need to backup?
|
|
Back to top |
|
|
Zanzer I post too much Reputation: 126
Joined: 09 Jun 2013 Posts: 3278
|
Posted: Fri Sep 04, 2015 6:01 pm Post subject: |
|
|
I simply mentioned that the backup can occur within the same script.
You just need to use READMEM before the code which overwrites the original bytes.
To try to keep things simple, I'll start with a default AOB Injection template.
I added comments to denote any additions.
Code: | [ENABLE]
aobscanmodule(INJECT,calc.exe,48 89 74 24 08 48 89 7C 24 10 41)
alloc(newmem,$1000,"calc.exe"+1B9D0)
label(code)
label(return)
label(backup) // add a label where you will store a backup
newmem:
code:
mov [rsp+08],rsi
jmp return
backup: // define the label >BEFORE< the overwrite at the 'INJECT' label
readmem(INJECT,5) // backup the 5 bytes that the code overwrites
// also note that this code is below the final 'jmp return' and will never execute
INJECT:
jmp code
return:
registersymbol(INJECT)
registersymbol(backup) // register the backup label so we can find it on disable
[DISABLE]
INJECT:
//db 48 89 74 24 08 // commented out the bytes to use our backup instead
readmem(backup,5) // read the 5 bytes we backed up
unregistersymbol(INJECT)
unregistersymbol(backup) // unregister our backup
dealloc(newmem) |
|
|
Back to top |
|
|
TheByteSize Advanced Cheater Reputation: 0
Joined: 06 Aug 2015 Posts: 62
|
Posted: Fri Sep 04, 2015 6:11 pm Post subject: |
|
|
What purpose do these lines serve?
Is it for the backup code into Global instead of Local Allocation?
Code: | alloc(backupCode,128)
createthread(backupCode)
backupCode:
cld |
|
|
Back to top |
|
|
Zanzer I post too much Reputation: 126
Joined: 09 Jun 2013 Posts: 3278
|
Posted: Fri Sep 04, 2015 6:22 pm Post subject: |
|
|
In essence, that code is just replicating the READMEM function.
I do not believe it is necessary or makes the backup easier to write or understand.
ALLOC creates a new block of memory
CREATETHREAD executes that block of memory one time
All of the code below 'backupCode' up to the RET performs a similar function as READMEM.
|
|
Back to top |
|
|
TheByteSize Advanced Cheater Reputation: 0
Joined: 06 Aug 2015 Posts: 62
|
Posted: Fri Sep 04, 2015 6:57 pm Post subject: |
|
|
Thank your for your clarifications. Time for me to generalize my FF Type Zero code.
|
|
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
|
|