feat: add PC fan-game migration tooling for RomM windows platform
Place RPG-Maker/Essentials hacks into RomM's windows library with dry-run migration, Playnite Game.exe launcher, and Ludusavi save-path manifest.
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
#!/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.ini Title>/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 `<base>` 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/<Game.ini Title> (Essentials v18+).")
|
||||
print("# Verify each on first save; for Essentials <=v17 uncomment the <base> 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> (Hack)"
|
||||
title = game_title(os.path.join(WIN, f))
|
||||
print(f"{sq(name)}:")
|
||||
print(" files:")
|
||||
if title:
|
||||
print(f" {sq('<home>/Saved Games/' + title)}:")
|
||||
print(" tags: [save]")
|
||||
else:
|
||||
print(f" # TODO no Game.ini Title found — set save path manually")
|
||||
print(f" {sq('<home>/Saved Games/' + name)}:")
|
||||
print(" tags: [save]")
|
||||
print(" # '<base>': # older Essentials saved in the game folder")
|
||||
print(" # tags: [save]")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user