The Hacking of Dark Souls III Continues with the Apocalypse
And now, the hacking of Dark Souls III shall begin in earnest as we start to make it Omnified. If you read my previous article, you will know that we have managed to hook into some important player-related data points, and that we have charted some important data structures out. Today, we’re going to implement the first of my game-neutral Omnified systems: the Apocalypse system.
In the near future I will be publishing articles on my hackpad that go over each of my game-neutral Omnified systems. The day I have one for the Apocalypse system published, I will link to it here. Until then, however, you can either watch the video below for a general overview, or read a brief summarization below as to what the Apocalypse system does.
The Apocalypse system is a replacement for how damage is handled in a game. When the player is hit by an enemy, instead of damage simply being applied, a ten sided die is cast, the result of which instead applies a devastating effect to the player, typically greatly increasing the original damage.
There is much more to the Apocalypse system than that, however I will leave it to the specific article I’ll be writing on the topic to explain it to you more fully. For today, I’ll be focusing on hacking the Apocalypse system into Dark Souls III and then enjoying the results.
But First…Let’s Grab Some More Player Data
In my previous article, I wrote some code that created pointers set to the player’s root and coordinate structures. One piece of data I neglected to include was the player’s health. It will be useful to have a separate pointer for our HP, even more so now that we intend on implementing the Apocalypse system.
So, let’s update our player hook code to also create a pointer named playerVitals as well. It will point to our player’s SprjCharDataModule, which we concluded was a container for all of our character’s most important vitals (health, stamina, FP, etc.).
Updated Player Hook with Vitals Support
// Gets the player's root, coordinates, and vitals structs. // rcx: Address of player's root struct. define(omniPlayerHook, "DarkSoulsIII.exe" + C1A78C) assert(omniPlayerHook, 48 8B 01 FF 90 C0 02 00 00) alloc(getPlayer,$1000, omniPlayerHook) alloc(player,8) alloc(playerCoords,8) alloc(playerVitals,8) registersymbol(omniPlayerHook) registersymbol(player) registersymbol(playerCoords) registersymbol(playerVitals) getPlayer: push rbx push rdx mov rbx,player mov [rbx],rcx mov rdx,[rcx+2428] mov rbx,playerCoords mov [rbx],rdx mov rdx,[rcx+23D8] mov rbx,playerVitals mov [rbx],rdx pop rdx pop rbx getPlayerOriginalCode: mov rax,[rcx] call qword ptr [rax+000002C0] jmp getPlayerReturn omniPlayerHook: jmp getPlayer nop 4 getPlayerReturn:
We now have a pointer pointing directly to our SprjCharDataModule, which will make it a bit easier to grab our character’s health; something we will need to do when hooking up the Apocalypse system.
Finding the Damage Application Code
For us to be successful in the hacking of the Apocalypse system into Dark Souls III, we need to create an initiation point. The initiation point for the Apocalypse system needs to be located in the area of game code where damage is being applied to a creature’s current health. This is not the same place in code where the creature’s health in memory is being updated to a new value.
Finding the place in code where the creature’s health in memory is updated to a new value due to damage is trivial; all one needs to do is use Cheat Engine’s “Find out what writes to this address” and capture the code executed when getting hit by an enemy.
What we need to do is find the code that calculates what that new value should be. That code will consist of a damage amount being applied to a current, working health value. This is the ideal spot to manipulate the amount of damage being done to the character; manipulating the damage at the place of memory commitment instead can lead to problems as other code affected by the damage amount will not be working off the updated value.
This “damage application” code can sometimes be miles away from where the updated value is written to memory, however. It really depends on the game. Let’s see how hard it is to find when hacking Dark Souls III!
To start our search, let’s find the code that updates our player’s health to a new value after receiving damage. Right click on our “Health” in the address list, and choose “Find out what writes to this address”.
After we do this, we need to do the easiest thing we have to do for this whole exercise: we need to get smacked by an enemy! So let us go forth, and get smacked by a baddie!
After getting smacked, we’ll see the following in the “opcode write” window that appeared from the previous step:
The instruction with the single count of 1 appeared at the same time our character was smacked by a baddie. This, then, is the code responsible for updating our health following a successful attack from an enemy. Let’s take a quick look at it.
Here we can see the updated health value being written to our health, located at [rbx+D8]. Our goal is to find the code that calculates the value of eax, the register that contains the updated health value. Scrolling up a bit, we don’t see much that looks relevant, just some loading of values into registers from the stack.
One “funny” thing is that while it is easy to look forward in assembly code execution, it can be horrendously difficult to look backward. I can easily tell you what the execution path is going forward in time, but it can truly be difficult to know what was executed before a given line of code.
Luckily, we have a few tools at our disposal to do just this. The first is by going up the call stack. To get a look at the call stack as it is when this code is executed, we insert a breakpoint at our instruction and then get smacked again.
Ouch. Looking at the call stack in the picture above, we don’t see much at all. But this can’t be possible right? How could there have been no calls prior to this code being executed?
The reason why we see an empty call stack is because the stack is “corrupted” more or less at this point. We need to wait for it to become aligned again before we will be able to see anything.
The easiest way to do this is to continue execution until we get to a ret instruction. If we scroll down a bit, we can see a ret instruction not to far from where we are, so let’s just throw a breakpoint there and hit the Run button.
Woot! A call stack. Not a very big call stack though, and still showing signs of “corruption”. Let’s continue execution until more stack correction occurs and then see if there’s more information to go off of.
After executing a few more instructions, we reach this bit of code, showing a much more healthy call stack.
This looks much better. Now we have a much clearer picture of the execution path taken in order to reach our damaged health update code. And by the way, I must say right here and now, this is some very odd assembly code. There are simply a plethora of jmp instructions, way more than usual. This bears further investigation at a later time…
Putting that aside, we need to figure out how that value stored in eax in the health update code is calculated. To do this, we are going to need to do an instruction execution trace. This will record a specified number of instructions executed from a point in code, allowing us to go through the history of instructions until we find the ones responsible for calculating that new health value.
To do this, we need to start a trace up a bit on the call stack from where we currently are. Let’s try a single call up first (there seems to be a large amount of instructions executed between each call here). We double click on the entry in the call stack displayed as DarkSoulsIII.exe+9B93FC.
We then right click on one instruction above the instruction we’ve been brought to from double clicking on that entry in the call stack and select “Break and trace instructions”.
A window will appear asking for the number of instructions to trace. Let’s bump it up a bit from 1000 to 2000. We then hit OK, and then get smacked again. Once that occurs, we are presented with a meaty trace window containing the instructions that executed between the specified instruction, the damaged health update code, and beyond.
This part is not simple, but at least we have a way of recording execution for later review. We need to find code that looks like it is calculating the value that will eventually be stored in eax. If we can actually locate the damaged health update code inside the trace, we can easily then go up from there until we find it.
I proceeded to examine all the instructions that executed prior to the damaged health update code, and I eventually came across this bit of unorthodox code; code that is very easily missed. The only reason it stuck out to me is because I’ve seen very similar code used in Dark Souls I and Sekiro.
This is it! The damage application code, located at DarkSoulsIII.exe+451C72D. However, this is probably not the code one may have expected to see.
If you had a health value, and a damage amount, how would you calculate the updated health value? Well, you’d probably load the damage amount in one register, the health in another, and then subtract the damage amount from the health with a sub instruction.
That is not what is happening here. They are instead performing the arithmetic with a lea (Load Effective Address) instruction, adding a strange looking value of FFFFFFC1 to 137 as if they were addresses. The result is then stored in the rcx register.
The strange looking value of FFFFFFC1 is actually the damage amount in two’s complement form! The damage amount being used is a negative number. We will need to bear that in mind later, when we code our initiation point, as the Apocalypse system expects the damage amount to be a positive number.
But, that aside, we have what we need! A perfect initiation point. We have our two required bits (or bytes I should say!) of data: the damage amount (found in the rdi register), and the working health value (found in the rax register). Let’s get to writing that initiation point so we can enjoy the ecstasy that is the Omnified Apocalypse!
Piecing Together the Apocalypse Initiation Hook
We now know where we’ll be hacking in our initiation point for the Apocalypse system in Dark Souls III. Here’s our starting template for our hook:
Apocalypse Initiation Hook – Template
// Initiates the Apocalypse system. define(omnifyApocalypseHook, "DarkSoulsIII.exe" + 451C72D) assert(omnifyApocalypseHook, 8D 0C 38 89 4C 24 48) alloc(initiateApocalypse,$1000,omnifyApocalypseHook) registersymbol(omnifyApocalypseHook) initiateApocalypse: initiateApocalypseOriginalCode: lea ecx,[rax+rdi] mov [rsp+48],ecx jmp initiateApocalypseReturn omnifyApocalypseHook: jmp initiateApocalypse nop 2 initiateApocalypseReturn:
Our code will go under the initiateApocalypse label, and it needs to do whatever is required so that the arithmetic operation realized by the lea instruction in the initiateApocalypseOriginalCode section is using values produced by the Apocalypse system. Let’s just slow down a bit though and go over real quick what the initiation point needs to actually do.
As the article on the general overview of the Apocalypse system will explain, there are two distinct components of the Apocalypse system: the Player Apocalypse and the Enemy Apocalypse. The Player Apocalypse needs to be executed when the player is receiving damage; the Enemy Apocalypse needs to be executed when an enemy is receiving damage (preferably only from the player).
The Player Apocalypse is initiated by calling executePlayerApocalypse. It requires the following parameters to be provided:
- The player’s coordinates, aligned at the X coordinate.
- The value for the player’s maximum health.
- The working health value (the player’s current health value which is having the damage amount applied to it).
- The damage amount.
All numeric values are expected to be in floating point (which, across all games I’ve Omnified, is the most common data type for health values).
The Enemy Apocalypse is initiated by calling executeEnemyApocalypse. It requires the following parameters to be provided:
- The working health value (the enemy’s current health value which is having the damage amount applied to it).
- The damage amount.
Both functions have the same return values: the eax register will have an updated damage amount, and the ebx register will have an updated working health value, both values having been deemed as appropriate by the Apocalypse system. These return values will be used to update the rax and rdi registers that are used in the original damage application code.
We will start off by writing some code that will prepare the parameters that are used by both Player and Enemy Apocalypse functions. We then will determine whether it is the player or an enemy being damaged, jumping to the appropriate section of code based on that so that the correct Apocalypse function is called.
Some Things to Keep in Mind…
We will want to ensure that the player is the source of the damage before calling the Enemy Apocalypse, for reasons that will be covered in the general Apocalypse overview article!
We’ll also need to keep in mind a few things when preparing the common parameters. We have to convert all the 4 byte integer data types into floating point, and we will need to take the two’s complement of the damage amount before passing that, as the Apocalypse system expects a positive damage number.
In order to determine whether it is the player or the enemy being damaged, we need to have access to the SprjCharDataModule from which the working health value (stored in rax) was sourced. After some experimenting, I found that the address for the SprjCharDataModule belonging to the damage target can be found in the rcx register.
To determine if the player is the one doing the damage to the enemy, we need to figure out if there is any shred of identifiable information in the immediate vicinity that tells us the source of the damage. This requirement can sometimes be very difficult to fulfill, as many times there’s no reason why the damage source identity needs to even be known in the very low level area that is the damage application code.
Luckily for us, solving this particular problem in the hacking of the Apocalypse system into Dark Souls III is no trouble at all. Following further experimentation, I found that the address to the root structure for the player/enemy responsible for the damage is stored on the stack at [rsp+20].
So, let’s take a look at the first bit of code we’ll be writing to prepare parameters used by both functions as well as determine our execution path.
Apocalypse Initiation Hook – First Steps
initiateApocalypse: pushf // Backing up a few SSE registers we'll be using to // hold our converted floating points. sub rsp,10 movdqu [rsp],xmm0 sub rsp,10 movdqu [rsp],xmm1 sub rsp,10 movdqu [rsp],xmm2 // Backing up rbx as it is used to hold a return value from the Apocalypse // system and is not meant to be updated upon completion of the initiation. push rbx // Backing up working registers that will hold pointer addresses and other // temporary values. push rdx push rsi // Take the two's complement of the damage amount so it is positive. neg edi // Convert the working health and damage amount to floating point. cvtsi2ss xmm0,edi cvtsi2ss xmm1,rax // If the player isn't the target, we need to ensure he/she is the source, // otherwise we want to bail out here and allow normal damage application to // occur. mov rdx,playerVitals cmp [rdx],rcx je initiatePlayerApocalypse // The damage source is originally at [rsp+20], however due to the code above // preserving registers values on the stack, its location has moved to [rsp+6A]. mov rdx,player mov rsi,[rsp+6A] cmp [rdx],rsi je initiateEnemyApocalypse jmp initiateApocalypseExit
I’ve documented the code so it should explain itself pretty well. When the above code is finished executing, we will have the properly prepared damage amount and working health values stored in the xmm0 and xmm1 SSE registers respectively.
Execution will then proceed to execute the Player Apocalypse if the player is receiving the damage, or the Enemy Apocalypse if the player is damaging an enemy. If an enemy is receiving damage not from the player, but from another source instead, then we jump to the cleanup portion of our hook and bail out.
Next bit of code here is the execution of the Player Apocalypse. We have two of the four parameters prepared at this point, all we need to do is acquire a coordinates structure for the player aligned at the X coordinate, as well as the player’s maximum health in floating point form.
Apocalypse Initiation Hook – Player Apocalypse
initiatePlayerApocalypse: // We realign the player's coordinates struct so it begins at the X coordinate. mov rdx,playerCoords mov rbx,[rdx] lea rdx,[rbx+80] // Convert the player's maximum health to floating point. mov ebx,[rcx+E0] cvtsi2ss xmm2,ebx // Push the damage amount parameter. sub rsp,8 movd [rsp],xmm0 // Push the working health value parameter. sub rsp,8 movd [rsp],xmm1 // Push the maximum health value parameter. sub rsp,8 movd [rsp],xmm2 // Push the aligned coordinates struct parameter. push rdx call executePlayerApocalypse jmp initiateApocalypseUpdateDamage
Once this code is done executing, we’ll have an updated damage amount in rax and an updated working health value in rbx. Execution then will shift to code that processes the return values and commits it to the registers being used by the damage application code.
With the Player Apocalypse execution implemented, all that remains is the Enemy Apocalypse, the processing of return values, and cleanup. For the Enemy Apocalypse, we actually have all the parameters we need right at the outset, as all it requires is just the damage amount and working health values.
Apocalypse Initiation Hook – Enemy Apocalypse
initiateEnemyApocalypse: // Push the damage amount parameter. sub rsp,8 movd [rsp],xmm0 // Push the working health value parameter. sub rsp,8 movd [rsp],xmm1 call executeEnemyApocalypse
Yup. That’s all there is to do with that. Finally, we need to write some code that is called for both Player and Enemy Apocalypses: the return value processing code that will actually update the parameters used in the damage application code. We will then wrap things up with some cleanup code to restore the stack, and we’re done!
Apocalypse Initiation Hook – Return Value Processing and Cleanup
initiateApocalypseUpdateDamage: // Convert the updated damage and working health values back to integers. movd xmm0,eax cvtss2si edi,xmm0 // Convert the updated working health value to an integer. movd xmm0,ebx cvtss2si rax,xmm0 initiateApocalypseExit: neg edi // Restore backed up values. pop rsi pop rdx pop rbx movdqu xmm2,[rsp] add rsp,10 movdqu xmm1,[rsp] add rsp,10 movdqu xmm0,[rsp] add rsp,10
Woot! We have attained success in our hacking of the Apocalypse system into Dark Souls III! The game is now technically 1/3rd Omnified. But the code actually won’t even be able to assemble yet without a few more actions on our part.
External Omnified References Are Required
If you’re not familiar with the Omnified codebase, you may have already asked: wait a second, where the hell are executePlayerApocalypse and executeEnemyApocalypse defined? Those can be found along with the rest of my game-neutral functions belonging to the Omnified framework of code that resides in the Omnified.lua library file. This file needs to be loaded and have its functions imported by our Omnified Dark Souls III hack code.
There is also the matter of importing display code I use to print an on-stream event log of the Apocalypse systems happenings, as well as a game data display showing various damage statistics maintained by the Apocalypse system. This code can be found in the OmnifiedDisplay.lua library file.
I’ll be writing some articles eventually that cover how the Omnified framework is organized as well as how I maintain and use what are essentially “include” files with our assembly hacks.
The contents of these files and their registration in our hack code will be covered in the Apocalypse general overview article that I will get to writing eventually. Regardless of that, all code will be published when I publish the Omnified Dark Souls III hack.
And…Does It Work?
Hooking in the code we’ve written, it does indeed work! Enemies are hitting me for 69x damage; some hits from enemies even send me underneath the ground, where I fall to my death while Tom Petty’s Free Fallin’ plays (yes that is seriously a feature of the Apocalypse system).
The Apocalypse display windows are showing up on the stream as well. Everything is lookin’ juicy.
There you have it. Apocalypse system is in.
Here is the complete code required for our successful hacking of the Apocalypse system into Dark Souls III.
Apocalypse Initiation Hook – Complete
// Initiates the Apocalypse system. // rcx: Vitals struct of damage target. // rsp+20: Root struct of damage source. // rax: Working health value. // rdi: Damage amount in two's complement form. define(omnifyApocalypseHook, "DarkSoulsIII.exe" + 451C72D) assert(omnifyApocalypseHook, 8D 0C 38 89 4C 24 48) alloc(initiateApocalypse,$1000,omnifyApocalypseHook) registersymbol(omnifyApocalypseHook) initiateApocalypse: pushf // Backing up a few SSE registers we'll be using to // hold our converted floating points. sub rsp,10 movdqu [rsp],xmm0 sub rsp,10 movdqu [rsp],xmm1 sub rsp,10 movdqu [rsp],xmm2 // Backing up rbx as it is used to hold a return value from the Apocalypse // system and is not meant to be updated upon completion of the initiation. push rbx // Backing up working registers that will hold pointer addresses and other // temporary values. push rdx push rsi // Take the two's complement of the damage amount so it is positive. neg edi // Convert the working health and damage amount to floating point. cvtsi2ss xmm0,edi cvtsi2ss xmm1,rax // If the player isn't the target, we need to ensure he/she is the source, // otherwise we want to bail out here and allow normal damage application to // occur. mov rdx,playerVitals cmp [rdx],rcx je initiatePlayerApocalypse // The damage source is originally at [rsp+20], however due to the code above // preserving registers values on the stack, its location has moved to [rsp+6A]. mov rdx,player mov rsi,[rsp+6A] cmp [rdx],rsi je initiateEnemyApocalypse jmp initiateApocalypseExit initiatePlayerApocalypse: // We realign the player's coordinates struct so it begins at the X coordinate. mov rdx,playerCoords mov rbx,[rdx] lea rdx,[rbx+80] // Convert the player's maximum health to floating point. mov ebx,[rcx+E0] cvtsi2ss xmm2,ebx // Push the damage amount parameter. sub rsp,8 movd [rsp],xmm0 // Push the working health value parameter. sub rsp,8 movd [rsp],xmm1 // Push the maximum health value parameter. sub rsp,8 movd [rsp],xmm2 // Push the aligned coordinates struct parameter. push rdx call executePlayerApocalypse jmp initiateApocalypseUpdateDamage initiateEnemyApocalypse: // Push the damage amount parameter. sub rsp,8 movd [rsp],xmm0 // Push the working health value parameter. sub rsp,8 movd [rsp],xmm1 call executeEnemyApocalypse initiateApocalypseUpdateDamage: // Convert the updated damage and working health values back to integers. movd xmm0,eax cvtss2si edi,xmm0 // Convert the updated working health value to an integer. movd xmm0,ebx cvtss2si rax,xmm0 initiateApocalypseExit: neg edi // Restore backed up values. pop rsi pop rdx pop rbx movdqu xmm2,[rsp] add rsp,10 movdqu xmm1,[rsp] add rsp,10 movdqu xmm0,[rsp] add rsp,10 initiateApocalypseOriginalCode: popf lea ecx,[rax+rdi] mov [rsp+48],ecx jmp initiateApocalypseReturn omnifyApocalypseHook: jmp initiateApocalypse nop 2 initiateApocalypseReturn:
That’s it for the Apocalypse system folks. The remaining Omnified systems to be implemented are the Predator and Abomnification systems, each of which will have their own article that I’ll be writing as I hack them in.
Thanks for reading, any questions, lemme know — just don’t forget, if you need to catch this Omnified action live baby, the only place you’re going to be able to do that is on my official Twitch stream: https://twitch.tv/omni
Keep it real my friends. š
-Omni