Skip to content

Instantly share code, notes, and snippets.

@Z1ni
Created March 21, 2021 20:55
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Z1ni/7a8c1927343dd5570fff0d015f425cca to your computer and use it in GitHub Desktop.
Save Z1ni/7a8c1927343dd5570fff0d015f425cca to your computer and use it in GitHub Desktop.
Yakuza save game "converter" for Xbox Game Pass for PC
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()
@revanmj
Copy link

revanmj commented Dec 23, 2021

This script also worked for extracting Final Fantasy XV saves (I only changed package name to "39EA002F.FINALFANTASYXVforPC_n746a19ndrrjg").

@BFRC-UPCH
Copy link

Would be wonderful to see something like that for MH Rise

@proigral
Copy link

proigral commented Feb 24, 2024

Hi! Thanks for the script, it works, but I can't use the resulting save in the version of the game from the GOG store. Is this script compatible with it or does it only work with Steam?

image
image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment