Save scummer for "Getting Over It with Bennett Foddy"
import winreg
from pprint import pprint
import codecs
import xml.etree.ElementTree as ET
import os
import json
from datetime import datetime
import traceback
import sys
LOCATION_REG_KEY = "SOFTWARE\\Bennett Foddy\\Getting Over It"
IMPORTANT_KEYS = ["SaveGame0_h1867918426",
"SaveGame1_h1867918427", "NumSaves_h765021473"]
SAVE_FOLDER = "GoI_Saves"
SAVE_EXTENTION = "goi-scum-json"
MENU_TEXT = (
""" Type an Option:
X = exit
Q = exit
S = Save Current State
L = Load last save-file as Current State
O = Open specific file and load as Current State
* Current State is what is written in the windows registry.
i.e. what the game will read.
* Rember to close the game before saving and loading.
""")
def print_exception(ex, msg, do_print=True):
tb = ''.join(traceback.format_exception(
etype=type(ex), value=ex, tb=ex.__traceback__))
out = "Exception:\n{}\n{}".format(msg, tb)
if do_print:
print(out, file=sys.stderr)
else:
return out
class CustomException(Exception):
pass
class BadXMLValues(CustomException):
pass
class NoSavesFound(CustomException):
pass
class BadSaveFile(CustomException):
pass
def read_registry_values(key_location, value_names):
reg_con = winreg.ConnectRegistry(None, winreg.HKEY_CURRENT_USER)
open_key = winreg.OpenKey(reg_con, key_location, 0, winreg.KEY_ALL_ACCESS)
registry_dict = dict()
for key_name in value_names:
val, rtype = winreg.QueryValueEx(open_key, key_name)
if rtype == 3:
val = codecs.encode(val, 'hex').decode("utf-8")
registry_dict[key_name] = (val, rtype)
return registry_dict
def write_registry_values(key_location, values_dict):
reg_con = winreg.ConnectRegistry(None, winreg.HKEY_CURRENT_USER)
open_key = winreg.OpenKey(reg_con, key_location, 0, winreg.KEY_ALL_ACCESS)
for key_name, vtuple in values_dict.items():
val, rtype = vtuple
if rtype == 3:
val = codecs.decode(val, 'hex')
winreg.SetValueEx(open_key, key_name, 0, rtype, val)
def read_and_check():
registry_dict = read_registry_values(LOCATION_REG_KEY, IMPORTANT_KEYS)
did_find_saves = False
for key_name, val_n_type in registry_dict.items():
value, rtype = val_n_type
if not key_name.startswith('SaveGame'):
continue
else:
did_find_saves = True
try:
xml_string = codecs.decode(value, 'hex').decode('utf-8')[:-1]
ET.fromstring(xml_string)
except Exception as ex:
# print_exception(ex, "OH NOE!")
raise BadXMLValues(
"A saved game in registry is malformed, try agian later")
if not did_find_saves:
raise NoSavesFound(
"Hey! You need to play the game first. There are no Saved Games")
return registry_dict
def generate_save_name():
ts = datetime.utcfromtimestamp(
datetime.now().timestamp()).timestamp() * 1000.0
tstr = [n for n in reversed(str(ts).split('.'))].pop()
return "{}.{}".format(tstr, SAVE_EXTENTION)
def check_make_dir(dir):
if not os.path.isdir(dir):
os.mkdir(dir)
def do_save():
check_make_dir(SAVE_FOLDER)
filename = None
try:
save_json = json.dumps(read_and_check())
filename = generate_save_name()
filepath = os.path.join(SAVE_FOLDER, filename)
with open(filepath, 'w') as fh:
fh.write(save_json)
except CustomException as ex:
print(ex)
else:
print('Saved game to "{}"'.format(filename))
def checked_load(filepath):
registry_dict = None
with open(filepath, 'r') as fp:
registry_dict = json.load(fp)
# pprint(registry_dict)
if registry_dict is None:
raise BadSaveFile()
if len(list(filter(lambda k: k in IMPORTANT_KEYS, registry_dict))) is not len(IMPORTANT_KEYS):
raise BadSaveFile()
write_registry_values(LOCATION_REG_KEY, registry_dict)
def do_load():
check_make_dir(SAVE_FOLDER)
files = os.listdir(SAVE_FOLDER)
known_saves = filter(lambda fn: fn.endswith(SAVE_EXTENTION), files)
last_save = sorted(known_saves).pop()
print('Would you like to load "{}"?'.format(last_save))
selection = input("Y/N > ").upper().lstrip().strip()
if not selection.startswith("Y"):
print("Will not load save, due to your selection.")
return
filepath = os.path.join(SAVE_FOLDER, last_save)
try:
checked_load(filepath)
except BadSaveFile as ex:
print_exception(ex, "Whoa, save file is bad.")
def do_open_and_load():
print("Enter file name (including path):")
filename = input("> ").lstrip().strip()
if not os.path.isfile(filename):
print("File doesnt exist!")
return
print('Are you sure you want to load "{}" into registry - making it the current save game?'.format(filename))
selection = input("Y/N > ").upper().lstrip().strip()
if not selection.startswith("Y"):
print("Will not load save, due to your selection.")
return
try:
checked_load(filename)
except BadSaveFile as ex:
print_exception(ex, "Whoa, save file is bad.")
def main():
stay_running = True
while stay_running:
print()
print()
print(MENU_TEXT)
selection = input("Enter option> ").upper().lstrip().strip()
if selection.startswith("X") or selection.startswith("Q"):
print('bye!')
stay_running = False
elif selection.startswith("S"):
do_save()
elif selection.startswith("L"):
do_load()
elif selection.startswith("O"):
do_open_and_load()
print("#################")
continue
if __name__ == '__main__':
main()