--- layout: post title: A simple GSC loader for CoD Black Ops 1 author: Dylan Müller author_url: https://linkedin.com/in/dylanmuller --- > Learn how GSC scripts are loaded into Black Ops 1 and how to write your own > simple GSC loader in C for the Microsoft Windows version of the game. 1. [Fond Memories](#fond-memories) 2. [Game Script Code](#game-script-code) 3. [GSC Script Call Chain](#gsc-script-call-chain) 4. [Format of raw GSC scripts](#format-of-raw-gsc-scripts) 5. [Loading and executing GSC script assets](#loading-and-executing-gsc-script-assets) 6. [Black Ops Offsets](#black-ops-offsets) 7. [Source Code](#source-code) 8. [Demo](#demo) # Fond Memories ![Black Ops 1 Zombies](https://lunarjournal.github.io/images/6/01.jpg) Call of Duty: Black Ops 1 Call of Duty: Black Ops 1 for Microsoft Windows was released all the back back in 2010! At the time I was still a teenager in high-school and a dedicated gamer. It seemed like another purchase on steam back then but I would have never expected a game to produce so many fond memories playing kino der toten zombies with friends from all over the world. I mean who doesn't remember all the iconic glitches, mystery boxes, etc of the game? Some say the Black Ops 1 zombies was the best in the Black Ops series. There is something about the game aesthetics that creates a rush of nostalgia :heart:. > Ok enough rambling, let's get technical... # Game Script Code GSC is an internal scripting language that is used to script missions and game modes for Black Ops 1. It has a syntax similar to C++ but is a very limited language. Here is an example: ``` #include common_scripts\utility; #include maps\_utility; init() { level thread on_player_connect(); // TODO: put weapon init's here maps\_flashgrenades::main(); maps\_weaponobjects::init(); maps\_explosive_bolt::init(); maps\_flamethrower_plight::init(); maps\_ballistic_knife::init(); } on_player_connect() { while( true ) { level waittill("connecting", player); player.usedWeapons = false; player.hits = 0; player thread on_player_spawned(); } } ``` GSC scripts are only loaded and executed when a map or game has started and in Black Ops 1 they are reloaded everytime a map or game mode is restarted. Writing custom GSC scripts is thus the basis for the vast majority of Black Ops 1 modding efforts. GSC scripts are first loaded into memory and then compiled with an internal GSC compiler. There is a public GSC compiler and decompiler on GitHub: [gsc-tool](https://github.com/xensik/gsc-tool) but support for Black Ops 1 (T5) seems to be missing. Luckily it turns out we don't actually need an external GSC compiler to compile our own GSC scripts. We can (in theory) simply call the `compile()` function of the internal GSC compiler and pass our mod script. Whenever we talk about hooking a function or calling an ingame function we usually assume that static analysis has been performed on the game binary to find functions and offsets of interest. `IDA Pro` is my dissasembler of choice but `Ghidra` is probably also worth mentioning. For debugging, `Cheat Engine` usually does the job quite well. However debugging Black Ops 1 is not as simple as attaching to the proccess and setting breakpoints. As far as I know this was possible in the official release version of the game, but the game received multiple updates/patches which introduced various primitive anti-debugging and anti cheat detections which must be bypassed. Usually if I am starting reverse engineering on a game, my first quick option is [scyllahide](https://github.com/lunarjournal/scyllahide). This seemed to work for the latest Black Ops 1 patch which allowed me to set breakpoints in game which aided in the search for various Black Ops 1 function addresses. As we know research is an important part of any reverse engineering task, without adequate research you could be wasting hours, days or even months of your time by doing the same work that others have done before. So I started research and found out that the source code for the CoD 4 server was leaked at some point and can be found in the following repo: [cod4x_server](https://github.com/lunarjournal/cod4x_server) This is very valuable information and was the basis for understanding how GSC scripts are loaded and compiled in game. # GSC Script Call Chain The next task was figuring out the call chain (sequence of function calls) which is required for loading custom GSC scripts. So I decided to search for the keyword `script` and stumbled upon a function called `Scr_LoadScript` [here](https://github.com/lunarjournal/CoD4x_Server/blob/14f0d8a205d80c9b30e308b57f0cd9bd6d7cdb1c/src/scr_vm_main.c#L1018) which seemed to be responsible for loading a GSC or CSC script (CSC scripts are almost identical to GSC scripts but are executed by the client instead of a server). The only argument to `Scr_LoadScript` was a const character string however: ``` unsigned int Scr_LoadScript(const char* scriptname) ``` which I assumed was some type of resource/asset string. I therefore proceeded to look for any functions that would load assets and stumbled upon `DB_AddXAsset` [here](https://github.com/lunarjournal/CoD4x_Server/blob/14f0d8a205d80c9b30e308b57f0cd9bd6d7cdb1c/src/bspc/db_miniload.cpp#L2919). The function signature for `DB_addXAasset` was as follows: ``` XAssetHeader __cdecl DB_AddXAsset(XAssetType type, XAssetHeader header) ``` I decided to search for the definition of `XAssetHeader`. `XAssetHeader` was defined as a union of various [structs](https://github.com/lunarjournal/CoD4x_Server/blob/14f0d8a205d80c9b30e308b57f0cd9bd6d7cdb1c/src/xassets.h#L81): ``` union XAssetHeader { struct XModelPieces *xmodelPieces; struct PhysPreset *physPreset; struct XAnimParts *parts; ... struct FxImpactTable *impactFx; struct RawFile *rawfile; struct StringTable *stringTable; void *data; }; ``` Out of all the structs listed `RawFile` seemed like the most interesting for our purposes (loading GSC script assets). Let's have a look at the `Rawfile` structure [itself](https://github.com/lunarjournal/CoD4x_Server/blob/14f0d8a205d80c9b30e308b57f0cd9bd6d7cdb1c/src/xassets/rawfile.h#L5): ``` struct RawFile { const char *name; int len; const char *buffer; }; ``` Seems simple enough: * `name` is the resource/asset identifier * `len` is the total buffer length * `buffer` is the buffer holding our script to compile. One thing worth noting is that we don't actually know the format that scripts are stored in before they are compiled (i.e format of `buffer`). For example, are they plaintext, compressed or encrypted at all? We will discuss this further on. Let us now for a brief moment turn our attention to the following code block: ``` XAssetEntry newEntry; newEntry.asset.type = type; newEntry.asset.header = header; existingEntry = DB_LinkXAssetEntry(&newEntry, 0); ``` This seems to be doing the actual asset allocation. The first agument is of type `XAssetEntry` which is defined as: ``` struct XAssetEntry { struct XAsset asset; byte zoneIndex; bool inuse; uint16_t nextHash; uint16_t nextOverride; uint16_t usageFrame; }; ``` The first element of this struct `asset` looks interesting: ``` struct XAsset { enum XAssetType type; union XAssetHeader header; }; ``` We know what `XAssetHeader` is and `XAssetType` is a simple enum which is used to indicate the asset type: ``` enum XAssetType { ASSET_TYPE_XMODELPIECES, ASSET_TYPE_PHYSPRESET, ASSET_TYPE_XANIMPARTS, ... ASSET_TYPE_RAWFILE } ``` `ASSET_TYPE_RAWFILE` seems like the right enum value in our case. With all this information we now know what is required to load a script asset: 1. Allocate `RawFile` struct with script name, size and data buffer. 2. Initialise `XAssetHeader` union with pointer to our `Rawfile` struct. 3. Allocate `XAssetEntry` struct. 4. Set `asset.type` to `ASSET_TYPE_RAWFILE` (`XAssetEntry`) 5. Set `asset.header` to `XAssetHeader` allocated earlier (`XAssetEntry`). 6. Call `DB_LinkXAssetEntry` to 'link' the asset After this series of steps it should be possible to call `Scr_LoadScript` with the path of our asset. This would be the same as the `name` member of the `RawFile` struct. # Format of raw GSC scripts We now have most of the required information to load GSC script assets into memory but one question remains, what format are they in? To answer this question I thought a good starting point would be to hook/detour `DB_LinkXAssetEntry` and dump the `RawFile` data buffer for a GSC script to a file for analysis. To detour functions in the game who will have to bypass an annoying anti-debug feature introduced into the game that uses `GetTickCount` to analyse a thread's timing behaviour. Using Cheat Engine I managed to trace the evil function to `0x004C06E0`: ```c #define T5_Thread_Timer 0x004C06E0 ``` ![Anti Debug Timer](https://lunarjournal.github.io/images/6/03.jpg) Anti-debug timer view in IDA So make sure you patch this out otherwise the game will simply close after some time after detouring. We would have to hook the function before loading a solo zombie match as script assets are loaded into memory shortly after starting a game mode. I wrote a simple detouring library called [cdl86](https://github.com/lunarjournal/cdl86) that can hook/detour functions either by inserting a `JMP` opcode at the target functions address to a detour function or through a software breakpoint. I will leave this task up to the reader to perform. What you need is an address and I took the liberty of finding the address of `DB_LinkXAssetEntry` for you in IDA Pro. If you are wondering how I found the address, it would be a combination of research, debugging and static analysis. A lot comes down to recognising patterns in the dissassembly. In general as you find the address of functions it gradually becomes easier to find the address of other related functions. Bare in mind this is the address of the last x86 (32-bit) PC version of the game: ```c #define T5_DB_LinkXAssetEntry 0x007A2F10 ``` Once you have a GSC script dump, open it in your favourite text editor (I used HxD). This is a dump of a small GSC script from memory: ![GSC Dump](https://lunarjournal.github.io/images/6/02.jpg) We can immediately see that the script is not stored in simple plaintext. In this case I like to look for magic bytes which may indicate if the file was compressed. zlib compression is one of the most common compression types for binary files. Based off research I did, zlib uses the following magic bytes: ``` 78 01 - No Compression/low 78 9C - Default Compression 78 DA - Best Compression ``` These bytes should appear not too far from the start of the file. Notice that in the dump these bytes appear at offset `0x8` and `0x9` so there is a good chance this file is zlib compressed. Let's test this theory, here is a simple zlib inflate python script: ``` import zlib with open(sys.argv[1], 'rb') as compressed_data: compressed_data = compressed_data.read() unzipped = zlib.decompress(compressed_data[8:]) with open(sys.argv[2], 'wb') as result: result.write(unzipped); result.close() ``` I get the following text: ``` // THIS FILE IS AUTOGENERATED, DO NOT MODIFY main() { self setModel("c_jap_takeo_body"); self.voice = "vietnamese"; self.skeleton = "base"; } precache() { precacheModel("c_jap_takeo_body"); } ``` This text has a size of `0xC6`. Great and `+1` for the data being stored in plaintext! So what are the first 8 bytes used for? If you have a sharp eye you might have noticed that this 8 bytes actually contains 2 values. The first 4 bytes seems to represent our inflated (decompressed) data size of `0xC6` and the second value seems to represent the `size of the file - 8 bytes` or the size of our compressed data which is `0x9A`. These values seem to be stored in little endian format with the 4 byte integers ordered as LSB. Great we now have enough information to load a plaintext GSC script, compress it and load it as an asset using `DB_LinkXAssetEntry`. It's pretty cool that it is this straightforward to decompress GSC scripts as it gives insight into how the game modes/logic were implemented and plenty of ideas for mods of course! So in theory we could inject a DLL, read our GSC script, compress it, link it with `DB_LinkXAssetEntry` and then call `Scr_LoadScript` right? We will discuss this in the next section. # Loading and executing GSC script assets It turns out that we cannot simply call `Scr_LoadScript` when we like. As mentioned above CoD Black Ops will load all script assets using `DB_LinkXAssetEntry` and then compile them using `Scr_LoadScript` while a map is loading. Therefore we have to load our GSC script at the correct time otherwise it won't work. The strategy is therefore to hook `Scr_LoadScript` and then load our custom GSC script asset when `Scr_LoadScript` is loading one of the last GSC scripts for the current map. It turns out that one of the last GSC scripts contains the current map name. To get the value of the current map we can utilize the function `Dvar_FindVar` which i found to be at address: `0x0057FF80`: ``` #define T5_Dvar_FindVar 0x0057FF80 ``` It's function prototype is defined as follows: ``` typedef uint8_t* (__cdecl* Dvar_FindVar_t)( uint8_t* variable ); ``` The variable in question is `mapname` which will return the current map. It should be noted that `Scr_LoadScript` does not execute our GSC script however, instead we defer execution of our main function in our GSC script until the map has finished loading. To this end i found a convenient function that is called when the game is just about to start at address `0x004B7F80`: ``` #define T5_Scr_LoadGameType 0x004B7F80 ``` with the following signature: ``` typedef void (__cdecl* Scr_LoadGameType_t)( void ); ``` To defer execution however we need to obtain the function handle after the GSC script has been loaded in our hooked `Scr_LoadScript`. In this case we utilize a function called `Scr_GetFunctionHandle` which returns the address of a function which has been loaded into the GSC execution environment or VM. It has the following function signature: ``` typedef int32_t (__cdecl* Scr_GetFunctionHandle_t)( int32_t scriptInstance, const uint8_t* scriptName, const uint8_t* functioName ); ``` `scriptInstance` is a variable which determines whether the script is a GSC or CSC type script. A value of `0` indicates a GSC script while `1` indicated a CSC script. The function handle to our loaded script is then stored in a global variable and the function can be executed in it's own thread using `Scr_ExecThread` which has the following prototype: ``` typedef uint16_t (__cdecl* Scr_ExecThread_t)( int32_t scriptInstance, int32_t handle, int32_t paramCount ); ``` I found this function at address: `0x005598E0`: ``` #define T5_Scr_ExecThread 0x005598E0 ``` I also noticed during static analysis that a call to `T5_Scr_FreeThread` was always made following a call to `Scr_ExecThread` so that the pseudo code would look something like: ``` int16_t handle = Scr_ExecThread(0, func_handle, 0); Scr_FreeThread(handle, 0); ``` We also obviously also need a compression library and for this I am using [miniz](https://github.com/lunarjournal/miniz). So to summarize the steps to inject our `Scr_LoadScript` hook: 1. Query map being loaded with `Dvar_FindVar` 2. Compare current `scriptName` with map name 3. On match open GSC script, compress and load asset as described above. 4. Call `Scr_LoadScript` on our loaded script asset. 5. Get function handle with `Scr_GetFunctionHandle` To execute function hook `Scr_LoadGameType_hk` then: 1. Call `Scr_ExecThread` on function handle. 2. Call `Scr_FreeThread` on handle returned by `Scr_ExecThread` # Black Ops Offsets I have included all the offsets I found for the game using IDA: ``` #define T5_Scr_LoadScript 0x00661AF0 #define T5_Scr_GetFunctionHandle 0x004E3470 #define T5_DB_LinkXAssetEntry 0x007A2F10 #define T5_Scr_ExecThread 0x005598E0 #define T5_Scr_FreeThread 0x005DE2C0 #define T5_Scr_LoadGameType 0x004B7F80 #define T5_Dvar_FindVar 0x0057FF80 #define T5_Assign_Hotfix 0x007A4800 #define T5_init_trigger 0x00C793B0 #define T5_Thread_Timer 0x004C06E0 ``` A simple challenge would be to find these offsets yourself, it is not difficult. Black Ops 1 uses the `__cdecl` calling convention for functions. # Source Code I have included the source code of my simple GSC loader in the [following](https://github.com/lunarjournal/gsctool/) repo. I have also included a sample GSC script that spawns a raygun at spawn in zombies solo mode and dumps GSC scripts as they are loaded via a `DB_LinkXAssetEntry` hook. # Demo {% include youtube.html id='Gs9mHXyiGNg' %}