Skip to content

Instantly share code, notes, and snippets.

@xlj44400
Forked from Z1ni/yakuza_save_convert.py
Created October 21, 2022 06:20
Show Gist options
  • Save xlj44400/b5e46a7c5f3ccbae82f49136eec68a82 to your computer and use it in GitHub Desktop.
Save xlj44400/b5e46a7c5f3ccbae82f49136eec68a82 to your computer and use it in GitHub Desktop.

Revisions

  1. @Z1ni Z1ni created this gist Mar 21, 2021.
    99 changes: 99 additions & 0 deletions yakuza_save_convert.py
    Original file line number Diff line number Diff line change
    @@ -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()