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

@SANTERCOREJZ
Copy link

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

у меня такая же проблема через Steam

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