play/scripts/yuzu2ryujinx.py

57 lines
1.9 KiB
Python
Raw Normal View History

2024-03-15 23:49:24 +00:00
#!/usr/bin/env nix-shell
#!nix-shell -i python3 -p python3
'''
Convert Yuzu saves to Ryujinx by putting them in profile 0 (RyuPlayer)
Note that this is rather destructive and won't deal well with multiple profiles,
so back saves up before trying it
(if anyone wants to modify it, it's CC0)
'''
from pathlib import Path
import os
import shutil
import struct
import typing
RYUJINX_DIR = Path.home() / ".config" / "Ryujinx"
YUZU_DIR = Path.home() / ".local" / "share" / "yuzu"
def get_yuzu_save_dir():
profile_path = YUZU_DIR / "nand" / "system" / "save" / "8000000000000010" / "su" / "avators" / "profiles.dat"
with open(profile_path, "rb") as f:
# Just go with profile 0
uid_low, uid_high = struct.unpack("<QQ", f.read(0x20)[0x10:])
return YUZU_DIR / "nand" / "user" / "save" / "0000000000000000" / f"{uid_high:016X}{uid_low:016X}"
RYUJINX_SAVE_DIR = RYUJINX_DIR / "bis" / "user" / "save"
YUZU_SAVE_DIR = get_yuzu_save_dir()
titles: typing.Dict[str, Path] = dict()
# The first 8 bytes of ExtraData0 are the program ID, next 16 are user ID.
2024-03-16 00:08:40 +00:00
# Ignore ExtraData1, that's just for journaling
2024-03-15 23:49:24 +00:00
# https://github.com/Thealexbarney/LibHac/blob/master/src/LibHac/Fs/Common/SaveDataTypes.cs#L97
# https://github.com/Thealexbarney/LibHac/blob/master/src/LibHac/Fs/Common/SaveDataTypes.cs#L131
for child in RYUJINX_SAVE_DIR.iterdir():
with open(child / "ExtraData0", "rb") as extra:
data = extra.read(24)
title_id, *uid = struct.unpack("<QQQ", data)
if uid not in ([0, 0], [1, 0]):
# Always use default profile
print("Found and ignored save for user {:016X}{:016X} ({:016X})".format(*uid, title_id))
continue
titles[f"{title_id:016X}"] = child / "0"
for title_id, ryujinx_dir in titles.items():
yuzu_dir = YUZU_SAVE_DIR / title_id
if not yuzu_dir.exists():
continue
print(yuzu_dir, ryujinx_dir)
shutil.rmtree(ryujinx_dir)
shutil.copytree(yuzu_dir, ryujinx_dir)