How Speedrunners Use Game Hacking Tools

TL;DR

In this post we will look at "time splitters" used by speedrunners and how it's related to game hacking.

Watch on YouTube

Preface

Before jumping into the cool stuff, you need to know a couple of things. In the last post, we saw that Cheat Engine could read the game's memory, and we used this to find the variable of the selected skill in memory. But when we restart the game, we'll loose the variable and see instead a bunch of ??? or random stuff - which indicates that the Cheat Engine has a wrong address/pointer.

Why did it change after restarting the game?

The addresses these processes get are different every time we run it (ASLR - Address Space Layout Randomization). So we need a generic way so that even if we restart the game we will find the correct pointers.

Let's attach Cheat Engine again to the game and search for the selected skill in memory and find the correct address by scanning for the selected skill slot number.

1

Now we want to find this value every time we restart the game. Obviously the game has to know where the value is present in memory - so can we somehow do the same?

Pointer Scan

Let's think about this in a programmers point of view. Here is how it could look like (this is just a guess):

var GameObject; 
GameObject->WorldObject;
WorldObject->CurrentPlayer;
CurrentPlayer->SkillSlotNumber;

There could be a general GameObject which has a reference to the WorldObject; which has reference to the CurrentPlayer and then we use the current player object to get a reference to the SkillSlotNumber.

If you know how this is implemented on a lower level, you'd know that these objects are somewhere in memory so they have an address and their variables are located at some offset from the start of that object.

GameObject->WorldObject;
 [address + offset]

Basically, you just have to follow these pointers and offsets until you find what we want. And Cheat Engine has a nice feature to simplify this for us and its called Pointer Scan.

2

This shows us a dialog window with various options - we'll keep most all of them with default settings except we change the "Base address must be in specific range" option. This lets us scan only a certain range of addresses to find the base address. From the last post, we know that the code that accesses this variable is in GameLogic.dll. So it makes sense to guess that this DLL might have some global variable pointing to some object which we can use to get to the actual value. So for the base address scanning range, I looked at the memory map of the GameLogic.dll and added the rough range there.

3

Now searching might take a while depending on the size of the memory. After the scan is complete, we find about 20000 pointers, 20414 to be precise. Let's look at one example.

4

From the base address of the DLL, if we go up 0x00097E48 and when following that address and if we go to the offset 0x110 we find the address or pointer to our variable. We can remember this as one possible pointer path.

We can also sort them by offset to find other interesting paths.

5

If you look closely, you see that the last offsets is often 0x110, but in the assembly code from the previous post, we found the offset 0x180 being used to access that variable:

mov [esi + 00000180], edx

Since I trust the assembly offsets a bit more than guesses that were made by the Cheat Engine, I decided to rescan/filter the pointer list for pointers with the last offset 0x180.

6

To check if the pointer paths are good, we can pick a bunch of these pointer paths and restart the game. The ones which still show the correct slot numbers are probably the reliable pointer paths that we can use. In this example, I picked 3, out of which 2 were correct, and 1 was pointing to somewhere else, hence it was wrong. It could also be a good idea to redo this process under different conditions just to make sure that the pointer path is reliable.

Why Pointer Paths?

These pointer paths allow us to find the variables in memory reliable. And so we can use them to make Trainers, Bots or Cheat. Essentially, we can write code that gets the base address, for example, GameLogic.dll and then follow that pointer chain to find the address of that value.  In our case we found a path that can be used to read or change the selected skill - of course not the most useful variable but its the same idea for health, mana and even teleport (special video on this).

How Does This Relate to Speedrunners?

You might've seen that the timer in-game changes once the map is changed, but it's not a built-in feature, nor the player manually changes it, but it still seems to work. Let me introduce you to LiveSplit.

Bioshock Infinite Any% Speedrun by glurmo - watch on YouTube

LiveSplit is a timer program for speedrunners that is both easy to use and full of features. In the official website livesplit.org, they also mention the following.

Game Time is automatically read directly from an emulator or PC game, and you can use it by switching to Game Time under Compare Against.

Do you know how this direct read from an emulator or PC game work? Let's check out the documentation.

LiveSplit has integrated support for Auto Splitters. An Auto Splitter can be one of the following:
* A Script written in the Auto Splitting Language (ASL).
* A LiveSplit Component written in any .NET compatible language.
* A third party application communicating with LiveSplit through the LiveSplit Server.

So what is ASL or Auto Splitting Language?

The Auto Splitting Language is a small scripting language made specifically for LiveSplit Auto Splitters. An ASL Script contains a State Descriptor and multiple Actions which contain C# code.

Here a State Descriptor is,

The State Descriptor is the most important part of the script and describes which game process and which state of the game the script is interested in. This is where all of the Pointer Paths, which the Auto Splitter uses to read values from the game, are described.

We already know what pointer paths are and they are used here to read values from the game like we read the selected skill number from memory!

Reading a bit further, we come across something important.

The optional base module name BASE_MODULE describes the name of the module the Pointer Path starts at. Every *.exe and *.dll file loaded into the process has its own base address. Instead of specifying the base address of the Pointer Path, you specify the base module and an offset from there. If this is not defined, it will default to the main (.exe) module.

As you can see, this is exactly what we figured out using Cheat Engine. We found a pointer path from the GameLogic.dll as the base module to select the skill.

Let's look at an example ASL file:

state("BioshockInfinite")
{
    float isMapLoading :     0x14154E8, 0x4;
    int overlaysPtr :        0x1415430, 0x124;
    int overlaysCount :      0x1415430, 0x128;
    int afterLogo :          0x135697C;
    int loadingScreen :      0x137CF94, 0x3BC, 0x19C;
}

start 
{
    //Check if The Lighthouse map was loaded
    if(old.isMapLoading > 0.00 && old.loadingScreen == 15)
        current.autostart = true;
    if(current.autostart)
        return current.afterLogo == 1 && old.afterLogo == 0;
}

From the code above

  • This ASL code is for the game BioShock Infinite
  • We see a state definition; inside this, we see some addresses along with offsets. isMapLoading is a variable that can be found when going from the base address +0x14154E8 and then following that pointer to the offset of +0x4.
  • Down below we have a start function that checks if the timer should be started and it will check if the map is loading and if the map that is being loaded is number 15.

All this means is that the LiveSplit is doing the same thing that cheat tools are doing! Of course they somehow have to interact with the game's memory.

When we look around the source code for LiveSplit we can also find interesting functions. For example inside the ProcessExtentions.cs file, we see a function named CreateThread which uses the API CreateRemoteThread. From the microsoft docs,

Creates a thread that runs in the virtual address space of another process.

This is a typical windows function that you can use to inject and run your code in a target process - a typical function a cheat might use. If you browse a bit more around the sources you can also find other helpful functions like WriteDetour - a comment inside the function explains:

// allocate memory to store the original src prologue bytes we overwrite with jump to dest
// along with the jump back to src

This simply means we can override a part of the game's code to jump to our own code and then jump back to continue execution. This is called a hook or a function hook. And that's exactly the same kind of stuff you would do for any kind of game hacking or cheating.

Livesplit is literally game hacking - but it's not only game hacking, this is what also malware might use to hide its code or steal data from a running process ;)

Just to be clear

  • LiveSplit is NOT cheating or malware
  • But cheats/malware use the same programming techniques

I think you can see that learning about game hacking or reverse engineering and how the basics of cheating works is useful. The cool thing is that game hacking can be really fun because you play around with games, but the skill you learn is applicable in many other areas as well.

I hope that shows you how interconnected the skills and areas are, and how learning one topic can benefit you somewhere else. And I also hope it shows you that some skills that might seem only be useful for malicious purpose (like online game hacking or malware development), they can also be used for awesome useful tools like LiveSplit - a tool that has probably had tremendous positive impact on the joy of thousands of people.

... I wouldn't be surprised if the developers of the tool haven't had a history in game hacking themselves.

Resources


LiveOverflow

LiveOverflow (and PwnFunction)

wannabe hacker...