Tools used: radare2 for disassembly, Frida for instrumentation.
Note: This is post is written from my understanding of the q3vm and is not meant to be a throughout explanation of it. If you want a more detailed, accurate description of is internals I encourage you to read about it in Fabien’s blog, which helped me fill some gaps.
What we are going to see in this post:
- A very basic understanding of how the Q3VM loads the game
- How to make small instrumentation script with Frida to detect new players connecting and userinfo changes without doing any engine modifications or loading server-side mods.
In essence, when the engine starts:
- Searches for PK3s (PK3 are the packed files that quake-based games parse) that are in the desired fs_game folder + the base game’s folder. E.g: If we use the JA’s JA+(japlus) mod, the engine will search for PK3 files in base/ and japlus/ folders.
- Initializes dynamic memory (com_hunkMegs, com_zoneMegs) and open sockets.
And it stops there (there are more things happening but it is not the point of this post) until a valid map is provided, which starts the game. An example of a JA valid map command would be: map mp/duel1:
- Loads the BSP file matching the levelname provided.
If it is not a base BSP, it tries to find it in any of the loaded PK3s
- Loads the QVM
- Jumps to vmMain()
- Does whatever syscall has received
- Jumps to vmMain() again and again and again.
Some of these vmMain arguments that can be received:
- GAME_INIT: Game startup
- GAME_SHUTDOWN: Map change or server shutdown.
- GAME_CLIENT_CONNECT: Client, bot or real connects.
- GAME_CLIENT_USERINFO_CHANGED: Client info changes, forcestrings, playermodels, name…
- … and more
For a more detailed list refer to gameExport_t struct in game/g_public.h https://github.com/jedis/jediacademy/blob/master/codemp/game/g_public.h#L734-L799
Alright, that is enough Q3VM for now…