Let’s Hack the Shit Out of Assassin’s Creed: Valhalla
Around a year ago today, the first game was Omnified and subsequently played with much fanfare on my stream. That game was Assassin’s Creed: Odyssey. Today we come full circle, by starting the Omnification of the next installment in the series: Assassin’s Creed: Valhalla.
We’ve come a long way since then. Omnified Assassin’s Creed: Odyssey had a single hack that caused any damage to kill you. For this next game in the series, we’ll be applying a much more comprehensive collection of baseline Omnified hacks, with maybe a little bit extra thrown in there for good measure.
Since the hacks I assembled for Odyssey were quite primitive in comparison to what I do these days, I have no idea as to how difficult Omnifying Valhalla will be. In order to correct this ignorance, we’re going to take a cursory look at all the important data structures and see how we can grab the requisite data we’ll need for our Omnified systems.
Dude, Where’s My Player’s Health?
One of the primary pieces of data required by a number of Omnified systems, notably the Apocalypse system is the player’s health. Finding the data structure holding this also gives us a nice head start in finding the all-important root structure for the player.
So let’s get this show on the road here by trying to find the source of truth for the player’s health. The source of truth is the single place in memory holding the health value sourced by all other health-related values. Let’s take a look at what kind of information we’re presented with in the game first:
Unfortunately, Valhalla is one of those games that assumes that numbers scare people. So no numeric indication of our actual health is provided to us, for our own safety of course.
That means we’re going to need to do a game-wide search of all floating point types (because this is a Western developer, duh) and isolate the health from everything else by tracking modifications made to its value. So, let’s start by doing a search using the scan type Unknown initial value for the Float value type.
Well, we got our work cut out for us. Valhalla is a modern, beefy boi game and is doing quite a bit in memory, as we can tell from the 2,326,575,104 results returned from the above search.
So let’s narrow down the results a bit by taking some damage and then doing a subsequent search with scan type Decreased value, and/or healing a bit and doing a scan type of Increased value.
After performing this rather routine technique in order to find the health, we ended up with no usable results! Where did we go wrong!? Was it performance anxiety? A slip of the finger? A fart of the brain?
Could it be that Ubisoft, in defiance of all Western developer norms and conventions is storing their health as integers!? Shrugging off the urge I felt to simply laugh at such a notion, I performed another Unknown initial value search, this time using 4 Bytes as the value type.
We have found the one and only source of truth for the player’s own health. It was verified to be just that by changing its value and observing the health bar updating in the game. Now that we have this, let’s start to take a look at the way data is organized in this game by diving deep into this health value’s containing data structure.
To get the data structure we need to see what offset is being used to access this health value. We’ll do this by right clicking on it and choosing Find out what accesses this address. This will show us all the instructions reading that health.
The first instruction shown above is a bit of an odd bird. It’s using a negative offset to access health, therefore I’m going to pretend it does not exist! It disgusts me.
Upon closer inspection of the second instruction (ACValhalla.exe+2169BB3
), it appears that it is an exclusive health polling function! Fantastic. This was verified by disassembling the instruction and choosing to Find out what addresses this instruction accesses.
The third and final instruction is instead a health polling function for all entities on the map, which will also prove very useful for us in the future when we need to comb over creature data.
Looking at either of these instructions, we can see that the data structure containing the health (rbx
register in both cases) can be found at 0x1DC698C7F10
. So let’s grab that and throw it in a dissect structure window and see what we’re getting ourselves into.
Unfortunately, yet again we are dealing with a game lacking that lovely RTTI. That’s really been the norm for most games I’ve Omnified, although it wasn’t until recently that Cheat Engine had support for RTTI.
Having no RTTI on hand means it’s going to require more data disassembly to be done before we can think about trying to establish relationships and pinpointing a particular structure type as being a root structure for player information. We’ll poke around more on the data organization side of things after we uncover some additional data such as the coordinates.
For right now, let’s write some code to grab that player health so it’s always available for us in code and on our table.
Player Health Structure Pointer Creation via Injection
The location for the exclusive player health polling function has already changed since starting this article; however, by utilizing its recorded unique array-of-bytes:
8B BB 38 01 00 00 75
…we were able to easily find the health polling function once again. Since we know where to inject our health pointer creation logic, let’s go ahead and do it.
Player Health Structure Hook
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | // Gets the player's health struct. // UNIQUE AOB: 8B BB 38 01 00 00 75 define (omniPlayerHealthHook, "ACValhalla.exe" + 2173053) assert (omniPlayerHealthHook, 8B BB 38 01 00 00) alloc (getPlayerHealth,$1000, omniPlayerHealthHook) alloc (playerHealth,8) registersymbol (omniPlayerHealthHook) registersymbol (playerHealth) getPlayerHealth: push rax mov rax ,playerHealth mov [ rax ], rbx pop rax getPlayerHealthOriginalCode: mov edi ,[ rbx +00000138] jmp getPlayerHealthReturn omniPlayerHealthHook: jmp getPlayerHealth nop getPlayerHealthReturn: |
Very simple, we just grab the address stored in rbx
as our health structure address.
Nothing much more to be said about this; again, we’ll investigate how we might find a unifying root structure and map that instead of just a health structure once we map out a few more data points to compare against.
Knowing the health is a big requirement for Omnified systems; another big one is the location coordinates of the player. Let’s look for that now.
Seeking the Player’s Coordinates
Since we lack a known root structure where we can just browse to the player’s coordinates via a Structure dissect window, we’ll need to find the coordinates the old fashioned way.
We locate a nice place in the sun with sufficient curvature to the earth.
Once here, we commence with an Unknown initial value search for Float value types. Now I know we just learned a lesson here with the health that one should really always do unconstrained type searches, given that there can be surprises in the actual type used, but I’m betting everything that the coordinates will be floats ok!
With 2,248,047,616 floats to sort through we start our search for the player’s coordinates by attempting to isolate them via changes made to the character’s position in-game. In other words, we move our character up the hill and then search for Increased values, and then move them down the hill searching for Decreased values.
To further remove some of the random crap I would open the pause menu, do an Ignore values search, and then follow up with Unchanged values. This gets rid of a lot of the random noise that has nothing to do with our location, while preserving location potentials which themselves are most likely constantly updating even if we’re not moving.
Once we narrow down the potentials to a manageable size, we add them all to the address list and try to find the source of truth through staggered value manipulation. Working with one half of the list at a time, we see if there’s any change to the player’s location when setting a new value.
If there’s no change, then all of those addresses are bunk and we throw them out. If our player moves, then that means we are on the right track.
Unfortunately, I observed multiple times while doing this routine process that the character would inevitably become launched up into the sky where they would eventually start falling from and then die horribly.
Because this would kill us, the location coordinates for the player would get reallocated to somewhere else in memory. This means we had to start from scratch again.
After the fourth time of this happening, I started to get a bit irritated and decided we weren’t going to be able to find the player’s coordinates by manipulating the vertical axis.
Instead of trying to find the coordinates via vertical scale manipulation, we were going to find it through the other (hopefully) safer to manipulate axes. It is difficult to isolate changes properly when using the non-vertical axes however, so we actually don’t want to start looking at the other axes until we’re at the step where we’re manipulating values manually.
Once we are at that step, we can find the values for the other axes simply by browsing to where the vertical axis is in memory and looking at its neighbors.
The suspected vertical axis actually appears to be the Z coordinate in this game, which, like this game’s stance on health data type, is also rather atypical among the games I’ve hacked. Regardless, to find the player’s location we were going to make use of either the X or Y coordinates instead. Hopefully this would avoid any launches up into the sky.
Looking at the above image, we see that the X coordinate is -2163.37, so we just do a search for all floats with this value and then perform our staggered value manipulation technique on the results from that.
Eventually we managed to (safely) stumble on the source of truth for the player’s location! It was now time to find code accessing these coordinates; preferably code accessing only the player’s coordinates and not everything else on the map.
Right clicking on the coordinate and selecting Find out what accesses this address yields the following list of opcodes:
This is a staggering amount of code accessing our coordinates. This is to be expected of a modern day game I suppose, but hopefully the sheer number won’t complicate things.
I’m probably just worrying for nothing.
A suitable candidate must be found from this list for us to inject into in order for us to be able to consistently locate our player’s coordinates. We want both a method that has a high execution rate as well as one that is only accessing our player’s coordinates. This allows us to forego the black magic involved with hacky filtering.
Luckily for us, the very first method pictured above meets our criteria. So let’s get on with the injection then, baby.
Player Location Structure Pointer Creation via Injection
The instruction we’ll be hooking into is located, at the time of writing, at ACValhalla.exe+12A8BAE
.
Player Location Structure Hook
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | // Gets the player's location struct. // Unique AOB: 0F 10 48 30 66 0F 38 14 CF 0F 5C F1 F3 45 define (omniPlayerLocationHook, "ACValhalla.exe" +12A8BB7) assert (omniPlayerLocationHook, 0F 5C F1 F3 45 0F 59 C0) alloc (getPlayerLocation,$1000, omniPlayerLocationHook) alloc (playerLocation,8) registersymbol (omniPlayerLocationHook) registersymbol (playerLocation) getPlayerLocation: wadw push rbx mov rbx ,playerLocation mov [ rbx ], rax pop rbx getPlayerLocationOriginalCode: subps xmm6 , xmm1 mulss xmm8 , xmm8 jmp getPlayerLocationReturn omniPlayerLocationHook: jmp getPlayerLocation nop 3 getPlayerLocationReturn: |
I actually had to move the injection a few instructions down because of an issue Cheat Engine seems to have with compiling select AVX instructions. Something that bears investigation at a later date…
That aside, we have code that reliably creates a pointer set to the location our player’s coordinates can be found. As far as the data we need to gather in order to get started on implementing the Omnified systems, we have, for the most part, what we need.
Before we jump on that train though, let’s take a look at the location structure itself and see if we can’t use that alongside the player’s health structure to locate some sort of unifying root structure housing all the player data.
Finding the Player’s Root Structure
A lofty goal we tend to have at the start of a game’s Omnification is the locating of a player’s root structure. A root structure for a creature is basically a binding container of all the disparate collections of data that pertain to said creature.
For example, we have just located our player’s health and location structures. While there is no link between these two structures within themselves, they should ideally both link to another structure that itself will point to both of them.
When we are fortunate enough to be graced with the presence of RTTI, it isn’t very difficult to find the root structure, as the exported name for the type typically says it all (i.e. PlayerIns
, short for PlayerInstance, was used in games by FromSoftware).
We don’t have RTTI here, so we’re going to try to do something I’ve never been able to do before: we’re going to try to find the root structure with zero help. This will be a long and involved process, but if we can successfully locate the root, this is a big step forward!
I. Finding Where the Health is Sourced
Since there are no obvious links within the health and location structures that establish some sort of relation between the two, let’s look at this at a different angle. Let’s try to see how the game itself is accessing our player’s health structure.
The first member shown above is the base of the health structure. It is the address that was being stored in the rbx
register in the code we hooked into first for this article.
Let’s try to figure out where the value of that rbx
register came from.
To do that, we right click on the highlighted member and add it to our address list. Then, we right click on the address in our list and select Find out what accesses this address. This yields the following opcodes:
Let’s take a look at the first instruction. We see the following in the code disassembly window:
We can see from the above code that our health structure’s base address is a calculated value deriving from a negative offset being applied to the rcx
register.
We need to figure out where the address in the rcx
register comes from. It is not loaded with anything inside this function, so we’re going to have to go up the call stack one level and see if rcx
is populated there.
So, we double click on the first entry in the call stack (ACValhalla.exe+226A512
) and we’re greeted with the following:
A few lines above the call to the previous function, we see that the rcx
register is the result of a mov rcx,[rax+rcx*8]
instruction. In this instruction, the rcx
register is being used as a type of index that is being applied to what suspiciously appears to be some sort of root structure.
II. Examining the Potential Root Structure
Could it be then that the address rax
holds is our root structure? Let’s find out! We open a new structure dissect window and throw it in there, creating a new structure definition in the process. We’ll name it Character Root because we’re just that optimistic!
After defining this new structure, we want to then search for relations between our structures by going to Structure Options -> Find Relations. This will pop up a window with the three defined structures listed, with appropriate memory addresses set for each one. After hitting OK, any instance of these structures within one or another should now show up.
Bingo. I can’t believe it, but it looks like we actually have stumbled on a root structure containing all the known (thus far) data for the player! Unfortunately the member pointers don’t point exactly to the health and location structures, but instead to their known locations plus-or-minus a particular offset.
The “Location (Movement)” structure shown in the picture above is essentially the location structure, albeit one with a -20 offset applied to it. It’s known as the “movement” variant of the structure because it is how the coordinates are accessed by the location update code, responsible for updating creature location due to movement.
The health structure pointer shown in the root structure is a bit of an annoyance since we’ll have to do some pointer arithmetic in order to get to where we need to go — again, however, not a big deal.
Now that we have found a root structure, we’ll want to make sure we’ll be able to locate it from within both the health and location structures. If we are unable to essentially crawl back up these structures to their roots, we will not be able to make use of the root structure for the purpose that we ultimately need it for (which is to be able to essentially link character data while deep within the bowels of generic processing functions).
Sadly, when searching for relations while within either the health or location structure, we see no pointer contained within them that itself points to the character root structure. Shit.
We are either out of luck, since there is nothing that says that children objects are required to link to their parents, or we simply need to go a few levels up still on the “object chain”.
III. Finding Where the Root is Sourced From
Let’s determine if the child structures aren’t themselves perhaps pointing to a kind of ‘source’ for the character root structure. Here is the image again from before where we witnessed the sourcing of the health structure from the root:
We can see that right above the previous instruction of interest that the rax
register is being populated through the dereferencing of itself. This means that the rax
register is pointing to a container of sorts for the root structure during the mov rax,[rax]
instruction.
Taking this address, we defined yet another new structure in the structure dissect window, this time naming it Character Root Container. After doing this we did another relation search, resulting in yet again nothing showing up in both the health and location structures. We still lacked a two-way relational link.
IV. Finding Where the Root Container is Sourced From
Refusing to give up, I decided to see if going up one more ‘level’ would yield any results. At this time I was clearly desperate; ranting and raving, I was foaming at the mouth.
Returning once again to the code in question from before:
rax
comes from [rax]
, but where does the rax
(which is not the same rax
as the first rax
mentioned) in [rax]
come from? HMMMMM.
Well, there’s a blatant mov rax,[rcx]
shown at the top of the disassembler window. So maybe it’s coming from there. I highly doubted it though. Because I’m a pessimist.
Putting a breakpoint there, it turns out I was right. rax
is indeed loaded with something here, however after the proceeding call
statement, it is set to the actual container address.
That means the container address is being sourced within a separate function call. Not only that, but this function call is one that is dynamic; in other words, a function whose address is not known to us at compile-time — only run-time.
Multiple addresses (including one not related to the player) were being resolved here, so we needed to chart out the register states as they were prior to a call occurring that would yield our container address.
This took a few clicks of the Run button, but after doing so, we were able to find our elusive dynamic character root container sourcing function:
As we can see from the sole instruction, lea rax,[rcx+1E4]
, the root container itself is actually being sourced from somewhere just below in memory from where it resides. The area it’s being accessed from is essentially more of a predecessor to the container, than a container of the container.
So, let’s take this character root container predecessor address and define one more goddamn structure with it, with the name Character Root Container Predecessor. Once that is done, let’s do one more relational search, and see if we finally get any hits.
Do we?
Yes! We’ve finally established a two-way relationship between the various data types of our player! This is a big achievement for me personally, as this is the first time I’ve actually been able to do this without RTTI help.
So, hopefully, with every game after this, I’ll be able to do it again without too much trouble. Or at least without dropping dead from the ever mounting pressure of all these requirements I’m placing on myself.
Root Structure Pointer Creation via Injection
During our journey of structural discovery, the names we initially attribute to certain chunks of data are the result of complete guesswork; we’ll want to eventually rename the data structures that we’ll be working with here, however we’ll do that at a later time.
What we definitely want to do now, is be able to consistently procure that ever important Character Root Container Predecessor we stumbled upon in the previous section. Looking for additional code to inject into is unnecessary, we can simply make use of our existing injection into the player health access code.
Taking another look at our Character Root Container Predecessor, we can see that the intermediate Character Root and Character Root Container structures are unnecessary (for now at least), given that the predecessor itself has links to both health and location.
Therefore, this ‘predecessor’ type can simply be treated as the root. We will create a pointer for the root in the health hook and set it to the address pointed to by the member at the 0x70 offset in the health structure (refer to the second last image posted above to see why).
We also no longer have a need for the separate player location hook, as we can determine where location data is simply by using the root. It will be aligned at a negative 0x20 offset in comparison with how we had it before, however this will end up being a prudent change as the movement update function expects the coordinates to start at a 0x50 offset anyways.
Updated Player Structures Hook
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | // Creates pointers for the player's root, health, and location // structures. // UNIQUE AOB: 8B BB 38 01 00 00 75 define (omniPlayerHealthHook, "ACValhalla.exe" + 2173053) assert (omniPlayerHealthHook, 8B BB 38 01 00 00) alloc (getPlayerHealth,$1000, omniPlayerHealthHook) alloc (player,8) alloc (playerHealth,8) alloc (playerLocation,8) registersymbol (omniPlayerHealthHook) registersymbol (player) registersymbol (playerHealth) registersymbol (playerLocation) getPlayerHealth: push rax push rbx push rcx mov rax ,playerHealth mov [ rax ], rbx // Root structure can be found at [playerHealth+70]. mov rax ,player mov rcx ,[ rbx +70] mov [ rax ], rcx // Location structure aligned for movement can be // found at [player+1D0]. mov rax ,playerLocation mov rbx ,[ rcx +1D0] mov [ rax ], rbx pop rcx pop rbx pop rax getPlayerHealthOriginalCode: mov edi ,[ rbx +00000138] jmp getPlayerHealthReturn omniPlayerHealthHook: jmp getPlayerHealth nop getPlayerHealthReturn: |
Now we are able to get all pertinent player information in a single hook — no additional hooks needed! This also gives us access to all other kinds of player-related data discoverable via the player’s root structure.
That’s a Pretty Good Start
We made some pretty good headway in our initial reverse engineering of the game Assassin’s Creed: Valhalla. The most important bits of player data required by most of the Omnified systems, namely health and location coordinates, have been found.
But not only that, I managed to, for the very first time ever, successfully establish some structural relations between data types through the discovery of a character root structure. All without the (previously required) help given by RTTI.
So that’s a big step forward personally, and hopefully the documented process is helpful for you.
This article, in almost its entirety, was written live on my stream at Twitch. It was a rare treat for folks, and I enjoyed myself a lot as well. I don’t do live hacking much anymore, as it is much more productive to simply hack away offline and write about it.
That being said, I was able to do the same while in the company of many enjoyable people. So thank you to all who watched.
We’ll continue our Omnification of Assassin’s Creed: Valhalla with the next article, where we’ll most likely end up implementing the first of the Omnified systems: the Apocalypse system.
Take care.
~Omni