The Witcher 3 is one of my favorite games to have come out in recent times. You won’t be surprised then to hear me say that my excitement towards the prospect of playing an Omnified version has increased with every new Omnified system I’ve implemented into it. Previously, we implemented the damage overhauling Apocalypse system; today, we implement the enemy speed boosting Predator system.
Even though I should no longer be surprised when a new game throws unexpected things my way, The Witcher 3 managed to throw me off balance (for just a few microseconds, mind you) with the manner in which creature locomotion is organized and managed. Despite this, implementation of the Predator system into the game was a relatively painless affair, and I will try to be brief.
At the time of writing, a detailed design article providing an overview of the Predator system has not yet been published. I could swear I added an todo item to write such an article on my little todo app that resides on my phone, but I suppose it’s been buried by other things.
For all those unversed in Omnispeak, the Predator system is a game-neutral overhaul to the game’s movement system that provides enemies with an intelligent speed boost based on their proximity to the player. Basically, it causes enemies to scare the crap out of me as they zoom in and end my existence.
Although no design article exists yet, here’s a nice video for you:
Where’s the Movement Application Code?
Well this is about the time in a Predator implementation article where I take you through a very interesting and illuminating reverse engineering experience of finding the elusive movement application code for the game. This is the code that is responsible for calculating new coordinates for a creature in motion through the application of a set of movement offsets to the creature’s current coordinates.
This is very distinct from the location update code, which is where new coordinate values are committed to the place in memory designated as the source-of-truth for a creature’s coordinates. Unlike the movement application code, this code is very easy to find. You would think that the movement application code would be nearby the location update code; however, because this is assembly we’re talking about, they can sometimes be literally hundreds of thousands of instructions apart.
The movement application code is typically the ideal place to manipulate the movement of creatures as it allows all of the sometimes many systems dependent on changes to location coordinates to be updated properly. This increased stability is a result of our changes happening much further up the line in the processing of a creature’s locomotion than where it would occur if we simply tweaked the location update code.
That’s all lovely, but it grieves me to inform you that finding it simply isn’t going to be happening with The Witcher 3. Are we screwed? No. While some games will definitely become completely unstable wrecks if we manipulate movement at the point of the location update code, not all games are so sensitive. I spent a bit of time looking for the movement application code, and was running into some really insane, very hard to navigate, clusters of physics-related instructions for every little movement update.
So, I value my time, and decided to take a gamble and see if we could get by on a bit of a different approach…
Time for a Dirty Speed Hack
When I affect changes to the movement of a creature at the place of a game’s location update code, I warmly refer to such a type of hook as a dirty speed hack. I call it such because, as I’ve gone on about in the previous paragraphs, depending on the composition of a game’s locational systems, implementing such a hack can cause the game to essentially blow up (creatures will phase out of existence, etc.).
Typically, no movement offsets will be on hand when doing a dirty speed hack. That’s fine, as we can simply figure out what the offsets are by taking the difference between the new and current coordinates. And that’s what I decided to try out for The Witcher 3.
And it worked out fine. No reason to bust our backs if we don’t need to.
Time to get dirty!
Finding the Location Update Code
To do the dirty speed hack, we need to figure out where the location update code is. This is rather easy: we just need to see what code is writing to our character’s source-of-truth coordinate values.
If we’re trying to hack a game that won’t make us pull out our hair, we shouldn’t see any instructions writing to our coordinates until we actually move our character in game. Luckily for us, that seems to be the case for The Witcher 3.
Alright. That was easy. Let’s get to implementing the initiation point for the Predator system then! Well, let’s hold up for just a second. Let’s make sure this is indeed the location code for not just the player, but for all creatures in the game. This is done by loading the disassembly window, right clicking on the instruction, and choosing Find out what addresses this instruction accesses.
While doing this, I made sure I was nearby other NPCs that were also moving. And…unfortunately, as you can see, there’s only one place in memory that’s actually being updated by our location update code. This means that the code we’re looking at handles exclusively the player’s own movement.
For the majority of games I’ve Omnified, both player and NPC coordinate updates were handled by the same piece of code. A separate system dedicated to managing the movement of just the player is something that I’ve found to actually be quite rare. Unfortunately, when a separate location update code exists for NPCs, it can be a bit troublesome to find.
Finding the NPC Location Update Code
How do we find the NPC location update code? The same way we found the player location update code: check for writes occurring to a particular set of coordinates. Of course, in this case the coordinates we need to look at are NPC coordinates, not our own. While we wrote code previously that automatically hooks into our coordinates, no such convenient feature exists for an NPC’s coordinates. So we’ll need to search for a pair.
An easy way to do this is to look into various coordinate polling functions that are reading (not writing) from the coordinates. Often there will be system-wide functions that need to be kept apprised of the whereabouts of entities on the map, and these functions will be accessing the coordinates for every loaded creature in the game. This includes the player.
So I started off the search for NPC coordinates by looking at what code was accessing my coordinates. Unfortunately, after looking through all the accessing code, I found that each and every one only was looking at the player’s coordinates.
Wow. The movement systems for NPCs and the player are very separated. This is new for me — usually the movement mechanics for NPCs and players are at least somewhat related. Sadly this all means I was going to have to find the coordinates for an NPC the old fashioned way: by standing close to one and then doing an approximate value range search for the NPC’s location using my own as a reference.
Unfortunately that yielded no results either. Argh. Eventually I found out it was due to even greater differences existing between the movement systems. If you recall, the value types for our player’s source-of-truth coordinates are doubles, which is definitely not the norm on its own. Well, as it turns out, NPCs actually use the more normal floats for the value types of their coordinates.
I eventually figured this out simply by doing a new search for the NPC coordinate using float instead of double. I really need to stop doing value type-specific searches…
The Predator Initiation Hook
Well, at least we don’t have to deal with converting a bunch of doubles to floats, since NPCs don’t use doubles. We will still need to convert the player’s coordinates to floats, since the player’s location is a parameter to Predator technically.
By the way, my theory as to why there is this strange separate movement system tacked onto the game that uses doubles is that perhaps it is the result of when CD Projekt had to hurriedly throw on an alternative movement system to the game (since the original movement/controller system was very unpopular).
Let’s whip up a starting template to work with.
Predator Initiation Hook – Template
// Initiates the Predator system. define(omnifyPredatorHook,"witcher3.exe"+60E5C0) assert(omnifyPredatorHook,8B 02 89 41 70) alloc(initiatePredator,$1000,omnifyPredatorHook) registersymbol(omnifyPredatorHook) initiatePredator: initiatePredatorOriginalCode: mov eax,[rdx] mov [rcx+70],eax jmp initiatePredatorReturn omnifyPredatorHook: jmp initiatePredator initiatePredatorReturn:
The updated coordinates, as a result of the NPC moving, are found in the rdx
register; the current coordinates are found in rcx
, starting at offset 0x70
.
We’re going to need to calculate what the movement offsets are, and then pipe those values along with other required to the Predator system. When that returns, we’ll have updated movement offsets, which we’ll simply add back to the original coordinate values, storing the results in memory where the game expects the new coordinates (pointed to by the rdx
register).
I don’t need to read anything from the stack, so no need to worry about no stack math! Let’s go ahead then and start with prepping our data prior to Predator system execution.
Predator Initiation Hook – First Steps
initiatePredator: pushf // An initialized playerLocation pointer is required prior to Predator execution. push rax mov rax,playerLocation cmp rax,0 pop rax je initiatePredatorOriginalCode // We'll need a few SSE registers in order to do double->float conversion. sub rsp,10 movdqu [rsp],xmm0 sub rsp,10 movdqu [rsp],xmm1 // We'll need to backup the registers used by Predator to store its return values. push rax push rbx push rcx // And another register to hold onto the current coordinates, since we'll need them to finish // calculating what the new coordinates should be following Predator execution. push rsi mov rsi,rcx mov rax,playerLocation mov rbx,[rax] // It is possible during a load screen following a death or area transition for a previously // initialized player location pointer to no longer be pointing to a valid place in memory. // We'll need to wait in that case. Eventually, our player hook will correct this. lea rcx,[rbx+1B8] call checkBadPointer cmp ecx,0 jne initiatePredatorCleanup // Our player's coordinates are stored as doubles. Predator expects them to be floats. // A packed conversion to handle both X and Y at once... cvtpd2ps xmm0,[rbx+1B8] // And then a single conversion to take care of Z. cvtsd2ss xmm1,[rbx+1C8]
Upon this code executing, the majority of the necessary preparations have been taken to ensure a successful Predator execution. We have our player’s coordinates converted, necessary data structures checked for, etc.
Next, we’ll need to push required parameters to the stack and call executePredator
.
Predator Initiation Hook – Function Execution
initiatePredatorExecute: // With our player's coordinates converted, we'll push them as the first parameter to the stack. // X and Y are pushed as quadwords first, followed by Z, as if they were pushed as two m64 // addresses. sub rsp,8 movq [rsp],xmm0 sub rsp,8 movq [rsp],xmm1 // Next are the current coordinates for the target. push [rsi+70] push [rsi+78] // An identity matrix is passed to represent the target's dimensional scales. True scaling is // most likely not present in this game; the "artificial" size of the creature shouldn't // impact movement. movss xmm0,[identityValue] shufps xmm0,xmm0,0 sub rsp,10 movdqu [rsp],xmm0 // Time to produce the movement offsets, which act as the final parameter for the Predator system. // Offsets are calculated as such: coordinatesNew - coordinatesCurrent = offsets. movups xmm0,[rdx] movups xmm1,[rsi+70] subps xmm0,xmm1 // The offsets are on xmm0, they are pushed to the stack as if we were pushing two m64 addresses // in memory. movhlps xmm1,xmm0 sub rsp,8 movq [rsp],xmm0 sub rsp,8 movq [rsp],xmm1 // All parameters have been provided. Execute the Predator! call executePredator
All requirements are finally checked off; all parameters have been passed. Once all of the above code has finished executing, we will have updated movement offsets in eax
, ebx
, and ecx
.
All that remains is to reapply them to the current coordinates of the creature, and replace the target coordinates with these new values.
Predator Initiation Hook – Return Value Processing
initializePredatorUpdateCoordinates: // We make some room on the stack to help transfer updated offsets found in eax, ebx, and ecx // to an SSE register. sub rsp,10 mov [rsp],eax mov [rsp+4],ebx mov [rsp+8],ecx movups xmm0,[rsp] add rsp,10 // The updated offsets are then added to the current coordinates, giving us new target coordinates. movups xmm1,[rsi+70] addps xmm1,xmm0 movups [rdx],xmm1 initiatePredatorCleanup: pop rsi pop rcx pop rbx pop rax movdqu xmm1,[rsp] add rsp,10 movdqu xmm0,[rsp] add rsp,10 initiatePredatorOriginalCode: popf mov eax,[rdx] mov [rcx+70],eax jmp initiatePredatorReturn omnifyPredatorHook: jmp initiatePredator initiatePredatorReturn:
That’s all she wrote. Before we wrap up, however, we do need to tweak some external parameters.
The vertical axis in The Witcher 3 is the Z axis, not the typical Y axis. Also, coordinate values are somewhat non-granular, meaning that small changes to coordinate values cause bigger changes on screen than in some games.
Predator Initiation Hook – External Parameters
threatDistance: dd (float)2.0 aggroDistance: dd (float)6.0 skipBoostY: dd 0 skipBoostZ: dd 1
Is The Witcher 3 Zooming Now?
Zooming and booming. I’d create some nice GIFs for ya’ll showing the difference, but why settle for GIFs when you can just watch the live gameplay of an Omnified Witcher on my stream?
The speed boost to enemies is freaking excellent, increasing the excitement I have towards the otherwise sometimes rather droll gameplay offered by the game. It also seems to be quite stable! Speed boosts are smooth, no extreme jankiness has been detected. The “dirty speed hack” approach is a success! I can’t wait to play it! And for you to watch me play it! Check out the stream and say hello!
Here’s the complete code; also, don’t forget that my hacking workspace is now published live to my source code repository. For the latest versions of my hacks, always be sure to check out what’s on the Bad Echo technologies source control!
Predator Initiation Hook – Complete
// Initiates the Predator system. define(omnifyPredatorHook,"witcher3.exe"+60E5C0) assert(omnifyPredatorHook,8B 02 89 41 70) alloc(initiatePredator,$1000,omnifyPredatorHook) registersymbol(omnifyPredatorHook) initiatePredator: pushf // An initialized playerLocation pointer is required prior to Predator execution. push rax mov rax,playerLocation cmp rax,0 pop rax je initiatePredatorOriginalCode // We'll need a few SSE registers in order to do double->float conversion. sub rsp,10 movdqu [rsp],xmm0 sub rsp,10 movdqu [rsp],xmm1 // We'll need to backup the registers used by Predator to store its return values. push rax push rbx push rcx // And another register to hold onto the current coordinates, since we'll need them to finish // calculating what the new coordinates should be following Predator execution. push rsi mov rsi,rcx mov rax,playerLocation mov rbx,[rax] // It is possible during a load screen following a death or area transition for a previously // initialized player location pointer to no longer be pointing to a valid place in memory. // We'll need to wait in that case. Eventually, our player hook will correct this. lea rcx,[rbx+1B8] call checkBadPointer cmp ecx,0 jne initiatePredatorCleanup // Our player's coordinates are stored as doubles. Predator expects them to be floats. // A packed conversion to handle both X and Y at once... cvtpd2ps xmm0,[rbx+1B8] // And then a single conversion to take care of Z. cvtsd2ss xmm1,[rbx+1C8] initiatePredatorExecute: // With our player's coordinates converted, we'll push them as the first parameter to the stack. // X and Y are pushed as quadwords first, followed by Z, as if they were pushed as two m64 // addresses. sub rsp,8 movq [rsp],xmm0 sub rsp,8 movq [rsp],xmm1 // Next are the current coordinates for the target. push [rsi+70] push [rsi+78] // An identity matrix is passed to represent the target's dimensional scales. True scaling is // most likely not present in this game; the "artificial" size of the creature shouldn't // impact movement. movss xmm0,[identityValue] shufps xmm0,xmm0,0 sub rsp,10 movdqu [rsp],xmm0 // Time to produce the movement offsets, which act as the final parameter for the Predator system. // Offsets are calculated as such: coordinatesNew - coordinatesCurrent = offsets. movups xmm0,[rdx] movups xmm1,[rsi+70] subps xmm0,xmm1 // The offsets are on xmm0, they are pushed to the stack as if we were pushing two m64 addresses // in memory. movhlps xmm1,xmm0 sub rsp,8 movq [rsp],xmm0 sub rsp,8 movq [rsp],xmm1 // All parameters have been provided. Execute the Predator! call executePredator initializePredatorUpdateCoordinates: // We make some room on the stack to help transfer updated offsets found in eax, ebx, and ecx // to an SSE register. sub rsp,10 mov [rsp],eax mov [rsp+4],ebx mov [rsp+8],ecx movups xmm0,[rsp] add rsp,10 // The updated offsets are then added to the current coordinates, giving us new target // coordinates. movups xmm1,[rsi+70] addps xmm1,xmm0 movups [rdx],xmm1 initiatePredatorCleanup: pop rsi pop rcx pop rbx pop rax movdqu xmm1,[rsp] add rsp,10 movdqu xmm0,[rsp] add rsp,10 initiatePredatorOriginalCode: popf mov eax,[rdx] mov [rcx+70],eax jmp initiatePredatorReturn omnifyPredatorHook: jmp initiatePredator initiatePredatorReturn: threatDistance: dd (float)2.0 aggroDistance: dd (float)6.0 skipBoostY: dd 0 skipBoostZ: dd 1
As always, thanks for reading, and thanks for your interest! Check out Omnified gameplay in action on my Twitch stream, and drop by my Discord server to say hello, if you please.
Otherwise, have a fabulous day my friends.