|
|
@@ -0,0 +1,99 @@ |
|
|
import os |
|
|
import struct |
|
|
import sys |
|
|
import uuid |
|
|
import zipfile |
|
|
|
|
|
# Yakuza 0 save "converter" (Xbox Game Pass for PC -> Steam) |
|
|
# Might work for other Yakuza games available with the Game Pass if the app name is changed. |
|
|
# Running: Just run the script with Python 3 to create a ZIP file that contains the save files |
|
|
|
|
|
# Thanks to @snoozbuster for figuring out the container format at https://github.com/goatfungus/NMSSaveEditor/issues/306 |
|
|
|
|
|
xgp_app = "SEGAofAmericaInc.Yakuza0PC_s751p9cej88mt" |
|
|
save_zip_path = "saves.zip" |
|
|
|
|
|
|
|
|
def read_utf16_str(f): |
|
|
str_len = struct.unpack("<i", f.read(4))[0] |
|
|
return f.read(str_len * 2).decode("utf-16") |
|
|
|
|
|
def get_file_paths(): |
|
|
# Find Yakuza save dir |
|
|
wgs_dir = os.path.expandvars(f"%LOCALAPPDATA%\\Packages\\{xgp_app}\\SystemAppData\\wgs") |
|
|
# Get the correct user directory |
|
|
dirs = [d for d in os.listdir(wgs_dir) if d != "t"] |
|
|
dir_count = len(dirs) |
|
|
if dir_count != 1: |
|
|
raise Exception(f"Expected one user directory in wgs directory, found {dir_count}") |
|
|
|
|
|
containers_dir = os.path.join(wgs_dir, dirs[0]) |
|
|
containers_idx_path = os.path.join(containers_dir, "containers.index") |
|
|
|
|
|
save_files = [] |
|
|
|
|
|
# Read the index file |
|
|
with open(containers_idx_path, "rb") as f: |
|
|
# Unknown |
|
|
f.read(4) |
|
|
|
|
|
file_count = struct.unpack("<i", f.read(4))[0] |
|
|
|
|
|
# Unknown |
|
|
f.read(4) |
|
|
|
|
|
store_pkg_name = read_utf16_str(f) |
|
|
|
|
|
# Unknown |
|
|
f.read(12) |
|
|
read_utf16_str(f) |
|
|
|
|
|
# Unknown |
|
|
f.read(8) |
|
|
|
|
|
for _ in range(file_count): |
|
|
# File name |
|
|
fname = read_utf16_str(f) |
|
|
# Duplicate of the file name |
|
|
read_utf16_str(f) |
|
|
# Unknown quoted hex number |
|
|
read_utf16_str(f) |
|
|
# Container number |
|
|
container_num = struct.unpack("B", f.read(1))[0] |
|
|
# Unknown |
|
|
f.read(4) |
|
|
# Read container (folder) GUID |
|
|
container_guid = uuid.UUID(bytes_le=f.read(16)) |
|
|
# Unknown |
|
|
f.read(24) |
|
|
|
|
|
# Read the container file in the file directory |
|
|
with open(os.path.join(containers_dir, container_guid.hex.upper(), f"container.{container_num}"), "rb") as cf: |
|
|
# Unknown |
|
|
cf.read(136) |
|
|
# Read file GUID |
|
|
file_guid = uuid.UUID(bytes_le=cf.read(16)) |
|
|
|
|
|
save_files.append((fname, os.path.join(containers_dir, container_guid.hex.upper(), file_guid.hex.upper()))) |
|
|
|
|
|
return save_files |
|
|
|
|
|
def main(): |
|
|
# Get save file paths with correct names |
|
|
save_files = get_file_paths() |
|
|
|
|
|
# Store the save files into a zip |
|
|
try: |
|
|
with zipfile.ZipFile(save_zip_path, "x") as save_zip: |
|
|
for info in save_files: |
|
|
orig_fname, container_fname = info |
|
|
save_zip.write(container_fname, arcname=orig_fname) |
|
|
except FileExistsError: |
|
|
print(f"Save file container ZIP \"{save_zip_path}\" already exists.") |
|
|
sys.exit(1) |
|
|
|
|
|
print(f"Save files stored to \"{save_zip_path}\"") |
|
|
sys.exit(0) |
|
|
|
|
|
if __name__ == "__main__": |
|
|
main() |