Time To Add Some LSD-Fueled Smooth Morphing Monsters
Good day to you. I’ve been busily hacking away at Assassin’s Creed: Valhalla, getting it to a state where we might be able to call it Omnified. I’ve provided several articles thus far covering data structure analysis, overhaul of the damage system, as well as implementation of my intelligent speed booster for enemies.
So, we’ve got the Apocalypse and Predator systems down, only one left: the Abomnification system. This system causes enemies in the game to randomly change shape and size at randomly determined speeds accordingly to randomly determined morphing modes.
It’s a pretty cool system that I plan on adding even more features to soon, and it will definitely have a detailed Omnified Design article dedicated to it soon. Unfortunately, it does not have one yet. So, I hope you will bear with me if any gaps of knowledge remain in your brain even if you fully read this article (fat chance of that happening hahaha).
One day, such an article will exist. Until that day, feel free to check out other Abomnification implementation articles to gather more information on anything I forget to include in this one.
Is Easy Scaling Support Built-In?
Implementation of the Abomnification system is a bit more complicated than the other systems. What actually needs to be done greatly depends on what the game makes available to us. Specifically, the first question we must answer is whether or not the game features built-in easy scaling support.
Easy scaling support takes the form of three (typically) exposed floating point values, each of which acts as a multiplier for a particular dimension (width, height, or depth). The presence of these dimensional floats make the reshaping of creatures a simple affair; for example, doubling the height multiplier would cause the creature to become twice as tall.
Ever since the creation of the Abomnification system, I’ve hoped to Omnify games with built-in scaling support, but the majority of the games that I’ve done since then (blame FromSoftware) lacked any. I hope this game has it, but I’m not holding my breath.
A Crappy Way of Finding Easy Scaling Support
Many times a game will have easy scaling support if there is some sort of character creation process where the height or one of the other dimensions can be manipulated by the player. Another reason for easy scaling support to exist is if differently sized enemies of the same type is a gameplay element (i.e. Monster Hunter World). The last typical reason for built-in easy scaling support to exist is if the game is using an engine that supports such a thing out of the box.
Some of the most prevalent (well, in the past at least) engines offer this, such as the Unreal engine I believe. Unfortunately, Assassin’s Creed: Valhalla is using the Ubisoft proprietary engine “AnvilNext”, and none of the other common reasons for having easy scaling apply here either. We’re most likely screwed, however I’m trying to be positive about things here OK!?
The scale multipliers, at least for games powered by the Unreal engine, are usually found in the same data structure that houses the coordinates. So, we’re going to browse the memory region belonging to our location structure, looking for values that…appear to be scale multipliers.
I have my memory browser loaded up so it is pointed right next to my coordinates. I’m going to look for floating points that are around 1.0, and then see if I can tweak them. If the change holds, then we hope for a visual change in the character’s size.
Yeah, this is a pretty primitive way to go about doing this. But, it actually ends up not taking too long to find the scale this way if standard easy scaling is present.
Typically, we’re going to see three floating point values, one for each dimension, all contiguous to each other in memory. So three floating points at around 1.0, as this is what the default for the player character is typically going to be…ya know, that only makes sense, right?
But I don’t spend a ton of time looking for it in this fashion. I mean, we could waste hours pouring through the memory if we don’t hit our own STOP button. If I don’t see anything in a quick fashion related to scale nearby the location coordinates, it is time to move on frankly.
Scanning for Contiguous Dimensional Scales
As was stated before, often and especially for the player character, the dimensional scales will be stored in memory all next to each other, and typically all at the value 1.0. Obviously, this will probably not be the case if there is any height/width/depth editing via character creation, but that’s not something we have to worry about here.
So, why don’t we attempt to search memory for all sequences of three floating point values of 1.0. We can do that if we search for a specific array of bytes. The 4 byte hex representation of a 1.0 is
0x3F800000. Since we’re running on a little-endian system, that ends up looking like this in memory:
00 00 80 3F 00 00 80 3F 00 00 80 3F
So let’s search for this exact sequence, however let’s constrain the address range of the search results to be within the area of memory where we see our player’s health and location stored, which is between
Well we have some results. Looks like a little over 16,000 of them. We probably should’ve constrained the results a bit more so the only results shown were closer to the location structure.
Some Time Passes…
After checking out the results closest to the location structure, I could not pinpoint any sort of floating point sequence that could be manipulated in order to change the character’s shape and size. This doesn’t mean that there is no built-in scaling; the scale multipliers may be getting stored in a structure entirely independent from the location structure. I mean, who knows?
But if such things do exist we are going to have a hell of a time finding them with just the information we currently have at hand. Every time I’ve found easy scaling support in a game, it has been located by the coordinates. And this has been across several different engines as well.
We’re not seeing that here, so we must conclude, unfortunately, that easy scaling support does not exist in this game. This means we must implement own custom scaling code in order to be able to apply Abomnification-generated scale multipliers.
Where, Oh Where, is the Character Model Rendering Code?
In order to be able to influence the size of a character’s model, we must locate the code responsible for calculating and storing the 3×4 matrix values that essentially define the physical characteristics of the character in three dimensional space. We’re going to definitely have our work cut out for us. A game’s model rendering code is usually very deep down the rabbit hole as far as code goes, and it’s always very engine specific.
Since creating the Abomnification engine, I’ve had to implement my own custom scaling code for five or six separate games. And, let me tell you, I’ve felt very fortunate each and every time I managed to crank one out. Sure, some might attribute it to “skill” or “deductive prowess”, but sometimes it seems like such a difficult task that it feels that I’d be lacking in humility if not attributing some of the success to luck.
If I actually manage to achieve implementing custom scaling code in this game, then it will be the first game I’ve ever done such a thing in without the aid of RTTI. Real time type information gives us information about types and complex data structures, which greatly aids locating recognizable character data (which is where we store the character-specific Abomnification parameters) within the rendering functions (which are very removed from other parts of the code).
The code which will be housing our custom scaler is where the mesh of polygons is being formed for a character through the processing of various edges and vertices. The data we’ll be manipulating are essentially vertex matrices, with each value basically representing the distance from some other point on the plane.
So, as you can probably surmise from above, this is way beyond the difficulty levels presented by other areas such as character movement and health damage. In order to do this stuff, we got to git gud baby.
Crouching for Success
To begin the search for our rendering function, we’ll be starting out in much the same way we initiate the disassembly of most things. We’ll locate the code of interest by searching for code involved in the manipulation of data isolated with controlled value changes. Of course, unlike the searches for more mundane things such as character location and health, it’s a bit less clear as to what to look for and how to look for it when we’re talking about the character’s model.
Well, we are looking for code that processes vertex matrices for the polygonal mesh of our character’s body. All of them put together essentially forms the edges and shapes that compose the character’s model. So we basically just need to effectuate changes to all these vertex points in a consistent manner in order to isolate them.
Lots of different actions change these vertex points; simply moving forward and/or backward changes these particular points. But this is difficult to track. We have to perform our search using changes on the character’s model that are both quantifiable and easily repeatable. The easiest thing to do, at least for this game, is simply crouch up and down.
When crouching, we’re basically folding up that mesh of polygons that makes up the character model; crumpling it up so it all fits in a small space. For the most part, we will have a decrease in values across the the character model as everything gets closer to each other. So, lets try an Unknown initial value search for floats, doing follow up searches for Decreased values when we crouch, and some follow up searches for Increased values when standing up.
PSA: A Superior Method for Searching for Changes
There will be millions of values at play here in memory. It will take awhile to narrow them all down, and we’ll still be flooded with many false positives at the end of it all. To increase precision, a breakpoint should be placed at code that is executing frequently, but not too frequently. We then set up a hotkey for Debug -> Run, and then spam that key while trying to initiate a crouch. If we are unable to get in any user input, we’ll want to find a different spot.
If everything is done correctly, we will get a few frames of animation every few clicks of the Debug -> Run button, allowing for us to do very fine tuned Decreased values and Increased values searches. This will cause the number of results to very quickly get to a small number of accurate results. This is the superior way to search for changed values.
Back to the Crouching Search…
Starting at two billion or so results, we narrow them down in short order using the superior method described above. I ended up with a quite small (given what we’re searching for) set of only about 800 results. Not bad! Taking a look at a number of them, and seeing what was writing to them, I ended up in the following bit of code:
Interesting. This doesn’t look exactly like what I’m used to seeing, which is three separate
movaps instructions essentially resulting in a complete write to a 3×4 matrix, but perhaps that is what it is going on here as well, given that everything is looped. Instead of each iteration writing to a separate 3×4 matrix, maybe each iteration is writing to a particular row in a matrix.
This is a bit of a complication, but let’s not worry about it until later. Let’s see what happens if we disable
movups [rax+rdi],xmm5 which is the instruction that’s writing to the data point we picked out of the search results earlier.
Apparently disabling this instruction results in a large portion of our body disappearing. We’re actually looking for a function that, if disabled, results in the size of the model no longer updating when it should (i.e. getting smaller when crouching). While this isn’t exactly the rendering function we need, it still has to do with the character’s model, so we are actually on the right track.
Global Scale Multipliers
I was about to start going through the list of search results again, looking for more rendering-related code, when my eye caught on the instruction resting one line above the code we were just looking at:
mulps xmm5,[ACValhalla.exe+415A470]. The product of this instruction is what is getting dumped into our data point.
As you can tell by looking at the operand, this is multiplying some value by a multiplier stored in static memory. This is basically a compile-time constant. Browsing to the static memory in question, we see the following:
A floating pair triplet, all members set to 2.0, with a bunch of boundary-forming zeroes following them. Very interesting. Could it be we are looking at a particular kind of global scale multiplier, with each value belonging to a separate dimension? After changing the values around myself, I observed that was indeed what it was; however, much more than just the character scale changed. Things such as the camera width, position, perspective, etc. changed.
We are definitely on the right track now. This only confirms it. Don’t start rejoicing too soon however, as just being able to tweak these global scale multipliers fails to give us what we need on a number of counts:
- We need to be able to apply scale multipliers that are unique to each creature being scaled. The randomly selected morphing modes (which is per-creature) will be applying randomly determined target morphing dimensions independent of each and every other creature on the map.
- Simply manipulating the global scale multipliers causes many things other than just the character model dimensions to change. The camera, its position and even angle, is affected.
All of that aside, I believe these global scale multipliers will lead us ultimately to where we need to be. Although they affect more than just the character model scales, by narrowing down where they’re actually used to influence said scales, we should be able to ultimately find the rendering code that we need.
Consumers of the Global Scale Multipliers
Adding one of the global scale multipliers to our address list, lets now take a look at the sort of code that is accessing this bit (well bytes, bah….puns bad) of static memory.
There’s a plethora (ah…Jefe) of code accessing these multipliers. Not surprising. We’re going to be interested in the code that is accessing the multipliers the most frequently, as that is indicative of character model matrix transformation scaling code. Just trust me OK. Highlighting the top instruction here, let’s hit that Show disassembler button and hope for the best.
The global scale multipliers are read by the highlighted instruction, whose product is then involved in many, many complex calculations and operations before being finally written to a matrix row. In order to see if this usage of the global scale multiplier pertains to just the character’s model (and not other things like the camera), let’s replace this highlighted portion of code with a bunch of
Excellent. When we remove the code that is applying the global scale multiplier (which will reduce the end product of all dimensions by half) we see the scale of our rendered character model change accordingly. That means this is the spot to effectuate scale changes! We can change the scale for whatever creature is being processed at the time of execution, without having to tamper with the global scale multiplier.
Testing A Custom Scaling Hook
Just to make sure that this is the proper place in the code to implement our Abomnification scale application hook, let’s create a quick and dirty hook into the
mulps xmm8,[ACValhalla.exe+415A470] code. We’ll want to test out some different sample scales applied to just a single “column” of data at a time in
xmm8 in order to see if they all belong to different dimensions.
In the past, games where I’ve had to hack into the rendering logic would dedicate entire rows of matrix data to each dimension. Typically, the first row (
[n]) would be for the width, the second row (
[n+10]) would be for the height, and the third row (
[n+20]) would be for the depth. Each row would have a separate instruction writing independently calculated data into it.
The rendering data structure is a bit more mysterious here. It seems to be 32-byte aligned, with the first 16 bytes containing three dimensional values (padded by 4 bytes of zeroes), and the last 16 bytes being for something else related to position. What it actually all is doesn’t matter right now; complete understanding is not required. I’m really only starting to understand what all the data I’ve previously worked with is. So, no need to do that here.
Moving on, I create a simple hook that just multiplies the third floating point value in
xmm8 by 1.5, and we get the following:
Hell yes! We’ve figured out how to change the scale of a character in this game! The only thing affected here is the character’s model; the camera and everything else is completely unaffected. Cool! Well, let’s see what happens when we screw with another matrix value (the first floating point). Let’s multiply that by…4?
Well hells yes again. The width has definitely increased greatly here, and while it looks freaking weird, that’s actually perfect. The point of the Abomnification system is not to create perfect scaling characters, but to create abominations!
How would we go about creating perfect scaling? Well, because that’s completely outside of what I need to achieve here, I will have to leave that one to you. But, if you’re interested, I’d recommend taking a look at all the other consumers of the global scale parameter, some of which will be responsible for arranging the shape and size of different parts of the character, and then provide the necessary additional hooks in those places in order to the uniformly distribute the scaling change.
Concerning what I’m trying to achieve, trying to do the above would just be something requiring loads of additional work for a more boring final product. Not going to happen. We will actually be able to prevent the most ridiculous of the potential visual aberrations from happening simply by our limits placed on the randomly generated dimensional maximums.
So it looks like we’ve found the perfect spot to implement our custom scaling code. Here is where we’ll be placing an Abomnification scale application hook, which will be responsible for applying scale multipliers generated by the Abomnification system to data involved in the rendering of the character’s model. Are we out of the woods yet though? Not at all.
Retrieving Character Data From Rendering Code
The primary functionality of the Abomnification system is the provisioning of what the scale multipliers should be for a particular character given the current step its on in its morphing journey to randomly determined morphing targets. If the previous sentence makes no sense to you, I am sad to say that it will have to remain nonsense until I get a detailed overview article on the Abomnification system published. Sorry!
Assuming that you’re following along still (hello there you brave warrior), in order to do the above, we need to be able to store character specific data in memory easily reachable while operating within contexts specific to that character. This is typically not the most arduous of things to accomplish; when processing movement related functions, for example, we’ll typically have a location structure for the creature on hand.
Not so in the dark, dank world of graphics rendering logic. This is typically far removed from other parts of code that manage more recognizable gameplay elements, and it also tends to be very generalized. This is code that needs to be highly performant, and essentially is just some sort of provider of math operations on matrices.
From the place in code where we’ve decided to inject our custom scaling mechanism, we need to be able to retrieve known data structures pertaining to the character whose model is being rendered. With all the games I’ve done this for, this has been very difficult, as it is typically buried very deeply in loosely related data structures.
This will be the very first time we try to accomplish such a thing without the presence of RTTI, so my skills are going to be tested here…and I’m a bit nervous about whether or not I’ll be able to do it. Let’s proceed.
Finding Some Player Data Being Rendered
We need to devise a method on how to retrieve useful character data from within the model rendering function. Here’s how we’re going to do it:
- We’ll acquire some data that we know belongs to our player that is being rendered by the code.
- A breakpoint will be placed on that code so that it is triggered when said data is being processed.
- From there, we’ll figure out if we can dig up any data that links to instances of known data types for our player.
If we can provide a means of determining that it is our player who is being rendered, then we should be able to do the same for all characters that are not our player. So let’s get a hold of some data we know is tied to our player first. To do that, let’s take a look at the instruction that is actually writing out the processed rendering data.
Let’s right click on this instruction and choose to Find out what addresses this instruction accesses. This is going to pop up a window displaying just that, and we should expect there to be many, many addresses listed.
As you can see from the image above, there are over 2000 different addresses being processed by this rendering function. The majority of these addresses have nothing to do with our player. Thankfully, we can easily determine which ones are related to our player by one principle in particular that rendering functions like this one tend to follow.
Rendering code is very performance sensitive. We’re talking about graphics here, this kind of thing is the bread and butter of most games these days. While there are many different things on a screen that will require rendering at a given point in time, less priority will be given to character models that are further off in the distance than ones nearby.
So, bearing that in mind, if our character is standing by himself in the snow, then we can conclude with certainty that the points of data receiving the greatest amount of attention are indeed the player character himself. By sorting the list of addresses by execution count (in reverse order in the image, sorry!) I can stake my life on the bet that the topmost (bottommost in the image, sorry!) address is indeed player related.
Finding the Link to Known Character Data Types
Now that we have a chosen player data point of
176D9026E60, let’s wire up a breakpoint to go off when this particular address is being processed.
Looking up, in memory, all the addresses stored in the various CPU registers, I saw nothing but unwieldy globs of rendering data. While some sort of link may have been able to have been discovered amongst all this, I highly doubt it. With the registers not giving us anything useful, there was only one place left to go: the stack.
Glancing over at the stack, it didn’t take long until I noticed the address stored at
176D9DA56F0. This looked very promising, as it was distinct from the other addresses stored in registers while also falling within the memory range of known player creature data. So, biting the bullet, I decided to grab the address and plop it into a Structure dissect window and see if we could discover any relations to useful bits of data.
Not knowing what type of data this was, I dubbed the structure Rendering Data and then proceeded to click on Structure Options -> Find relations. In the resulting pop up window, we made sure all of the other data types had valid addresses associated with them, and then hit OK, hoping for the best.
Holy shit. I can’t believe it, but we’ve found a linking data structure already! Just a little down the stack, we have a path that will lead us to the location structure of the character being rendered! Wow, we’re actually going to be able to do this.
Implementing the Abomnification Initiation Point Code
Everything we’ve been talking about up until now has been about the Abomnification scale application code: the code that will be responsible for applying the scales generated by the Abomnification system. The reason why we had to spend time on that whole thing was because there is no easy scaling support built into the game’s engine.
I wanted to focus on the custom scaling code first because, without it, what we hope to achieve is impossible. So, now that we know we can actually implement this system, we’re going to switch gears to the writing of the initiation point for the Abomnification system. This is what gives us the per-character scale multipliers that are going to be applied by our custom scaling function. We’ll get back to implementing the custom scale function later.
Where to Initiate the Horror?
The first question we need to ask ourselves: where should the initiation point go? Well, we actually want it to be somewhere other than the rendering logic. For optimal results, we need scale generation to be frequent, yet not as frequent as the actual rendering of it. Otherwise, the smoothness of the morphing animation will suffer as not all generated scales will be caught by the naked eye.
My favorite place to initiate the Abomnification system is in a coordinate polling function that is checking on coordinates of the various entities on a map at a high rate. Preferably, a coordinate polling function that is only polling NPCs and not all the rocks and bushes is ideal. Unless we want the bushes to be on acid too. Which sounds kind of fun…
To start the search for the perfect place, let’s right click on one of our coordinates and choose Find out what accesses this address to get a list of all the coordinate polling functions.
I’ve said it before, but this game has way more coordinate polling functions than any other game I’ve hacked. Just an interesting little tidbit. I must assume there’s a lot of movement related code, or systems that respond to our player’s movement, and that’s not surprising at all given that this is Assassin’s Creed.
Ideally, we want a coordinate polling function that is high up on the list in terms of execution rate, and one that is polling not just our player, but other NPCs as well. Let’s start off by disassembling the topmost called function and seeing what addresses that one is accessing.
This is a shame, but not entirely surprising. Typically, from what I’ve seen, the coordinate polling functions that are most frequently executing tend to be just looking at the player, for reasons that should be obvious (there is usually a vested interested in knowing where the player is at all times right?).
This function was executing too much anyway, using it might’ve resulted in animations that were moving too fast. Let’s go down the list then to the next one, and see if the second most commonly executed coordinate polling function is pinging entities other than just the player. If that doesn’t work either, then we’ll keep on going down the list until we get something that does.
Some Time Passes…
We’re not getting anywhere looking down this list. I was under the impression that I would easily find something usable near the top. Wrong.
Let’s pivot here a bit, and instead take a look at the location update code, which we worked with during our implementation of the Predator system, and get a list of all of the addresses whose locations are being updated.
There’s over 177 different entities moving on this map! My thoughts are that perhaps there are a separate class of polling functions dedicated to tracking non-player entities. So, let’s grab one of these non-player coordinates and see what code is accessing them.
This is a much more useful list of instructions to look at. Whether or not these also show up on the list of player coordinate accessing instructions is something unknown to me at the moment, but something I also know that will be made abundantly clear later on. Let’s look at one of the three most commonly executing instructions and see what addresses that one is accessing.
Looks like our entity coordinate polling function is accessing a healthy number of non-player coordinates. Great. One worry I had, though, is that the list was consistently growing. This is indicative of temporary objects being introduced into existence for a brief amount of time so they can do what they have to do before they’re snuffed out of existence.
Why are these temporary objects being accessed in a supposed NPC coordinate polling function? Do we actually know that the coordinates we were looking at originally belong to NPCs? Just because they were having their locations updated means little!
Looking at the Movement Application Code for Inspiration
One of our achievements during the implementation of the Predator system was the locating of the movement application code in this game. This is code purposed for facilitating changes to NPC coordinates in response to movement. We should make use of this code to get a good sampling of actual enemy NPC coordinates.
I’m going to right click now on the new instruction here, buried deep within the game’s movement application code, and check out what addresses it is accessing. This will produce a list of coordinates that are most definitely NPC coordinates.
This is a much smaller list of coordinates than what we were seeing from the more generalized location update function. Let’s take a few of these coordinates and check out what kind of polling functions they got.
Not as many functions as the player had, but more than the previous coordinates we were looking at had. I must conclude that those other coordinates were most likely not NPC coordinates. Well, we all learned something today.
Here we go again. Let’s go through these, starting at the most frequently executed and working our way down, and see if we can’t find one accessing around the same number of addresses as the movement application code was. We want something that’s precise and only looking at actual NPC coordinates.
After reaching the fourth instruction on the list, we hit gold! The number of addresses being accessed was something much more inline with the number of NPC’s I imagined to actually be on the map. Note that the player’s coordinates do actually show up here if we make any kind of movement. Thankfully, the enemy coordinates are consistently polled even if they aren’t moving.
This indicates to me, that this is a supremely perfect spot for the Abomnification initiation point hook. Here is where we’ll be injecting it in:
Now that we know where to implement the call to the Abomnification system, we should be good to go right? Nope, there’s one more itsy bitsy thing we got to do.
Figuring Out Where to Store the Morph Scale ID
The morph scale ID basically acts as an index, used to access a particular character’s morphing data in our sandboxed morphing data blob. We maintain all of the various Abomnification characteristics in memory separated from the game’s own, however we still need to store the morph scale ID somewhere in the memory used by the game so a data association is persisted.
This morph scale ID only requires 4 bytes of memory, and we want to store it in a commonly accessed type of character data, mainly for convenience’s sake. Because we know from earlier that the character location structure is accessible from within the rendering method we’ll be hooking into, we’re going to want to find 4 bytes of available data there to store it in.
Naturally, just throwing bytes around in program memory being used by a program is a recipe for disaster. Nevertheless, we need to find an empty pocket, where it won’t cause a crash and where it won’t be overwritten.
We’ll first want to get together some sample data to test it with, here’s how we’re going to do that:
- Browse to the code we identified as the ideal location for our Abomnification initiation point hook. This part of the code just deals with non-player character location structures.
- Right click on the instruction that is loading the location structure and then click Find out what addresses this instruction accesses.
- Highlight all the addresses in the window that pops up, right click and select Open dissect data with selected addresses.
- Name the structure anything you want, and then hit OK a bunch of times.
If we had a preexisting structure defined that would work with this data, you’d probably want to load up a Structure dissect window of that first, and then just use that window. However, while we do have a location structure defined, it is aligned slightly differently (X coordinate starts at
Now that we have some sample data loaded up, we can look around the memory to find some empty pockets. We want to find green rows of 0’s, preferably one padded by a bunch of other rows of empty memory. There should be some somewhere, no program ever uses all the memory allocated to it.
Once we find a row of data we want to try out, we write it down for later reference. We’re going to write some code now that will set our selected 4 bytes of memory to an arbitrary value. We will run the hook, and then disable the hook. The goal will be for the data to stick (not disappear) and for no crashes to happen.
Morph Scale ID Test Hook
define(address,"ACValhalla.exe"+EC71DE) define(bytes,0F 58 49 30 0F C6 D2 00) [ENABLE] assert(address,bytes) alloc(newmem,$1000,"ACValhalla.exe"+EC71DE) label(code) label(return) newmem: mov [rcx+194],0x5 code: addps xmm1,[rcx+30] shufps xmm2,xmm2,00 jmp return address: jmp newmem nop 3 return:
This is just some brutally simple code which will try to set
[rcx+194] to our arbitrary value. Let’s hook it into the code and…see if the game crashes.
After running the hook and then disabling it (while holding my breath), it appears that most of the row got its value changed to our arbitrary value. Scrolling up on the column for which the data did not stick, I quickly saw that it had actually become recycled junk memory. So I removed it.
That was fast! And lucky! Hmm…the fastest and luckiest I’ve ever been actually. Which leaves me with a sense of dread; a type of foreboding feeling permeating through my very soul. There are some additional things we probably want to do in order to ensure that this is a stable place to store our morph scale ID:
- Run by one of the enemies whose coordinates belong to our sample data. See if the memory we’re using to store the morph scale ID doesn’t suddenly get overwritten by data that appears due to combat/aggression code activating.
- Walking some distance in order to recycle the creature data, see if that crashes the game.
- Reload the game or even quit to the main menu, see if that crashes the game.
These are all things you probably want to do in order to ensure that we found the right place. I’m in a good mood though, and I’m feeling lucky, so I’m just going to get on with it and deal with the fallout later. I’m sure I won’t regret this.
Well, we got everything we need. Everything! Not even lying. Let’s get to the code writing.
Writing the Initiation Point Code
We know where we’re going to store the morph scale ID, and we know where we’re going to hook our initiation point in. Let’s get it on then, and start with a barebones hook template that we’ll be filling in.
Abomnification Initiation Hook – Template
// Initiates the Abomnification system. // UNIQUE AOB: 0F 58 49 30 0F C6 D2 00 0F define(omnifyAbomnificationHook, "ACValhalla.exe" + EC71DE) assert(omnifyAbomnificationHook, 0F 58 49 30 0F C6 D2 00) alloc(initiateAbomnification,$1000, omnifyAbomnificationHook) registersymbol(omnifyAbomnificationHook) initiateAbomnification: initiateAbomnificationOriginalCode: addps xmm1,[rcx+30] shufps xmm2,xmm2,00 jmp initiateAbomnificationReturn omnifyAbomnificationHook: jmp initiateAbomnification nop 3 initiateAbomnificationReturn:
The Abomnification system must be executed so that the next scale multipliers for the creature are locked and loaded prior to execution reaching the
initiateAbomnificationOriginalCode label. This is actually very simple. Trust me.
The main Abomnification function is named
executeAbomnification. As is tradition, I must apologize here for not having a dedicated Omnified Design article on the Abomnification system yet, which would explain all of this. One day I shall have it though, and all wrongs will be righted.
But we barely need any documentation, because this function is very simple. Here are the parameters and return values:
- Address to memory holding the creature’s morph scale ID.
eax: Set to the updated width scale.
ebx: Set to the updated height scale.
ecx: Set to the updated depth scale.
So, that’s not bad right? Usually I have at least four parameters required to call these fuckers. We don’t even need to use the return values, even though it would be nice if we could, as we don’t actually apply the scale here. That’s what the Abomnification scale application hook does (needed because this game has no built-in easy scaling).
The only thing we need to bear in mind is to exclude the player’s own coordinates from being processed, as we don’t want the player to be morphing out of the box.
Anyway, this is so simple I can just whip it out all at once, so here it is baby:
Abomnification Initiation Hook With Morph Scale ID
// Initiates the Abomnification system. // UNIQUE AOB: 0F 58 49 30 0F C6 D2 00 0F define(omnifyAbomnificationHook, "ACValhalla.exe" + EC71DE) assert(omnifyAbomnificationHook, 0F 58 49 30 0F C6 D2 00) alloc(initiateAbomnification,$1000, omnifyAbomnificationHook) registersymbol(omnifyAbomnificationHook) initiateAbomnification: pushf // Ensure that the player location struct has been initialized. push rax mov rax,playerLocation cmp [rax],0 pop rax je initiateAbomnificationOriginalCode // Ensure that the player isn't going to be Abomnified. push rax push rbx mov rax,playerLocation mov rbx,[rax] // The addresses in this function are aligned so that the coordinates // begin at 0x30, whereas with our pointer, the coordinates start at // 0x50. add rbx,20 cmp rbx,rcx pop rbx pop rax je initiateAbomnificationOriginalCode // Back up the registers used to hold return values. push rax push rbx push rcx // Load the address for where the morph scale ID is stored. lea rax,[rcx+194] // Push our sole parameter and call the Abomnification system. push rax call executeAbomnification // Restore the preserved values on the stack. pop rcx pop rbx pop rax initiateAbomnificationOriginalCode: popf addps xmm1,[rcx+30] shufps xmm2,xmm2,00 jmp initiateAbomnificationReturn omnifyAbomnificationHook: jmp initiateAbomnification nop 3 initiateAbomnificationReturn:
In addition to this code, we’re going to make some initial tunings of the Abomnification system’s external parameters. We’ve gotten an idea of what we want our maximum dimensional values to be during our testing of the custom scaling function, but we won’t know what our desired morph step maximum is until we have everything setup.
Here are my initial guesses on what some good tunings might be:
abomnifyWidthResultUpper: dd #250 abomnifyHeightResultUpper: dd #160 abomnifyDepthResultUpper: dd #200
And that’s it! Because there is no built-in easy scaling, we simply disregard the return values here. We’ll be retrieving them later on in the Abomnification scale application hook to get them rendered.
We can tell that everything is working by just taking a peek at the
morphScaleData region of memory. If everything is all good, it’ll look very busy.
Since this code has been tested pretty heavily through its use with other games, normally it is expected that it will just work right off the bat. Not so in this case.
4 Byte Memory Woes
Although it would appear that everything worked hunky-dory right from the get go, you would know that not to be the case if you were watching me write this article live on my stream. In fact, the first time we tried running the code, the game crashed.
Restarting the game, I added some thread isolation code so that I could monitor a single thread of execution at a time; needed since this area of the code was very multithreaded. I then monitored a single execution of the hook, and verified that the stack was uncorrupted with the correct address being pointed to by
rsp at the hook’s completion.
But it would still crash as soon as I hit Run after one successful execution of the new code. It had to be something to do with where or how we were storing the morph scale ID in the game’s memory.
If you look at the image, you’ll notice that right underneath the morph scale ID row (which is highlighted) there is some “string” data being harbored. What was happening, before I fixed it, is that the string data (which is really probably not meant to be string data, but whatever) would disappear after our Abomnification system code committed a morph scale ID.
Ahh. After seeing that, it became very clear: we were corrupting data that was used by the game for important things. This is a great illustration of the elusive nature of the many kinds of problems one gets when messing with the power of the GODS.
As was mentioned before, and as it is shown in the structure dissect window, we only meant to dedicate 4 bytes of room for storing our morph scale ID. However, when we were saving it, we must’ve been writing 8 bytes at a time, screwing with the “string” value neighbor. Here is the Abomnification code (from the Omnified framework) responsible for that:
Bad Abomnification Code
incrementMorphDataIndex: inc [morphScaleIndex] mov rcx,[morphScaleIndex] mov [rbx],rcx
In the final instruction, by moving the entire
rcx register into the memory pointed to by the
rbx register, we were overwriting 8 bytes of data. This was a no-no. To overwrite 4 bytes of data instead, we want to make use of the 32-bit form of the register.
Good Abomnification Code
incrementMorphDataIndex: inc [morphScaleIndex] mov rcx,[morphScaleIndex] mov [rbx],ecx
As you can see, we’re using the
ecx register in the operand now as opposed to the
rcx register. With this change committed, we no longer were guilty of overwriting important data used by the game! Yay!
But problems still persisted. When looking at the
morphScaleData region of code, I noticed that nothing was being written to it. Oops. Clearly some other part of the Abomnification code was continuing to have issues with the data neighboring our morph scale ID. In fact, it was the code right below where we just were.
More Bad Abomnification Code
applyMorphScaleFromData: mov rdx,morphScaleData mov rcx,[rbx] cmp rcx,#999 ja executeAbomnificationCleanup push rax push rdx mov rax,rcx mov rcx,#48 mul rcx mov rcx,rax pop rdx pop rax add rdx,rcx cmp [rdx],0
There are two things wrong with the code above:
- The entire 8 bytes of the
rcxregister is being compared in the
cmp rcx,#999instruction. This will include the junk data from the morph scale ID’s neighbor, which will cause the comparison to always return false (even if the morph scale ID is way below 999).
- The morph scale ID is being used as the multiplier in the
mulinstruction found halfway through the code. This resulted in the entire 8 bytes of data being multiplied and included in the product used as the ultimate address to the character’s morphing scale data. We have no control over the data widths used when something is the multiplicand, we instead need to use it as the multiplier so we can specify to only include the lower 4 bytes of data.
Taking all of that into account, we made the appropriate corrections to the code.
More Good Abomnification Code
incrementMorphDataIndex: inc [morphScaleIndex] mov rcx,[morphScaleIndex] mov [rbx],ecx applyMorphScaleFromData: mov rdx,morphScaleData mov rcx,[rbx] cmp ecx,#999 ja executeAbomnificationCleanup push rax push rdx mov rax,#48 mul ecx mov rcx,rax pop rdx pop rax add rdx,rcx cmp [rdx],0
And after that, everything worked beautifully! Hopefully this serves as a lesson to someone out there who runs into a similar problem.
A Better Morph Scale ID Location
As I feared earlier on in the article, our designated location of
0x194, while appearing stable, ultimately was not. I noticed that the game began to crash upon dying, or when getting too close to enemies. I immediately recognized that as something symptomatic of data being in the wrong place at the wrong time; the bad data, in this case, being the morph scale ID.
I loaded up a dissect structure window with a bunch of NPC location data again and proceeded to jot down all the empty memory. After trying a number of different ones, all leading to crashes, we finally settled on
0x178, which was the first one for which values stuck and no crashes occurred.
Hopefully it will hold true pending further testing. But, as it turns out…it did not.
Doing Away With the Morph Scale ID
Nearly two days of stream time passed where I would make some kind of progress and then things would inevitably regress back to me having to fix something to do with the morph scale ID.
Out of all the things required for the sometimes complicated implementation of the Abomnification system, the most problematic has always been the storage of the morph scale ID, used for looking up a creature’s morphing data.
The idea of a morph scale ID itself was an improvement upon the original solution, which required storing 13 or so separate data points in creature data. That was a giant pain in the ass at the time, and I can’t believe I ever got that to work. But I did. You can call me a lot of things, but you can’t call me lazy!
The placement of the morph scale ID into Assassin’s Creed: Valhalla was becoming too big of a problem, however. Having only a meager amount of data relations mapped out due to no RTTI (though, to be fair, we have the most data ever mapped without it), we consequently have limited mobility in terms of data placement. It essentially has to be somewhere nearby the location data.
After ending the stream one night in a huff and puff, I finally did away with the morph scale ID requirement. It is no longer required in order to lookup a creature’s morphing data. Now, all we need is some sort of identifying address to do the lookup; in the case of Assassin’s Creed: Valhalla, we will be using the creature’s location structure address as the identifying address.
Details on how I got this magic to work will be covered in the Abomnification design article. So, if you are curious about how I managed to do it, I hope you’re looking forward to reading that!
Because we changed how we’re looking up morphing scale data, we need to redo our implementation of the initiation point hook. So, I now present to you the initiation point code, in its final form.
Abomnification Initiation Hook Without Morph Scale ID
// Initiates the Abomnification system. // UNIQUE AOB: 0F 58 49 30 0F C6 D2 00 0F define(omnifyAbomnificationHook, "ACValhalla.exe" + EC71DE) assert(omnifyAbomnificationHook, 0F 58 49 30 0F C6 D2 00) alloc(initiateAbomnification,$1000, omnifyAbomnificationHook) registersymbol(omnifyAbomnificationHook) initiateAbomnification: pushf // Ensure that the player location struct has been initialized. push rax mov rax,playerLocation cmp [rax],0 pop rax je initiateAbomnificationOriginalCode // Back up the registers used to hold return values. push rax push rbx push rcx // The addresses in this function are aligned so that the coordinates // begin at 0x30, whereas their alignment in most of the other places we // work with them is such that the coordinates start at 0x50. add rcx,-20 // Ensure that the player isn't going to be Abomnified. mov rax,playerLocation mov rbx,[rax] cmp rbx,rcx je initiateAbomnificationExit // Push the identifying address parameter and call the Abomnification system. push rcx call executeAbomnification initiateAbomnificationExit: // Restore the preserved values on the stack. pop rcx pop rbx pop rax initiateAbomnificationOriginalCode: popf addps xmm1,[rcx+30] shufps xmm2,xmm2,00 jmp initiateAbomnificationReturn omnifyAbomnificationHook: jmp initiateAbomnification nop 3 initiateAbomnificationReturn:
Implementing the Abomnification Scale Application Code
As has been stated time and time again in this article, we were unable to find any sort of built-in easy scaling support in Assassin’s Creed: Valhalla. Therefore, we must provide our own scaling mechanism. We have already spent the time, sweated the sweat, and bled the blood required in order to find the primary character model rendering code.
This rendering code will serve as an excellent home to our custom scaling function, and it is located at the lofty address of
ACValhalla.exe+E8F453. Lets get going then, and whip up a template for injecting into this code, and work from there.
Abomnification Scale Application Hook – Template
// Applies Abomnification generated scale multipliers. // Unique AOB: 44 0F 59 05 15 B0 2C 03 define(omnifyApplyAbomnificationHook, "ACValhalla.exe" + E8F453) assert(omnifyApplyAbomnificationHook, 44 0F 59 05 15 B0 2C 03) alloc(applyAbomnification,$1000, omnifyApplyAbomnificationHook) registersymbol(omnifyApplyAbomnificationHook) applyAbomnification: applyAbomnificationOriginalCode: mulps xmm8,[ACValhalla.exe+415A470] jmp applyAbomnificationReturn omnifyApplyAbomnificationHook: jmp applyAbomnification nop 3 applyAbomnificationReturn:
Our goal here is to apply the Abomnification system’s generated scale multipliers for the character being rendered prior to the
applyAbomnificationOriginalCode label being hit. The multipliers need to be applied to the contents of the
xmm8 register, whose values have the following characteristics:
- The first floating point affects the width.
- The second floating point affects the depth.
- The third floating point affects the height.
- The fourth floating point will be zeroed out later on. So it doesn’t matter.
In order to retrieve the multipliers we need to apply here, we’ll need to retrieve the identifying address of the character in question. As we noted in the previous section, we are using the location structure address for a character to identify said character. And, as we also figured out earlier, we’ll be able to get the location address by traversing the following path on the stack:
If this data is not always present, we will need to do some pointer checks. Currently the only bad data we’re aware of is the address returned by
[[rsp+40]+18], which can sometimes be a bad pointer. So we’ll add a pointer check for that.
Also, sometimes the data returned by
[rsp+40] is a structure containing a pointer to a non-location structure at the same offset of
0x18. A simple check for a 0 at offset
0x14 filters this problematic data out.
Finally, one other thing we need to account for here is the shifting of the stack from the bit of data preservation we’ll be doing. The stack will be shifted by the following actions:
- Conditional flags will be backed up, as is the norm for us. That’s 2 bytes.
- We’ll be backing up the
rcxregisters, as these are used to hold the return values by
getAbomnifiedScales. That’s three registers at 8 bytes each for a total of 24 bytes.
- We’ll need to back up a SSE register to hold the multipliers we’ll be multiplying against
xmm8. That’s 16 bytes.
Adding that all up, we get a total of 42 bytes. This means
[rsp+40] will be shifting to
Once we have the location address, we’ll be executing the Abomnification system exported function
getAbomnifiedScales (a new function I wrote while doing this) with the identifying address as the sole parameter to retrieve the scale multipliers for the character.
Without further adieu, here is the complete Abomnification scale application code.
Abomnification Scale Application Hook
// Applies Abomnification generated scale multipliers. // Unique AOB: 44 0F 59 05 15 B0 2C 03 define(omnifyApplyAbomnificationHook, "ACValhalla.exe" + E8F453) assert(omnifyApplyAbomnificationHook, 44 0F 59 05 15 B0 2C 03) alloc(applyAbomnification,$1000, omnifyApplyAbomnificationHook) registersymbol(omnifyApplyAbomnificationHook) applyAbomnification: pushf // Ensure that the player location struct has been initialized. push rax mov rax,playerLocation cmp [rax],0 pop rax je applyAbomnificationOriginalCode // Backing up an SSE register to hold our multiplier. sub rsp,10 movdqu [rsp],xmm0 // Backing up the registers used to hold return values by the // Abomnification system. push rax push rbx push rcx // A rendering supplementary data structure is stored on the stack. mov rax,[rsp+6A] // If the bytes at 0x14 are not zeroed, then the rendering data structure // is not pointing to a location structure. mov rbx,[rax+14] cmp ebx,0 jne applyAbomnificationExit // The location structure is found here, however we need to check that // it's a valid pointer, as there is still a chance for some junk data here. mov rbx,[rax+18] push rcx lea rcx,[rbx] call checkBadPointer cmp ecx,0 pop rcx jne applyAbomnificationExit // Ensure that the player isn't going to be Abomnified. mov rax,playerLocation cmp rbx,[rax] je applyAbomnificationExit // Push the identifying address parameter and get the Abomnified scales. push rbx call getAbomnifiedScales // Make some room on the stack so we can construct the multiplier SSE register. sub rsp,10 movss xmm0,[identityValue] shufps xmm0,xmm0,0 movups [rsp],xmm0 mov [rsp],eax mov [rsp+4],ecx mov [rsp+8],ebx movups xmm0,[rsp] add rsp,10 // Apply the Abomnified scale multipliers to the register that will be applied // against the global scale parameters. mulps xmm8,xmm0 applyAbomnificationExit: // Restore the preserved values on the stack. pop rcx pop rbx pop rax movdqu xmm0,[rsp] add rsp,10 applyAbomnificationOriginalCode: popf mulps xmm8,[ACValhalla.exe+415A470] jmp applyAbomnificationReturn omnifyApplyAbomnificationHook: jmp applyAbomnification nop 3 applyAbomnificationReturn:
And that’s the code.
Is AC: Valhalla Abomnified?
Yes! Enemies morph randomly and present to the viewer an experience that is equal parts comical and horrific. I’m looking forward to playing this game! This article took a lot of work to produce, but we learned a lot from it that will make our endeavors in the future easier.
I present to you, the patient reader, the complete implementation of the Abomnification system for Assassin’s Creed: Valhalla.
Abomnification Implementation – Complete
// Initiates the Abomnification system. // UNIQUE AOB: 0F 58 49 30 0F C6 D2 00 0F define(omnifyAbomnificationHook, "ACValhalla.exe" + EC71DE) assert(omnifyAbomnificationHook, 0F 58 49 30 0F C6 D2 00) alloc(initiateAbomnification,$1000, omnifyAbomnificationHook) registersymbol(omnifyAbomnificationHook) initiateAbomnification: pushf // Ensure that the player location struct has been initialized. push rax mov rax,playerLocation cmp [rax],0 pop rax je initiateAbomnificationOriginalCode // Back up the registers used to hold return values. push rax push rbx push rcx // The addresses in this function are aligned so that the coordinates // begin at 0x30, whereas their alignment in most of the other places we // work with them is such that the coordinates start at 0x50. add rcx,-20 // Ensure that the player isn't going to be Abomnified. mov rax,playerLocation mov rbx,[rax] cmp rbx,rcx je initiateAbomnificationExit // Push the identifying address parameter and call the Abomnification system. push rcx call executeAbomnification initiateAbomnificationExit: // Restore the preserved values on the stack. pop rcx pop rbx pop rax initiateAbomnificationOriginalCode: popf addps xmm1,[rcx+30] shufps xmm2,xmm2,00 jmp initiateAbomnificationReturn omnifyAbomnificationHook: jmp initiateAbomnification nop 3 initiateAbomnificationReturn: abominifyWidthResultUpper: dd #250 abominifyHeightResultUpper: dd #160 abominifyDepthResultUpper: dd #200 // Applies Abomnification generated scale multipliers. // Unique AOB: 44 0F 59 05 15 B0 2C 03 define(omnifyApplyAbomnificationHook, "ACValhalla.exe" + E8F453) assert(omnifyApplyAbomnificationHook, 44 0F 59 05 15 B0 2C 03) alloc(applyAbomnification,$1000, omnifyApplyAbomnificationHook) registersymbol(omnifyApplyAbomnificationHook) applyAbomnification: pushf // Ensure that the player location struct has been initialized. push rax mov rax,playerLocation cmp [rax],0 pop rax je applyAbomnificationOriginalCode // Backing up an SSE register to hold our multiplier. sub rsp,10 movdqu [rsp],xmm0 // Backing up the registers used to hold return values by the // Abomnification system. push rax push rbx push rcx // A rendering supplementary data structure is stored on the stack. mov rax,[rsp+6A] // If the bytes at 0x14 are not zeroed, then the rendering data structure // is not pointing to a location structure. mov rbx,[rax+14] cmp ebx,0 jne applyAbomnificationExit // The location structure is found here, however we need to check that // it's a valid pointer, as there is still a chance for some junk data here. mov rbx,[rax+18] push rcx lea rcx,[rbx] call checkBadPointer cmp ecx,0 pop rcx jne applyAbomnificationExit // Ensure that the player isn't going to be Abomnified. mov rax,playerLocation cmp rbx,[rax] je applyAbomnificationExit // Push the identifying address parameter and get the Abomnified scales. push rbx call getAbomnifiedScales // Make some room on the stack so we can construct the multiplier SSE register. sub rsp,10 movss xmm0,[identityValue] shufps xmm0,xmm0,0 movups [rsp],xmm0 mov [rsp],eax mov [rsp+4],ecx mov [rsp+8],ebx movups xmm0,[rsp] add rsp,10 // Apply the Abomnified scale multipliers to the register that will be applied // against the global scale parameters. mulps xmm8,xmm0 applyAbomnificationExit: // Restore the preserved values on the stack. pop rcx pop rbx pop rax movdqu xmm0,[rsp] add rsp,10 applyAbomnificationOriginalCode: popf mulps xmm8,[ACValhalla.exe+415A470] jmp applyAbomnificationReturn omnifyApplyAbomnificationHook: jmp applyAbomnification nop 3 applyAbomnificationReturn:
Some issues persist, such as a break in the morphing when the enemies switch to some particular animations, as well as the size of the player’s weapons being slightly reduced for some reason, except during some particular animation frames. However, these are very minor, and will be corrected in a follow up article if I deem it necessary.
But whew, what an article! I hope this small contribution to science and reverse engineering benefits somebody, somewhere. But whether or not that happens, be sure to catch the live gameplay of Omnified Assassin’s Creed: Valhalla on my stream at: https://twitch.tv/omni
If you want to chat at all, say hello to me and my community on my Discord at: https://discord.gg/omni
Keep it real folks, until next time.