Skip to content

Instantly share code, notes, and snippets.

@widberg
Created June 5, 2025 23:32
Show Gist options
  • Select an option

  • Save widberg/c9c0692b963c8def7accdf51c0f9895b to your computer and use it in GitHub Desktop.

Select an option

Save widberg/c9c0692b963c8def7accdf51c0f9895b to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
# Monster Prom Save Editor
# Usage: python3 monster_prom_save_editor.py decrypt 76561198083837418_GameProgress.xml 76561198083837418_GameProgress_decrypted.xml
# Usage: python3 monster_prom_save_editor.py encrypt 76561198083837418_GameProgress_decrypted.xml 76561198083837418_GameProgress.xml
# See https://www.pcgamingwiki.com/wiki/Monster_Prom for save file locations
# Backup your save file before using this script
import argparse
import base64
import xml.etree.ElementTree as ET
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad, pad
MONSTER_PROM_KEY = b"07372495761297179318254963745127"
MONSTER_PROM_RIJNDAEL = AES.new(MONSTER_PROM_KEY, AES.MODE_ECB)
MONSTER_PROM_ENCODING = "us-ascii"
def decrypt(args):
encrypted_file = args.encrypted_file
decrypted_file = args.decrypted_file
tree = ET.parse(encrypted_file)
root = tree.getroot()
ciphertext = base64.b64decode(root.text)
plaintext = unpad(
MONSTER_PROM_RIJNDAEL.decrypt(ciphertext), AES.block_size, style="pkcs7"
)
inner_tree = ET.fromstringlist(["<root>", plaintext, "</root>"])
root.clear()
root.extend(inner_tree)
new_tree = ET.ElementTree(root)
ET.indent(new_tree)
with open(encrypted_file, "wb") as f:
# Use double quotes and \r\n in the declaration
f.write(
f'<?xml version="1.0" encoding="{MONSTER_PROM_ENCODING}"?>\r\n'.encode(
"ascii"
)
)
new_tree.write(f, encoding=MONSTER_PROM_ENCODING, xml_declaration=False)
def strip_whitespace(elem):
if elem.text:
elem.text = elem.text.strip()
if elem.tail:
elem.tail = elem.tail.strip()
for child in elem:
strip_whitespace(child)
def encrypt(args):
decrypted_file = args.decrypted_file
encrypted_file = args.encrypted_file
tree = ET.parse(decrypted_file)
root = tree.getroot()
strip_whitespace(root)
plaintext = b"".join(
ET.tostring(child, encoding=MONSTER_PROM_ENCODING) for child in root
)
ciphertext = MONSTER_PROM_RIJNDAEL.encrypt(
pad(plaintext, AES.block_size, style="pkcs7")
)
root.clear()
root.text = base64.b64encode(ciphertext).decode("ascii")
new_tree = ET.ElementTree(root)
with open(encrypted_file, "wb") as f:
# Use double quotes and \r\n in the declaration
f.write(
f'<?xml version="1.0" encoding="{MONSTER_PROM_ENCODING}"?>\r\n'.encode(
"ascii"
)
)
new_tree.write(f, encoding=MONSTER_PROM_ENCODING, xml_declaration=False)
def main():
parser = argparse.ArgumentParser(description="Monster Prom save editor")
subparsers = parser.add_subparsers(dest="command", required=True)
parser_decrypt = subparsers.add_parser("decrypt")
parser_decrypt.add_argument("encrypted_file", type=str)
parser_decrypt.add_argument("decrypted_file", type=str)
parser_decrypt.set_defaults(func=decrypt)
parser_encrypt = subparsers.add_parser("encrypt")
parser_encrypt.add_argument("decrypted_file", type=str)
parser_encrypt.add_argument("encrypted_file", type=str)
parser_encrypt.set_defaults(func=encrypt)
args = parser.parse_args()
args.func(args)
if __name__ == "__main__":
main()
pycryptodome==3.23.0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment