Legalizing Gay Marriage in Crusader Kings III with Ghidra

Waffle Ironer
13 min readApr 5, 2021

--

Edit Apr. 6, 2021: Paradox has made an official response to the issue. It’s good news!

Crusader Kings III is a pretty impressive game. It’s impressive not only for its official content, but also for its extensive modding tools. Even before its release, I was drawn in by its promises of an improved modding experience. Relevant to this post, I was also impressed by an apparent dedication towards improved inclusivity.

Now while the game does allow the player to reform a variety of medieval cultures to be accepting of same-sex couples, as of CK3 version 1.3.1 it currently does not allow a reformer to permit same-sex marriage. That should be fine though; that’s what mods are for!

The Modding Problem

An impressive amount of CK3 is implemented in Paradox Interactive’s in-house scripting language (which is kind of like what you’d get if you made a LISP without any parentheses). The game also provides good utilities for players to create, package, and distribute their own scripts on places like the Steam workshop.

Getting two characters to marry (without any fancy conditions) is fairly simple. All you really need is a script like this:

do_ye_a_marriage = {
category = interaction_category_friendly
auto_accept = yes
is_shown = { always = yes }
on_auto_accept = {
scope:actor = {
marry = scope:recipient
}
}
}

This adds an option when right-clicking a character like this:

Which when chosen will marry the Duchess of Tuscany with whomever you’re playing as, no questions asked.

Creating a mod to marry same-sex characters should be should be pretty simple then. The King of Sweden is unmarried, so you can apply the effect to him and…

… nothing? Well nothing will happen in-game at least. CK3 does keep fairly detailed error logs, which on Windows are usually stored in C:\Users\<Username>\Documents\Paradox Interactive\Crusader Kings III\logs\error.log . There we will see:

[jomini_script_system.cpp:169]: Script system error!
Error: marry effect [ Svend Estrid of k_denmark (Internal ID: 15228 - Historical ID 101515) is not allowed to marry Erik Stenkiling of k_sweden (Internal ID: 17969 - Historical ID 100525) ]

Unfortunately, it turns out that the game logic to marry two characters is contained in the game binary, not in the scripting files, and the game engine will unilaterally block same sex marriages. This makes trying to marry two same-sex characters in Cruisader Kings 3… an illegal operation.

(Look, it’s not like everything I do is an excuse to make bad puns. Just most things I do.)

This was discovered pretty quickly by modders when the game released. I put together what has become the main thread asking for change on the official Paradox forums. As time went on, and patches only managed to make things worse, it became clear from statements both on the forums and off of it that while the concern was noted, fixing the issue would be difficult, and it wasn’t a current priority.

Now game modders, being used to taking issues of gameplay into their own hands, can get up to all kinds of things given enough time. I took inspiration from some recent work on GTA Online, and spent my time engaging in some light reverse-engineering of the game engine. Out of that, I managed to produce a 390 byte unofficial game patch to bypass the hard-coded restrictions on same-sex marriage and some other effects needed for same-sex partners to adopt children. The response from posting this on the official forums was…

… not what I was hoping for. Concerningly, in addition to the ban and the removal of the patch thread, there’s also been a removal of any posts mentioning the patch from when it was live, including whole pages from the original request thread.

Now, I could try to find some other site that would accept the patch, but there are some downsides to that. The patch that I made was only good for the current Steam version of CK3, on my operating system, with my cpu architecture. But I can give something more portable than that: knowledge!

So without further ado, lets go through a step-by-step guide to see how anyone with a bit of technical know-how can reverse engineer a game like Crusader Kings 3.

(If you don’t have much technical know-how, or you’re just an impatient modder like me, feel free to skip to the Quick Fix section near the bottom)

Setting Up the Tools

Ghidra is a suite of reverse engineering tools built by the US government’s National Security Agency ̶t̶o̶ ̶h̶a̶c̶k̶ ̶t̶h̶e̶ ̶C̶h̶i̶n̶e̶s̶e̶ ̶w̶i̶t̶h̶. It was open sourced in 2019, and has since enabled curious coders to poke their noses into all sorts of programs.

I’ll leave the installation instructions to its website, but after opening and clicking through the tutorial greeting, you’ll be met with a window like this:

To start you’ll have to go through the formality of starting a new project. This is done from File -> New Project... . Give your project a name and location and you’re ready to import your game file.

To do that you’ll want to find the location of your ck3.exe file. Mine is a steam version and can be found in D:\SteamLibrary\steamapps\common\Crusader Kings III\binaries but you may have to do some hunting to find yours.

Once you’ve found your ck3.exe file, you can import it through the File -> Import File... interface. That should leave you looking at something like this:

On hitting OK and loading your binary, you will be greeted with a report that you can OK through, and a new file in your Active Project pane. Double click that file to start the real fun.

The interface for Ghidra is intimidating when first encountered, but is pretty straightforward with a little experience. Just remember that if you accidentally exit from a navigation pane, you can usually bring it back with the Windows option on the top-bar.

On opening a binary for the first time, you’ll be prompted to start an analysis:

Ghidra can analyze a lot of things about a file. If you’re not sure yet what you’re looking for, the simplest way to proceed it to accept the defaults and hit Analyze. For a binary the size of ck3.exe, this will take a while. I left mine running overnight. Once analysis is finished, you’ll want to hit the save button in the upper left if you want any of that analysis to still be there on reopening.

Following the Trail

From there the whole world opens up. The hardest part of reverse engineering a particular feature can sometimes be figuring out where to start. Luckily we do have a lead: the very error message that started this. Commonly used text is often stored as a sequence in a program’s file. Text like Svend Estrid of k_denmark is probably generated dynamically, but text like is not allowed to marry is likely stored in the file itself.

From the menu bar, select Search -> For Strings... and click Search with default options. From there, you can search for a string of text with the Filter box. Once initialized, this will (slowly) search the game file for any strings that match your filter.

And we’re in luck! Double clicking that line item will bring you to the text string in the Listing pane (in the other window, get used to switching windows).

This shows some information about our string of text at its location in memory. Now the string itself doesn’t tell us much. We’re more interested in the code using the string. By right clicking on the highlighted line, you can select References -> Show References to Address to find those.

Looks like there are two places referencing it in memory. We may as well start with the first one. Double-click the first line item to go to its location. In your main window, this should open the location in both the Listing and Decompile panes (if you do not have a Decompile pane, you can open one with Window -> Decompile).

Into the Guts

Now there’s a lot of gibberish that gets spit out in the Decompile pane. Games like CK3 are usually made with a programming language like C++, and when distributed developers normally strip the names of functions and variables out of their game files to make those files smaller. To compensate, Ghidra just gives dummy names to most things.

One interesting thing about reverse-engineering a binary is that you often don’t need to know what most of that gibberish means. In our case, we can scroll down until we see the line with our string.

We could examine the function being called by clicking on FUN_14034cb90 (spoiler, it mostly just prints the error), but we’re more interested in keeping it from running at all. Scrolling up to the top of the if-block this function is in, we see this:

So cVar2 is the result of the function FUN_140a1b9c0 , and if that result is zero, the error handling code is triggered. Given that the error is about how two characters can’t marry, it’s a good bet that lVar7 and lVar3 are the two characters being checked. If we double-click on FUN_140a1b9c0 , we can take a look inside of it.

Now this is where some knowledge of C programming really helps. We’re looking for a spot where our input parameters are compared to each other, and a zero is returned. Near the top of the function, I find a block like this:

This takes our inputs (which we assume are character structures of some kind) and compares the byte at offset 0x124 in each of them. If those bytes are equal, the whole function returns zero. This looks like it could be comparing character gender, as long as a character’s gender can fit in a single byte (and I know from some playing with Cheat Engine that it’s actually a single bit, 0 for male and 1 for female).

Being a Bit Wizard

The best way to see if we’re right about our if-statement is to try bypassing it. Clicking on the “if” in the Decompiler pane will bring the Listing pane to the corresponding assembly instruction.

This is as low-down as we can get when examining a binary. JZ is short for “Jump if Zero”. It takes the result of a previous comparison (in this case our two genders), and jumps to a new position in memory if that comparison results in a zero (which it will if the genders are equal). If we were to follow the black line at the left side of the instruction, it would lead us to some code that makes the function return zero.

One way to remove an instruction like this is to change where it jumps to. To do this, right click the line in the Listing pane and select Patch Instruction .

Notice that the jump location now looks a lot like a memory location. If we want our JZ instruction to never jump, we can change its jump location to the very next line at 140a1ba00 . That way it will end up there whether or not our character genders are equal.

Notice that the arrow to the left has changed to the new jump location. Once Ghidra has finished decompiling the function again, the if-statement we were editing should no longer be there.

Testing our Changes

All of this editing so far has just been within our Ghidra project. If we want to test our change, we need to get it back into the binary. Ghidra has some known issues exporting files in the form we’re using, so we’ll want to make use of a free script to compensate.

The official instructions at that link didn’t work for me, so I placed my SavePatch.py file into my <Ghidra Installation Directory>\Ghidra\Features\Python\ghidra_scripts\ folder. Once there, you need to drag select your line of assembly in the Listing pane so that the whole thing is green. Then click on the Script Manager button near the top of the interface:

Enter “SavePatch” into the Finder bar and you should see the downloaded script.

Double-click on the found line item, and you should be prompted to select a file. Path to the ck3.exe file that you originally imported and select it (you may wish to copy it somewhere else as a backup first). If all this goes correctly, your game on-disk should be patched with our modified jump instruction.

If we jump back in-game and try to do_ye_a_marriage the king of Sweden again, we…

… still get nothing. But we do have a new message in the error log:

[pdx_assert.cpp:568]: Assertion failed: Can only marry characters of different gender.

Round Two

It looks like the Paradox developers have some additional checks for their own usage. We can use the same method on this one though. Search -> For Strings... , filter on “Can only marry characters of different gender”, then on that string References -> Show References to Address .

This time there’s only one. Now that we have an idea what to look for, the relevant code in this function is easy to find.

This is a similar construction to the first one. It compares our two parameters at 0x124 and checks if they’re equal. Selecting our “if” the corresponding assembly looks a little different:

Our highlighted instruction is a JNZ or “Jump if Not Zero” instruction, and the arrow is pointing past our “Can only marry” string. So we want this instruction to always jump instead of never jump. To do that, once again right-click the highlighted line and select Patch Instruction . Next change the JNZ to JMP , which is the instruction for an unconditional jump.

To apply to your game, follow the same process of highlight, Script Manager, and select SavePatch.py.

Then we can open our game and…

… we can finally, after all that, get the personal union of Denmark-Sweden that we always knew we wanted.

The patch I originally posted had this change and several others to enable same-sex couples to jointly adopt. They all used the same assembly tricks though.

The King and I, the King

But is that all these changes do? CK3 has the checks that we removed duplicated in the script files, so the base game shouldn’t see any change at all. I’ve played for a decent amount of time with some simple gay-marriage mods, and so far the only outstanding bug I’ve noticed is this:

It turns out that the CK3 marry effect doesn’t have a specific check against marrying yourself. Some effects, like set_father will explicitly prevent a character from becoming their own father, but any given character is always the same gender as themselves, so the gay-marriage block was also serving as the self-marriage block. Any modders will probably want to check for this explicitly in their mods.

Even this was pretty stable while I played with it though. Kudos to the CK3 devs, their engine is quite robust.

(If any CK3 devs do happen to read this, I would recommend a specific check for self-marriage regardless.)

Quick Fix

But what if you don’t want to start a career reverse-engineering software? Well, if you happen to have the Windows 10 Steam version of CK3 1.3.1, you can open your ck3.exe file in a hex editor of your choice to make the following changes:

Which does the same thing, minus all the digging. Do note the “marrying yourself” issue above when making your mods though (or don’t, if you’re a narcissist).

A Final Note

I know some people will be frustrated with how Paradox has handled this so far. I know I have been. I would ask though that people please not harass individual Paradox employees about this. The ones I’ve chatted with have seemed like nice people.

--

--