CheatEngineMonoHelper

Better mono options for Cheat Engine

View on GitHub

Raft tutorial

Ensure you have monohelper.lua saved to your Cheat Engine ‘autorun’ directory. Open up cheat engine and connect to your Raft game instance.

You should see the normal cheat engine ‘Mono’ menu appear, but it will have a new ‘Search’ option at the bottom.

Clicking the ‘Search’ menu item will display the image select window and automatically select the game’s assembly because it uses the standard name ‘Assembly-CSharp’ and open the ‘Mono Search’ window.

Start typing ‘Oxygen’ in the box. As you type the lists will be filtered to classes, fields, and methods that contain what you’re typing. There are some interesting things. There’s a seperate ‘Stat_Oxygen’ class, but what looks really interesting is the ‘stat_oxygen’ field on the ‘PlayerStats’ class.

Search for Oxygen

Double-click on this to open a window with the ‘PlayerStats’ class. Click on the ‘Options’ menu and check ‘Include Inherited’ to see fields and methods from base classes also. Here we see that there are other stats with their own classes for hunger and thirst also, along with health that is inherited from ‘Network_Entity’. That’s very interesting because that means there are probably other entities with health besides players, probably enemies.

PlayerStats fields

Let’s concentrate on oxygen for now though. Remember the offset is 68 for stat_oxygen. Change to the ‘Methods’ tab and we see some cool things such as ‘Damage()’ which exists on both PlayerStats and Network_Entity. Those might be handy for god mode and one hit kill cheats. For now though look down to find the ‘Update’ method. Methods named update are usually frequently called to do housekeeping on objects.

Double-click the update method without text in the ‘Class’ column, this means it’s directly on the PlayerStats class and not on a parent class. We don’t want to be changing oxygen for enemies, and in fact the stat doesn’t exist so that might crash the game.

PlayerStats methods

This changes the code address in the memory viewer to the method’s start and opens an auto assemble window with an injection script. This doesn’t do anything at the moment but jump to relocated code and jump back, but save it to the table (File->Assign to current cheat table) and close the window, then rename it in the table and open the table version of the script back up:

TODO: Either automatically or have a right-click option to do this automatically

define(hook,"PlayerStats:Update")
define(bytes,55 48 8B EC 48 83 EC 60)

[enable]

assert(hook, bytes)
alloc(newmem,$1000, hook)
{
  RCX: PlayerStats (this)

  Returns (RAX) System.Void
}

newmem:
  // original code
  push rbp
  mov rbp,rsp
  sub rsp,60
  jmp hook+8

hook:
  jmp newmem

[disable]

hook:
  db bytes

dealloc(newmem)

Notice the comment tells you what registers are passed and what types they are along with the expected return type. This currently only shows the registers as if they were ints (RCX, RDX, R8, R9), if any parameters are floats they will actually be XMM0-XMM3. For class methods RCX is always the instance the method is called on. Say there is a second parameter and it’s a float, that would actually be XMM2 and not R8.

Well, we know that stats_oxygen is of type Stat_Oxygen and at offset +68, so go back to the search window double-click ‘Stat_Oxygen’ in the Classes panel to open it, then switch to the fields tab. You can see there’s a float ‘value’ at offset 1C and a float ‘maxValue’ at offset 20:

Stat_Oxygen

We can change our script now to change value to maxValue every time Update() is called, which is frequently:

define(hook,"PlayerStats:Update")
define(bytes,55 48 8B EC 48 83 EC 60)

[enable]

assert(hook, bytes)
alloc(newmem,$1000, hook)
{
  RCX: PlayerStats (this)

  Returns (RAX) System.Void
}

newmem:
  // cheat: maximize oxygen every time PlayerStats.Update() is called
  push rax // save rax
  push rbx // save rbx
  mov rax,[rcx+68] // load pointer at PlayerStats.stat_oxygen into rax
  test rax,rax // is it zero?
  jz @f // jump forward to nearest @@ label to prevent null pointer error
  mov ebx, [rax+20] // load Stats_Oxygen.maxValue into ebx
  mov [rax+1C], ebx // save as Stats_Oxygen.value
@@: // temp label for jz @f earlier
  pop rbx // restore rbx
  pop rax // restore rax

  // original code
  push rbp
  mov rbp,rsp
  sub rsp,60
  jmp hook+8

hook:
  jmp newmem

[disable]

hook:
  db bytes

dealloc(newmem)

Activate this script and you will never run out of oxygen. The table I created for this tutorial is here:

Raft.CT