In order to fully understand what offset are, you need to understand how memory and assembly works.
Offsets are addresses, often used with another address, for example a player address. To understand offsets, you need to know about the size of datatypes. On a 32 bit machine, an integer if not otherwise specified is 4 bytes long. A bool is one byte long. These variables will later be layed out in memory at certain addresses. I’ll show you an example by coding a small player class.
Code:
class CPlayer
{
public:
CPlayer( int Health, int Team, int Armor, CVector Pos, bool Valid, bool Alive )
: m_iHealth( Health ), m_iTeam( Team ), m_iArmor( Armor ), m_vecPosition( Pos ), m_bValidPlayer( Valid ), m_bIsAlive( Alive )
{
}
~CPlayer( )
{
}
int m_iHealth;
int m_iTeam;
int m_iArmor;
CVector m_vecPosition;
bool m_bValidPlayer;
bool m_bIsAlive;
};
If we instantiate a player object, give it dummy values and browse the memory region in cheat engine, we can rebuild our data structure.
Here we can see our class layed out in memory, along with it’s addresses. The address 18FDAC is our base address, the address of our player class. If the game wants to check if the player is valid, it might check the bool m_bIsValid inside this class. In most cases, the address of the player is saved in one of the registers. The game will most likely have an instruction like
Code:
mov eax, byte ptr [ecx + 00000012h]
mov is an assembly instruction. It moves whatever is on the right side into the left side. eax and ecx are CPU registers. The instruction above will move whatever is at ecx + 18 bytes into the eax register. The brackets around ecx + 12h are indicating that it's dereferencing. Once you get to know pointer arithmetic, you'll know about dereferencing. For now, this is the simplest explaination:
The black box is your ram. You've got your addresses on the left, your values on the right. 18FDAC holds the number 64 ( hexadecimal ).
I've skipped a lot of addresses. Instead of 18FDB8 - 400A2C97 it would actually look like this:
I just put all variables in one box and sorted them to big endian already to make it easier to read and understand.
As said before, on the left side is the memory address, on the right side is the value that's in this memory address ( in hexadecimal ). I've written up the actual values converted that we would see on the right side next to the box. Dereferencing means that we go to the address and take the value at that address. Lets say that ecx contains the base address of our class, which is 18FDAC, which is by the way also the address of the very first variable inside of that class. If our class would have virtual methods, the vtable would be at that address.
So the game moves whatever is at ecx ( 18FDAC ) + 18 bytes ( 12 bytes in hex; thats why its being displayed as 12h ), and moves it into eax.
The code and assembly for a simple get function could look like this:
Code:
bool CPlayer::IsValidPlayer( )
{
return m_bValidPlayer;
}
Since this function is a class function, it will be a __thiscall ( calling convention ).
__thiscall passes the class instance ( the this* ) within the ecx register. This way we can access specific instances of a class.
So in theory, it would rather look something like this ( if you decompile the function later on ):
Code:
bool __thiscall IsValidPlayer( void* this )
{
return *reinterpret_cast< bool* >( reinterpret_cast< DWORD >( this ) + 0x12 ); // reinterpret casting the this ptr since this can lead to weird issues
}
The assembly could look like this:
Code:
push ebp
mov ebp, esp
mov eax, [ ecx + 12h ]
pop ebp
ret
12h is called an offset from the base of the class. In games, there are usually pointers towards entities, for instance, players. You usually have an EntityList, which contains pointers to each entity. They are considered the base address. However, this does not only apply to classes. You can find static pointers aswell. Counter-Strike: Global Offensive for instance has multiple pointers that point towards the LocalPlayer. Once pointer is at address client.dll + 4F3336C. Client.dll being the base address of where ever the client.dll will be located at runtime. You can grab the base address of modules via the Windows API. This is called a static pointer. It will always be at client.dll + something. Something is in this case 4F3336C, which is an offset. However, this offset can change. All of these offsets can change. They change whenever the developers make modifications to the game that lead towards these offsets not working anymore. For instance, if i decide to change my CPlayer class and add another bool between CVector m_vecPosition and m_bValidPlayer, 12h or 18 bytes wouldn't be the right offset anymore. I'd now access the new boolean, which i dont want. I'd now have to update the offset to 19 bytes ( or 13h ).
A way to prevent your offsets from getting outdated is a concept called pattern scanning.
You can find a small tutorial about pattern scanning here: https://www.mpgh.net/forum/showthread.php?t=1094622
I've used some of the material from the tutorial in this explaination though.
As you can see, offsets have nothing to do about the security of the cheat. They are just a way to access specific memory locations.