Rakion: Entities and editing cell creatures
Part I: Gamehacking: Rakion, the begining
Parte II: Rakion: Entities and editing cell creatures
A principle in the gamehacking is that if a value changed we can scan the memory, get its address, and modify the value arbitrarily. For example, if player’s health has 64 or 0x100 as max value and receives one of damage, that value will decrease one, allowing us to scan the memory, obtain a group of results, and on that group keep scanning everytime that health value is modified to reduce the result group to a few addresses until getting the health player address with the purpose of modifying that value at our whim.
Of course there are variants, and it isn’t the only way, but it’s a good first step.
In this game existing cell’s creatures are allies, and can be summoned by consuming cell points (CP). That cell’s creatures can be equipped in the lobby, and that action set a value in memory (a byte is written) or removed (a null byte is written), that cell’s creature value will be named in this blogpost as cell’s creature ID.
Editing cell creatures in-lobby
There are two ways to get the cell’s creature ID address in the lobby, scan an unknow value to get the group of results, remove the cell creature (that value will change to 0), and scan over that group, then repeat until getting few addresses. The other way is by knowing the value of cell creature in memory previously, this way is possible because in past, Rakion had a resource file called Datasetup.xfs
in plain text where we found cell’s creature IDs, items stats, stage properties (the stages also are called as quests in other games), etc.
In the following video we can see how to get the cell creature address in lobby using the first method.
Searching and editing a cell creatures in-lobby (2015-dic version)
In the video my player is Xeen and, to reduce the results group, I started looking if my player name is close, because it is usual that entity player structure has value of items, stats, and related information like the player’s name.
And we have our first cheat :D. Anyway, if you restart the game, the cell’s creature ID address will change. How we can fix it? Check Entity in-lobby section.
Entities
A player entity is a structure that contains player’s information (pointers, properties, names, etc). In this game, we can say that already exist two entities, in-game, and in-lobby, and the last one probably wasn’t created as a player entity but due the posibility to modify some player’s items in lobby we’ll call it in that way for didactic purpose.
Entity in-lobby
Trying to figure out a method to get the cell’s creature ID address, I notized that there are some hardcoded adresss in entitiesmp.dll
. Then, I looked up if those addresses were lower than cell’s creature ID address, and if that lower addresses have a constant delta toward cell’s creature ID address.
You can see the hardcoded address implementation in differents versions of entitiesmp.dll
.
2010-dic:
// IDA Pro 6.x decompiler
int __cdecl CPlayer__EndGame()
{
return (*(int (**)(void))(*(_DWORD *)dword_354B7FC4 + 284))();
}
2012-may:
void __thiscall CPlayer::EndGame(CPlayer *this)
{
(*(void (__thiscall **)(_DWORD))(**(_DWORD **)_pRakionWorldNet + 256))(*(_DWORD *)_pRakionWorldNet);
}
2015-dic:
void __thiscall CPlayer::EndGame(CPlayer *this)
{
(*(void (__thiscall **)(int))(*(_DWORD *)dword_356647B4 + 280))(dword_356647B4);
}
Let’s see it on CheatEngine:
Editing cell creatures in-lobby (2012-may version)
Indeed, 2015 version (and probably later), the cell creature level is found 2 bytes after the ID.
Entity in-game
There is a function that return a CPlayer object called: class CPlayer *__thiscall CPlayer::GetLocalPlayer(CPlayer *this)
from entitiesmp.dll
. It’s important to mention that this function returns the current active CPlayer object, it means that the function will return an object when your character is in-game.
class CPlayer *__thiscall CPlayer::GetLocalPlayer(CPlayer *this)
{
FieldInfo *FieldInfo; // eax
FieldInfo = CPlayer::GetFieldInfo(this);
return FieldInfo::GetLocalPlayer(FieldInfo);
}
For test purposes, I had to figure out a way to get the CPlayer’s object address every time I need it. Then, I made the following CE script but there is an issue, when I disabled it the game crashes that’s why I wrote a infinite loop :P. So, I only added the symbol objCPlayer
in CE Table to get the CPlayer object address.
alloc(mythread, 512)
createthread(mythread)
alloc(objFieldInfo, 4)
alloc(objCPlayer, 4)
[ENABLE]
registerSymbol(objFieldInfo)
registerSymbol(objCPlayer)
mythread:
xor eax, eax
call CPlayer::GetFieldInfo
mov [objFieldInfo], eax
get_entity:
mov ecx, [objFieldInfo]
call FieldInfo::GetLocalPlayer
test eax, eax
mov [objCPlayer], eax
push 2000
call kernel32.Sleep
jmp get_entity
ret //terminates thread
[DISABLE]
unregisterSymbol(objFieldInfo)
dealloc(objFieldInfo)
dealloc(mythread)
Some days before, I found out a variable which is initialized with the CPlayer object address every time. In 2012-may version entitiesmp.dll + 0x4b42f0
, and 2015-dic version entitiesmp.dll + 00xx87CB28
Editing cell’s creatures in-game
As Editing cell creatures in-game section explained, we need to find out the exact address in-game where cell’s creature ID is located.
In 2015-dic version, there is a function called void __thiscall CPlayer::SpawnNPC_n(CPlayer *this, struct CellInfo *a2, struct CPlayer *a3, int a4)
used when a cell’s creature is summoned, but there isn’t any direct crossreference because it’s accessed from CPlayer’s vftable. That function calls to sub_35158F20(a2, a3, i);
void __cdecl sub_35158F20(int *a1, int a2, int a3)
{
// [...]
GlobalFieldInfo = GetGlobalFieldInfo();
if ( FieldInfo::IsRoundState(GlobalFieldInfo, 1) )
{
if ( *((_BYTE *)GetGlobalFieldInfo() + 313) != 1
&& *(_BYTE *)(*(unsigned __int8 *)(*((_DWORD *)_pNetwork + 9) + 0x294C) + *((_DWORD *)_pNetwork + 9) + 0x294D) < 9u )
{
CPlacement3D::CPlacement3D((CPlacement3D *)v21);
v4 = (int *)sub_35158C30(v20, a2, a3);
v21[0] = *v4;
v21[1] = v4[1];
v21[2] = v4[2];
v21[3] = v4[3];
v21[4] = v4[4];
v21[5] = v4[5];
CTString::CTString((CTString *)&v18, "pwoCurrentWorld");
v22 = 0;
INDEX = (CWorld *)CShell::GetINDEX(_pShell, (const struct CTString *)&v18);
v22 = -1;
CTString::~CTString(&v18);
CreatureStr = (const struct CTString *)GetCreatureStr(v17, a1[1]);
// [...]
In 2012-may version, CPlayer::SpawnNPC_n
doesn’t exist, but sub_35158F20(a2, a3, i);
(2015-dic version) does exist, and is in void __cdecl sub_350E3260(int *a1, int a2)
(2012-may version). Almost, there is a crossreference from CPlayer::ButtonsActions
. With a quick analize I can conclude both functions have as first argument struct CellInfo*
type, and the second is struct CPlayer*
type.
Reviewing the function caller CPlayer::ButtonsActions
2012-may version:
v7 = (int *)sub_351DBF00(*((_DWORD *)this + 0x980), *((_DWORD *)this + 0x982), (int)this + 0x26B0);
v8 = v7;
if ( v7 && v7[1] < 47 )
{
if ( CEntity::IsLocalEntity(this) )
sub_350E3260(v8, (int)this);
}
2015-dic version:
v10 = sub_3530DAA0(*((_DWORD *)this + 0x9AE), *((_DWORD *)this + 0x9B0), (int)this + 0x2778);
v11 = v10;
if ( v10 )
{
if ( *(int *)(v10 + 4) < 160 && CEntity::IsLocalEntity(this) && *((_BYTE *)CPlayer::GetFieldInfo() + 313) != 1 )
{
v12 = *(void (__thiscall **)(CPlayer *, int, CPlayer *, int))(*(_DWORD *)this + 484);
if ( *(_DWORD *)(v11 + 4) == 14 )
v12(this, v11, this, 3);
else
v12(this, v11, this, 1);
}
}
The third argument of sub_351DBF00
(2012-may version), and sub_3530DAA0
(2015-dic version) is a buffer, and also is the value returned. That buffer is struct CellInfo*
or cplayer->CellInfo
. Let’s see it.
#ifdef 2012_MAY
struct CellInfoBySlot{
DWORD n_slot; // 0 to 3
enum CellType cell_type;
DWORD unknow1;
DWORD unknow2; // flags?
DWORD unknow3;
float cell_points_cost;
BYTE cell_level;
BYTE unknow4;
BYTE unknow5;
BYTE unknow6;
DWORD padding; // ?
};
#endif
#ifdef 2015_DIC
struct CellInfoBySlot{
DWORD n_slot;
enum CellType cell_type;
enum state; // Encrypted: 0=Not enough points; 1=available; 2=summoned
DWORD unknow1; //
float cell_points_cost;
BYTE cell_level;
BYTE unknow2;
BYTE unknow3;
BYTE unknow4;
DWORD unknow5;
};
#endif
struct CellInfo
{
DWORD n_slot_available; // 0 to 3
struct CellInfoBySlot cell_info_by_slot[3];
};
Then, We need to do following to modify the cell creatures:
struct CellInfo* pCellInfo = cplayer->CellInfo;
pCellInfo->cell_info_by_slot[0].cell_type = RedTaurus;
pCellInfo->cell_info_by_slot[0].cell_level = 99;
pCellInfo->cell_info_by_slot[0].cell_points_cost = 0;
Editing cell creatures in-game (2012-may version)
Now, in 2015-dic version we have a field called state
, if we write always pCellInfo->cell_info_by_slot[0].state = available
the first slot will be avaible to be summoned ‘always’ :D. Anyway, there is a check to bypass, let’s review again the two functions mentioned before, sub_35158F20(a2, a3, i);
(2015-dic version), and void __cdecl sub_350E3260(int *a1, int a2)
(2012-may version) both of which have first argument struct CellInfo*
.
2012-may version:
void __cdecl sub_350E3260(int *a1, int a2)
{
// [...]
v20 = dword_353927B8;
v2 = (*(int (__thiscall **)(_DWORD, int))(**(_DWORD **)_pRakionWorldNet + 8))(*(_DWORD *)_pRakionWorldNet, 1);
if ( FieldInfo::IsRoundState(v2, v10[4]) )
{
if ( *(_BYTE *)(*(unsigned __int8 *)(*(_DWORD *)(*(_DWORD *)_pNetwork + 36) + 0x2946)
+ *(_DWORD *)(*(_DWORD *)_pNetwork + 36)
+ 0x2947) < 9u )
{
CPlacement3D::CPlacement3D(v14);
v3 = (int *)sub_350E2EF0((int)v18, a2);
// [...]
2015-dic version:
void __cdecl sub_35158F20(int *a1, int a2, int a3)
{
// [...]
GlobalFieldInfo = GetGlobalFieldInfo();
if ( FieldInfo::IsRoundState(GlobalFieldInfo, 1) )
{
if ( *((_BYTE *)GetGlobalFieldInfo() + 313) != 1
&& *(_BYTE *)(*(unsigned __int8 *)(*((_DWORD *)_pNetwork + 9) + 0x294C) + *((_DWORD *)_pNetwork + 9) + 0x294D) < 9u )
{
CPlacement3D::CPlacement3D((CPlacement3D *)v21);
// [...]
What does those ‘if statements’ do? Basically checks that summoned creatures are not greater than 9. ¿Why 9?, because the cell creatures white spawn three creatures by slot. Then, we need to write constatly 0 in that address to bypass the check, as well as force to change the slot state to available, so we will be able to perform infinite creatures summons. Only is possible to summon one time per slot while the creature is still alive in-game, but in the following video you will see that I can summon many creatures from first slot.
Unlimited summons (2015-dic version)
The state
field is ‘encrypted’, I mean, that value changes constatly and it have a special way to write or read it. In a next blogpost I’ll show you how we can do that, to make others cheats or you can see my talk at Ekoparty 2022 called The game (life) and how to hack it in spanish :D.
Written by Nox