barrass.dev

Hacking Prison Architect's Names In The Game

Posted Friday 01 August 2025 at 11:20

Estimated reading time: 19 mins

Prison Architect for a long time had a “Name In The Game” DLC. This was effectively a crowdfunding bonus, a neat way for the developers to raise some extra money to fund the game’s development, whilst allowing those interested to leave a mark in the game that could appear at random in other players’ games. Name In The Game was discontinued in 2019, and, whilst the existing names remain in the game, if you want to add a new name to the game or edit the name/bio you’ve already paid for, you’re out of luck.

Or at least you would be without hacking the game files.

The names in the game are all stored within the game’s asset files, so if you’re willing to dig around, you can add, remove, or otherwise modify as many names as you want. In the past, these were all stored in a human-readable text file called names_in_the_game.txt, and adding a new name by hand was pretty trivial. In around 2018, however, this was changed to names_in_the_game.bin, a binary file with an undocumented format. Modifying this correctly and without breaking anything is much more of a challenge, and one that I’m certainly up for!

A Brief Introduction to Prison Architect Mods

Loosely speaking, Prison Architect’s built-in mod support is essentially an asset replacement system. The contents of your mod directory are injected into the game when loaded with mods enabled. Mods can add new assets of their own, or overwrite the game’s existing assets. names_in_the_game.bin is treated the same as any other game asset, so can be overwritten by mods. This gives us an unobtrusive and impermanent way of changing out the names in the game.

The basic structure of a mod is a directory containing two things:

  1. a data directory, containing all of the assets we want to inject into the game.
  2. a manifest.txt file, which contains the mod metadata.

At first, the only asset we’re looking to replace is names_in_the_game.bin, so this is the only file we need in our data directory. We can extract this from the original game files. In the game’s installation directory is a main.dat file, which is just a standard RAR archive containing its own data directory. Inside here, we can find the original names_in_the_game.bin and extract it to our mod directory. The manifest for this mod is pretty simple too; I just used the following:


Name                 "Hacking Names In The Game"
Author               "Joshua Barrass"
Description          "Modify the names in the game."
Version              1.0
IsTranslation        false

If we now start up the game and check our mod list, we’ll see this new mod. Now it’s time to figure out how this file works!

Understanding Strings

If we open up names_in_the_game.bin in a hex editor, we are immediately confronted by a lot of strings. Even in the first 256 bytes, we can see lots of obvious strings:

00000000: 7870 0000 486d 0000 3003 0000 0400 0000  xp..Hm..0.......
00000010: 4164 616d 0700 0000 4368 6565 7461 6804  Adam....Cheetah.
00000020: 0000 0044 6965 6c13 0000 0041 6461 6d20  ...Diel....Adam 
00000030: 2243 6865 6574 6168 2220 4469 656c 0a00  "Cheetah" Diel..
00000040: 0000 3139 3836 2e30 372e 3237 ef00 0000  ..1986.07.27....
00000050: 4d65 6368 616e 6963 616c 2045 6e67 696e  Mechanical Engin
00000060: 6565 7220 616e 6420 6365 7274 6966 6965  eer and certifie
00000070: 6420 456d 6572 6765 6e63 7920 4d65 6469  d Emergency Medi
00000080: 6361 6c20 5465 6368 6e69 6369 616e 2e20  cal Technician. 
00000090: 4265 6361 6d65 2061 206d 6572 6365 6e61  Became a mercena
000000a0: 7279 2061 6674 6572 2073 6572 7669 6e67  ry after serving
000000b0: 2069 6e20 7468 6520 552e 532e 204e 6176   in the U.S. Nav
000000c0: 793b 2068 6f6e 6f72 6162 6c65 2064 6973  y; honorable dis
000000d0: 6368 6172 6765 2e20 4172 7265 7374 6564  charge. Arrested
000000e0: 2062 7920 434c 4153 5349 4649 4544 2061   by CLASSIFIED a
000000f0: 7420 5245 4441 4354 4544 2077 6869 6c65  t REDACTED while

We have the forename, surname, nickname, and the formatted combination of the three; the date of birth; the bio; and more as we go deeper into the file. None of these strings are null-terminated, however, so these are not standard C-style strings. If we look closer, we can see a different pattern. Before the four-character string “Adam”, we see the bytes 04 00 00 00; before the seven-character string “Cheetah”, we see the bytes 07 00 00 00. These are little-endian 32-bit values corresponding to the numbers 4 and 7, respectively. These are all, therefore, length-prefixed strings, using 32-bit words for the length.

Now we understand how the strings work, and we’ve also uncovered a little extra information in the process: the word size. Of course, there are no guarantees that every number in this file is going to be a 32-bit integer, but this at least gives a starting point for further exploration of the file.

Exploiting The Word Size

We know now that the first name in the file is Mr. Adam “Cheetah” Diel. The length-prefixed string for his forename starts at offset 0x0000000c, 12 bytes into the file. With our 32-bit (4 byte) word size, that would imply exactly three values precede the first prisoner! Let’s take a closer look…

00000000: 7870 0000 486d 0000 3003 0000            xp..Hm..0...

Upon first inspection, there’s nothing special about this. They don’t appear, for example, to be more length-prefixed strings. So what could they be? The first of these values is 0x00007078 = 28792, the second is 0x00006d48 = 27976, and the third is 0x00000330 = 816. The first thing I notice here is that the last two numbers sum to the first: 27976 + 816 = 28792. Prison Architect allows you to build both male and female prisons, so what if…

In-game screenshot showing the game contains 28792 names

There we have it! The first number it seems corresponds to the number of names in the game. If we try changing it to 1 (bytes 01 00 00 00), then the names in the game list updates and only Mr. Adam “Cheetah” Diel appears in the list. Now, there doesn’t seem to be any easy way of determining the gender ratios in-game, and changing the other two values seems to have no obvious effect on the game, even when filtering the names by gender. That said, there are considerably more male prisoners in the game than female, so I’m willing to hazard a guess that this was the original purpose of these second and third values. Whether it’s a redundant feature of the file, or whether I’ve just not delved deep enough to establish their purpose, I don’t know. This first value however, is absolutely crucial for adding our own new names to the game; incrementing this value will allow us to add a new prisoner to the file and have them show up in the game, rather than just replacing existing names.

Optional Strings

Currently, for each prisoner, we have:

  1. Forename — string
  2. Nickname — string
  3. Surname — string
  4. Full name — string
  5. Date of birth — string, "%Y.%m.%d"
  6. Bio — string

Some of the prisoners at the start of the list, those who paid for the “face in the game” DLC, have custom mugshot art. This is, once again, just a length-prefixed string that comes after date of birth. Most, but not all, prisoners also have a skin colour a few bytes after the mugshot path. Again, this is just a length-prefixed string, encoding the skin colour in hexadecimal RGBA as a string literal. For example, for Mr. Adam “Cheetah” Diel, "0x9e6329ff". The inclusion of an alpha channel in this is funny; whilst I haven’t found any prisoners with transparent skin, the game seems perfectly capable of rendering them.

In-game screenshot showing a prisoner with semi-transparent head and hands
Sorry drunkapple, seems you did pass away in prison after all, but that doesn't mean you can't finish your sentence as a ghost!

So what about prisoners who don’t have a custom mugshot? Or those who didn’t specify a skin colour? Or those with no nickname? Well, fortunately, the use of length-prefixed strings makes this easy for us. If a string is optional, we just set the length to 0 and move on! The vast majority of prisoners in the file don’t have a custom mugshot, so they do exactly this to avoid specifying one.

Let’s Start Breaking Things

This addresses a lot of the low-hanging fruit of the format now. We understand the header of the file and the string format, which is already quite a lot of the information we need to create our own prisoners. However, after the first five strings encoding the prisoner’s basic info, we have a few numbers we still need to figure out. Their purpose is not obvious from the outset, so how can we figure out what all of this does? We have two main options: 1) compare between different entries to see what looks the same and what looks different, and 2) start changing numbers and find out what breaks!

We understand everything up to and including the path to the mugshot image. What’s left? Here’s how our old friend Mr. Adam “Cheetah” Diel’s entry ends:

00000140: 0000 0064 6174 612f 6d75 6773 686f 7473  ...data/mugshots
00000150: 2f32 3238 3835 392e 706e 6700 0000 0000  /228859.png.....
00000160: 0080 3ffb 7d03 0002 0000 000a 0000 0030  ..?.}..........0
00000170: 7839 6536 3332 3966 66e8 0000 00ff dede  x9e6329ff.......
00000180: ff00 0000 00ff ffff ff00 0000 00         .............

So the first number after his mugshot… is zero. Seems uninteresting. What about the next entry, Mr Adrian “Corporate Lackey” AwYoung?

000002ca: 6461 7461 2f6d 7567 7368 6f74 732f 3132  data/mugshots/12
000002da: 3539 3837 2e70 6e67 0000 0000 0000 803f  5987.png.......?
000002ea: 23ec 0100 ffff ffff 0000 0000 d700 0000  #...............
000002fa: ffca c0ff 0000 0000 ffff ffff 0000 0000  ................

Also a zero. So what do these two have in common? Well, if you compare their mugshots side-by-side, you’’ see that they both have the same body shape: a long, thin, pill shape. These two have custom mugshots, though, so we aren’t able to test this theory on these two. Let’s go back to our friend drunkapple, whose entry ends

0083cf30: 6361 6e21 0000 0000 0200 0000 0000 803f  can!...........?
0083cf40: 158e 0600 0800 0000 0a00 0000 3078 6362  ............0xcb
0083cf50: 3934 3663 6666 ffff ffff 0000 0000 0000  946cff..........
0083cf60: 0000 ffff ffff 0000 0000                 ..........

We don’t have a mugshot here, so there’s an extra 0 after the bio where the mugshot path would be, but after that is a 2, and we know that drunkapple doesn’t have the pill-shaped body. Let’s try changing this 2 to a 0.

In-game screenshot showing the prisoner from above with a differently shaped body

Changing that 2 to a 0 clearly gives them a differently-shaped body, which appears to be the same pill-shaped body as the others. That’s another field cracked!

The next number we see is 0x3f800000 = 1065353216. This number seems way too big to be something as simple as an index for another body part, and it’s a number that seems to be the same across every prisoner I can see on initial inspection. Let’s go to plan 2! Let’s try changing that 3f byte to ff and see what happens.

In-game screenshot showing a prisoner with a missing body

…ah. Well, that’s an interesting outcome. So we know this value has something to do with the body, but what exactly? Let’s change it back and change the 80 to ff instead.

In-game screenshot showing a prisoner with a wider and noticeably more pixellated body

That’s interesting; the body’s back, but wider than it was. It’s clearly been stretched out, because the edges are also more jagged and blurred. This number clearly controls the scale of the body, then; but what’s the format of it? A scale generally isn’t just an integer, we usually want to represent decimal points, like 0.5, or 1.25. In this case, what if the number is a floating point?

Interpreting a floating point by hand is a little bit tricky, so let’s use Python’s struct package to unpack it. We’ll assume the same 32-bit word size, i.e., a single-precision floating point number. So what do all of the different values we tried correspond to?

>>> import struct
>>> struct.unpack("<f", bytes([0x00,0x00,0x80,0x3f]))
(1.0,)
>>> struct.unpack("<f", bytes([0x00,0x00,0x80,0xff]))
(-inf,)
>>> struct.unpack("<f", bytes([0x00,0x00,0xff,0x3f]))
(1.9921875,)

It seems like all of these values are consistent with the idea that this value is a 32-bit float corresponding to the body scale. The default value used by most prisoners is 0000 803f, which corresponds to 1.0, i.e., the standard default body size. The second value we tried corresponds to negative infinity, so it makes sense that things break when we use that value; a negative scale, and an infinite one at that, is bound to create whacky behaviour. But the third value corresponds to just under 2.0, and matches up with the stretching out of the body. Let’s test this theory by trying to set the scale to 0.25. First, we can use Python again to figure out the float representation of 0.25:

>>> struct.pack("<f", 0.25).hex()
'0000803e'
In-game screenshot showing a prisoner with a narrower body

And that’s that! Instead of stretching the body out, this time it gets compressed inwards, as expected.

(Mostly) Full Picture

The rest of the file can be approached in a pretty similar way — pattern matching against what I can see, or just changing things and seeing what breaks, so I won’t bother going through the process for the whole file. Instead, I leave below the format of the file as far as I’ve figured out (note all strings are length-prefixed strings, as discussed):

Full file

Data Type Value
int32 Number of prisoners in the file
int32 Number of male prisoners (unused?)
int32 Number of female prisoners (unused?)
Prisoner[] Array of prisoners

Prisoner

Data Type Value
string Prisoner forename
string Prisoner nickname
string Prisoner surname
string Full name: <forename> "<nickname>" <surname>
string Date of birth: %Y.%m.%d
string Bio
string Path to mugshot photo (can be zero-length)
int32 Body type
float32 Body scale
int32 Prisoner ID number
int32 Hairstyle
string RGBA skin colour in hexadecimal: 0x<RR><GG><BB><AA> (can be zero-length)
int32 Head sprite index (for face in the game prisoners). If -1 (0xFFFFFFFF), then no custom face sprite.
Unknown For most prisoners, 00000000, but differs for face in the game prisoners.
int32 Gender. 0 for male, 1 for female.
Unknown For seemingly all prisoners, FFFFFFFF00000000.

There are still a few fields here that haven’t been figured out in full. The first unknown field must be connected to the face in the game, as it is preceded by the sprite index (which can be found in the objects.spritebank file within main.dat — they’re each labelled under the BEGIN Sprites header with their index, e.g. BEGIN "[i 237]") and only seems to take a non-zero value for prisoners with custom face sprites. This is something to figure out for the future. The second unknown field seems to be there for every prisoner, and may just be a closing header. It would be interesting to write a parser for this file and find out whether any prisoner exists with a different value here — again, something for the future.

Working Example of a Custom Prisoner

To close out, let’s put all of this together to make our own custom prisoner. To make this easier, we can use the following Python code to pack all the data into the file:

from typing import Tuple, Optional
import struct
import datetime

def lps(s):
    return struct.pack("<i", len(s)) + s.encode()

def build_prisoner(
    forename:str,
    nickname:Optional[str],
    surname:str,
    dob:datetime.date,
    bio:str,
    mugshot:Optional[str],
    body:int,
    body_scale:Optional[float],
    id_number:int,
    hairstyle:int,
    skin_color:Optional[Tuple[int,int,int,int]],
    female:bool=False):
    if nickname is None:
        nickname = ""
    if mugshot is None:
        mugshot = ""
    if body_scale is None:
        body_scale = 1.0
    if skin_color is None:
        skin_color = ""
    else:
        skin_color = "0x{0:02x}{1:02x}{2:02x}{3:02x}".format(*skin_color)
    if female is None:
        gender = 1
    else:
        gender = 0

    data = b""
    data += lps(forename)
    data += lps(nickname)
    data += lps(surname)
    if len(nickname) > 0:
        data += lps(f'{forename} "{nickname}" {surname}')
    else:
        data += lps(f'{forename} {surname}')
    data += lps(dob.strftime("%Y.%m.%d"))
    data += lps(bio)
    data += lps(mugshot)
    data += struct.pack("<ifii", body, body_scale, id_number, hairstyle)
    data += lps(skin_color)
    data += bytes([0xff,0xff,0xff,0xff,0,0,0,0])
    data += struct.pack("<i", gender)
    data += bytes([0xff,0xff,0xff,0xff,0,0,0,0])
    return data

def append_prisoner(path:str, prisoner:bytes, female=False):
    with open(path, "rb+") as f:
        f.seek(0)
        count_total, count_m, count_f = struct.unpack("<iii", f.read(4*3))
        f.seek(0)
        if female:
            count_f += 1
        else:
            count_m += 1
        count_total += 1
        f.write(struct.pack("<iii", count_total, count_m, count_f))
        f.read() # skip to EOF
        f.write(prisoner)

I used Behind the Name to generate a random prisoner name — I’m going to call him Gregory “The Ripper” Ward, born 25 April 1946 — and we’ll just cook up a random bio about him being a violent bank robber: “Gregory Ward is best known for a string of bank robberies across south-east England, which eventually culminated in a stand-off with police. He was taken into custody and passed from prison to prison, earning the nickname ‘The Ripper’ for his savage attacks on other prisoners on the inside.” Let’s feed this into the code:

append_prisoner("path/to/names_in_the_game.bin",
    build_prisoner("Gregory", "The Ripper", "Ward", datetime.date(1946,4,25), "Gregory Ward is best known...", None, 1, 1.1, 12345, 1, (0xc6, 0x9f, 0x82, 0xff), False),
    False,
)

…and just like that…

In-game screenshot showing the new custom prisoner's mugshot and bio in the name in the game list. In-game screenshot showing the new custom prisoner as they appear when transferred to a prison, with their bio and rapsheet shown.
Our custom prisoner appears in the game, and can be transferred to prisons.

…everything works. We can add custom mugshots, too, though these are a little buggy — in the pause menu, the rendered mugshot is still displayed over the top of the custom mugshot, but it works just fine once the custom prisoner is transferred into a prison.

Conclusion

So there you have it! By diving into the names_in_the_game.bin file, we can finally modify and add custom prisoners to Prison Architect, long after the cessation of the official Name In The Game DLC. We have almost as much control as the original DLC offered — perhaps more, with the custom mugshots, which weren’t previously available to Name In The Game purchasers. This opens the game back up to personalisation, whether you missed the opportunity to pay for a name in the game originally, or just want to further customise your existing prisoner. Happy hacking!