Health regeneration

Memory scanning, code injection, debugger internals and other gamemodding related discussion
Post Reply
Fruitpunch
What is cheating?
What is cheating?
Posts: 3
Joined: Sat Sep 09, 2017 1:07 pm
Reputation: 0

Health regeneration

Post by Fruitpunch » Sat Sep 09, 2017 2:02 pm

Hello everybody!

I've been learning little assembly while I've been doing some code injections with Cheat engine,
so I'm by no means a coder. I would very much like to learn though.

Right now I'm trying to create a health regeneration cheat in a game where health does not regenerate.

The idea in this code is to assign the time we want to delay health regeneration after hit to FinishTime.

The code works with one significant issue. The whole game stops once the code gets to the delay loop and continues after the designated time.

As you can imagine this is not the result hoped for.

Code: Select all

[ENABLE]
alloc(newmem,2048)
label(Wait)
label(DelayCode)
label(FinishTime)
label(returnhere)
label(originalcode)
label(exit)

newmem: 
fst dword ptr [edi+00000118]
pushad

DelayCode:
call kernel32.GetTickCount
cmp [FinishTime],0
jne Wait

add eax, #15000
mov [FinishTime],eax
jmp DelayCode

Wait:
cmp eax,[FinishTime]
jb DelayCode

mov [edi+118],(float)200
mov [FinishTime],0
popad 
jmp exit

FinishTime:
dd 0

originalcode:
fst dword ptr [edi+00000118]

exit:
jmp returnhere

"CLOS2.exe"+F7080:
jmp newmem
nop
returnhere:

[DISABLE]
//code from here till the end of the code will be used to disable the cheat
dealloc(newmem)
"CLOS2.exe"+F7080:
fst dword ptr [edi+00000118]
//Alt: db CC 97 18 01 00 00
Anyone willing and/or able to help with this?

The final aim is to create a loop which will regenerate health little by little until health gets to max but before that I need to get this issue sorted out.

User avatar
FreeER
Cheater
Cheater
Posts: 37
Joined: Fri Mar 10, 2017 7:11 pm
Reputation: 2
Contact:

Re: Health regeneration

Post by FreeER » Sat Sep 09, 2017 5:57 pm

[note: written last]
hm... this may be simpler but I didn't think of it immediately :D Alternative to using a thread you may be able to store the result of GetTickCount somewhere and then subtract the stored value from the current value and if the result is greater than 1500 (if ticks are in milliseconds, otherwise you'd need to do some conversion) then you'd do the regen and store the current tick count to reset the time. something like:

Code: Select all

...
call kernel32.GetTickCount
push eax // save current if needed later
sub eax, [StoredTime]
cmp eax, 1500 // adjust if not in millseconds
pop eax // restore current
jl exit // don't forget to popad
// otherwise greater so 1.5 seconds has passed so regen
mov [StoredTime], eax // set stored time to current so time resets
mov [edi+118], (float)200 // regen
...
FinishTime:
dd 0
StoredTime:
dd 0
...
That way the code isn't sitting there looping until the time changes but moving on to let other code run and just checking to see how much time has passed since it last ran. If you were regening a set amount (like 10) rather than full health then a better way (for lag) would be to divide the time difference by 1500 and multiply the regen rate by that so that if you have a lag spike that causes a 3 second delay instead of just a 1.5 second delay then the code would properly add 2* the regen rate instead of just 1, but that's a bit more complicated :)

[thread info written first]
You'd need to create another thread so that you don't freeze the main game thread. You can do that with createThread, also there's the sleep function that lets you wait a certain amount of time in milliseconds. With a pointer you could also use lua quite easily

Code: Select all

{$lua}
[ENABLE]
healthRegenTimer = createTimer()
healthRegenTimer.Interval = 1500 -- 1.5 seconds

local healthPointer = '[[game.exe+baseOffset]+offset1]+offset2' -- infinite offsets, or none if static (no []s)
local maxHealth = 200
local healthRegen = 3
healthRegenTimer.OnTimer = function()
  local curHealth = readFloat(healthPointer)
  if curHealth and curHealth < maxHealth then -- if successfully read and not full
    writeFloat(healthPointer, curHealth+healthRegen) -- increment health by 1
  end
end
[DISABLE]
healthRegenTimer.destroy()
note that timers don't seem to work in asynchronous scripts for some reason, you could reimplement it using "createThread(function() while true sleep(1500) ... end end)" if you really wanted it to be marked as asynchronous or wanted to avoid someone accidentally breaking the script because they right clicked and then clicked randomly on the screen to remove the menu and accidentally enabled that.

For asm createthead you have to allocate memory and register it as a symbol for the code to run, globalalloc can do both for you

Code: Select all

globalalloc(healthRegenThreadMem, 2048)

label(loop)
label(maxHealth)
label(regenRate)
healthRegenThreadMem:
loop:
  // different calling conventions in x86 vs x64, use lua to put the correct one in the script
  {$lua}
  if targetIs64Bit() then return [[
    mov ecx, 1500
    sub rsp, 20 // shadowspace
    call sleep
    add rsp, 20]]
   else return [[
    push 1500
    call sleep]]
  end
  {$asm}

  // CE will fix rax to be eax in x86 targets
  mov rax, [game.exe+baseoffset]
  lea rax, [rax+offset1]
  lea rax, [rax+offset2]
  // ... infinite offsets
  
  fld [rax] // load health
  fld [maxHealth]
  // compare based on https://stackoverflow.com/questions/7057501/x86-assembler-floating-point-compare
  // so may be wrong, I don't do this often enough to know it off the top of my head :(
  fcomip
  fstp st(0)
  jb loop
  fadd [regenRate]
  fstp [rax]
  jmp loop
maxHealth:
  dd (float)200
regenRate:
  dd (float)10
if you don't have a pointer then you can combine a thread and hooking to get the address

Code: Select all

globalalloc(playerptr,4) // 8 for pointers in x64
newmem:
  mov [playerptr], edi
code:
...


globalalloc(healthRegenThreadMem, 2048)
...
  // CE will fix rax to be eax in x86 targets
  mov rax, [playerptr]
  test rax,rax
  jz loop // if not set yet (hook hasn't ran) don't do anything because we don't have a valid pointer to health
  fld [rax+118] // load health
...
I'm sure there's a couple other methods as well (like using win32 code to create the thread in the hook lol)

Fruitpunch
What is cheating?
What is cheating?
Posts: 3
Joined: Sat Sep 09, 2017 1:07 pm
Reputation: 0

Re: Health regeneration

Post by Fruitpunch » Sun Sep 10, 2017 3:58 pm

Thank you very much!

The first code works, and is definitely simple. I'm using opcode that writes which means that once the time has run the next hit adds health. Probably need to find an accessing opcode to make it work correctly, right?

User avatar
FreeER
Cheater
Cheater
Posts: 37
Joined: Fri Mar 10, 2017 7:11 pm
Reputation: 2
Contact:

Re: Health regeneration

Post by FreeER » Sun Sep 10, 2017 10:59 pm

sigh, not sure how that email got marked as spam but whatever, fixed now.

Yeah, you'll want to hook something that runs often like an access used to display health on the screen.

Fruitpunch
What is cheating?
What is cheating?
Posts: 3
Joined: Sat Sep 09, 2017 1:07 pm
Reputation: 0

Re: Health regeneration

Post by Fruitpunch » Sun Sep 17, 2017 3:07 pm

If I might get a liitle more help.

For some reason the code below works as intended but if I replace mov [ecx+118]... with add, health goes to zero and once time has run the add happens but then health gets back to zero.

I don't understand why that happens. Any ideas?

Code: Select all

call kernel32.GetTickCount
push eax
sub eax, [StoredTime]
cmp eax, #15000
pop eax
jl originalcode
mov [StoredTime], eax
mov [ecx+118], (float)200

originalcode:
fld dword ptr [ecx+00000118]
jmp returnhere

StoredTime:
dd 0

User avatar
FreeER
Cheater
Cheater
Posts: 37
Joined: Fri Mar 10, 2017 7:11 pm
Reputation: 2
Contact:

Re: Health regeneration

Post by FreeER » Sun Sep 17, 2017 3:55 pm

It's possible that once your health reaches 0 the game sets some other values that declare you as dead and checks those, moving 0 into your health if it considers you dead. Just a guess however.

Post Reply

Who is online

Users browsing this forum: No registered users