So far, I think I have spent a total of 8 hours working on a reverse engineering project to mod Wargroove. I thought it would be trivial, but it has been a little more convoluted than I expected due to my inexperience in reverse engineering. The end goal is to make an Advance Wars total conversion mod.
Wargroove is based on the Halley engine. It is a well-designed C++14 game engine – arguably one of the best-designed game engines I have seen to date (and that’s saying something). The CMake build process is straightforward, and most libraries can be statically linked without hassle. The engine also encourages scripting using Lua, which means that how much of the game that can be modded might be much greater than I originally anticipated (which was just image resources).
I was able to hack up Halley’s asset pack inspector to try to extract Wargroove’s assets, and lo and behold, it listed out all of the files in each pack. But there was one problem: the files it extracts were all gibberish. Naturally, all of the asset packs are encrypted using AES. That means I have to find the IV and a decryption key, which is just a string.
The IV is easy to find: it’s in the header of the asset pack. However, the key is not so easy to find: you have to take a deep dive into the executable to find it.
I’ve done reverse engineering before as part of a university course, and of course a little bit in my spare time using Radare. I thought this would be relatively easy: find the code that corresponds to the decrypt function, then find cross-references to it, tracing upward until I can find a reference to some kind of string. But while I have come close, I have not been able to find the string through static analysis.
All right, so let’s try dynamic analysis. The problem with dynamic analysis, however, is that it’s difficult to hook into the executable right on game startup due to its dependency on Steam, so I can’t put a breakpoint on the decrypt function. Instead, however, I intentionally caused an error on load time to trigger an error message, which gives me a chance to hook a debugger and inspect the memory.
My first attempt to cause an error was by modifying what I thought was a decryption key and then watching the game try to decrypt without success. I’m basically looking for a string that is at least 16 characters long, and I found some good candidates that were close to some game initialization-related strings. However, none of the keys that I modified caused the game to crash.
My second, fail-safe attempt, was to simply rename one of the .dat files, which surely caused the game to fail to run.
Yet still no dice. None of the references in the stack pointed to anything that looked like a string, except for the error message in the dialog box. It was almost as if the error message that was produced overwrote the decryption key that I needed, which doesn’t seem like it’s supposed to happen.
After a while, I considered inspecting the network to see if the decryption key was being received from a server. But the game makes zero network communication except in the multiplayer and user-generated content (UGC) modes, and besides, getting a decryption key from a server would imply always-online DRM.
While IDA has given many clues, it does not provide any useful cross-references for the supposed decryption keys that I found. I don’t believe there is anything devious going on here – the developers would not take so much time to obfuscate a single string – so I’m going to give those strings a hard pass and keep looking.
I think I will focus my efforts on finding a way to hook into the executable right at start time, so that I can lay a breakpoint right at the decrypt function, which will give me the most accurate stack trace. I think this is possible if I hook into the Steam client and then watch for child process creation.
None of this is easy, though, when you are using Steam on Wine and launching Wargroove with an old version of Proton, in OpenGL mode, with the –no-intro flag, and then simultaneously launching x64dbg from a terminal with the same Wine prefix as Wargroove, with WINEESYNC set to 1. Yikes! I should probably do the debugging on my Windows machine.
At times, I feel like hitting my head against the wall, but I have confidence that I will find the key eventually so that I can move forward.
Unfortunately, after another six hours sunk into the project, I haven’t been able to make much headway. The Steam library is interfering with the debug hook on my Windows machine. Using the Image File Execution Options in the Windows registry, I can immediately hook a debugger on process startup; however, the interruption from hooking x96dbg causes the Steam API to fail initialization with a cryptic error code, and therefore halts the entire game from starting up.
Thinking that maybe the interference was solely an x96dbg issue, I tried Cheat Engine instead. I was originally hesitant about using Cheat Engine due to my unfamiliarity with it, but nevertheless it does not seem to support just-in-time debugging since elevation is required. Moreover, on automatically detecting the process and manually pausing it on startup, upon hooking any debugger, the game crashes.
Heck, when the engine fails to load an asset pack, it even prints a stack trace in the message box! The problem is that the stack trace string seems to overwrite the decryption key that I need.
I think what I need to do is patch a breakpoint into the executable by way of an INT 3.
Okay, well, that still didn’t work. I’ll have to try again later.
5/7: Darn it! Someone beat me to it. They created a closed-source tool in Java called ModPacker. Let me find out what the decryption key ultimately came out to be:
The tool uses only the first 16 characters of this Base64-encoded string.
I am not even sure what process was taken to find this string. It humiliates me that I was not able to find this string, but for someone else, the process seemed effortless.
I do not feel worthy of having this decryption key, and the only gripe I have against this ModPacker tool is that it is closed-source and reinvents the wheel in how it uses its own implementation of the Halley packer and unpacker tools instead of using the official tools.