ginnoirandClaude Opus 5 b8c69ce789 fix: repair 7 mojibake note filenames
Seven hack notes had filenames whose UTF-8 bytes had been decoded as cp437 and
re-encoded, e.g. "Anil.md" stored as "A" + U+251C + U+2592 + "il.md". Only the
filenames were affected; each note's frontmatter title was already correct.

Repaired by reversing the corruption (name.encode('cp437').decode('utf-8')) and
cross-checking every result against that note's own title field. PokeRogue was
left alone - its U+00E9 cannot be cp437-encoded, proving it is genuine UTF-8 and
not mojibake.

This matters because build-wiki-data.py derives each catalog stem from the
filename and the site derives its URL slug from that. Four published URLs were
wrong and are now fixed:

  /hack/a-il/                -> /hack/anil/
  /hack/fused-adimensions/   -> /hack/fused-dimensions/
  /hack/yu-gi-oh-pok-duel/   -> /hack/yu-gi-oh-pokeduel/
  /hack/recharged-yellow-previously-known-as-pok-mon-yellow-cross/
                             -> ...-as-pokemon-yellow-cross/

The other three were already served correctly, because slugifyStem in the site
repo carries a replaceAll for the mojibake apostrophe. Those URLs are unchanged.

Fallout handled: of 19 vault references, 18 live in files build-vault-mocs.py
regenerates and were fixed by rerunning it. The one hand-written reference, in
Nuzlocke-Friendly (Vanilla+).md, was updated directly. annotate-engines.py also
keyed its ENGINE_MAP on the corrupted stem, so a future run would have silently
skipped Recharged Yellow; that key is corrected here.

Verified: zero mojibake sequences remain anywhere in the vault or this repo, all
seven notes resolve under their new names, catalog steady at 411, and the site
builds 486 pages with banners regenerated under the new slugs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 18:11:31 -05:00

Pokémon ROM Hacks — tooling

Acquisition + patching pipeline for the Pokémon ROM-hack collection in the valhalla homelab library. Spun off from the homelabstack repo on 2026-06-06.

The catalog (the notes) lives in the self-hosted Obsidian vault, not in this repo — under Pokémon ROM Hacks/: one note per hack, plus Index, Types, Play Queue, Bases/, Platforms/, a Wanted acquisition list, and a _Claude.md ops reference. Access it via the Obsidian MCP (mcp__obsidian__*) or the vault skill. New hacks are added there manually (see the vault's _Claude.md checklist). This repo is just the tooling.

The ROMs live on valhalla under /storage1/Emulation/roms/<platform>/, served by the RomM stack (roms.ginnoir.com) — deployed from homelabstack (stacks/roms/).

Layout

Path What
scripts/romhack-import.py Import a drop folder of mixed ROMs/patches/docs into the valhalla library (validate, clean-name, apply patches, archive). Runs on valhalla.
scripts/romhack-fetch.py Acquire + patch + place worthwhile hacks from direct (non-gated) sources. Manifest-driven. Runs on valhalla.
scripts/romhack-apply.py Apply a single IPS/BPS/UPS patch to a base ROM with checksum verification.
scripts/romhack-pc-migrate.py Place the PC/Windows fan-games (RPG-Maker/Essentials) from the scrape staging tree into RomM's windows platform so RomM catalogs + serves them like the console hacks. Dry-run default; --apply copies, repackaging .rar→.zip. Runs on valhalla.
scripts/launch-pc-hack.ps1 Playnite custom-emulator launcher for the PC hacks: finds Game.exe in the RomM-downloaded folder, sets CWD, runs it. Runs on the Windows client.
scripts/setup-playnite-pc-hack.ps1 One-shot Playnite setup: registers the custom emulator + RomM pc_windows mapping (AutoExtract, download dir). Playnite must be closed.
scripts/build-vault-mocs.py Regenerate the vault's static MOC tables + header counts (Index/Platforms/Bases) from the notes. Read-only on Hacks/*.md. Re-run after any note change.
scripts/scrape_match.py Match the Discord-scrape metadata.json entries to vault notes → .scrape/_match_report.json (curated-overlap / discord-match / unmatched).
scripts/enrich_vault.py Synthesize the scrape into notes: backfill (frontmatter for imports), merge (scrape into curated), create (new notes), rebuild (normalize import bodies). --apply writes; default dry-runs to .scrape/_preview/.
scripts/enrich_web.py Apply hand/web-researched facts from .scrape/web_facts*.json onto notes (summary, features, dev, version, notability), idempotently.
scripts/build-wiki-data.py Export all notes → wiki/catalog.json, the data source for the wiki site (see wiki/SPEC.md).
scripts/build-romhack-vault.py Archived. Original 75-hack bootstrap generator — do not re-run against the live vault. Kept only for its structured HACKS dataset.
wiki/ catalog.json (exported 368-hack dataset) + SPEC.md (build spec for the future static wiki site).
drop/ gitignored staging area (~1.3 GB) of ROM/patch binaries pending import.
.scrape/ gitignored per-machine scratch: Discord-scrape metadata.json pulled from valhalla + match report + web_facts*.json + note backups.

Enrichment pipeline

The vault catalog (368 notes) was enriched from the Discord scrape on valhalla (/storage1/labdata/romhacks/metadata/*/metadata.json) plus web research. To re-run or extend:

# 1. pull the scrape metadata locally (one-time / refresh)
ssh valhalla "cd /storage1/labdata/romhacks/metadata && tar cf - */metadata.json" | tar xf - -C .scrape

# 2. match scrape -> notes
python scripts/scrape_match.py --report .scrape/_match_report.json

# 3. synthesize (drop --apply first to preview into .scrape/_preview/)
python scripts/enrich_vault.py backfill --apply
python scripts/enrich_vault.py rebuild  --apply
python scripts/enrich_vault.py merge    --apply
python scripts/enrich_vault.py create   --apply

# 4. web-enrich more hacks: add a .scrape/web_factsN.json batch, then
python scripts/enrich_web.py --apply

# 5. refresh static surfaces + the site data
python scripts/build-vault-mocs.py
python scripts/build-wiki-data.py

Art for every scraped hack is web-served at https://romhacks-files.ginnoir.com/_meta/<scrape_dir>/<file> (copied into the Caddy-served library/_meta/ tree on valhalla).

PC / Windows hacks

The 40 platform: PC catalog entries are RPG-Maker XP / Pokémon Essentials fan-games (Game.exe + Data/*.rxdata), not emulatable ROMs. 32 have files in the scrape staging tree (/storage1/labdata/romhacks/library/<dir>/); the other 8 are gated (Reborn, Rejuvenation, Infinite Fusion, …) and live catalog-only on the vault Wanted list.

They are served through the same RomM stack as the console hacks, under the windows platform (RomM's "Microsoft Windows"; note pc is excluded in RomM's config.yml, windows is not). Each hack is a single archive in the platform root — Pokemon - <Hack> [Hack].zip — NOT in a subfolder (a subfolder would make RomM treat the whole thing as one multi-file game).

# place the PC hacks into roms/windows/ (dry-run first)
python3 scripts/romhack-pc-migrate.py --catalog wiki/catalog.json
python3 scripts/romhack-pc-migrate.py --catalog wiki/catalog.json --apply
# then scan just that platform in RomM, or via API
  • Windows play: RomM Playnite plugin downloads + extracts the zip, then launches via scripts/launch-pc-hack.ps1 registered as a Custom emulator.
  • Steam Deck: no automated path — RetroDECK has no Windows runtime. mkxp-z ports (ship mkxp.json) run natively on Linux; the rest need manual Proton.
  • Save sync: RomM's save sync is emulator-only (won't cover these). Use Ludusavi + Syncthing; Essentials saves land in ~/Saved Games/<Game.ini Title>/.

Acquisition policy

Pre-3DS handhelds (gb/gbc/gba/nds) + pre-Wii consoles. Direct, non-anti-bot sources only for auto-fetch; gated hacks are tracked in the vault's Pokémon ROM Hacks/Wanted.md for manual hand-off. No offensive/"shitpost" content (e.g. Clover excluded).

S
Description
Pokemon ROM-hack acquisition tooling. Catalog notes live in the Obsidian vault (Pokemon ROM Hacks/).
Readme
5.7 MiB
0 Stars 1 Watchers 0 Forks
Languages
Python 91.4%
PowerShell 5.6%
Shell 3%