Reverse engineering aim gods.
Preamble
Many years ago, the peripheral company Finalmouse released a game to promote its new magnesium mice “Starlight”. Although Aim Gods was quickly disfavored by many due to its flipped assets and poor graphics, a group of players formed who saw incredible competitive potential in the game. Just for the sake of my love for this game I will share someone who captured his first experiences playing it in a video. A few months after this video was released, the game had already disappeared; its servers were shut down, and the fanbase, as well as I myself, was left upset. In this series of blogposts I will explain how I went from knowing barely anything about C++, let alone reverse engineering, to reverse engineering Aim Gods and writing my own private servers.
First Steps in 2022
My yearning to play this game again grew stronger and stronger over time, with no chance of fading any time soon. Thankfully one of my good friends still had the game files buried deep on his drive. After launching the game and trying to login I quickly got greeted by ,to no surprise, an error message and quickly assumed that the servers are completely shut down. Which I later realized, I was too quick to assume. Since my experience in C++ was basically non existent at this point of time, I was scouting the Internet for any work already done on Aim Gods. This quickly proved to be a good decision since I found a cheat for Aim Gods, published on Unknowncheats. This was the first time I was confronted with C++, thankfully the friend that also gave me the game files shared the same enthusiasm for Aim Gods and had experience with C++. Thanks to him I picked up the basics quite quickly and started messing around with the game. Since the cheat already had a working SDK I did not bother making a new one which turned out to be a mistake since the game shipped with both a shipping version and a development version which wasn’t stripped of important server functions and also had a pdb shipped with.
Not fully shut down?
I quickly hit a dead end messing with the games functions. So I went to take a look at why the login is failing. After installing Fiddler and inspecting the traffic I noticed that the endpoints are still up and that only their ssl certificate was expired. Thanks to fiddlers SSL decryption which works by replacing the certificate with their own root certificate (ig, im not too sure tbh?). I was able to login and see that all api endpoints are still up and that they used a mix of aws gamelift and fleet servers. The gamelift matchmaking system was still up, however no actual gameservers were available anymore. Now I had a concrete Project to work on, to somehow make the game behave as a server.
Reverse Engineering Unreal Engine Games
Unreal Engine has a pretty powerful reflection system to access type information at runtime. However this reflection system comes with the big downside that, of course, all type information is available to people trying to hack the game. This type information comes from the FNamePool and GObjects list which represent type names and instances of all UObjects existing in the unreal engine game. There are a lot of dumpers written by people way smarter than me which are able to generate full on SDKs for the game you are working on.
First signs of success
In late 2022/early 2023 I managed to get two clients to connect trough a long process of reading through unreal engine games launch arguments and bruteforcing the right ones. What I quickly realized though is that once the clients were connected, nothing happened. The clients were connected but there was no hud, no signs of the match starting whatsoever.
Dumb assumption that costed me a lot of time
For a long time I thought that the functions and classes in the SDK are the only ones in the game. I never questioned if there maybe could be unreflected classes and methods. So I spent tens if not close to hundreds of hours of time finding the server relevant methods. Only after realizing that some methods in the sdk are missing I finally re-questioned myself and came to the conclusion that not all methods and classes are reflected. But only those that the developer marked with the UFUNCTION or UPROPERTY macro. After coming to that insight, I spent the next few weeks reverse engineering the code that is responsible for beginning a match and came to the conclusion that the crucial NotifyBeginPlay function is never called. This function notifies all relevant actors and objects in the level that the match is now starting. After calling NotifyBeginPlay I saw that the gates protecting the spawn area are now opening, which at the time felt like huge process and gave me the relevant confidence and hope to keep going.
Final Steps and the whole nine yards
I still had one big problem, which was the the players weren’t getting spawned. This however was a quick fix since there are some neat unreal engine functions that allow pretty easy Actor spawning. After spawning the relevant Player Pawns, I just needed the PlayerControllers to possess the newly created Pawns which again was a trivial process. I still wondered how to implement the connection of the clients. After looking into Unreal Engine networking I realized that p2p connection was coming with a lot of weird bugs and issues like the hosting player having higher privileges and so on. I then decided to just make one running game the server. It would create the match, destroy its own PlayerController and then await connections by the clients. Making the game behave like a server came with the issue that some asserts failed which make sure that players can’t call privileged functions. However I fixed that by byte patching the relevant if statement leading to the assert(I know it’s not the cleanest way to do it).
Playtest
I still wanted to host a small playtest with interested people, mostly moderators from the finalmouse discord server. I then just hosted a windows machine on azure that handled the servers. Anyways the playtest went horribly wrong since the other players were from NA which lead to the server crashing. (I guess because of lack of ping compensation or desync?) I never really investigated into what is causing the issue and didn’t work on this project in months, but who knows maybe I will continue this project and get it more stable. :) Thanks for reading!