Skip to content

Instantly share code, notes, and snippets.

@lincoln-lm
Created April 17, 2025 05:06
Show Gist options
  • Select an option

  • Save lincoln-lm/aa09cd89ff338ac5ee0404942d528154 to your computer and use it in GitHub Desktop.

Select an option

Save lincoln-lm/aa09cd89ff338ac5ee0404942d528154 to your computer and use it in GitHub Desktop.
Xenoblade Chronicles X Definitive Edition save file encryption used for files in sts/game (gamedata{:02d}, socialdata, systemdata, avatarmakedata)
import random
CRC_TABLE = [
0x46D1E192,
0x66EDF9AA,
0x927FC9E5,
0xA53BAACC,
0x29B47658,
0x5A411A01,
0x0E66D5BD,
0x0DD5B1DB,
0xCB38340E,
0x04D4EBB6,
0x98BC4F54,
0x36F20F2C,
0x4A3047ED,
0x1EC1E0EB,
0x568C0C1F,
0x6A731432,
0x81367FC6,
0xE3E25237,
0xE7F64884,
0x0FA59F64,
0x4F3109DE,
0xF02D61F5,
0x5DAEC03B,
0x7F740E83,
0x056FF2D8,
0x2026CC0A,
0x7AC2112D,
0x82C55605,
0xB0911EF2,
0xA7B88E4C,
0x89DCA282,
0x4B254D27,
0x7694A6D3,
0xD229EADD,
0x8E8F3738,
0x5BEE7A55,
0x012EB6AB,
0x08DD28C8,
0xB5ABC274,
0xBC7931F0,
0xF2396ED5,
0xE4E43D97,
0x943F4B7F,
0x85D0293D,
0xAED83A88,
0xC8F932FC,
0xC5496F20,
0xE9228173,
0x9B465B7D,
0xFDA26680,
0x1DDEAB35,
0x0C4F25CB,
0x86E32FAF,
0xE59FA13A,
0xE192E2C4,
0xF147DA1A,
0x67620A8D,
0x5C9A24C5,
0xFE6AFDE2,
0xACAD0250,
0xD359730B,
0xF35203B3,
0x96A4B44D,
0xFBCACEA6,
0x41A165EC,
0xD71E53AC,
0x835F39BF,
0x6B6BDE7E,
0xD07085BA,
0x79064E07,
0xEE5B20C3,
0x3B90BD65,
0x5827AEF4,
0x4D12D31C,
0x9143496E,
0x6C485976,
0xD9552733,
0x220F6895,
0xE69DEF19,
0xEB89CD70,
0xC9BB9644,
0x93EC7E0D,
0x2ACE3842,
0x2B6158DA,
0x039E9178,
0xBB5367D7,
0x55682285,
0x4315D891,
0x19FD8906,
0x7D8D4448,
0xB4168A03,
0x40B56A53,
0xAA3E69E0,
0xA25182FE,
0xAD34D16C,
0x720C4171,
0x9DC3B961,
0x321DB563,
0x8B801B9E,
0xF5971893,
0x14CC1251,
0x8F4AE962,
0xF65AFF1E,
0x13BD9DEE,
0x5E7C78C7,
0xDDB61731,
0x73832C15,
0xEFEBDD5B,
0x1F959ACA,
0xE801FB22,
0xA89826CE,
0x30B7165D,
0x458A4077,
0x24FEC52A,
0x849B065F,
0x3C6930CD,
0xA199A81D,
0xDB768F30,
0x2E45C64A,
0xFF2F0D94,
0x4EA97917,
0x6F572ACF,
0x653A195C,
0x17A88C5A,
0x27E11FB5,
0x3F09C4C1,
0x2F87E71B,
0xEA1493E4,
0xD4B3A55E,
0xBE6090BE,
0xAF6CD9D9,
0xDA58CA00,
0x612B7034,
0x31711DAD,
0x6D7DB041,
0x8CA786B7,
0x09E8BF7A,
0xC3C4D7EA,
0xA3CD77A8,
0x7700F608,
0xDF3DE559,
0x71C9353F,
0x9FD236FB,
0x1675D43E,
0x390D9E9A,
0x21BA4C6B,
0xBD1371E8,
0x90338440,
0xD5F163D2,
0xB140FEF9,
0x52F50B57,
0x3710CF67,
0x4C11A79C,
0xC6D6624E,
0x3DC7AFA9,
0x34A69969,
0x70544A26,
0xF7D9EC98,
0x7C027496,
0x1BFB3BA3,
0xB3B1DC8F,
0x9A241039,
0xF993F5A4,
0x15786B99,
0x26E704F7,
0x51503C04,
0x028BB3B8,
0xEDE5600C,
0x9CB22B29,
0xB6FF339B,
0x7E771C43,
0xC71C05F1,
0x604CA924,
0x695EED60,
0x688ED0BC,
0x3E0B232F,
0xF8A39C11,
0xBAE6E67C,
0xB8CF75E1,
0x970321A7,
0x5328922B,
0xDEF3DF2E,
0x8D0443B0,
0x2885E3AE,
0x6435EED1,
0xCC375E81,
0xA98495F6,
0xE0BFF114,
0xB2DA3E4F,
0xC01B5ADF,
0x507E0721,
0x6267A36A,
0x181A6DF8,
0x7BAFF0C0,
0xFA6D6C13,
0x427250B2,
0xE2F742D6,
0xCD5CC723,
0x2D218BE7,
0xB91FBBB1,
0x9EB946D0,
0x1C180810,
0xFC81D602,
0x0B9C3F52,
0xC2EA456F,
0x1165B2C9,
0xABF4AD75,
0x0A56FC8C,
0x12E0F818,
0xCADBCBA1,
0x2586BE56,
0x952C9B46,
0x07C6A43C,
0x78967DF3,
0x477B2E49,
0x2C5D7B6D,
0x8A637272,
0x59ACBCB4,
0x74A0E447,
0xC1F8800F,
0x35C015DC,
0x230794C2,
0x4405F328,
0xEC2ADBA5,
0xD832B845,
0x6E4ED287,
0x48E9F7A2,
0xA44BE89F,
0x38CBB725,
0xBF6EF4E6,
0xDC0E83FA,
0x54238D12,
0xF4F0C1E3,
0xA60857FD,
0xC43C64B9,
0x00C851EF,
0x33D75F36,
0x5FD39866,
0xD1EFA08A,
0xA0640089,
0x877A978B,
0x99175D86,
0x57DFACBB,
0xCEB02DE9,
0xCF4D5C09,
0x3A8813D4,
0xB7448816,
0x63FA5568,
0x06BE014B,
0xD642FA7B,
0x10AA7C90,
0x8082C88E,
0x1AFCBA79,
0x7519549D,
0x490A87FF,
0x8820C3A0,
]
XOR_KEY = b'@\x81\x000A\x81\x010B\x81\x020C\x81\x0c\xffD\x81\x0e\xffE\x81\xfb0F\x81\x1a\xffG\x81\x1b\xffH\x81\x1f\xffI\x81\x01\xffJ\x81\x9b0K\x81\x9c0L\x81\xb4\x00M\x81@\xffN\x81\xa8\x00O\x81>\xffP\x81\xe3\xffQ\x81?\xffR\x81\xfd0S\x81\xfe0T\x81\x9d0U\x81\x9e0V\x81\x030W\x81\xddNX\x81\x050Y\x81\x060Z\x81\x070[\x81\xfc0\\\x81\x15 ]\x81\x10 ^\x81\x0f\xff_\x81\\\x00`\x81\x1c0a\x81\x16 b\x81\\\xffc\x81& d\x81% e\x81\x18 f\x81\x19 g\x81\x1c h\x81\x1d i\x81\x08\xffj\x81\t\xffk\x81\x140l\x81\x150m\x81;\xffn\x81=\xffo\x81[\xffp\x81]\xffq\x81\x080r\x81\t0s\x81\n0t\x81\x0b0u\x81\x0c0v\x81\r0w\x81\x0e0x\x81\x0f0y\x81\x100z\x81\x110{\x81\x0b\xff|\x81\x12"}\x81\xb1\x00~\x81\xd7\x00\x80\x81\xf7\x00\x81\x81\x1d\xff\x82\x81`"\x83\x81\x1c\xff\x84\x81\x1e\xff\x85\x81f"\x86\x81g"\x87\x81\x1e"\x88\x814"\x89\x81B&\x8a\x81@&\x8b\x81\xb0\x00\x8c\x812 \x8d\x813 \x8e\x81\x03!\x8f\x81\xe5\xff\x90\x81\x04\xff\x91\x81\xa2\x00\x92\x81\xa3\x00\x93\x81\x05\xff\x94\x81\x03\xff\x95\x81\x06\xff\x96\x81\n\xff\x97\x81 \xff\x98\x81\xa7\x00\x99\x81\x06&\x9a\x81\x05&\x9b\x81\xcb%\x9c\x81\xcf%\x9d\x81\xce%\x9e\x81\xc7%\x9f\x81\xc6%\xa0\x81\xa1%\xa1\x81\xa0%\xa2\x81\xb3%\xa3\x81\xb2%\xa4\x81\xbd%\xa5\x81\xbc%\xa6\x81; \xa7\x81\x120\xa8\x81\x92!\xa9\x81\x90!\xaa\x81\x91!\xab\x81\x93!\xac\x81\x130\xb8\x81\x08"\xb9\x81\x0b"\xba\x81\x86"\xbb\x81\x87"\xbc\x81\x82"\xbd\x81\x83"\xbe\x81*"\xbf\x81)"\xc8\x81\'"\xc9\x81("\xca\x81\xac\x00\xcb\x81\xd2!\xcc\x81\xd4!\xcd\x81\x00"\xce\x81\x03"\xda\x81 "\xdb\x81\xa5"\xdc\x81\x12#\xdd\x81\x02"\xde\x81\x07"'
def crc32(data: bytes) -> int:
hash_ = len(data)
for byte in data:
hash_ = CRC_TABLE[(hash_ ^ byte) & 0xFF] ^ (hash_ >> 8)
return hash_
def decrypt(data: bytearray, encryption_index: int) -> bytearray:
for data_index in range(0, len(data) - 4):
data[data_index + 4] ^= data_index & 0xFF
data[data_index + 4] ^= XOR_KEY[encryption_index] ^ 0xFF
encryption_index += 1
encryption_index &= 0x1FF
def encrypt(data: bytearray, encryption_index: int) -> bytearray:
for data_index in range(0, len(data) - 4):
data[data_index + 4] ^= XOR_KEY[encryption_index] ^ 0xFF
data[data_index + 4] ^= data_index & 0xFF
encryption_index += 1
encryption_index &= 0x1FF
file_path = input("Filepath: ")
with open(file_path, "rb") as f:
data = bytearray(f.read())
encryption_index = int.from_bytes(data[0:4], "little") & 0x1FF
print(f"{encryption_index=}")
decrypt(data, encryption_index)
assert len(data[16:]) == int.from_bytes(data[12:16], "little")
assert crc32(data[16:]) == int.from_bytes(data[8:12], "little")
assert int.from_bytes(data[4:8], "little") == 1
with open(f"{file_path}_decrypted", "wb") as f:
f.write(data)
# ... modify data
# Mersenne Twister 32-bit random number
# seemingly never actually read
random_value = int.from_bytes(random.randbytes(4), "little")
# frames since game boot & 0x1FF
encryption_index = random.randint(0, 0x1FF)
first_value = (random_value & 0xFFFFFE00) | (encryption_index & 0x1FF)
data[0:4] = first_value.to_bytes(4, "little")
data[4:8] = (1).to_bytes(4, "little")
data[8:12] = crc32(data[16:]).to_bytes(4, "little")
data[12:16] = len(data[16:]).to_bytes(4, "little")
encrypt(data, encryption_index)
with open(f"{file_path}_modified", "wb") as f:
f.write(data)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment