The Omnification of Nioh 2 is going swimmingly, with our initial analysis and implementation of Apocalypse done. We have two game-neutral Omnified systems left to implement: the Predator and Abomnification systems.
You may have noticed that this single article is covering both Predator and Abomnification system implementations. That is because implementing these systems was a rather simple affair, at least in regards to the Abomnification system (for reasons we’ll get into later). Writing about both of them in a single article is me doing my part to conserve some of those precious Interweb resources.
So, let’s start things off with a writeup on the Predator system’s implementation, which, as you may know, intelligently boosts enemy speed so they can zoom towards you at alarming speeds.
Movement Application Code for Samurais
Proper implementation of the Predator system requires us to locate and hook into the movement application code for the game. As the section of the Predator system design article I just linked will explain to you, this particular piece of code is responsible for adding the movement offsets to current working coordinates, yielding new coordinate values that will be committed as the source-of-truth values by the location update code.
Please consult the appropriate sections of the Predator system design article for more information on these terms. Let’s now go over how we went about finding ye olde holy movement application code for Nioh 2, a task which can be quite difficult for some games.
Luckily for us, Nioh 2 was put together by sensible humans. There is a single primary location update code and movement application code for all entities (that I know of, at least) in the game. This makes things so much simpler.
To begin our search, we start at the location update code — easily found by simply logging all instructions responsible for writing new values to our source-of-truth coordinates. This leads us to the following code:
As we can see above, the location update code is snuggled away in its own tiny function space. The new coordinate values being committed to memory are read from xmm0
. Our job is to find the code responsible for performing the arithmetic that transforms the current coordinate values into the ones we see in xmm0
.
To do this, we’re going to go up the call stack a bit, and see if we can’t capture that operation happening in a trace. How high up to go is pure guesswork, as going too high will cause many problems. So, I decided to go two calls up the stack, leading us to the following code:
Let’s see if we can’t capture the movement application code in a trace starting from here. The only thing I needed to do is figure out a way to isolate a call here to a single call to the location update code. With the location update code, I can easily filter on the entity whose location is being updated, as rcx
will be pointing to that particular creature’s location structure.
I figured out that we can filter on a particular location structure at the parent function call by looking at the rdi
register, which will be the register pointing to the location structure being updated instead of rcx
. So, we queue up a trace, with the starting conditions set to rdi
being equal to a particular creature’s location structure.
This led us to the following trace, which did capture the location update code:
Capturing useful information is vital to success, as it becomes quite difficult to find the movement application code if the movement offsets to be applied are actually zero throughout a particular chain of calls (and it will be zero if the creature isn’t moving, a very common thing). Luckily, we captured a meaningful change in position as shown above.
Now that we have that, the only thing we need to do is go backwards, or up, in the trace. We travel backwards in time until we see where the new coordinate values are coming from.
We eventually get to the code that is pictured above. It is not the movement application code, but it is code showing where the updated values are being loaded from in memory. Simply by looking at the address rcx
, we can tell that it is pointing to instanced, non-temporary memory most likely attached to a particular creature.
That means/ we should be able to easily get the code writing to this “new coordinate holding place” by simply looking at the code writing to the particular address. Doing that, we get the following results:
Looks interesting. Taking a looking at the second result here, we don’t have to scroll up too far (actually, only one instruction) for us to collide headfirst into the lovely movement application code for Nioh 2.
There we have it. We can hook in here and initiate the Predator system, and hopefully enjoy insane, intelligent enemy speed. I have only myself to blame for all the horrendous deaths that will be visited upon me.
Predator Initiation Hook
Here is the code that makes enemies move like insane devils. Worked pretty much the first time or so! Woot.
// Initiates the Predator system. // [rdx+0-8]: Movement offsets for x, y, and z-coordinates respectively. // [rbx]: Target location structure // UNIQUE AOB: F3 0F 58 02 F3 0F 11 81 80 01 00 00 define(omnifyPredatorHook,"nioh2.exe"+852588) assert(omnifyPredatorHook,F3 0F 58 02 F3 0F 11 81 80 01 00 00) alloc(initiatePredator,$1000,omnifyPredatorHook) alloc(identityValue,8) alloc(playerSpeedX,8) registersymbol(playerSpeedX) registersymbol(identityValue) registersymbol(omnifyPredatorHook) initiatePredator: pushf push rax mov rax,playerLocation cmp [rax],0 pop rax je initiatePredatorOriginalCode // In some instances, rbx does not point to a valid location structure, such as when // a shrine is being used. cmp rbx,1 je initiatePredatorOriginalCode // Make sure the player isn't being treated as an enemy NPC! push rax mov rax,playerLocation cmp [rax],rbx pop rax je applyPlayerSpeed // Backing up the registers used to hold Predator system output, as well as an SSE to // hold some of the parameters we'll be passing. sub rsp,10 movdqu [rsp],xmm0 push rax push rbx push rcx // The first parameter is our player's coordinates. mov rax,playerLocation mov rcx,[rax] push [rcx+F0] push [rcx+F8] // The next parameter is the target NPC's coordinates. push [rbx+F0] push [rbx+F8] // The third parameter is the NPC's dimensional scales. Jury is still out whether this // game has True Scaling, so we'll just be passing a good ol' identity matrix for now. movss xmm0,[identityValue] shufps xmm0,xmm0,0 sub rsp,10 movdqu [rsp],xmm0 // The fourth parameter is the NPC's movement offsets. Wow this has been easy! push [rdx] push [rdx+8] call executePredator // Now we just take the updated movement offsets and dump them back into [rdx]. mov [rdx],eax mov [rdx+4],ebx mov [rdx+8],ecx pop rcx pop rbx pop rax movdqu xmm0,[rsp] add rsp,10 jmp initiatePredatorOriginalCode applyPlayerSpeed: sub rsp,10 movdqu [rsp],xmm0 sub rsp,10 movdqu [rsp],xmm1 push rax sub rsp,10 // We don't use the Predator system to influence player speed; rather, we just introduce // the use of a simple multiplier that is applied to the character's movement offsets. // This multiplier will be, except under special circumstances, typically 1x. movss xmm0,[playerSpeedX] shufps xmm0,xmm0,0 movups [rsp],xmm0 mov rax,[identityValue] mov [rsp+4],eax mov [rsp+C],eax movups xmm1,[rsp] movups xmm0,[rdx] mulps xmm0,xmm1 movups [rdx],xmm0 add rsp,10 pop rax movdqu xmm1,[rsp] add rsp,10 movdqu xmm0,[rsp] add rsp,10 initiatePredatorOriginalCode: popf addss xmm0,[rdx] movss [rcx+00000180],xmm0 jmp initiatePredatorReturn omnifyPredatorHook: jmp initiatePredator nop 7 initiatePredatorReturn: identityValue: dd (float)1.0 aggroDistance: dd (float)1250.0 threatDistance: dd (float)300.0 positiveLimit: dd (float)1000.0 negativeLimit: dd (float)-1000.0 playerSpeedX: dd (float)1.0
And that’s the Predator system implementation for Nioh 2. Check out the stream to see the results.
I Love Easy Abomnifications
The Abomnification system, which is my game-neutral system that has the uncanny ability to apply randomly determined morphing modes that change the shape and size of creatures to new random shapes and sizes at random times, can either be one of the easiest or hardest systems to implement.
To understand why it can sometimes be the hardest of my systems to implement, just check out some of my Abomnification implementation articles where I had to write my own custom scaling rendering code. It is definitely a few levels above in terms of reverse engineering difficulty in comparison to what most people are typically looking to manipulate in a game.
But, if the game provides its own easy scaling parameters — that is, data points that directly control the dimensional scale of a character, it is truly one of the easiest and quickest systems to implement. Sadly, I feel like most games I’ve Omnified have lacked easy scaling parameters — thankfully, Nioh 2 is not one of those.
Praise the Nioh 2.
I found the scaling parameters very easily by simply manipulating the character’s height in the character creator and searching for changes while doing so. You can find the dimensional scales starting at the 0x140
offset in the creature’s location structure. There are three floats there, one for each dimensional scale (height, width, and depth).
With that figured out, all we need to do is implement the Abomnification initiation hook in a place where all creatures are being polled. We want to do it at a place that is being executed less frequently than your super-fast rendering code, more along the lines of a map-wide creature location coordinate poll.
This was easy enough to find by simply looking at code that was accessing player and NPC coordinates. Then, we just throw in a simple hook, and we have absolute magic in our game. I mean, it really does look like LSD-fueled magic.
Abomnification Initiation Hook
// Initiates the Abomnification system. // This polls both the player's and NPC coordinates. // [rdi+140]: Height // [rdi+144]: Depth // [rdi+148]: Width // UNIQUE AOB: 4C 8D BF F0 00 00 00 41 define(omnifyAbomnificationHook,"nioh2.exe"+84AA21) assert(omnifyAbomnificationHook,4C 8D BF F0 00 00 00) alloc(initiateAbomnification,$1000,omnifyAbomnificationHook) alloc(abomnifyPlayer,8) registersymbol(abomnifyPlayer) registersymbol(omnifyAbomnificationHook) initiateAbomnification: pushf push rax mov rax,playerLocation cmp [rax],rdi pop rax jne skipAbomnifyPlayerCheck cmp [abomnifyPlayer],1 jne initiateAbomnificationOriginalCode skipAbomnifyPlayerCheck: // Back up the registers used as outputs of the Abomnification system. push rax push rbx push rcx // Push the address to the creature's location structure as its identifying // address to the stack. push rdi call executeAbomnification // Load the Abomnified scales into the creature's location structure. mov [rdi+148],eax mov [rdi+140],ebx mov [rdi+144],ecx pop rcx pop rbx pop rax initiateAbomnificationOriginalCode: popf lea r15,[rdi+000000F0] jmp initiateAbomnificationReturn omnifyAbomnificationHook: jmp initiateAbomnification nop 2 initiateAbomnificationReturn: abominifyMorphStepsResultUpper: dd #550 abominifyHeightResultUpper: dd #200 abominifyDepthResultUpper: dd #180 abominifyWidthResultUpper: dd #240 unnaturalBigX: dd (float)1.5 abomnifyPlayer: dd 0
That’s literally all there is to implementing some absolute crazy magic into Nioh 2. Abomnification sounds ridiculous at first (and it is), but it is a very creative, unique, and effective way to boost difficulty, as it completely messes with your perceptions. Reading enemies becomes incredibly more difficult, and you are definitely more on your toes.
It’s the best.
Nioh 2 Is Omnified
In record time, Nioh 2 is fully Omnified. It has been my fastest full Omnification of a game to date. What a thrill, as it gives me time to work on other things. To give you an idea of how fast I Omnified this game, know that I’ve had around a good 8 or so streams of a fully Omnified Nioh 2 at the time of this article’s writing.
Literally took just a few evenings worth of work.
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.
If you can, please try to support what I do by following my stream, and letting me know if what I do is something entertaining and worthwhile to you. I really appreciate knowing about any and all who get anything out of this strange little thing that I do, a thing I put so much time and heart into.
Thanks for reading!