I have always been interested in learning reverse engineering techniques, but the tedium and complexity of tasks involved in such an endeavor had always stood in the way. To be able to truly engage in the subject, I needed to target reverse engineering something that piques my interest; something that had prior documentation, and something that had an active community behind it. The problem was, I didn’t know where to look.
…and then I stumbled upon ElDorito, a modification for the game Halo Online. For those who are unfamiliar with Halo Online, it is a Russian-only game based on the Halo 3 game engine (internally titled Blam!). Halo 3 was initially released on the Xbox 360, which is powered by PowerPC processor architecture. Because of this, among other onvious reasons, the game could not run on Intel’s x86 processor architecture. When Halo: The master Chief Collection was released on the Xbox One, the game’s code had to have been ported to the x86 architecture, as that is what the Xbox One’s hardware uses. Because of this, the Blam! Engine could now run on PC, and that is exactly what Saber Interactive did with the ported code.
Halo Online isn’t Halo 3, however, and many diehard fans of the series were upset by this. Saber Interactive had essentially gutted features from the engine and turned the game into a multiplayer only free-to-play pay-to-win game where players could spend real money on power ups, giving them the edge over other players. Left over in the game’s code, however, were many of the base game’s functions, and thanks to a team of highly talented individuals at the #eldorado channel on Snoonet, some of the functionality was re-enabled in the game.
When I learned about this, I immediately took interest, and wanted to learn as much as I could about reverse engineering from the community responsible for creating the mod. I hopped online and started asking questions.
A few weeks had passed when a user noticed that I was taking interest in the development of the mod. The source code for the project was on Github, and as I browsed the thousands of lines of C++ and raw x86 Assembly, he was willing to answer my questions.
This led to me learning reverse engineering using CheatEngine as a debugger and IDA Pro as a decompiler. While my skills in this field are still extremely rudimentary, I was able to whip up a script in x86 Assembly that fixes a core game function (in a very hackish way).
The way the ElDorito library works is it attaches itself to the process on startup, much like what hacks for popular games do. Once attached, it replaces lines of instructions with jmp statements to effectively block out the old code and replace it with the new assembly, which is stored in the allocated memory of the library. Once the main changes have been made, the new code then must replicate what the old code did to prevent errors that will ultimately crash the process.
In this script, which is viewable in the image above or in my fork of the project on Github, the code responsible for prompting the player of nearby equip-able weapons is changed. The community had previously re-enabled the ability to equip two weapons at the same time (dual wielding as it was called in Halo 3), but the Pickup Weapon prompt never displayed the text responsible for notifying the player that a second weapon could be picked up. The problem with this fix, however, is that it modifies the current pickup text instead of displaying the proper text on the left hand side of the screen. Regardless, creating this hotfix was a great way to familiarize myself with x86 Assembly, how data is dynamically stored in memory, and how to consistently find the right memory addresses to alter.
Here’s a more in-depth explanation of this hack:
- At address 0x4EDAA0 is the subroutine responsible for reading strings from the memory when they are displayed on the HUD. The hack starts by replacing the instructions at this address with a jmp to the memory location of the replacement subroutine in the DLL (see CodeCaves)
- Now, every time the game calls the original subroutine, the processor jumps to the new code
- The main goal here is to tweak the code while allowing it to flow like it normally would. To accomplish this, we need to push ebp into the stack so we can pop it back into place later. We do the same for ebx.
- Next up, we move a single byte from address 0x216184A into bl. As it turns out, this address is a constant location of memory that stores whether or not the currently held weapon uses both hands or if a second weapon can be picked up. Here’s what I determined after some testing:
- The value is 0 when there is no nearby weapon that can be picked up.
- The value is 2 when a weapon is nearby, but the weapon requires both hands
- The value is 4 when the same weapon as the one currently being held is nearby and is dual-wieldable
- The value is 6 if a different weapon is nearby but can be dual-wielded with the current weapon
- So naturally, we want to tweak the code on the condition that the value is less than or equal to two. On line 113 we do just that by calling a cmp instruction, followed by a jle (jump if less than or equal to) to the end of the modified subroutine.
- Assuming the byte was greater than 2, the script continues to another cmp call. This time, we take the value inside of eax as a pointer to the data that is being read and compare it against the value 0x73657250. This value corresponds to the string “Pres” in little endian, which is a concatenated version of “Press” (this is a hack mind you). If it does indeed spell out “Pres” at that location, we jmp to fixCommand. Otherwise, we jmp to dualWieldState.
- If fixCommand is jumped to, we take the pointer used in the previous cmp and add 7 to it. This makes the subroutine read and display “Hold” instead of “Press”.
- If dualWieldState is jumped to, we compare the byte at pointer [eax + 6] to value 0x9F. 0x9F is linked to the icon of the right bumper button of the Xbox 360 controller (provided you’re using a controller). If [eax + 6] is indeed equal to 0x9F, 0x23E is added to eax. Essentially, this takes the location that the function should be reading at and changes it to the location of the next “to dual-wield” string in memory. Since the strings are always static, this works.
- Now that all of the modifications have been taken care of, we need to wrap up this modified subroutine by restoring its original structure. When code reaches the “end” label (or jumps to it), ebx is popped from the stack. Sequentially, lines of the original function are replicated in the modified function. When everything is synced back up, we just push 0x4EDAAA (10 lines down from the original jmp hook) to the stack and call a retn to end the subroutine and jump to that line of code.
Whew, that was exhausting. It’s not a practical solution either. If you analyze the code, you’ll realize that the modified subroutine is ran three times per tick of the game engine (or one frame rendered). This didn’t produce any noticeable performance overhead, but diving into the disassembly of the game engine and re-enabling nop‘ed out code would be a better approach.
I began work on a real-time memory editor for the same project, which I internally named Goldputsch. It’s still in early development, as I haven’t had much time to work on it, but I hope to finish it some day.