#!/usr/bin/env python3 """Generate a Ludusavi secondary manifest for the PC hacks served by RomM. Pokémon Essentials (v18+) writes saves to: %USERPROFILE%/Saved Games//Game.rxdata The exact subfolder is the `Title=` field of each game's Game.ini, so we read it straight out of the placed archive instead of guessing. Older Essentials (<=v17) saved into the game folder itself — covered by the commented `` fallback. Point Ludusavi at the emitted YAML as a secondary/custom manifest, then pair its backup target with Syncthing to replicate saves across Windows / Steam Deck. Run on valhalla after romhack-pc-migrate.py --apply: python3 gen-ludusavi-manifest.py > ludusavi/pokemon-pc-hacks.yaml """ import os, sys, zipfile, configparser, io WIN = "/storage1/Emulation/roms/windows" def sq(s): """YAML single-quoted scalar: an embedded apostrophe must be doubled.""" return "'" + str(s).replace("'", "''") + "'" def game_title(zip_path): """Pull Title= from the first */Game.ini inside the archive, if present.""" try: with zipfile.ZipFile(zip_path) as z: ini = next((n for n in z.namelist() if n.lower().endswith("game.ini")), None) if not ini: return None raw = z.read(ini).decode("utf-8", "ignore") cp = configparser.ConfigParser(strict=False, interpolation=None) cp.read_string(raw if raw.lstrip().startswith("[") else "[Game]\n" + raw) for sect in cp.sections(): if cp.has_option(sect, "Title"): return cp.get(sect, "Title").strip() except Exception as e: print(f"# WARN {os.path.basename(zip_path)}: {e}", file=sys.stderr) return None def main(): print("# Ludusavi secondary manifest — Pokémon PC hacks (generated)") print("# Add via Ludusavi > Settings > Manifests, or merge into a custom manifest.") print("# Save path = ~/Saved Games/ (Essentials v18+).") print("# Verify each on first save; for Essentials <=v17 uncomment the line.\n") zips = sorted(f for f in os.listdir(WIN) if f.lower().endswith(".zip")) for f in zips: name = os.path.splitext(f)[0] # "Pokemon - (Hack)" title = game_title(os.path.join(WIN, f)) print(f"{sq(name)}:") print(" files:") if title: print(f" {sq('/Saved Games/' + title)}:") print(" tags: [save]") else: print(f" # TODO no Game.ini Title found — set save path manually") print(f" {sq('/Saved Games/' + name)}:") print(" tags: [save]") print(" # '': # older Essentials saved in the game folder") print(" # tags: [save]") if __name__ == "__main__": main()