Skip to content

Instantly share code, notes, and snippets.

@jesux
Created June 13, 2023 07:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jesux/d9b3c66e82a0086b6c16acede80faceb to your computer and use it in GitHub Desktop.
Save jesux/d9b3c66e82a0086b6c16acede80faceb to your computer and use it in GitHub Desktop.
Hollow Knight PermaDeath changer
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Requires pycryptodome
import sys, os
if sys.version_info.major<3:
sys.stderr.write("Python3 required\n")
sys.exit(0)
import json, base64
from Crypto.Cipher import AES
from datetime import datetime
permadeathModes = {0:'Normal', 1:'Steel Soul', 2:'Steel Soul Broken πŸ’€'}
header = bytes.fromhex('0001000000ffffffff01000000000000000601000000')
header_end = bytes([11])
key = b'UKu52ePUBwetZ9wNX88o54dnfKRu0T1l'
cipher = AES.new(key, AES.MODE_ECB)
modified = False
# https://msdn.microsoft.com/en-us/library/cc236844.aspx
def header_len(length):
length = min(0x7fffffff, length); # maximum value
x = []
for i in range(4):
if length >> 7 == 0:
x.append(0x7f & length)
length = length >> 7
break
else:
x.append(0x7f & length | 0x80)
length = length >> 7
if length != 0:
values.x(length)
return bytes(x)
def decode(data):
# Remove header
data = data[len(header): len(data) - 1]
for i in range(5):
if ((data[i] & 0x80) == 0):
break
data = data[i+1:]
data = base64.b64decode(data)
data = cipher.decrypt(data)
data = data[:-data[-1]] # Remove AES padding
return data
def encode(data):
# Add AES padding
length = 16 - (len(data) % 16)
data += bytes([length]) * length
data = cipher.encrypt(data)
data = base64.b64encode(data)
# Add header
data = header + header_len(len(data)) + data + header_end
return data
# OPEN FILE
try:
infile = open(sys.argv[1], mode="rb")
data = infile.read()
infile.close()
except:
print("[-] Error opening file")
exit()
# DECODE
data_decoded = decode(data)
y = json.loads(data_decoded)
# PRINT DATA
print('[VERSION] {0}'.format(y['playerData']['version']))
m,s = divmod(y['playerData']['playTime'], 60)
h,m = divmod(m, 60)
print('[playTime] {0:.0f} hours {1:.0f} minutes'.format(h,m))
print('[permadeathMode] {0}'.format(permadeathModes[y['playerData']['permadeathMode']]))
print('[πŸ’°] {0}'.format(y['playerData']['geo']))
print('[nailDamage] {0}'.format(y['playerData']['nailDamage']))
print('[completionPercentage] {0}%'.format(y['playerData']['completionPercentage']))
# Remove PermaDeath
if y['playerData']['permadeathMode'] == 2:
print("πŸ’€πŸ’€ Reparing Steel Soul πŸ’€πŸ’€")
y['playerData']['permadeathMode'] = 1
modified = True
if modified:
data_encoded = encode(bytes(json.dumps(y), 'utf-8'))
print('[+] Saving data backup')
bakfile = open(sys.argv[1] + '_' + datetime.now().strftime('%Y%m%d%H%M%S') + '.bak', mode="wb")
bakfile.write(data)
infile.close()
print('[+] Saving modified data')
outfile = open(sys.argv[1], mode="wb")
#outfile = open('output.txt', mode="wb")
outfile.write(data_encoded)
outfile.close()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment