Skip to content

Instantly share code, notes, and snippets.

@Microflame
Created September 13, 2022 02:46
Show Gist options
  • Select an option

  • Save Microflame/1c7bfb4634fa685478c2f17015a41bb3 to your computer and use it in GitHub Desktop.

Select an option

Save Microflame/1c7bfb4634fa685478c2f17015a41bb3 to your computer and use it in GitHub Desktop.
Simple save editor for Hollow Knight game
import argparse
from base64 import b64decode, b64encode
import json
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
c_sharp_header = [0, 1, 0, 0, 0, 255, 255, 255, 255, 1, 0, 0, 0, 0, 0, 0, 0, 6, 1, 0, 0, 0]
aes_key = b'UKu52ePUBwetZ9wNX88o54dnfKRu0T1l'
cipher = AES.new(aes_key, AES.MODE_ECB)
def load_savefile(path):
with open(path, 'rb') as f:
data = f.read()
# remove header
data = data[len(c_sharp_header):-1]
counter = 0
for i in range(5):
counter += 1
if (data[i] & 0x80) == 0:
break
data = data[counter:]
# b64 decode
data = b64decode(data)
# AES decode
data = cipher.decrypt(data)
data = data[:-data[-1]]
# To JSON
data = json.loads(data)
return data
def write_savefile(path, data):
data = json.dumps(data, separators=(',', ':'))
data = data.encode('UTF-8')
data = pad(data, 32)
data = cipher.encrypt(data)
data = b64encode(data)
length_header = bytearray()
length = len(data)
for i in range(4):
if (length >> 7) != 0:
length_header.append(length & 0x7F | 0x80)
length >>= 7
else:
length_header.append(length & 0x7F)
length >>= 7
break
if length != 0:
length_header.append(length)
res = bytearray()
res.extend(c_sharp_header)
res.extend(length_header)
res.extend(data)
res.append(11)
with open(path, 'wb') as f:
f.write(res)
def get_var(data, path):
for p in path:
data = data[p]
return data
def set_var(data, path, new_val):
for p in path[:-1]:
data = data[p]
data[path[-1]] = new_val
def split_path(path):
return path.split('/')
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('file')
parser.add_argument('mode', choices=['write', 'read'])
parser.add_argument('variable_name')
parser.add_argument('new_value', nargs='?')
parser.add_argument('new_file_name', nargs='?')
args = parser.parse_args()
data = load_savefile(args.file)
path = split_path(args.variable_name)
if args.mode == 'read':
res = get_var(data, path)
if isinstance(res, dict):
res = res.keys()
print('%s: %s' % (args.variable_name, res))
else:
new_val = args.new_value
new_file_name = args.new_file_name
if new_val is None:
print('new_value is required')
exit(1)
if new_file_name is None:
print('new_file_name is required')
exit(1)
old_val = get_var(data, path)
set_var(data, path, type(old_val)(new_val))
print(get_var(data, path))
print('%s: %s -> %s' % (args.variable_name, old_val, new_val))
write_savefile(new_file_name, data)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment