Skip to main content

MO Files (Translations)

MO files are GNU gettext Machine Object files used for language translations. The mobile builds of Dead Cells use the standard MO binary format but may additionally encrypt the string contents.

Format

File

Size (bytes)NameDescriptionStruct
4Magic0x950412de (LE) or 0xde120495 (BE)Unsigned int
4RevisionFormat revision, 0x00Unsigned int
4Number of stringsTotal msgid/msgstr pairsUnsigned int
4Original table offOffset to the msgid descriptor tableUnsigned int
4Translation table offOffset to the msgstr descriptor tableUnsigned int
4Hash table sizeSize of the hash tableUnsigned int
4Hash table offsetOffset to the hash tableUnsigned int
VariableString descriptorsTwo parallel tables of length/offset pairs, one for msgid, one for msgstrArray of Descriptor
VariableString dataRaw string bytes for all msgid and msgstr entriesRaw binary data

String Descriptor

Each string in the MO file is described by an 8-byte entry in either the original or translation table:

Size (bytes)NameDescriptionStruct
4LengthLength of the string in bytesUnsigned int
4OffsetOffset of the string data from file startUnsigned int

Mobile Encryption

Some mobile builds encrypt MO string data, as far back as Google Play v3.5.9 (every MO in res4.pak was scrambled) and it goes even earlier. The MO header (magic, offsets, string counts) is left untouched and remains valid; only the raw bytes of each msgid and msgstr are encrypted.

Algorithm

The cipher is an XOR stream keyed by a 384-byte table, where plaintext[i] = ciphertext[i] ^ key[(len ^ i) % 384] for a string of length len. The key table holds the first 384 terms of the Fibonacci sequence (starting from 0), each taken modulo 256, and is generated once. The same key is reused for every string in the file — the index formula (len ^ i) % 384 ensures strings of different lengths draw from different portions of the key even at the same byte position. Because XOR is symmetric, the same operation performs both encryption and decryption.

def make_key():
key = []
a, b = 0, 1
for _ in range(384):
key.append(b & 0xff)
a, b = b, a + b
return key

def crypt(data: bytes, key: list[int]) -> bytes:
length = len(data)
return bytes(b ^ key[(length ^ i) % 384] for i, b in enumerate(data))