Giving Enemies in Valhalla the Power of the Apocalypse
Greetings all. We had a rather enjoyable initial dive into the internals of Assassin’s Creed: Valhalla in the first article in this series, and now it’s time to put the knowledge we gained to use. This game is going to start getting its butt Omnified.
As per tradition, the first of the Omnified systems we’ll be implementing is the Apocalypse system. Click on that link there for a very detailed explanation as to what that is; or, if you hate reading (why are you reading this then!?), here’s a nice summary video for you:
In addition to hooking Assassin’s Creed: Valhalla up to the Apocalypse system, I’ll also be making a number of improvements to it (the Apocalypse system, not Valhalla…well, I guess making it Omnified is an improvement so…bah, never mind). I’m not going to document these changes in this article, but rather in a supplemental Omnified Design article.
On the Quest for the Damage Application Code
Implementing the Apocalypse system into Assassin’s Creed: Valhalla requires the construction of an initiation point that will be injected directly into the game’s damage application code. To learn about what this code is, what it looks like, and all other related implementation requirements, please consult the appropriate section in my article on the Apocalypse system.
Let’s go look for the health update code, which is going to be the code that assigns new values to our player’s health after some sort of change has occurred to it. The type of change that we’re interested in of course, is the one resulting from getting our booty smacked by a baddie.
After we do get smacked, we observe a single instruction has executed, causing our health to update. Let’s take a look at it:
Hmm, not the most helpful piece of code I’ve seen. We can see here that the health update code is a single instruction function; we’re going to have to put a breakpoint here, get hit again, and then go up the call stack.
After doing so, we are greeted with a more reasonable amount of code to work with:
Immediately preceding the call to our health update code are two conditional branches of execution, both of which appear to be manipulating the value (stored in
edx) that ends up being written to our health.
Could it be that we have already stumbled upon not only the damage application code, but also its opposite (for when the player is healed)? I assume the branch with the
subss instruction is the damage application code (makes sense, it is subtracting a value from one that I assume is holding our health); but, you never know, so let’s put a breakpoint there and get smacked one more time.
After we place a breakpoint at
ACValhalla.exe+212E403 and get smacked again…nothing happens. Aw. Well, maybe a negative number is being added to it? Let’s throw a breakpoint onto the
addss instruction, and get hit again.
Nothing. Bah. Given that neither of these branches are executing, we can assume that the health update code is being jumped to from somewhere whose distance from the
call instruction is significant. We can spend a bunch of time trying to go further and further up the code to see if we get lucky, or we can just swallow our fears and do an execution trace.
The First Trace
Now, in order to do an execution trace, we will need to find a place up the call stack from which we can launch it, with the confidence that execution will be heading in the direction of the code of interest.
Although the health update code is only executing when someone is receiving damage in the game, as we go further and further up the call stack, the likelihood that we begin to run into general purpose code used for many things increases. Starting a trace from such code will result in traces that have veered far off the desired execution path.
So, we want to make sure that the place we’re starting the trace from is only going to go off when we receive a nice smack on the ass from the enemy. We place a breakpoint one more place up on the call stack from where the health update calling code, and luckily it does not appear to be going off other than when we get hit.
Now that we have gained immeasurable confidence in ourselves, this is where we’ll be running our trace from:
To run a trace, we should resume execution, remove the breakpoint we have there already, and then right click on the instruction and choose Break and trace instructions. A little window asking for options will pop up; let’s leave the settings at their default and just hit the OK button.
The trace is now ready to go. Let’s get smacked again. After that occurs, our Tracer window will be populated full of instructions. The first thing we’ll want to do is see if we can locate our health update code, and then we can work backwards from there.
Sadly, I’m unaware of a simple way to find a particular instruction inside a trace log. I wish we could simply place a breakpoint at a point in the code where we no longer care what is happening (like after the health update code runs), and then perform the trace; however, any breakpoints you have set are ignored if a trace is still collecting instructions.
But we can use this shortcoming to our own advantage: if we place a breakpoint on our health update code, start a trace, and then observe that the breakpoint is hit, that means the trace wasn’t large enough to capture it!
I should’ve done that from the get-go! Well now I know, and you know too. So, let’s put a breakpoint at the health update code and run the trace again. I grit my teeth, ready to get hit again, hoping that the breakpoint doesn’t hit.
But it does, and not only that, but our Tracer window contains no results. Uh oh. That means that the calling stack has changed on us. Indeed, when going up the calling stack from the health update code breakpoint, we are greeted with unfamiliar code.
In fact, placing a breakpoint back exactly where our health update code is, we will begin to observe it executing many times each time we’re hit, with different call stacks every time and…what’s this?! Different values for
rcx (which is the address to the health structure being written to)!
Isolating Updates to Only Our Player’s Health
Oops. Looks like we needed to isolate writes to our own health structure vs everyone else’s. Apparently this code is going off many times every time we’re smacked, updating different things. It could be that this is some sort of ‘UpdateProperty’ function.
Let’s add a condition for the breakpoint to only break when
rcx points to our health structure base by right clicking the breakpoint and choosing Set/Change break condition and entering the following in the resulting popup:
After hitting OK and getting hit a few more times, we can see that it is indeed only going off once per hit, and just for our character’s health. Let’s go up one call on the stack again and check out the calling code:
OK. And ouch. The call to our health update code is a dynamic
call instruction, which increases the likelihood that this particular code here is being executed by many different things.
That aside, we don’t see any immediate arithmetic operations in this tiny function code, so let’s just go up further on the call stack.
Alright. At least this one isn’t calling a dynamic address. Scrolling up, we can see this is your typical huge assembly function, and that there are no immediately visible arithmetic operations. So, time to trace this then.
The Second Trace
Let’s go up one more on the call stack, and test that location to see if it is only going off for the player. The concern here is a parent function that is literally executing constantly. If you go further and further up a call stack, you eventually will find just that kind of code.
After testing it a bit, it appears that this new tracing point will execute only when the player is smacked. Alrighty then, let’s do a trace from this code, using the same procedure as before.
Leaving a breakpoint on our health update code, we begin a new trace. If the breakpoint hits, that means the trace wasn’t wide enough — if it doesn’t, that means we got something to look at.
Luckily the breakpoint does not hit after we get smacked. Now, just because we know that the trace contains our code of interest, it doesn’t mean finding it is going to be instantaneous. At least we only have to dig through 1000 instructions…ha ha.
We can use the call stack (as it is when the health update breakpoint is hit) to help guide us to the code. We can construct a reverse path of sorts that illustrates to us which
call instructions we should explore in the trace. Doing this, we find the health update code in the trace:
Now that we found it in the trace, we can slowly start to go up the instructions, seeing where each register gets its value. For our purposes, we need to figure out where the value in the
edx register is getting calculated.
Slowly going up the trace, we sadly cannot observe any sort of arithmetic calculation of our new health occurring. That means it was already calculated by the time this code executed, which itself means that we need to go further up the call stack.
What we can see here though is that the updated health is originating from from memory with the following instruction:
mov ebx,[rsi]. If we could see what is writing to that place in memory, then that might get us closer to our damage application code.
Sadly, that particular place in memory is very volatile, meaning that it is being written to by a lot of stuff. It is most likely some temporary working memory used by a lot of different functions for processing values.
The Third and Fourth Traces
The only option here is to do yet another trace, going another level up on the call stack. Fortunately, the next function up on the call stack appears to also only go off when the player’s health has been damaged.
After running the third trace, we locate our health update code once again. We want to check whether the address previously being read from the
rsi register is the same as before or different. This time it is
0x44D6EFEB08, and unfortunately I already forgot what the previous one was, so we can’t be too sure whether it changed or not. We write it down anyway.
Going up to the top of the trace, we observe that the
rsi register is being sourced from an
rdx register, and that’s all the information we get. Not very helpful. Time for plan B.
What I’m going to do now, is run the exact same trace one more time to see if the
rsi register address is indeed the same between these two health updates. If it is, then we’re going to look for writes to this address starting at an as of yet undetermined place.
We run the same trace again, and unfortunately the address used by the
rsi register has indeed changed. Can’t use that approach then.
The Final (Hopefully) Trace
What we need to now is maybe what we should’ve done from the beginning, and that’s a much, much more expansive trace. We’re going to go very far up the call stack from where the health update code executes, and see if we can find anything stable enough to trace from.
We smack ourselves again so the health update code gets hit, and we work on testing for trace stability on every function on the call stack above where we last did our trace, one function at a time. If we find that one particular level is unstable (i.e. it is executing constantly outside of being smacked), we still want to try one or two levels above it because it simply could be an example of specifically-purposed code calling general-purpose code.
After going a number of levels up from where we were, and finding a particular function parented by 5 levels of unstable tracing code, I think we have found the perfect place to do the hopefully final bit of tracing.
We now perform the trace again, leaving the options at their default, but ensuring that the health update code breakpoint doesn’t go off. On our first attempt, the breakpoint does go off, so we must increase the number of instructions to trace. We’ll be making increments in the 1000’s.
After failing to capture the health update code in the trace multiple times, I started incrementing the maximal trace count by greater amounts. After finally trying one with a maximal trace count of 50,000, we were finally able to do a trace from this lofty perch without hitting the breakpoint.
So now we have a trace of 50,000 instructions to sift through. We’ll use the call stack as it is when the health update code goes off as a reverse history of sorts to find our health update code. We eventually find our health update code, and we realize the this trace was barely large enough to contain it.
Now that we have expansive trace results, I want to search for all manipulations to that temporary address that was assigned to
rsi. To do that, I first discover, once again, what the address actually is. Then, I go to the very top of the trace and click on Search -> Find… and check for various registers that might contain this address.
And this very query shown above is what led us to our mark! Scrolling a few lines above from where we landed, we eye what suspiciously appears to be the damage application code.
Now it is time to verify our findings. If this is indeed the damage application code, then if I replace the code with nothing, our character should no longer take any more damage when hit. We double click the entry in the trace, and right click the instruction in the disassembler window and click Replace with code that does nothing.
Success. No damage is received with the code removed. Now, let’s hope for double success by smacking the enemies back, and hopefully they too receive no changes in their health.
After smacking them around like we’re armed with freshly caught trout, we observe that their health bars too are exempt from any change. Their stamina bars decrease, but not their health.
Analyzing the Damage Application Code
While it is great that we have the damage application on hand, for that is where we’ll be injecting the Apocalypse system, there are some other details we need to figure out before we can do so.
First, we need to be able to determine whether it is the player or the enemy receiving damage here. Let’s throw a breakpoint onto the damage application instruction and look at the various register states.
Interestingly enough, it appears as the player gets smacked, and only when the player gets smacked, that the
rdi register will point to their player’s location structure. I’ve never had to associate player damage via their location before, but it seems consistent enough that it might work for us.
In the event that this doesn’t prove a consistent solution for identifying if the player is being hit, we also observe that the player’s root structure is present at
[rsp+88] when we get smacked.
Let’s assume that’s good enough for now. That marks off the requirement of knowing who is being damaged. The second thing we need is the ability to discern when it is the player damaging an NPC vs just another NPC. This is important for Enemy Apocalypse accuracy.
Because there are no other NPCs fighting on my side at the moment, this is a rather difficult thing to test. All we can do at this point is engage in pure guesswork.
One thing we could check for is to see whether my player’s location coordinates are present anywhere when smacking a bad guy, as I have seen in multiple games a concept of a “damage source” being used, and that source being represented via location coordinates.
While nothing immediately is jumping out at us (nothing ever does for this particular item), it appears that the player’s root structure address appears at
[rsp+408] when we’re doing the damage. We confirm that it does not appear anywhere near there when we’re the ones taking the damage. We won’t be able to confirm how solid of a marker this is until we get into a fight with NPCs on our side, but it is good enough for now.
With that, we have everything we need in order to implement the Apocalypse system perfectly:
- The player’s health structure disassembled.
- The player’s location structure disassembled.
- The damage application code.
- A way to discern if the player is getting smacked.
- A way to discern if the player is doing the smackin’.
Let’s Write the Apocalypse Initiation Hook
We’ll be injecting right into the code we identified as the damage application code. Let’s take a look at our initial template.
Apocalypse Initiation Hook – Template
// Initiates the Apocalypse system. // Unique AOB: 2B 45 BC 89 44 24 58 define(omnifyApocalypseHook, "ACValhalla.exe" + 2129171) assert(omnifyApocalypseHook, 2B 45 BC 89 44 24 58) alloc(initiateApocalypse,$1000, omnifyApocalypseHook) registersymbol(omnifyApocalypseHook) initiateApocalypse: initiateApocalypseOriginalCode: sub eax,[rbp-44] mov [rsp+58],eax jmp initiateApocalypseReturn omnifyApocalypseHook: jmp initiateApocalypse nop 2 initiateApocalypseReturn:
Our goal is to engage the Apocalypse system and have it do everything it needs to in the area of code underneath the
initiateApocalypse label. We will consult the
rdi register to see if it is pointing to the player’s location when determining if the player is getting smacked.
As far as telling if the player is the one doing the damage, we will need to look at wherever
[rsp+408] is in memory. Because we’ll be backing up a number of registers onto the stack for purposes of data preservation however, we’re going to need to do a little bit of stack math in order to figure out where we’ll actually need to look.
We’ll be making the following changes to the stack:
- We’re going to need to back up the conditional flags, which will eat up 2 bytes.
- We’ll want to back up the
rbxregister, as it is one of the two registers used to hold return values by the Apocalypse system. We don’t care about the
raxregister because we’ll be updating that with the Apocalypse-determined working health value anyway. So, that’s one register giving us 8 bytes.
- All of the health and damage values are integers, whereas the Apocalypse system expects them to be in floating point. We will need to back up a total of three SSE registers to hold the converted values for health, maximum health, and damage. So that’s three SSE registers at 16 bytes each for a total of 48 bytes.
- Finally, we’ll want to back up an additional register (
rcx) to use as a place to hold temporary values, etc. That’s another 8 bytes.
Adding that all up, we know that we will be shifting the stack by a total of 66 bytes. Therefore
For more information on the requirements involved when writing the Apocalypse initiation points, please consult the relevant sections in the Apocalypse overview article on the Player Apocalypse API and the Enemy Apocalypse API. That should answer all your questions!
With everything said that needs to be said, let’s write the start of our initiation hook.
Apocalypse Initiation Hook – First Steps
initiateApocalypse: pushf // Backing up a few SSE registers to hold converted floating points. sub rsp,10 movdqu [rsp],xmm0 sub rsp,10 movdqu [rsp],xmm1 sub rsp,10 movdqu [rsp],xmm2 // Backing up the rbx register as it will be overwritten by // the Apocalypse register. We don't care about the rax register // as we'll be changing it anyway. push rbx // Backing up a working register to aid in value calculations, etc. push rcx // Let's grab the damage source right now before the stack shifts // anymore. mov rcx,[rsp+44A] // We'll convert the working health and damage amount to floats. cvtsi2ss xmm0,eax cvtsi2ss xmm1,[rbp-44] // If the player is the one receiving the damage, then the rdi register // will point to the player's location structure. mov rbx,playerLocation cmp [rbx],rdi je initiatePlayerApocalypse // The player is the one damaging the NPC if the player's root structure // is listed as the damage source. mov rbx,player cmp [rbx],rcx je initiateEnemyApocalypse jmp initiateApocalypseExit
The code is fairly well documented (some might say excessively so for assembly!) so it should explain itself pretty well. When all of the above black magic is done doing its thing, we have all common parameters loaded and primed, as well as the general execution direction determined.
If the player is being damaged, execution will continue on towards the Player Apocalypse section. Let’s write that baby up right now.
Apocalypse Initiation Hook – Player Apocalypse
initiatePlayerApocalypse: // Realign the player's coordinates so it begins at the X coordinate. mov rcx,[rbx] lea rax,[rcx+50] // Convert the player's maximum health to floating point. mov rbx,playerHealth mov rcx,[rbx] cvtsi2ss xmm2,[rcx+13C] // Push the damage amount parameter. sub rsp,8 movd [rsp],xmm1 // Push the working health value parameter. sub rsp,8 movd [rsp],xmm0 // Push the maximum health value parameter. sub rsp,8 movd [rsp],xmm2 // Push the aligned coordinates struct parameter. push rax call executePlayerApocalypse jmp initiateApocalypseUpdateDamage
When the above code finishes executing, we will have an updated damage amount stored in the
rax register and an updated working health value in the
rbx register. We shift at the very end to a part that will process these return values, however we’ll get to that later.
If instead it was determined earlier that the player was damaging an NPC, the Enemy Apocalypse will go off instead. Let’s write that baby up right now.
Apocalypse Initiation Hook – Enemy Apocalypse
initiateEnemyApocalypse: // Push the damage amount parameter. sub rsp,8 movd [rsp],xmm1 // Push the working health value parameter. sub rsp,8 movd [rsp],xmm0 call executeEnemyApocalypse
It’s very tiny. Easy stuff. Let’s take a look at the return value processing section then.
Apocalypse Initiation Hook – Return Value Processing and Cleanup
initiateApocalypseUpdateDamage: // Convert the updated damage amount to an integer. movd xmm0,eax cvtss2si eax,xmm0 mov [rbp-44],eax // Convert the updated working health value to an integer. movd xmm0,ebx cvtss2si eax,xmm0 initiateApocalypseExit: pop rbx pop rcx movdqu xmm2,[rsp] add rsp,10 movdqu xmm1,[rsp] add rsp,10 movdqu xmm0,[rsp] add rsp,10
And that’s a wrap! A complete implementation of an Apocalypse initiation hook for Assassin’s Creed: Valhalla. After writing this code (and I have recorded evidence and a number of people who bore witness), I must brag that it worked during the first attempt. Oh yeah.
External Parameter Tweaking
After testing Apocalypse for a little bit, it became very apparent that negative vertical displacement was a very, very bad thing. The player would get teleported underneath the ground, not fall to their death, and then whenever they died from then on, they’d respawn in this underground cavern.
So we wanted that off. Also, we needed to tell the Apocalypse system that the Z axis was the vertical axis. Additionally, I made a small boost to the displacement values for teleportitis, however this is a change that will probably require additional fine tuning.
Apocalypse External Parameters
negativeVerticalDisplacementEnabled: dd 0 teleportitisDisplacementX: dd (float)5.0 yIsVertical: dd 0
Disabling One Hit Kill Protection
Sadly, Ubisoft apparently intended that this game be played by people afraid of failure in any shape or form since, after a bit of testing with the Apocalypse system, it became very evident that one hit kill protection was present in the game.
Here is an example from the Apocalypse logs of the player getting hit by a sixty nine attack:
19:53-Enemy rolls a 5: HOLY SHIT! Player has been SIXTY NINED causing 3243 damage to the player! Player now has 7 health.
In the game at the start we only have, at most, 100 health. So clearly getting nuked with 3243 should have killed us and then some. Clearly some nanny wanny bit of code was preventing that from happening.
I’ve observed this type of behavior while Omnifying Resident Evil 7, but that’s it. The way it worked with that one was it operated off a specific health threshold — if the player’s health was above this threshold, it wouldn’t allow for one hit kills to happen.
Let’s see if this game follows a similar approach. I first started out by placing a breakpoint in the part of the Apocalypse code that handled the
sixtyNine effect. After getting execution to stop at a sixty nine in progress, we continued to step outside of the call up until we reached the original code we were injecting into:
sub eax,[rbp-44] mov [rsp+58],eax
At this point we wanted to start monitoring all writes to our known health value location as well as this
[rsp+58] to see if the value was being corrected by the one hit kill protection code before it ever reached our health update code.
When execution finally got to the health update code, we saw that it already was corrected with a value of 7 being the new health. Looking at our opcode monitoring window, we saw the following adjustments having occurred to our
A number of these functions were indeed updating the temporary health to be 7, however I decided after a little bit of thinking that maybe a trace would be an easier way to see what the hell was up.
Here are the results of the trace:
Multiple traces were taken, this particular trace was made when the player was, in the end, protected from a one hit kill. If the highlighted instruction doesn’t result in a jump when we’re receiving a ton of damage, then our health gets set to 7 and we don’t die.
If the jump does occur when we’re about to receive a lot of damage, we do die. However, if the jump always occurs, our Apocalypse system code never executes because the damage application code doesn’t execute. Yes. Interesting.
Essentially, what the above code is doing, is taking our maximum health, dividing it by 4, and then subtracting that from the maximum health to give us a value that is 75% of our maximum health.
That is the threshold health. If our health exceeds this amount, then we cannot die from one shots. I can’t just increase the divisor used in the above equation, because it will result in thresholds that only approach 100 infinitesimally, but never actually hit 100.
At least, that’s what I thought, then I remembered we were doing math with integers here; if we increase the threshold divisor to a value that is high enough, it should essentially result in no threshold at all.
And indeed, setting this divisor to 0xFF essentially bypassed the one hit kill protection system. Let’s write up a hook for that then.
One Hit Kill Protection Bypass Hook
// Bypasses the one hit kill protection system. // Unique AOB: 0F B6 8D EC 00 00 00 define(omnifyDepussifyGameHook, "ACValhalla.exe" + 20D1D06) assert(omnifyDepussifyGameHook, 0F B6 8D EC 00 00 00) alloc(depussifyGame,$1000, omnifyDepussifyGameHook) registersymbol(omnifyDepussifyGameHook) depussifyGame: // This is a hardcoded value used as a divisor applied against // the player's maximum health to see if the player is protected // from being one shot. By increasing this to max we essentially // make all values of health unsafe. mov [rbp+EC],0xFF depussifyGameOriginalCode: movzx ecx,byte ptr [rbp+000000EC] jmp depussifyGameReturn omnifyDepussifyGameHook: jmp depussifyGame nop 2 depussifyGameReturn:
Yay! With this code enabled, getting hit with a ton of damage ended up always murdering our player dead.
There you have it. The Apocalypse system is in. Here is the complete code required to initiate the Apocalypse system in Assassin’s Creed: Valhalla.
Apocalypse Initiation Hook – Complete
// Initiates the Apocalypse system. // Unique AOB: 2B 45 BC 89 44 24 58 define(omnifyApocalypseHook, "ACValhalla.exe" + 2129171) assert(omnifyApocalypseHook, 2B 45 BC 89 44 24 58) alloc(initiateApocalypse,$1000, omnifyApocalypseHook) registersymbol(omnifyApocalypseHook) initiateApocalypse: pushf // Backing up a few SSE registers to hold converted floating points. sub rsp,10 movdqu [rsp],xmm0 sub rsp,10 movdqu [rsp],xmm1 sub rsp,10 movdqu [rsp],xmm2 // Backing up the rbx register as it will be overwritten by // the Apocalypse register. We don't care about the rax register // as we'll be changing it anyway. push rbx // Backing up a working register to aid in value calculations, etc. push rcx // Let's grab the damage source right now before the stack shifts // anymore. mov rcx,[rsp+44A] // We'll convert the working health and damage amount to floats. cvtsi2ss xmm0,eax cvtsi2ss xmm1,[rbp-44] // If the player is the one receiving the damage, then the rdi register // will point to the player's location structure. mov rbx,playerLocation cmp [rbx],rdi je initiatePlayerApocalypse // The player is the one damaging the NPC if the player's root structure // is listed as the damage source. mov rbx,player cmp [rbx],rcx je initiateEnemyApocalypse jmp initiateApocalypseExit initiatePlayerApocalypse: // Realign the player's coordinates so it begins at the X coordinate. mov rcx,[rbx] lea rax,[rcx+50] // Convert the player's maximum health to floating point. mov rbx,playerHealth mov rcx,[rbx] cvtsi2ss xmm2,[rcx+13C] // Push the damage amount parameter. sub rsp,8 movd [rsp],xmm1 // Push the working health value parameter. sub rsp,8 movd [rsp],xmm0 // Push the maximum health value parameter. sub rsp,8 movd [rsp],xmm2 // Push the aligned coordinates struct parameter. push rax call executePlayerApocalypse jmp initiateApocalypseUpdateDamage initiateEnemyApocalypse: // Push the damage amount parameter. sub rsp,8 movd [rsp],xmm1 // Push the working health value parameter. sub rsp,8 movd [rsp],xmm0 call executeEnemyApocalypse initiateApocalypseUpdateDamage: // Convert the updated damage amount to an integer. movd xmm0,eax cvtss2si eax,xmm0 mov [rbp-44],eax // Convert the updated working health value to an integer. movd xmm0,ebx cvtss2si eax,xmm0 initiateApocalypseExit: pop rbx pop rcx movdqu xmm2,[rsp] add rsp,10 movdqu xmm1,[rsp] add rsp,10 movdqu xmm0,[rsp] add rsp,10 initiateApocalypseOriginalCode: popf sub eax,[rbp-44] mov [rsp+58],eax jmp initiateApocalypseReturn omnifyApocalypseHook: jmp initiateApocalypse nop 2 initiateApocalypseReturn: negativeVerticalDisplacementEnabled: dd 0 teleportitisDisplacementX: dd (float)5.0 yIsVertical: dd 0 // Bypasses the one hit kill protection system. // Unique AOB: 0F B6 8D EC 00 00 00 define(omnifyDepussifyGameHook, "ACValhalla.exe" + 20D1D06) assert(omnifyDepussifyGameHook, 0F B6 8D EC 00 00 00) alloc(depussifyGame,$1000, omnifyDepussifyGameHook) registersymbol(omnifyDepussifyGameHook) depussifyGame: // This is a hardcoded value used as a divisor applied against // the player's maximum health to see if the player is protected // from being one shot. By increasing this to max we essentially // make all values of health unsafe. mov [rbp+EC],0xFF depussifyGameOriginalCode: movzx ecx,byte ptr [rbp+000000EC] jmp depussifyGameReturn omnifyDepussifyGameHook: jmp depussifyGame nop 2 depussifyGameReturn:
Thanks for reading this article. It was composed almost entirely live on my stream. People actually watched me type this article while hacking the game! Amazing! In reward for their dedication, they get to say a few things now:
Gr8 b8 m8 I r8 8/8gunthler
Next time we’ll be implementing the Predator system. Thank you for your time and your attention. I appreciate it greatly.