Nioh 2

Onwards with the implementation of the Apocalypse system into the game Nioh 2. It is the first of the game-neutral Omnified systems I tend to implement, and it completely changes the consequences of letting yourself get hit by enemies, raising the stakes to ridiculous degrees.

It also tends to be the easiest of my systems to implement, although this will, of course, vary from game to game. Each and every implementation is a learning experience, from which we’re (hopefully) better off from how we were before.

While one can watch the times I hack on stream and maybe learn something from that, you don’t always know what’s going on in my head, and it is a lot to take in; that’s why I write these articles. I’m still learning how to trim unnecessary information from these writeups, but there’s just so much to cover always.

Today, we’ll go over how we found the ideal place to insert the Apocalypse system into Nioh 2, as well as some other challenges we had to face and correct. We’ll be starting off, however, with an examination of how fall damage works in the game.

I’ve Fallen and It Doesn’t Hurt

As soon as I hacked the player’s location coordinates in the previous article, I observed what I knew would end up being a big problem once the Apocalypse system was implemented. If I changed my player’s y-coordinate (which is the vertical axis in this game) to a ridiculously high value, I would observe my player falling down from orbit, smashing into the ground, and sustaining no damage.

Uh oh. A big part of what the Apocalypse system does is inflict the teleportitis effect on the character. This is a random displacement of the character to new coordinates, a “teleport” if you will. If the player is unlucky enough to have their vertical axis increased across a particular threshold, then music and fanfare plays as the player falls to the ground and dies horrifically.

Well, there is no cause for music and fanfare if there is no death, so we had to fix that. Immediately, I suspected that the reason there was no damage was due to how the game calculated fall damage. Simply falling down and hitting the ground was not enough, rather a reference coordinate would be needed in order for the game to determine how far the player has fallen.

This would be similar then to how the fall damage worked in The Witcher 3, as I eventually discovered when Omnifying that game. Of course, as far as Nioh 2 went, this was just a complete theory at this point in time by myself. Just a very educated guess. Regardless, we must trust our own experience, so I went ahead with attempting to locate a last known location coordinate triplet for the player, live on stream of course.

The Search for the Last Known Location

I suspected that there would be a coordinate value in memory, similar to our source-of-truth y-coordinate value. If I could find this last location y-coordinate value and update it to the boosted value the y-coordinate was being set to following a teleportitis, it should then result in the player sustaining fatal damage upon hitting the ground.

Right now, because the last location y-coordinate was remaining unchanged, the game would end up using a last location value equal to the ground position at the time of the player hits the ground when calculating the fall distance. It was my guess that the game would take the last known location, subtract the player’s source-of-truth y-coordinate from it upon smacking into the ground, and then use that difference as the fall distance.

Shows the relation between the last y-coordinate, y-coordinate, and fall damage.
We need to set the last known (before falling) y-coordinate in addition to sending the player up into the air if we want them to take damage.

So, to find the last location y-coordinate value, I first looked for values close to the character’s y-coordinate value while we were on the ground. Following this, I set the player’s y-coordinate value to a very high height, while having a breakpoint at the character’s location update code.

After allowing a few movement frames to pass, I looked among the previously searched for values for ones that remained unchanged. This took a few tries, with the first few attempts failing due to not observing the fact that the last known location would typically be underneath the source-of-truth y-coordinate value, as the player will tend to exist above the ground itself (it would tend to vary, however).

Eventually, after widening the search enough, we got some useful search results.

Shows the results of searching for the coordinates that track the last solid ground the player has stood on.
Here are the results of our search for the “last stable ground” coordinates for the player — or, as I also termed them: the “where we were” coordinates.

After finding the last location y-coordinate value, I observed that setting both the last location and source-of-truth y-coordinate values to a fatal height would result in the player falling down a great height and dying horrifically. Score.

Once found, I didn’t want to lose this precious bit of data ever again, so I set out to find a good place to hook into where we could reliably retrieve this data. It was important that the last location be available prior to any falling event, as we would need to be able to set it at any point in time while playing.

Player Last Location Hook

// Gets the player's last location structure and ensures Omnified changes 
// to the player's vertical position is properly reflected.
// [rsp+120] | {rsp+132}: Points to root structure of NPC the coordinates belong to.
// [rdi+C8]: Player's last known y-coordinate on solid ground.
// UNIQUE AOB: 89 87 C8 00 00 00 8B 47
// Correct instruction will be single result found in nioh2.exe (not nvd3dumx.dll).
define(omniPlayerLastLocationHook,"nioh2.exe"+8549BB)

assert(omniPlayerLastLocationHook,89 87 C8 00 00 00)
alloc(getPlayerLastLocation,$1000,omniPlayerLastLocationHook)
alloc(playerLastLocation,8)

registersymbol(playerLastLocation)
registersymbol(omniPlayerLastLocationHook)

getPlayerLastLocation:
    pushf
    // The player location structure must be initialized in order for us to identify the player's
    // last location structure.
    push rax
    mov rax,playerLocation
    cmp [rax],0
    pop rax
    je getPlayerLastLocationOriginalCode
    push rbx
    push rcx
    mov rbx,playerLocation
    mov rcx,[rbx]    
    // The player's location structure can be found here on the stack.
    cmp rcx,[rsp+132]
    jne getPlayerLastLocationCleanup
    mov [playerLastLocation],rdi    
getPlayerLastLocationCleanup:
    pop rcx
    pop rbx
getPlayerLastLocationOriginalCode:
    popf
    mov [rdi+000000C8],eax
    jmp getPlayerLastLocationReturn

omniPlayerLastLocationHook:
    jmp getPlayerLastLocation
    nop 
getPlayerLastLocationReturn:

A perfect hook for our purposes. As you can tell, more than just the player’s last location is being polled by the code here; rather, I suspect it was polling all the loaded NPCs as well. I managed to figure out we could filter out the player from the non-players by looking at the stack, on which the source-of-truth location structure for the creature being polled could be found.

Finding the Damage Application Code

In order to implement the Apocalypse system effectively, we need to hook it into the game’s damage application code. For more information on what that is exactly, you can always check the relevant section in the Apocalypse system’s design article.

In summary, the damage application code is where the arithmetic is being performed to calculate a new health value in light of some damage that needs to be applied to it. It often can be quite far away from the more easily found health update code, which is simply the code that writes new health values to a particular source-of-truth health address.

To find it, we’ll need to do a trace, several calls upon the stack from the health update code, which means we need to find that particular bit of code. We look for code writing to our already discovered health address, and find the following code to be writing to it after we get damaged by an enemy:

Shows the health update code.
Here’s our health update code, now to find the damage application code that calculates the value found in rdx.

You can see the call stack in the lower right of the image. We want to go as high as we can go (within reason) while still being able to maintain a single parent call to health update call ratio. If we go too high, we’ll run into methods that are continuously polling health values to update, and we won’t be able to capture the information related to a particular damage-derived change.

Going three functions calls up the stack leads us to this code:

Shows the code where we started our trace for the damage application code.
Here’s the parent function to the health update code we’ll be starting our trace from.

It seems to be going off once per health update call. Hopefully it is high enough for our purposes! We perform a trace here, locate our health update code within the trace by searching for the proper instruction pointer address (held in rip), and then scroll up until we come to the following:

Shows the results of the trace for the damage application code.
Here’s the results of the trace, along with (what appears to be) the damage application code highlighted.

This is the damage application code. The damage amount can be found in the edi register, and it is being subtracted from the working value for the health, stored in eax. We can determine the target entity by looking at ebx. If it is the player, this will point to the player’s root structure, otherwise it will point to some NPC’s root structure.

This is sufficient for our purposes in order to implement the Apocalypse system, however let’s first take a look at one of the problems I encountered after implementing it: the game rejecting my teleportitis effects.

No Teleportitis for You!

Nioh 2 has an “army” of validation functions that will go off in order to prevent the character from walking out of bounds. It’s quite the interesting setup: there are a number of coordinate write functions that will only go off if we actually try to manually set our coordinates to bad values.

If we disable those coordinate validation functions, we will actually see some other, new validation functions that will go off to once again correct our character’s coordinates. Kind of interesting and funny, and speaks of some very resilient code, in my opinion at least.

I discovered we can actually bypass the various validation instructions by propagating the values we set our source-of-truth coordinates to, to a coordinate triplet I termed the validation coordinates — although destination coordinates could also be used as a name, I suppose. These begin at [playerLocation]+0x220.

This is required in order to force a teleport in general, however I observed that teleportitis effects were still being blocked due to what else was going on when they would go off, which is typically during combat. Upon being hit by an enemy, the player would essentially get locked into a type of animation (played in response to getting smacked) that would result in any attempted changes in movement to be rejected.

So, in order to get around this, I put in a similar workaround that I implemented during some other Omnified games, which would be to force the coordinates to stick by implementing a sequence of movement frame skips, which would reject the unwanted coordinate values the game would be throwing on us during a combat animation.

This is implemented within the location update code for the player, which is where the game assigns new coordinate values for the player during movement.

Player Location Update Frame Skip Hook

// Processes Omnified events during execution of the location update code for the player.
// rcx: The target location structure being updated.
// UNIQUE AOB: 66 0F 7F 81 F0 00 00 00 C3
define(omnifyLocationUpdateHook,"nioh2.exe"+801863)

assert(omnifyLocationUpdateHook,66 0F 7F 81 F0 00 00 00)
alloc(updateLocation,$1000,omnifyLocationUpdateHook)
alloc(teleportLocation,16)
alloc(framesToSkip,8)

registersymbol(omnifyLocationUpdateHook)

updateLocation:
    pushf
    // Since we need to update it upon a teleport occurring, the last location structure player is
    // required for further processing.
    push rax
    mov rax,playerLastLocation
    cmp [rax],0
    pop rax
    je updateLocationOriginalCode
    // Only events pertaining to the player are processed here.
    push rax
    mov rax,playerLocation
    cmp [rax],rcx
    pop rax
    jne updateLocationOriginalCode
    // Upon a teleport, we engage in a movement frame skip sequence, this is so the desired
    // teleport coordinates actually get committed to the player's location (and stay there); 
    // otherwise, an army of validation-related code will revert the coordinates depending on the
    // situation.
    cmp [framesToSkip],0
    jg skipMovementFrame
    push rax
    mov rax,teleported
    cmp [rax],1
    pop rax
    jne updateLocationOriginalCode
    push rax
    push rbx
    push rcx
    mov rax,teleported
    mov [rax],0    
    mov [framesToSkip],#25
    mov rbx,playerLastLocation
    mov rax,[rbx]
    // Need to set our last location (vertically) so that proper fall damage will occur if we just
    // got Tom Petty'd.
    mov rcx,teleportedY
    mov rbx,[rcx]
    mov [rax+C8],rbx
    pop rcx
    pop rbx
    pop rax    
skipMovementFrame:    
    dec [framesToSkip]        
    push rax
    push rbx        
    push rcx
    // Simply ignoring the updated coordinates in xmm0 is not enough, as other validation code
    // might've already reverted our source-of-truth coordinates. So, we load up the
    // teleport coordinates set during the Apocalypse pass.
    mov rcx,teleportLocation
    movdqu [rcx],xmm0
    mov rax,teleportedX
    mov rbx,[rax]
    mov [rcx],rbx
    mov rax,teleportedY
    mov rbx,[rax]
    mov [rcx+4],rbx
    mov rax,teleportedZ
    mov rbx,[rax]
    mov [rcx+8],rbx    
    movdqu xmm0,[rcx]
    pop rcx
    pop rbx
    pop rax    
    // Update the validation, destination coordinates as well.
    movdqu [rcx+220],xmm0
updateLocationOriginalCode:
    popf
    movdqa [rcx+000000F0],xmm0
    jmp updateLocationReturn

omnifyLocationUpdateHook:
    jmp updateLocation
    nop 3
updateLocationReturn:

This takes care of setting the last location y-coordinate value as well upon a teleport happening.

Teleportitis effects are now incredibly enjoyable and deadly.

Detecting Player As the Damage Source

One final requirement for a perfect Apocalypse system implementation is being able to determine whether the player is the cause for damage made to an NPC, as opposed to another NPC being the source. This is something I put off at first, as the player is usually solo in this game; however, it turns out the player isn’t always solo.

I didn’t want those damn “helpful” NPCs stealing my Kamehameha! Yes, that’s a thing, check the Apocalypse article for more information.

Figuring out how to detect whether or not the player is doing damage is typically a tough task. Luckily, it didn’t take too long for me to figure out how to do this on stream. Basically, I found, via a trace, a section of code that would execute prior to the damage application code that would have one of the registers set to the damage source’s location structure.

If this register was reflecting the player’s location structure, then the player is the source of the attack. So, we would just set a flag then and there and reference this flag in the main hook. Here’s the code.

Player Hit Detection Hook

// Detects if the player is responsible for a damaging attack.
// UNIQUE AOB: 4C 8B E8 48 85 C0 0F 84 B4
define(omnifyPlayerHitDetectionHook,"nioh2.exe"+8B065D)

assert(omnifyPlayerHitDetectionHook,4C 8B E8 48 85 C0)
alloc(detectPlayerHit,$1000,omnifyPlayerHitDetectionHook)
alloc(playerAttacking,8)

registersymbol(playerAttacking)
registersymbol(omnifyPlayerHitDetectionHook)

detectPlayerHit:
    pushf
    push rbx
    mov rbx,playerLocation
    cmp [rbx],rax
    pop rbx
    jne detectPlayerHitOriginalCode
    mov [playerAttacking],1
detectPlayerHitOriginalCode:
    popf
    mov r13,rax
    test rax,rax
    jmp detectPlayerHitReturn

omnifyPlayerHitDetectionHook:
    jmp detectPlayerHit
    nop 
detectPlayerHitReturn:

Apocalypse Initiation Hook

Everything is now in place for us to implement the Apocalypse system into Nioh 2. The code will now be provided, which should be documented enough so that further explanation isn’t required.

// Initiates the Apocalypse system.
// This is Nioh 2's damage application code.
// [rbx+10]: Working health.
// edi: Damage amount.
// rbx: Target entity (i.e. [player] or an NPC) structure.
// UNIQUE AOB: 8B 43 10 2B C7
// Correct instruction will be single result found in nioh2.exe (not the other two DLL's).
define(omnifyApocalypseHook,"nioh2.exe"+79C590)

assert(omnifyApocalypseHook,8B 43 10 2B C7)
alloc(initiateApocalypse,$1000,omnifyApocalypseHook)

registersymbol(omnifyApocalypseHook)

initiateApocalypse:
    pushf
    // An empty r12 register indicates the damage originates from falling.
    // We don't want this to trigger Apocalypse, as it may have been Apocalypse that caused the 
    // falling...
    cmp r12,0
    je initiateApocalypseOriginalCode
    // A r15 register set to 1 means we're drowning like a big dumb baby.
    cmp r15,1
    je initiateApocalypseOriginalCode
    // An r8 register set to 0x400 indicates environmental fire damage.
    cmp r8,0x400
    je initiateApocalypseOriginalCode    
executeApocalypse:
    // Ensure the required player data structures are initialized.
    push rax
    mov rax,player
    cmp [rax],0
    pop rax
    je initiateApocalypseOriginalCode
    push rax
    mov rax,playerLocation
    cmp [rax],0
    pop rax
    je initiateApocalypseOriginalCode
    // Backing up a SSE register to hold converted floating point values for the health
    // and damage amount.
    sub rsp,10
    movdqu [rsp],xmm0
    // Backing up the outputs of the Apocalypse system.
    push rax
    push rbx
    // Backing up a register to hold the address pointed to by rbx, as we need to write one 
    // of our outputs to it when all is said and done.
    push rcx    
    mov rcx,rbx    
    // Both Player and Enemy Apocalypse functions share the same first two parameters. 
    // Let's load them first before figuring out which subsystem to execute.
    // We'll need to convert the working health and damage amount values from being integer types 
    // to floating point types, as this is the data type expected by the Apocalypse system.    
    cvtsi2ss xmm0,edi    
    // Load the damage amount parameter.
    sub rsp,8
    movd [rsp],xmm0
    mov rax,[rcx+10]
    cvtsi2ss xmm0,rax
    // Load the working health amount parameter.
    sub rsp,8
    movd [rsp],xmm0    
    // Now, we need to determine whether the player or an NPC is being damaged, and then from 
    // there execute the appropriate Apocalypse subsystem.
    mov rax,player        
    cmp [rax],rcx
    je initiatePlayerApocalypse
    jmp initiateEnemyApocalypse    
initiatePlayerApocalypse:        
    // Check if the normal damage is enough (alone) to kill the player -- if it is, we forbid 
    // teleports, as we cannot move the character after he or she is dead.
    mov rax,[rcx+10]
    sub eax,edi
    cmp eax,0
    jg skipTeleportitisDisable
    mov rax,disableTeleportitis
    mov [rax],1
skipTeleportitisDisable:
    // Convert the maximum health for the player to the expected floating point form.    
    mov rax,[rcx+8]
    cvtsi2ss xmm0,rax
    // Load the maximum health parameter.
    sub rsp,8
    movd [rsp],xmm0
    // Align the player's location coordinate structure so it begins at our x-coordinate and pass 
    // that as the final parameter.
    mov rax,playerLocation
    mov rbx,[rax]
    lea rax,[rbx+F0]
    push rax
    call executePlayerApocalypse
    jmp initiateApocalypseUpdateDamage
initiateEnemyApocalypse:
    // Check if the player is responsible for the attack, if not, then the damage may be from 
    // a friendly NPC, or otherwise berserk and rampaging Yokai.
    mov rax,playerAttacking
    cmp [rax],1
    jne abortEnemyApocalypse
    call executeEnemyApocalypse
    jmp initiateApocalypseUpdateDamage
abortEnemyApocalypse:
    add rsp,10
    jmp initiateApocalypseCleanup
initiateApocalypseUpdateDamage:
    // To make use of the updated damage and working health amounts returned by the Apocalypse 
    // system, we'll need to convert them both back to integer form.
    movd xmm0,eax
    cvtss2si edi,xmm0
    movd xmm0,ebx
    cvtss2si ebx,xmm0
    mov [rcx+10],ebx
    mov rax,disableTeleportitis
    mov [rax],0
initiateApocalypseCleanup:
    mov rax,playerAttacking
    mov [rax],0
    pop rcx
    pop rbx
    pop rax
    movdqu xmm0,[rsp]
    add rsp,10
initiateApocalypseOriginalCode:
    popf
    mov eax,[rbx+10]
    sub eax,edi
    jmp initiateApocalypseReturn

omnifyApocalypseHook:
    jmp initiateApocalypse
initiateApocalypseReturn:


negativeVerticalDisplacementEnabled:
    dd 0

teleportitisDisplacementX:
    dd (float)180.0

gokuResultUpper:
    dd #1500

fatalisBloodlustDamageX:
    dd (float)1.5

That’s it. Nioh 2, already a hard game, is now much, much harder.

More Omnification To Come

Please remember that code posted in articles can very often end up outdated and no longer reflective of the current product. You can always find the latest version of the source code by checking out the Bad Echo technologies repository, with the Nioh 2 target files residing here.

Next up, we’ll make the enemies move with greater and deadlier speed with the implementation of the Predator system.

Thanks for your interest, and be sure to follow and watch the stream if you wish to see this in action, or simply to give your support.