Hacking the Ruler Designer in Crusader Kings II
I had started playing Crusader Kings II again and like every other time before it, I always get to the point when I want to mess up with my ruler, i.e. cheat a bit. And by a bit, I mean a whole lot. Nothing short of the most over powered ruler possible is acceptable. Even though the game provides all the in-game cheat commands necessary to achieve this, it is not as easy as one may think. You can easily set the age of any character and change their stats, but their traits need to be added one by one. The list of good and bad traits is a long one, and you have to do a bit of searching for it on the web or by finding the right plain-text file in the game configuration folder that defines these traits (and then making sure you only add the good ones). This is further complicated if you happen to use any of the popular mods for CKII (e.g. Lux Invicta) that more than doubles the list of ruler traits. Furthermore, the cheat command requires you to input the ID of the trait, not its display name in game. For the vanilla game, I think these two values coincide most of the time, but that is certainly not the case for mods. There are plenty of situations where the ID of the trait is either a synonym word for it or worse, a misspelling. So there is no escape, you have to find the configuration files that define the traits for every mod and dig in. There must be a better way!
Well, the game has this wonderful DLC called Ruler Designer that allows you to customize your ruler when setting up your game, which undoubtedly adds more flavor to the game and can be an excellent tool for role playing purposes. I, and many others, do not consider this designer feature a cheating tool. Every stat and trait has an associated “age” cost and your configuration cannot go above the age of 50. This means you cannot go about creating an overpowered ruler as you always have to counter balance buffed up stats and good traits with a set of bad traits to lower the age. While this is fun and great, it doesn’t help me with creating the most OP ruler ever known to man. Not without a little bit of hacking. If we could just somehow get rid of the age cost, creating an OP ruler via the Ruler Designer is so much simpler than using cheat commands and digging through configuration files, because everything you need is on your screen and you can just select what you want. This is how the ruler designer looks like on the attributes panel.
The list of traits clearly displays the age cost. If I were to pick the Genius trait in Figure 2, my age would increase by 30. If I were to pick Genius plus Attractive, I would be 16+30+16=62 years old, 12 years above the maximum 50 so I wouldn’t be able to create that ruler. I would need to pick some bad traits already to decrease the age, and I have only picked up 2 good traits so far!
In these situations I usually use any random memory hacker to open the CKII process, scan the memory for the value that I am looking for and modify it. For example, here I would search for the value 16 which would yield an insane amount of results, then I would narrow the results by modifying a stat or picking up an ability that modifies the age, and then I would search again for the new value from the result list, rinse and repeat until I narrow it down to 1-2 memory addresses. Then I just modify that value to set the age that I want. Sometimes the value you are looking for is not a 4-byte integer, it may be smaller or bigger, a decimal or floating point number (where decimals might be truncated). Sometimes it’s not the wrong data type you’re searching for, but the value itself. For example, age may be computed and stored in months, but a conversion function is used to display it in years.
Unfortunately, it is not that simple here. That value, the age, is never stored in memory (or not persistently; it is computed, displayed, and then forgotten), so searching the memory for it doesn’t yield any result. And even if it were, this scenario is a little more complicated than that as there is a “Finish” button you can see in the screenshots above, which gets grayed out when age goes above 50. Finding and modifying the age may not un-gray that button. This means we have to do a bit of debugging here: search for a starting point, set some breakpoints, disassemble the code and figure out how we can modify stats and traits without changing the age. You can pretty much use any debugger for this, but there are a few simple ones written specifically for game cheating such as T-Search or Cheat Engine. I will be using the latter.
The plan of attack:
- Try to find the variables that change when a stat or trait is modified. Some portion of the code will have to access these variables to then compute the age and enable or disable the “Finish” button.
- Once we find the memory address of such a variable, we can set memory access breakpoint which will show us what code is reading or writing to that memory address. There can be multiple pieces of code that do this for various reasons.
- We will look through each part of code that accesses those variable to understand its purpose, figuring out if it has anything to do with age or the “Finish” button.
- Alter the code so that the age always remains the same, and the “Finish” button is enabled so we can create our ruler.
- Max out the stats and choose every last good trait in that god damn long list to create the most OP ruler known to man.
Actual steps with screenshots:
- Fire up CKII, start a new game and go to the ruler designer on the Attributes tab. Fire up Cheat Engine and open the CKII process.
- In Cheat Engine, search for every 4-byte integer with the value of 6. This will yield a bunch of results. Go back in-game and modify the Diplomacy stat from 6 to 7. Go back to Cheat Engine and continue the search for the new value of 7. This should yield only 1 memory address. (If not, change the value again, search it up, rinse and repeat)
- We found the variable that stores the diplomacy. Double click on it to add in the address list at the bottom of the window, where you right-click on it and select “Find out what accesses this address”. Itwillinform you that it needs to attach a debugger to the process and you must accept. A window will pop up for the results,currently empty. You need to back in-gameandmodify the Diplomacy stat again. The new window will then populate with a list of instruction pointers where the Diplomacyvaluewas accessed.
- Foreach of the results you have a snapshot of the register space at the time and of the stack. If you look closely, the value stored intheEDX registerplus3C (hexadecimal) is the address for the Diplomacy value above. In the result window above, you are seeing the op codes or instructions executed by the processor that involved the [edx+3C] address, in other words, your Diplomacy. The first result is an instructionthatwrites to your Diplomacy variable, it adds whatever was in the EDI register to your Diplomacy. This instruction was obviously executed when I clicked to increase the value of my Diplomacy, so it’s not what we’re looking for. We’re looking for code that just reads the Diplomacy, to see what for. The second instruction in list does that. It reads the Diplomacy value and stores it intheECX register. Let’s disassemble the code to see what is going on there.
- We seethatthe instruction from our resultlistis executed part of a humongous function (you’d have to scroll up and way down to get to the beginning and the end of it). What could it possibly do? So, our instruction is the one highlighted above.TheECX register stores the diplomacy value. The following instruction subtracts 6 from it. Interestingly,theminimum base diplomacy value you can have is 6 (or any other stat). The second instruction after thatmultiplesECX by 1000. Basically,thisa normalization of sort.ECX eventually gets storedinEAX to serve as a parameter for the function call that follows. I dug into this function and this is the one that computes the age. But we’re not really interested in that function now because of the following observation. This piece of code keeps repeating throughout this functionforeach main stat, constantly computing the age for whatever reason. This huge function actually returns the total age computed based on stats and traits. For some reason, when you change a stat once, this function will get called 3 times. I assume once to get the age to display it in the Ruler Designer, once to update the enabled/disabled status of the Finish button, and once for probably updating the avatar (the older you are, the older you look). It is inefficient, but it’s not really a time-consuming / heavy processing type of operation. We could dig into the functionthatdoes the conversion to age so that it always returns 16 (minimum age), or we could just change this function to always return 16 in the end, no matter what it computed along the way. Let’s look how the end of the function looks like.
- The highlighted code is the end of the function. After the first instruction in the highlight, EBX is storing the age after taking into account all the stats and traits selected.
mov eax,[CK2game.exe+B7FE6C] <- this sets EAX to 0x10 (16). That is the minimum age possible.
cmp ebx,eax <- this compares the calculated age with 16
jnge CK2game.SDL_GetNumAudioDrivers+2A564D <- if the calculated age is smaller than 16, this instruction jumps below and the function exits returning age 16.
mov eax, ebx <- if the calculated age is not smaller than 16, this instruction is executed and stores the age in EAX
The rest of the instructions deal with correctly exiting the function and returning the value of EAX, which is either 16 or the calculated age.
- Our solution is for this function to always return 16 no matter what the calculated age. You can invert the “jnge” instruction such that the function return 16 when the calculated age is higher than 16. But this leaves a problem for the scenario where you select a bunch of bad traits that would lower your age below 16. I guess you could use that to create child rulers (untested), but you stillhave the possibility of going below 0 andunderflowing the buffer such that your age is now 2 billion years. We can avoid all that by simply NOP-ing out the instructions in that if-statement (basically deleting the if-statement) such that the function returns right after settingEAX to 16. It looks like this:
We end up with a function that goes over all the stats and traits and computes the value for the age, but it always returns 16 at the end. This function is used by user interface to display the age number, to display your portrait based on age, and to let you click on the Finish button. After the change above, I could do this in my designer and the age remains at 16:
Since this part of the function is unlikely to change with game updates, you can right away just search for those opcodes plus bytes I just NOP-ed out and do it again. I believe Cheat Engine allows you to write a very small and simple LUA script to do that, so next time you fire up the game and cheat engine, you just execute the script and be done with it. Similarly, you can write a small program (a trainer) that does the same thing.
Well, now, back to my game of conquering the Scottish Highlands, driving out the Norse, uniting Scotland and educating the “English” in speaking proper Gaelic 🙂
[UPDATE] To summarize, you can achieve this by following these two steps:
- Launch any memory hacker and open the CKII process
- Search for byte sequence 38D87C0288C3 and replace it with 909090909090
Its like you learn my thoughts! You appear to grasp a lot about
this, like you wrote the e book in it or something. I feel that you could do with
a few % to pressure the message home a bit, but other than that, that is magnificent blog.
A great read. I will certainly be back.
Even the short version of this post is confusing to me.
Your update really doesn’t help people who don’t know how memory hackers work. Could we have a more detailed step by step process of how to change that one value, without having to go through the entire process outlined at the beginning of the post?
you speak chinese.
“Search for byte sequence 38D87C0288C3 and replace it with 909090909090”
I think you’re reading Bs as 8s. That string should be… 3BD87C028BE5.
argh… did it again. The E5 at the end is wrong… should be 3BD87C028BC3