Update: Code has changed due to evolving needs and patches to the game. Check out the Omnified Cyberpunk 2077‘s hooks at the official source repository for the latest code.
As was always the plan for 2021, it’s time to get on with the Omnification of Cyberpunk 2077. I’ve been holding off from Omnifying this game due to both a feeling of trepidation, as well as the need to work on Vision. I’ve mostly taken care of the latter, so now it is time to face my fears and just get on with the former.
I know very little about the game, but one thing I do know is that regardless of the game, it’s always better when Omnified. For me at least. Probably not for you.
We’ll be starting off by getting a good look at the data structures for the game, as well as taking care of finding all of the data points we require for the implementation of our first Omnified system: the Apocalypse system.
The Not-So-Clear Matter of Finding the Health
Tradition dictates that the first thing we do when Omnifying a game is to locate the source-of-truth for the player’s health. So that’s just what we did.
In order to find something like the player’s health, it is helpful to know its exact value at any given point in time. Some games are nice and tell us the numeric value for the health somewhere in the game; this game, thankfully happened to be one of those.
Well, that should make our search a bit easier. After learning my lesson (again, and again) in the past, I decided to forego trying to guess the value type used for the health and just did a simple Exact value scan type for 105 of All value types. I expected an easy victory.
Except that it ended up being anything but. Following the first scan, I did some successive scans for changes in the health; in the end, I was left with only a handful of values that appeared to be the real deal.
When I tested out the values by changing them and then seeing if it had any effect on my health in the game, I was sad to see that none of them did. So, I found myself quickly rescanning for the health, determined to find it no matter the cost!
A great deal of time slowly passed…
After changing up my scanning technique a number of times, I failed to find any sort of source-of-truth value responsible for governing the player’s health. This was a bit worrying as, given my experience by now, I sort of expect to be able to find these things quickly (with style even) in front of a live audience.
Convinced that the health value stored in memory was not what was being displayed, I did an Unknown initial value scan for All value types and carefully searched for changes in values by decreasing and increasing my health in the game.
This required several attempts as matters were complicated by the fact that values being displayed in the game are not actually live values, but values resulting from a change event occurring. That really made verifying the values a bit of a nightmare, and probably ended up causing me to ignore the results of a few searches that were, more than likely, actually successful.
Finally, during the 20th scan or whatever, I ended up with the following results:
The highlighted value above was tested to be the source-of-truth for the health. Changing it did not immediately update the health being displayed, but rather could only be observed upon my health changing again from an in-game event.
It was also rather alarming how the source-of-truth value actually differed rather substantially from the health value displayed in the game. I knew the truth as soon as I saw it: player vital stats in this game aren’t stored as discrete values.
Everything Is a Goddamn Percentage
For the first time, in the year or so since I’ve started Omnifying games, it appears we’ve run into a game that actually stores values for attributes (such as health and stamina) as percentages. No wonder it was such a pain to find them in memory.
I can see the benefits of tracking vital statistics as percentages, as opposed to discrete values, however one would think that the constant need to have to apply them against their corresponding maximum values would end up adding a bit of unnecessary burden on everything.
Let’s also not forget that these “percentages” aren’t really being stored percentages, which I would store as value between zero and one, not zero and one hundred. It’s all very odd.
And speaking of unnecessary burdens, this would probably complicate some of the game-neutral Omnified frameworks I was planning on implementing, but we’ll deal with all that when we get there.
Looking at accesses being made to the health percentage, we got the following:
Unfortunately, the topmost method, while being polled continuously (making it a perfect candidate for hooking into), was also being used for a plethora of other value types. The highlighted method, while only executed while the game is not paused, is only accessing the structure for our health.
In the end, I decided I wanted to go with a method that was executing both constantly and while the game was paused.
Filtering Out Health
The topmost code listed in the image above was dealing with way too much data, and I couldn’t find a nice approach towards filtering it, so I switched things up a bit by instead looking at code accessing the base of the health structure, which led me to some more palatable code to hook into, as shown below:
This code executes even while the game is paused, so it’s a great place to scoop up some needed values. Even though it is accessing less data than the other constantly-executing function mentioned earlier, it still was hitting up a number of unwanted data points while the game was running:
There’s a number of ways we can go about figuring out a proper filtering mechanism here. The simplest (but most brittle) way, and the way which we ended up choosing, is to look for patterns among the states of the CPU registers.
The chances of there being a unique combination of CPU register states, corresponding to the desired data being accessed, for code responsible for accessing this much data are quite low. Regardless, I tried anyway and found the following combination to yield the health while the game is both paused and not paused:
RDI == 0x1 and R12 ~= 0x0
Awesome, an actual easy solution that works.
We Can Do Stamina Here Too!
Some issues would arise that would prompt me to need to reverse the source-of-truth for stamina as well. I wouldn’t know it until later, but stamina shares the same kind of structure as the one that houses health (I refer to these structure types as a vitals structures).
And, it looks like we can grab our stamina here as well. Only downside to grabbing the stamina here is that the game must be unpaused and we must be using the stamina. But unfortunately, there exists no other code referencing our stamina that runs outside of usage of said stamina.
The only superior solution to hooking into it here is if we re-implemented Cyberpunk‘s data structure resolution system (more on that later), which I’ve decided is not worth it.
All we needed is another filter for stamina, and after looking at the patterns a bit during execution we have the following:
RDI == 0x1 and R10 ~= 0x0 and R13 ~= 0x0 and R15 ~= 0x0
This needs to be checked before checking health, as stamina will also have a non-zero
r12 value. We’ll see if these filters work throughout the game (and/or subsequent patches from the time this was written).
Player Health and Stamina Structure Hook
// Gets the player's vitals structures. // UNIQUE AOB: C6 10 49 3B D8 ** ** 48 8B 0B 48 8D 54 24 30 8B 43 0C 48 C1 E0 04 // This isn't truly unique -- this is the most granular I could get it. Of the resulting 5 functions, // however, only the one of interest will hit a breakpoint while the game is paused. define(omniPlayerVitalsHook,"Cyberpunk2077.exe"+1707DBE) assert(omniPlayerVitalsHook,48 8B 0B 48 8D 54 24 30) alloc(getPlayerVitals,$1000,omniPlayerVitalsHook) alloc(playerHealth,8) alloc(playerStamina,8) registersymbol(playerHealth) registersymbol(playerStamina) registersymbol(omniPlayerVitalsHook) getPlayerVitals: pushf // The RDI is set to 1 when there is potentially data we care about. cmp rdi,1 jne getPlayerVitalsOriginalCode // The data being polled is our stamina if R15 isn't 0. cmp r15,0 je checkForHealth push rax mov rax,playerStamina mov [rax],rbx pop rax jmp getPlayerVitalsOriginalCode checkForHealth: // The data being polled is our health if R12 isn't 0. cmp r12,0 je getPlayerVitalsOriginalCode push rax mov rax,playerHealth mov [rax],rbx pop rax getPlayerVitalsOriginalCode: popf mov rcx,[rbx] lea rdx,[rsp+30] jmp getPlayerVitalsReturn omniPlayerVitalsHook: jmp getPlayerVitals nop 3 getPlayerVitalsReturn:
There we have it. Nice and stable. This covers most of the requirements for the first system we’ll be implementing; the rest of these requirements will be covered in the Apocalypse implementation article coming up next.
But It Ended Up Not Working…
Fast forward a few hours, and some problems began to emerge that resulted in me having to ditch the previous approach and code I just wrote for finding the player’s health and stamina! Such is the life of the Omnified hacker…
I was actually in the middle of implementing the Apocalypse system (which, again, I’ll cover in detail in the next article), and noticed that a “sixty-nine times damage” effect went off when I shot a lady (a bad lady!) in the head. After laughing my ass off for a bit, I was a bit dumbstruck as I couldn’t recall the last time I’ve ever seen a Player Apocalypse effect being triggered for an enemy, believe it or not.
Right away I knew that, because an enemy was being treated like a player by my Apocalypse system, that our health hook was apparently sometimes confusing an enemy’s health with the player’s health. So, the filter we had in place was still correct in that it was isolating health from other types of vitals; unfortunately, a non-player’s health is still health.
So, to correct this I went back to one of the original pieces of code we were looking at. This was the second most frequently executing method directly accessing my health percentage value. If you remember, this method required no filtering in order to isolate the player’s health.
Or so it appeared, at first. Some non-health vitals statistics were occasionally getting through — a filter of
r8 == 0x0 seemed to blocked out everything except the health.
We probably should’ve nipped our ambitions in the bud and went with what was really the most promising solution from the start.
Updated Player’s Health Structure Hook
// Gets the player's health structure. // UNIQUE AOB: F3 0F 10 80 90 01 00 00 0F 54 define(omniPlayerHealthHook,"Cyberpunk2077.exe"+1B5F32B) assert(omniPlayerHealthHook,F3 0F 10 80 90 01 00 00) alloc(getPlayerHealth,$1000,omniPlayerHealthHook) alloc(playerHealth,8) registersymbol(omniPlayerHealthHook) registersymbol(playerHealth) getPlayerHealth: pushf cmp r8,0 jne getPlayerHealthOriginalCode push rbx mov rbx,playerHealth mov [rbx],rax pop rbx getPlayerHealthOriginalCode: movss xmm0,[rax+00000190] jmp getPlayerHealthReturn omniPlayerHealthHook: jmp getPlayerHealth nop 3 getPlayerHealthReturn:
Unfortunately, the place in code we’re hooking into gets no exposure to the player’s stamina. We’ll need a separate hook for that, as I no longer wanted anything to do with that original hook we wrote (too high of a chance for things getting switched up with enemy vitals).
In order to find another place to hook into, I grabbed the address to the player’s stamina with our original hook and looked for code accessing it again. I took the one executing the most, and found I could filter out stamina with
r12 == 0x1 and
r8 == 0x18.
This bit of code also accesses the health, albeit only sometimes. The health will sometimes satisfy the filter we have set up; I noticed this typically happening during a save game load. Because it’s almost guaranteed that the health structure will always be discovered prior to this ever going out, we can simply check if the address we’re being presented points to the health.
For any fellow opportunists out there: this may seem like a nice place to grab the stamina and health all in one go. While possible, I decided that it wasn’t accessing the health enough to warrant using it to actually find my health, although I’ll do some further tests in the future.
Updated Player’s Stamina Structure Hook
// Gets the player's stamina structure. // UNIQUE AOB: 48 89 44 24 70 48 89 7C 24 78 E8 B6 define(omniPlayerStaminaHook,"Cyberpunk2077.exe"+1B622E7) assert(omniPlayerStaminaHook,F3 0F 10 BF 90 01 00 00) alloc(getPlayerStamina,$1000,omniPlayerStaminaHook) alloc(playerStamina,8) registersymbol(omniPlayerStaminaHook) registersymbol(playerStamina) getPlayerStamina: pushf cmp r12,1 jne getPlayerStaminaOriginalCode cmp r8,0x18 jne getPlayerStaminaOriginalCode sub rsp,10 movdqu [rsp],xmm0 push rax mov rax,playerHealth cmp [rax],rdi je getPlayerStaminaCleanup mov rax,playerStamina mov [rax],rdi getPlayerStaminaCleanup: pop rax getPlayerStaminaOriginalCode: popf movss xmm7,[rdi+00000190] jmp getPlayerStaminaReturn omniPlayerStaminaHook: jmp getPlayerStamina nop 3 getPlayerStaminaReturn:
The other piece of data I like to attempt to find at the start of Omnifying a game is the player’s root structure; that is, the data structure from which all other player-related data can be found. Can we do it with Cyberpunk 2077?
At the Root of It All….
This game apparently lacks any kind of RTTI being coupled with its binaries, which makes finding the root structure (as well as establishing any meaningful relationships between structures) much more difficult.
Since we already have some structures mapped out, like the health and stamina structures, we should see how the game resolves their location. Chances are, it’s doing so using some sort of binding structure as a base context.
We already were taking a look at code accessing the base of our health structures, as that led us to the place we ended up putting our hook for grabbing the health and stamina. If you’ll recall, the base structures were being accessed through the dereferencing of the
rbx register. What we need to do is figure out where the value that ends up in
rbx comes from.
Starting from the
mov rcx,[rbx] instruction we were hooking into before, I went up about four or five functions on the call stack, as high as we could go while still maintaining a one-to-one call ratio between the parent and child functions.
From here we did a trace, and we were able to find what appeared to be the root structure near the start, in code that was engaged in what appeared to be a rather complicated bit of data property resolution.
Cyberpunk’s Complicated Data Resolution System
From the trace, I observed some code that appeared to retrieve the address to our health data structure using an approach I first saw while Omnifying The Witcher 3. I was expecting to see something like this in Cyberpunk 2077, and it looks like I found it.
This type of code can be recognized when you see base addresses loaded off of a structure, which are then added to an offset that is calculated by taking an index for the desired data, multiplied against a common
sizeof value. These indices and
sizeof values may or may not come from some property definition type or even the same place the base addresses are being loaded from.
In The Witcher 3, the source for this base address was, in actuality, the root structure for the player or NPC. So, for Cyberpunk 2077, I decided that the structure being used as the source for these base addresses would be this game’s root structure.
What’s the method for resolving a type of particular data from the root structure? Something like the following:
- Given the root structure
root, we first need to load a separate linking structure found at
- The linking structure contains the indices we need to use in order to calculate the proper offset to apply to the root structure’s base data structure address. The location on the linking structure for a particular index is found by multiplying an identifier number by four. The exact procedure to determine a particular data type’s identifier number is still unknown.
- For the purposes of retrieving the health structure, we can find its index at the offset
0x14, as its identifying number is
0x5, but only at the start of a game session — the game is actually constantly reallocating places in memory for storing source-of-truth values for important statistics (never seen that before either).
- Taking this index we’ve retrieved from the linking structure, we grab the
sizeofvalue for data structures from
[root+0xC4]and multiply them together.
- Taking the product, we add it to the base data structure address retrieved from
- We’re not done yet, as the offset we calculated in the previous steps wasn’t for the health structure — it was for what I can only describe as a pre-health structure which itself is used in the same process in order to ultimately get to said health structure.
- So, we grab the actual index to use to calculate the health structure offset by dereferencing this pre-health structure at its base address.
- We take this new index and multiply that by the
[root+0xC4], and then add that to
- Congratulations, we have the health structure address. Actually, not quite yet, we still need to add a static
0x20to our calculated address.
- Congratulations, we now actually have the health structure address.
But only for a little bit — there are additional steps that need to take place not too long after a game session starts (perhaps due to combat) that likely are related with whatever is happening when the game is reallocating where everything is all the time.
Too Big of a Pain To Be Worth It
If anything, I need to do hinges on us completely replicating the property resolution mechanism, then I guess I’ll have to figure it out completely. Until then, we have to remember that any reverse engineering done here is a means to an end (that end being to make games Omnified so I can play them how I need to play them!), and not the goal itself.
For now, we’re much better off just using hooks into areas of code we know to be accessing the data we need. So, I’m not changing anything in regards to using the root structure in order to find the health and stamina structures! Though, I unfortunately did for a little bit before tearing my hair out.
Still, knowing the root structure might be useful for purposes of identification, so I went ahead and looked at what code was actually accessing it, and I was pleased to see a few always-executing bits of code doing so:
The third option above is one we could use, and there was no filtering required either. That meant all we had to do is write a very simple hook!
Player Root Structure Hook
// Gets the player's root structure. // UNIQUE AOB: 00 41 B8 0A 00 00 00 48 8B 01 define(omniPlayerHook,"Cyberpunk2077.exe"+1A5A190) assert(omniPlayerHook,41 B8 0A 00 00 00) alloc(getPlayer,$1000,omniPlayerHook) alloc(player,8) registersymbol(omniPlayerHook) registersymbol(player) getPlayer: push rax mov rax,player mov [rax],rcx pop rax getPlayerOriginalCode: mov r8d,0000000A jmp getPlayerReturn omniPlayerHook: jmp getPlayer nop getPlayerReturn:
That’s it! Some nice foundational reverse engineered code for us to Omnify Cyberpunk 2077 with. If I ever end up actually using the root structure in order to locate a vitals structure of interest, I may write about it in the future.
Almost Forgot! The Player’s Location!
I was about to publish this article, and then remember that one of the other pieces of data I like to find at the beginning is the source-of-truth for the player’s coordinates. And find them I did!
Finding the coordinates for this game was much like any other game, except for one important difference: we had some big clues shown to us in the game’s photo mode!
Photo mode in games is something I always harp on about during my livestreams, and when this game’s tutorials mentioned the existence of it, I gleefully opened it up in order to engage in some good-natured idiocy.
I was more than a little surprised to see that this game’s photo mode actually displays the coordinates of the camera used when taking the picture! That is a big help, as it gives us an initial range of values we can use to guess where our player is, which cuts down on the total amount of time to find the coordinates.
It also told me some other important bits of information, such as the fact that the z-axis is the vertical axis, something I have been seeing more and more with newly released games (the majority of games I’ve hacked tend to use the y-axis as the vertical axis).
Player Location Structure Hook
// Get the player's location structure. // Unique AOB: 0F 10 81 10 02 00 00 F2 0F 10 89 20 02 00 00 0F 11 02 F3 define(omniPlayerLocationHook,"PhysX3CharacterKinematic_x64.dll"+1EE0) assert(omniPlayerLocationHook,0F 10 81 10 02 00 00) alloc(getPlayerLocation,$1000,omniPlayerLocationHook) alloc(playerLocation,8) registersymbol(omniPlayerLocationHook) registersymbol(playerLocation) getPlayerLocation: pushf push rax mov rax,playerLocation mov [rax],rcx pop rax getPlayerLocationOriginalCode: popf movups xmm0,[rcx+00000210] jmp getPlayerLocationReturn omniPlayerLocationHook: jmp getPlayerLocation nop 2 getPlayerLocationReturn:
Some other observations: this game uses doubles for coordinates, for the player at least. Unfortunately, none of the code accessing the player’s coordinates are accessing NPC coordinates. Also, the function responsible for facilitating movement is player-only.
This is going to complicate matters when we get around to the Predator system, but we’ll worry about that when we get there.
This Is Going To Be Fun
I think Omnified Cyberpunk 2077 is going to be freaking amazing! I did all of the hacking and even the writing of this article on my official Twitch channel. Stop by sometime to say hello and enjoy the fun with me!
To find the most updated versions of my code, make sure to check out the official Bad Echo technologies source code repository.
The next article will cover the implementation of the Apocalypse system. Hope you learned something! As always, I sure did!