Created
September 13, 2022 02:46
-
-
Save Microflame/1c7bfb4634fa685478c2f17015a41bb3 to your computer and use it in GitHub Desktop.
Simple save editor for Hollow Knight game
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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