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()
@monodop
Copy link

monodop commented Jun 29, 2021

Thanks for building this! I was able to modify it to work with Snowrunner
https://gist.github.com/monodop/f74ee058c173d526713317e1aa2870aa

@Z1ni
Copy link
Author

Z1ni commented Jul 2, 2021

@monodop

Thanks for building this! I was able to modify it to work with Snowrunner
https://gist.github.com/monodop/f74ee058c173d526713317e1aa2870aa

Cool! I've modified this Gist after publishing to support more games and be a bit more extensible and I just pushed that to a separate repo at https://github.com/Z1ni/XGP-save-extractor, so if you'd like to make (mostly the same) contributions there, feel free to do so!

@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