import sys import json import os import re from lib.gvas import GvasFile from lib.noindent import CustomEncoder from lib.palsav import compress_gvas_to_sav, decompress_sav_to_gvas from lib.paltypes import PALWORLD_CUSTOM_PROPERTIES, PALWORLD_TYPE_HINTS def main(sav_file: str, guid: str): if sav_file[-4:] == ".sav": sav_file = sav_file[:-4] if guid[-4:] == ".sav": guid = guid[:-4] convert_sav_to_json(filename=f"savs/{sav_file}.sav", output_path=f"./savs/{sav_file}.json") # do work on user.sav edit_user_json(sav_file, guid) convert_json_to_sav(filename=f"savs/{sav_file}.json", output_path=f"./savs/{guid}.sav") os.remove(f"savs/{sav_file}.json") convert_sav_to_json(filename="savs/Level.sav", output_path="./savs/Level.json") os.remove("savs/Level.sav") # do work on level.sav edit_level_json(sav_file, guid) convert_json_to_sav(filename="savs/Level.json", output_path="./savs/Level.sav") os.remove("savs/Level.json") def format_id_string(guid: str): return f"{guid[0:8]}-{guid[8:12]}-{guid[12:16]}-{guid[16:20]}-{guid[20:]}" def edit_user_json(old_id: str, new_id: str): print(f"Editing user.sav from {old_id} to {new_id}") filename = f"savs/{old_id}.json" old_id = format_id_string(old_id) new_id = format_id_string(new_id) with open(filename, "r+") as old_file: data = str(json.load(old_file)) new_data = eval(re.sub(old_id, new_id, data, flags=re.I)) # eval(data.replace(old_id, new_id)) os.remove(filename) with open(filename, 'w') as new_file: indent = "\t" json.dump(new_data, new_file, indent=indent) def edit_level_json(old_id: str, new_id: str): print(f"Editing Level.sav from {old_id} to {new_id}") filler_id = "00000000-0000-0000-0000-000000000006" filename = "savs/Level.json" old_id = format_id_string(old_id) new_id = format_id_string(new_id) with open(filename, "r+") as old_file: data = str(json.load(old_file)) temp_data = re.sub(new_id, filler_id, data, flags=re.I) # data.replace(new_id, filler_id) new_data = eval(re.sub(old_id, new_id, temp_data, flags=re.I)) # eval(temp_data.replace(old_id, new_id)) os.remove(filename) with open(filename, 'w') as new_file: indent = "\t" json.dump(new_data, new_file, indent=indent) def convert_sav_to_json(filename: str, output_path: str): minify = False print(f"Converting {filename} to JSON, saving to {output_path}") if os.path.exists(output_path): print(f"{output_path} already exists, this will overwrite the file") if not confirm_prompt("Are you sure you want to continue?"): exit(1) print(f"Decompressing sav file") with open(filename, "rb") as f: data = f.read() raw_gvas, _ = decompress_sav_to_gvas(data) print(f"Loading GVAS file") gvas_file = GvasFile.read(raw_gvas, PALWORLD_TYPE_HINTS, PALWORLD_CUSTOM_PROPERTIES) print(f"Writing JSON to {output_path}") with open(output_path, "w", encoding="utf8") as f: indent = None if minify else "\t" json.dump(gvas_file.dump(), f, indent=indent, cls=CustomEncoder) def convert_json_to_sav(filename: str, output_path: str): print(f"Converting {filename} to SAV, saving to {output_path}") if os.path.exists(output_path): print(f"{output_path} already exists, this will overwrite the file") if not confirm_prompt("Are you sure you want to continue?"): exit(1) print(f"Loading JSON from {filename}") with open(filename, "r", encoding="utf8") as f: data = json.load(f) gvas_file = GvasFile.load(data) print(f"Compressing SAV file") if ( "Pal.PalWorldSaveGame" in gvas_file.header.save_game_class_name or "Pal.PalLocalWorldSaveGame" in gvas_file.header.save_game_class_name ): save_type = 0x32 else: save_type = 0x31 sav_file = compress_gvas_to_sav( gvas_file.write(PALWORLD_CUSTOM_PROPERTIES), save_type ) print(f"Writing SAV file to {output_path}") with open(output_path, "wb") as f: f.write(sav_file) def confirm_prompt(question: str) -> bool: reply = None while reply not in ("y", "n"): reply = input(f"{question} (y/n): ").casefold() return reply == "y" if __name__ == "__main__": a = sys.argv[1] b = sys.argv[2] main(sav_file=a, guid=b)