In the last chapter, we looked at hooking and how it works. However, the last chapter only touched hooking in theory. The code shown was not what actuals hooks in your code look like. For instance, we do not have access to GD’s source code, so we can’t exactly just writeDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/geode-sdk/docs/llms.txt
Use this file to discover all available pages before exploring further.
return ourDetour() at the start of the function we want to hook. Instead, we need to figure out some way to insert hooks into GD’s binary code.
Manual hooking
The main way to create hooks in Geode is using an abstraction called$modify - however, it is a very powerful tool and hard to explain without some preface. It is also just an abstraction; you can create hooks manually in Geode as well through the Mod::addHook interface - although in practice you should never be creating manual hooks unless necessary.
Manual hooks are sometimes necessary, for example to hook some obscure low-level functions that only exists on one platform, like GLFW on Windows. However, for 99% of mods, you should just be using
$modify, since it makes your code much more portable and easier to work with.The traditional way in 2.1 of placing hooks used a library called MinHook. If you’ve been in the GD modding scene before 2.2, you’ve almost certainly heard of MinHook before, or at least seen its 32-bit dynamic library
minhook.x32.dll. However, using MinHook directly is no longer considered good practice. While it can work perfectly fine for a single mod, MinHook has a few issues: it’s Windows-only. hard to use, and if you don’t link to it as a dynamic library, it will cause hook conflicts [Note 1].geode::base::get() + 0x27b480. This is the address of the function. When C++ is compiled down to machine code, all variable and function names are erased and functions are instead given memory addresses. A memory address is just the location in a binary that the function resides in. For example, geode::base::get() + 0x27b480 means that the function is located at offset 0x27b480 (or 2602112 in decimal) bytes from the base address - GD’s base address, given by the function geode::base::get().
The reason we need to add the base address to the function’s address is because the base address of GD is dynamic - it changes between startups!
Addresses
So how do we find out these things? Usually, this is done through reverse engineering; however, RE is quite a complex skill, and would take far too much time to explain here, so it has its own dedicated volume instead. And on top of that, most common functions have already been found. This means that instead of REing the function yourself, you can use the GD bindings that come packaged with Geode.Traditionally, the most common GD header library was gd.h. However, nowadays gd.h is completely obsolete, as it is only for 2.1 and fully unmaintained.
$modify. How it works will be explained in a later chapter, but first we must talk a bit about GD’s game engine: Cocos2d.
Chapter 1.4: Cocos2d
Notes
[Note 1] Hook conflicts are a type of race condition and it happens when two mods try to hook the same function at the same time. If the mods do this sufficiently close to one another, there is a high chance that one mod’s hook will replace the other’s. The end result of this is that one of the mods functions incorrectly, when it fails to hook the function it expected to. In the best case, this just results in the mod losing functionality, but in the extreme case this could cause crashes.