Not the case here, but haven't you ever pushed rcx, rdx and after a function CALL, you got something else in them on pop?


Not the case here, but haven't you ever pushed rcx, rdx and after a function CALL, you got something else in them on pop?
I assumed they were referring to your video where there are no calls unless its written with invisible ink.SunBeam wrote: ↑Thu Jul 25, 2024 3:00 pmNot the case here, but haven't you ever pushed rcx, rdx and after a function CALL, you got something else in them on pop?I guess he's using statics to make sure those addresses won't be stack-overwritten by something unforeseen
Some sort of "best practices" of his. For short operations with no CALLs in-between, sure, push/pop works; but for more extensive ones, you may end-up with wrong data when pop-ing. So always check the registers states before a CALL and after a CALL, on pop, to make sure you got 1:1 matches.
Make space on the stack with sub rsp, ???. For a 128-bit register thats 16 bytes, so use sub rsp, 10. Now that you have space you can move shit onto the stack using the appropriate move instruction. I'll use movss to save the first section of the register. It will look like this:
Code: Select all
[ENABLE]
aobscanmodule(BuidingSpeedTest2,JWE2.exe,F3 0F 10 46 04 F3 0F 5C C1) // should be unique
alloc(newmem,$1000,BuidingSpeedTest2)
alloc(QuickBuild,8)
registersymbol(QuickBuild)
QuickBuild:
dd 1
label(code)
label(return)
newmem:
cmp [QuickBuild],1
jne code
// Set the construction time to 5 seconds
mov [rsi+04],(float)5
movss xmm0,[rsi+04]
mov [QuickBuild],0
code:
movss xmm0,[rsi+04]
jmp return
BuidingSpeedTest2:
jmp newmem
return:
registersymbol(BuidingSpeedTest2)
// Lua script to re-enable QuickBuild after 6 seconds
{$lua}
if syntaxcheck then return end
function reenableQuickBuild()
while true do
local QuickBuildAddress = getAddressSafe("QuickBuild")
if QuickBuildAddress then
if readInteger(QuickBuildAddress) == 0 then
-- Wait for 6 seconds
sleep(6000)
-- Re-enable QuickBuild
writeInteger(QuickBuildAddress,1)
end
end
sleep(500) -- Check every second
end
end
-- Start the re-enable thread
createThread(reenableQuickBuild)
{$asm}
[DISABLE]
BuidingSpeedTest2:
db F3 0F 10 46 04
unregistersymbol(QuickBuild)
unregistersymbol(BuidingSpeedTest2)
dealloc(newmem)
dealloc(QuickBuild)
Code: Select all
[ENABLE]
QuickBuild:
dd 1
[DISABLE]
QuickBuild:
dd 0
If you're set out to work on gamehacking like you do, without extra documentation or read-ups, then you'll keep wondering why this and that. I'm not here to apply morals on you or shove information into your head; but please, learn the difference between volatile and non-volatile registers (see here: [Link] --> "Microsoft x64 calling convention"). Aside from that, you should check the way the function is constructed and what happens at your hook/injection point. If some register (or multiple) is overwritten IMMEDIATELY after your hook, then you can use that safely:
Code: Select all
addr0: mov rax,[rcx+20] <-- hook here and notice how RBX is overwritten below
addr1: mov rbx,[rax+40]
Code: Select all
addr0: jmp CodeCave
CodeCave:
mov rbx,pPlayer
mov rbx,[rbx+20]
..
.. // etc. using rbx without pushing or poping it
original:
mov rax,[rcx+20]
jmp back/addr1
back/addr1:
mov rbx,[rax+40]
SunBeam wrote: ↑Tue Mar 25, 2025 2:32 pmIf you're set out to work on gamehacking like you do, without extra documentation or read-ups, then you'll keep wondering why this and that. I'm not here to apply morals on you or shove information into your head; but please, learn the difference between volatile and non-volatile registers (see here: [Link] --> "Microsoft x64 calling convention"). Aside from that, you should check the way the function is constructed and what happens at your hook/injection point. If some register (or multiple) is overwritten IMMEDIATELY after your hook, then you can use that safely:
Given the above you can use RBX directly without doing push rbx/pop rbx. So this would happen:Code: Select all
addr0: mov rax,[rcx+20] <-- hook here and notice how RBX is overwritten below addr1: mov rbx,[rax+40]
People only use push/pop when not needed because they're lazy. In x86 assembly you would always see pushad/pushfd + popfd/popad. Everyone and their dog would do that.. cuz "it's safer" T_T..Code: Select all
addr0: jmp CodeCave CodeCave: mov rbx,pPlayer mov rbx,[rbx+20] .. .. // etc. using rbx without pushing or poping it original: mov rax,[rcx+20] jmp back/addr1 back/addr1: mov rbx,[rax+40]
Apart from that, as explained in the my next reply, right underneath yours (viewtopic.php?p=363640#p363640 -- hope you read that), in x64 programming only the first 4 or 5 SSE2 registers are really important (xmm0, ..., xmm3). The rest (unless the function context implies it) are not used, so basically "volatile". Unless the function context is not using any of them (like my xmm15), you're free to use it. You can load ANYTHING in it (you can use it as stack space). You have room for 4 quads in each of them, 16 bytes in total, so go crazy!I recommend using the unaligned version of the instructions (e.g.: movups) instead of the 16-bytes aligned ones (movaps). Why? To avoid crashes if your RSP is not 16-bytes aligned.
Let me know if you have any other questions; if remotely interested in ASM (I saw you push on to show us what you made for this game rather than confirm you learned anything out of this discussion).
Best regards,
Sun
Code: Select all
[ENABLE]
aobscan(TreeGatheringSpeedOnGhateringZa,48 63 46 ?? 03 C7 89 46 ?? 48 8B 46) // should be unique
alloc(newmem,$1000,TreeGatheringSpeedOnGhateringZa)
label(code)
label(return)
newmem:
code:
movsxd rax,dword ptr [rsi+30]
add eax,edi
jmp return
TreeGatheringSpeedOnGhateringZa:
jmp newmem
nop
return:
registersymbol(TreeGatheringSpeedOnGhateringZa)
[DISABLE]
TreeGatheringSpeedOnGhateringZa:
db 48 63 46 28 03 C7
unregistersymbol(*)
dealloc(*)
Code: Select all
[ENABLE]
aobscan(TreeGatheringSpeedOnGhateringZa,48 63 46 ?? 03 C7 89 46 ?? 48 8B 46) // should be unique
alloc(newmem,$1000,TreeGatheringSpeedOnGhateringZa)
label(code)
label(return)
newmem:
code:
movsxd rax,dword ptr [rsi+45]
add eax,edi
jmp return
TreeGatheringSpeedOnGhateringZa:
jmp newmem
nop
return:
registersymbol(TreeGatheringSpeedOnGhateringZa)
[DISABLE]
TreeGatheringSpeedOnGhateringZa:
db 48 63 46 28 03 C7
unregistersymbol(*)
dealloc(*)
Code: Select all
[ENABLE]
aobscan(TreeGatheringSpeedOnGhateringZa,48 63 46 ?? 03 C7 89 46 ?? 48 8B 46) // should be unique
alloc(newmem,$1000,TreeGatheringSpeedOnGhateringZa)
label(code)
label(return)
newmem:
mov [rsi+30],(int)100000000
code:
movsxd rax,dword ptr [rsi+45]
add eax,edi
jmp return
TreeGatheringSpeedOnGhateringZa:
jmp newmem
nop
return:
registersymbol(TreeGatheringSpeedOnGhateringZa)
[DISABLE]
TreeGatheringSpeedOnGhateringZa:
readmem(originalbytesA,9)
//db 48 63 46 28 03 C7
unregistersymbol(*)
dealloc(*)
Code: Select all
[ENABLE]
aobscan(TreeGatheringSpeedOnGhateringZa,48 63 46 ?? 03 C7 89 46 ?? 48 8B 46) // should be unique
alloc(newmem,$1000,TreeGatheringSpeedOnGhateringZa)
alloc(originalbytesA,9)
registersymbol(originalbytesA)
originalbytesA:
readmem(TreeGatheringSpeedOnGhateringZa,9)
label(code)
label(return)
label(offset_changed)
newmem:
mov rbx,[rsi+45]
cmp rbx,0x12345678
jne offset_changed
jmp return
offset_changed:
mov [rsi+45],(int)100000000
movsxd rax,dword ptr [rsi+45]
add eax,edi
jmp return
code:
movsxd rax,dword ptr [rsi+45]
add eax,edi
jmp return
TreeGatheringSpeedOnGhateringZa:
jmp newmem
nop
return:
registersymbol(TreeGatheringSpeedOnGhateringZa)
[DISABLE]
TreeGatheringSpeedOnGhateringZa:
readmem(originalbytesA,9)
//db 48 63 46 28 03 C7
unregistersymbol(*)
dealloc(*)
Code: Select all
[ENABLE]
aobscan(TreeGatheringSpeedOnGhateringZa,48 63 46 ?? 03 C7 89 46 ?? 48 8B 46) // should be unique
alloc(newmem,$1000,TreeGatheringSpeedOnGhateringZa)
alloc(originalbytesA,9)
registersymbol(originalbytesA)
originalbytesA:
readmem(TreeGatheringSpeedOnGhateringZa,9)
label(code)
label(return)
newmem:
push rbx // Save rbx (non-volatile register)
mov rbx,[rsi+45] // Load the original value
cmp rbx,0x12345678 // Compare with the "magic" value
je code // If it matches, jump to original code
// MODIFY THE VALUE IF NECESSARY
mov [rsi+45],(int)100000000 // Set the new value
code:
movsxd rax,dword ptr [rsi+45] // ORIGINAL CODE (always executed)
add eax,edi
pop rbx // Restore rbx
jmp return // Return to the game
TreeGatheringSpeedOnGhateringZa:
jmp newmem
nop
return:
registersymbol(TreeGatheringSpeedOnGhateringZa)
[DISABLE]
TreeGatheringSpeedOnGhateringZa:
readmem(originalbytesA,9)
//db 48 63 46 28 03 C7
unregistersymbol(*)
dealloc(*)
Code: Select all
[ENABLE]
aobscan(TreeGatheringSpeedOnGhateringZa,48 63 46 ?? 03 C7 89 46 ?? 48 8B 46) // should be unique
alloc(newmem,$1000,TreeGatheringSpeedOnGhateringZa)
alloc(originalbytesA,9)
registersymbol(originalbytesA)
originalbytesA:
readmem(TreeGatheringSpeedOnGhateringZa,9)
label(return)
label(offset_changed)
newmem:
mov rbx,[rsi+45]
cmp rbx,0x12345678
jne offset_changed
jmp return
offset_changed:
mov [rsi+45],(int)100000000
movsxd rax,dword ptr [rsi+45]
add eax,edi
jmp return
bkpGatheringSpeeds:
readmem(TreeGatheringSpeedOnGhateringZa,6)
TreeGatheringSpeedOnGhateringZa:
jmp newmem
nop
return:
registersymbol(TreeGatheringSpeedOnGhateringZa)
registersymbol(bkpGatheringSpeeds)
[DISABLE]
TreeGatheringSpeedOnGhateringZa:
readmem(originalbytesA,9)
unregistersymbol(*)
dealloc(*)
Users browsing this forum: No registered users