Hours of work to save 10 seconds
EU4 is a fantastic game. When trying to earn achievements however, the super convenient load game option is locked. As a result, the community has developed a habit of manually relaunching the game. In a campaign, that requires a lot of these restarts, the time to do this can quickly add up. Another game in the same engine, (CK2) has an additional mode called Bronzeman, that does not limit the option to save and allows certain achievements. That made me curious how hard it would be to enable some sort of Bronzeman in EU4.
A smooth start
My only experience with reverse engineering comes from cluelessly watching smarter people do it. As a result, I just chose the tools I had seen before. IDA for decompilation and WinDbg to debug the game. Configuring these tools is pretty self explanatory, so the first real task is figuring out where to start in the executable. Typically, when you change any game files, the game recognizes that and disables the Ironman mode outright. I obviously expected the same to happen here, so to even have Ironman, while working on this, I looked at how the game handles the validation.
Because the game proudly displays a checksum at every start of the game, I figured, they would probably just compare this to a known good checksum and set some flag. Turns out that is exactly what they were doing. Just searching for the current checksum returns a result for an even longer string of random chars. Following the references to this string quickly lead me to a strcmp() between the checksum and some other freshly computed value. Setting a breakpoint there showed, that this was in fact the place where the correct game Version flag gets set.
After searching the depth of this internet for the hex values of the x86 instructions required to set the required bits in the output register, I just opened the game in a hex editor, changed a few bytes and... it worked 🎉. I could freely modify the executable without breaking Ironman. However, I quickly realized, that modifying the exe does not even change the checksum. Only the external game files get validated. Presumably this is because they don't want multiple checksums for different platforms. My patch however also worked when editing external game files, so I almost accidentally created a checksum bypass for playing with mods in Ironman.
The actual problem
Even though this meant, I had not made any progress to the actual goal, I had noticed, that a lot of other useful strings are still in the game. Typically executables get stripped and loose all human readable information with it. In this case however there were some symbol names and even local paths to more extensive debug symbols in the game. I don't know if they just ship the same version they work with (considering there are shell scripts and a profile build in the game folder, as well as status output when debugging, that seems quite likely), but it definitely means, that the best approach would be to just search for keywords related to the challenge.
Said and done, I found an Ironman symbol and when watching its value during debugging, it corresponded with the Ironman state of the current game. Changing this value in game was also trivial, as there is a lot of "hover this button effect" logic, that can just be overwritten. Sadly, this did not magically make the load menu appear. It did however allow for a poor man's Rakaly, by letting you convert Ironman to nonIronman saves and back, as you can just create a save with the current state (Ironmanning a non Ironman save does not include any achievements though, so there is not even any meme potential for crazy impossible Ironman saves to post somewhere).
All of this meant, that the pause menu gets build once, when the save is loaded and has no option to change after that. To figure out where the Ironman and non-Ironman build processes diverge, I set breakpoints at everything, that had an Ironman symbol next to menu symbols and loaded a save with a debugger. There I marked all executed parts and then looked at the same logic with a normal save. This was a bit more annoying than I thought, because it often spiralled into SDL2 code and seemingly infinite loops, but at some point it did reveal the correct branch. Inserting a constant result like before worked and I could load saves in Ironman 🎉!
Finishing touches
Despite the advancement, in that moment my biggest fear materialized. Only non-Ironman saves showed up. I expected this, but it meant finding the filter rules for eu4 saves, using some load_Ironman_save function on them and hoping they work while a game is loaded, or writing that myself and all of that from a debugging environment, that easily takes multiple minutes to even start the game.
In an incredible stroke of luck, it turns out I had to do basically non of that. Just finding and then changing the filter rules, with the branch marking method was enough. The Ironman saves could immediately be loaded and even had a little Ironman logo next to them.
If I had to guess, this is debugging code for developers, that don't want to go through the same struggle I feared of minute long reloads, every time they load another Ironman save (with an actual debug build maybe even longer?). They could of course also just be reusing code from another load save menu here, that does normally support Ironman saves.
So, this is where we are now. I implemented a patcher as proof of concept here for anyone interested. As I originally reverse engineered this a few months ago, I already had to port this to two new versions of the game, which as it turns out is actually super fast to do. The binary in that part of the game is basically remaining the same, so just searching for a few surrounding instructions is enough to find the correct patch location.
Thinking back, I wonder what other annoying things could be patched this way. Maybe removing some unnecessary checks could improve late game performance? Maybe there is a hidden follow enemy army button? Who knows, I might check another time.