111 lines
2.9 KiB
Python
Executable File
111 lines
2.9 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
import os
|
|
import os.path
|
|
import shutil
|
|
from struct import pack, unpack
|
|
import sys
|
|
import zlib
|
|
|
|
FOLDER = sys.argv[1]
|
|
|
|
if not os.path.isdir(FOLDER):
|
|
print(f"Syntax: {sys.argv[0]} SAVEDIR")
|
|
sys.exit(10)
|
|
|
|
FOLDER = os.path.realpath(FOLDER)
|
|
folder_name = os.path.basename(FOLDER)
|
|
print(f"Selected folder: {folder_name}")
|
|
|
|
if not os.path.isfile(FOLDER + "/SavedState.dat"):
|
|
print("ERROR: Save folder doesn't have a SavedState.dat. Make sure to specify a Switch savegame.")
|
|
sys.exit(11)
|
|
|
|
output_folder = FOLDER + "_PC"
|
|
|
|
try:
|
|
os.mkdir(output_folder)
|
|
except:
|
|
print(f"Error creating output folder: {output_folder}")
|
|
sys.exit(12)
|
|
|
|
shutil.copy2(FOLDER + "/Metadata.dat", output_folder)
|
|
shutil.copy2(FOLDER + "/SaveGameScreenshot.png", output_folder)
|
|
|
|
print("Reading SavedState.dat...")
|
|
with open(FOLDER + "/SavedState.dat", "rb") as f:
|
|
zlib_save = f.read()
|
|
print(f"Read {len(zlib_save)} Bytes.")
|
|
|
|
print("Decompressing savegame...")
|
|
save = zlib.decompress(zlib_save)
|
|
size_real = len(save)
|
|
print(f"Size after decompression: {size_real} Bytes.")
|
|
|
|
size_header = unpack("<I", save[0:4])[0]
|
|
print(f"Size read from savegame : {size_header} Bytes.")
|
|
|
|
if size_real != size_header:
|
|
print("ERROR: Savegame data size mismatch!")
|
|
sys.exit(13)
|
|
|
|
num_records = unpack("<I", save[4:8])[0]
|
|
print(f"Savegame has {num_records} records according to header.")
|
|
|
|
save = save[8:]
|
|
|
|
output = b""
|
|
output += pack("<I5sIH", 5, b"SGDF", 1, 10)
|
|
|
|
records = 0
|
|
|
|
while True:
|
|
name_bin_len = unpack("<Q", save[0:8])[0]
|
|
name_utf16_len = name_bin_len * 2
|
|
name_utf16 = save[8:8+name_utf16_len]
|
|
save = save[8+name_utf16_len:]
|
|
payload_length = unpack("<I", save[0:4])[0]
|
|
save = save[4:]
|
|
name = name_utf16.decode("utf16")
|
|
print(f"- {name} ({payload_length} Bytes)", end="")
|
|
name_len = len(name) + 1 # add cstr NUL byte
|
|
output += pack("<I", name_len)
|
|
output += name.encode("utf8")
|
|
output += b"\x00"
|
|
output += pack("<I", payload_length)
|
|
output += save[:payload_length]
|
|
print(" ✔")
|
|
records += 1
|
|
save = save[payload_length:]
|
|
if len(save) == 0:
|
|
break
|
|
|
|
print(f"Processed {records} records.")
|
|
if records != num_records:
|
|
print(f"WARNING: This doesn't match the {num_records} indicated in the header! Savegame might not work.")
|
|
|
|
output_len = len(output)
|
|
print(f"New savegame size: {output_len} Bytes.")
|
|
|
|
# DEBUG:
|
|
#with open(output_folder + "/SaveGame.dec.dat", "wb") as f:
|
|
# f.write(output)
|
|
|
|
zlib_output = zlib.compress(output)
|
|
|
|
print(f"New savegame size after compression: {len(zlib_output)} Bytes.")
|
|
|
|
print("Writing SaveGame.dat...")
|
|
with open(output_folder + "/SaveGame.dat", "wb") as f:
|
|
f.write(zlib_output)
|
|
print("Output file written successfully.")
|
|
|
|
# Metadata.dat has the uncompressed size and if it doesn't match
|
|
# the savegame won't load
|
|
print("Fixing metadata.")
|
|
with open(output_folder + "/Metadata.dat", "rb+") as f:
|
|
f.seek(-28, 2)
|
|
f.write(pack("<I", output_len))
|
|
|
|
print("All done.")
|