✅Prep: Speed Hack
Motivation
We really need speed hack to save time, this should the very first step in our journey. If there is no anti-cheat system on the server-side, we should be able to change character's speed by patching game client binary. For our case the game logic is implemented in GameLogic.dll
.
GameLogic.dll
In game client's folder, we can find two interesting files GameLogic.dll
and GameLogic.pdb
at C:\PwnAdventure3\PwnAdventure3\Binaries\Win32
.
Dropping GameLogic.dll
into IDA, we figure out that this file is indeed what it is called: all the C++ game logic implementations are stored here. What is GameLogic.pdb
?
Program database (PDB) is a file format (developed by Microsoft) for storing debugging information about a program (or, commonly, program modules such as a DLL or EXE). PDB files commonly have a .pdb extension. A PDB file is typically created from source files during compilation. It stores a list of all symbols in a module with their addresses and possibly the name of the file and the line on which the symbol was declared. This symbol information is not stored in the module itself, because it takes up a lot of space.
Loading GameLogic.pdb
in IDA could give us the actual function names, which helps a lot in reverse engineering. If DLL file and PDB file are in the same folder, IDA is going to detect PDB file automatically:

Speed Hack
"Speed" should be a property of the main character object. Usually the main character is called something like "pawn", "player" or "actor". Here I used a trick: searching for Player::
in the symbols. It returns some interesting properties. Among them we find a Player::GetSprintMultiplier
function:

Double click on it to view its content:

Press F5 to decompile it into pseudocode:

Currently the multiplier is 3.0. If we modify it to 30.0, our speed should be 10 times faster. Back to the flow chart and double click on __real@40400000
:

Why 0x40400000
stands for float 3.0 in C++? Here is a nice video explaining how floating point numbers work:
Here is an online converter:
I am tempted to change this multiplier to max float. Write a simple C++ program to print out max float:
#include <float.h>
#include <math.h>
#include <stdio.h>
int main()
{
printf("FLT_MAX = %e\n", FLT_MAX);
return 0;
}
Use an online compiler such as https://www.onlinegdb.com/online_c++_compiler to run it. The output is:
FLT_MAX = 3.402823e+38
3.402823e+38 converts to 0x7f7ffffd
in hex, so we want to patch the binary and change the following bytes:
00 00 40 40 -> FD FF 7F 7F
To change bytes in IDA, select .rdata:10078B34
with mouse and go to "Edit -> Patch code -> Change Byte":

After patching it, we have to "apply" the patch so that it overwrites the old binary. Go to "Edit -> Patch code -> Apply patches to input file":

Don't forget to select the "Create backup" option. Click "OK" and log into the game. Enjoy the speed of light 😄
Appendix: Ghidra edition
In 2025 I revisited this hack and chose to use Ghidra this time. Go to Symbol Tree
-> Player
:

Scroll down and you will see GetSprintMultiplier
:

A few things to discuss here.
Cross References (XREF)
?GetSprintMultiplier@Player@@UAEMXZ
?GetSpreadAngle@Pistol@@UAEMXZ
?GetCooldownTime@RubicksCube@@UAEMXZ
Ghidra lists several cross references to this function:
XREF[3]:
?GetSprintMultiplier@Player@@UAEMXZ
?GetSpreadAngle@Pistol@@UAEMXZ
?GetCooldownTime@RubicksCube@@UAEMXZ
These references indicate that other functions (or methods) in the project call this function, providing insight into how it is used in the larger codebase.
Identical implementation
Pistol::GetSpreadAngle
Player::GetSprintMultiplier
RubicksCube::GetCooldownTime
The functions Player::GetSprintMultiplier
, Pistol::GetSpreadAngle
, and RubicksCube::GetCooldownTime
all return the same constant value (3.0 in this case). Because they are defined (probably inline) to do nothing more than return a constant, their compiled machine code is identical.
Floating point number
FLD dword ptr [__real@40400000]
FLD instruction stands for "floating-point load". The __real
stands for real number (I think).
IEEE-754 Representation:
IEEE-754 is the standard for representing floating-point numbers. In single precision (32 bits), a floating-point number is divided into:
1 bit for the sign
8 bits for the exponent (with a bias of 127)
23 bits for the fraction (mantissa)
The hexadecimal number
40400000h
(01000000010000000000000000000000
in binary format) represents the value3.0
in this format. Here's how:Sign Bit (1 bit):
0
(indicating a positive number)Exponent (8 bits):
10000000
(or128
in decimal). After subtracting the bias (127), the actual exponent is1
.Fraction (23 bits): The bits represent
0.5
(because the fraction starts with1
in the binary significand for normalized numbers, making it1 + 0.5 = 1.5
).
Thus, the number is calculated as:
The formula can be found here: https://en.wikipedia.org/wiki/Single-precision_floating-point_format. An example:

Patch the binary
Double click on __real@40400000:

Location 10078b34 is what we are looking for. Here you just need to find a suitable multiplier, turn it into floating number in hex and patch the binary. Go to Window -> Bytes: GameLogic.dll and look for location 10078b34:

Click the pencil icon on the top right to enter edit mode. Here I change the multiplier to 100 (0x42c80000):

Press ctrl+s to save. Finally, export the patched binary in File -> Export Program. The "format" should be "Original File":

Before overwriting GameLogic.dll, don't forget to save a copy of the original file as backup. Speed hack done.
Last updated