feat: ROM-hack dedup, conflict resolution, and vault library_path resync

Catalog cleanup pass across gb/gbc/gba/nds/3ds:
- Standardize nonstandard hack filenames to `Pokemon - <Name> [Hack].<ext>`;
  remove verified byte-identical duplicates (conform-rom-names.sh).
- Resolve 9 version-conflicts via side-by-side emulator comparison
  (resolve-version-conflicts.sh): promote newer builds, keep genuine
  distinct hacks, fix a mislabeled Dark Rising slot (was the Digimon
  crossover "Worlds Collide"), and rebuild Mega Power 5.77 + HeartGold
  Generations v2.0 fresh from their patches.
- Resync 286 vault notes' `library_path` to the real on-disk [Hack] files
  and fix 15 stale platform fields (resync-vault-library-paths.py).
- Regenerate MOC tables + wiki/catalog.json (396 notes).

Also includes pending Playnite PC-hack setup/repair scripts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
ginnoir
2026-06-25 14:29:05 -05:00
co-authored by Claude Opus 4.8
parent ae56d101b7
commit 809600a04d
8 changed files with 755 additions and 435 deletions
+32 -106
View File
@@ -1,127 +1,53 @@
<#
.SYNOPSIS
Configure Playnite + RomM plugin for Pokémon PC hacks (windows / pc_windows).
Configure RomM plugin mapping for PC hacks. Emulator must be added in Playnite UI (see below).
Playnite must be fully closed before running.
Playnite must be fully closed before running (only touches RomM plugin config.json).
#>
$ErrorActionPreference = 'Stop'
$PlayniteDir = Join-Path $env:LOCALAPPDATA 'Playnite'
$LiteDbDll = Join-Path $PlayniteDir 'LiteDB.dll'
$EmuDb = Join-Path $PlayniteDir 'library\emulators.db'
$RommConfig = Join-Path $PlayniteDir 'ExtensionsData\9700aa21-447d-41b4-a989-acd38f407d9f\config.json'
$Launcher = 'C:\Users\MattC\Documents\pokemon\scripts\launch-pc-hack.ps1'
$DestPath = 'C:\Users\MattC\roms\windows'
$EmuName = 'Pokemon PC Hack Launcher'
$RommConfig = Join-Path $env:LOCALAPPDATA 'Playnite\ExtensionsData\9700aa21-447d-41b4-a989-acd38f407d9f\config.json'
$DestPath = 'C:\Users\MattC\roms\windows'
$PlatformSpec = 'pc_windows'
$ProfileName = 'Default'
$ProfileId = [guid]::NewGuid().ToString()
if (Get-Process -Name 'Playnite*' -ErrorAction SilentlyContinue) {
throw 'Close Playnite completely, then re-run this script.'
}
foreach ($p in @($LiteDbDll, $Launcher, $RommConfig)) {
if (-not (Test-Path $p)) { throw "Missing: $p" }
}
if (-not (Test-Path $RommConfig)) { throw "RomM config not found: $RommConfig" }
$pwshCmd = Get-Command pwsh -ErrorAction SilentlyContinue
if ($pwshCmd) { $pwsh = $pwshCmd.Source } else { $pwsh = (Get-Command powershell).Source }
$emu = Get-Content -Raw $RommConfig | ConvertFrom-Json | ForEach-Object { $_.Mappings } |
Where-Object { $_.PlatformId -eq $PlatformSpec } | Select-Object -First 1
Add-Type -Path $LiteDbDll
if (-not $emu -or -not $emu.EmulatorId) {
Write-Host @'
$installDir = Split-Path -Parent $Launcher
$arguments = "-ExecutionPolicy Bypass -File `"$Launcher`" `"{ImagePath}`""
Add the Playnite emulator first (Library -> Configure Emulators -> Add):
function New-BsonArray([object[]]$items) {
$list = New-Object 'System.Collections.Generic.List[LiteDB.BsonValue]'
foreach ($item in $items) {
if ($item -is [LiteDB.BsonDocument]) { $list.Add($item) }
elseif ($item -is [LiteDB.BsonValue]) { $list.Add($item) }
else { $list.Add([LiteDB.BsonValue]$item) }
}
return [LiteDB.BsonArray]::new($list)
}
Name: Pokemon PC Hack Launcher
Install: C:\Users\MattC\Documents\pokemon\scripts
Profile: Default
Platforms: PC (Windows)
Extensions: zip
Executable: C:\Program Files\PowerShell\7\pwsh.exe
Arguments: -ExecutionPolicy Bypass -File "C:\Users\MattC\Documents\pokemon\scripts\launch-pc-hack.ps1" "{ImagePath}"
Working dir: {InstallDir}
$profileDoc = [LiteDB.BsonDocument]::new()
$profileDoc['_id'] = [LiteDB.BsonValue][guid]::Parse($ProfileId)
$profileDoc['Name'] = $ProfileName
$profileDoc['Platforms'] = New-BsonArray @($PlatformSpec)
$profileDoc['ImageExtensions'] = New-BsonArray @('zip')
$profileDoc['Executable'] = $pwsh
$profileDoc['Arguments'] = $arguments
$profileDoc['WorkingDirectory'] = '{InstallDir}'
$profileDoc['UseShellExecute'] = $false
$profileDoc['OverrideDefaultArgs'] = $false
$profileDoc['ScriptStartup'] = $false
$profileDoc['ScriptGameImport'] = $false
$profileDoc['TrackingMode'] = 0
$profileDoc['TrackingPath'] = $null
Then in RomM plugin settings, map pc_windows -> that emulator, destination
'@ + $DestPath + @', Auto-extract ON.
$db = [LiteDB.LiteDatabase]::new("Filename=$EmuDb")
try {
$col = $db.GetCollection('Emulator')
$existing = $col.FindOne([LiteDB.Query]::EQ('Name', $EmuName))
if ($existing) {
$emuDoc = $existing
$emuId = $existing['_id'].AsGuid.ToString()
if ($emuId -eq '00000000-0000-0000-0000-000000000000') {
$emuId = $existing['_id'].AsString
}
$profiles = $emuDoc['CustomProfiles'].AsArray
if ($profiles.Count -gt 0) {
$p0 = $profiles[0]
if ($p0['_id'].IsGuid) { $ProfileId = $p0['_id'].AsGuid.ToString() }
else { $ProfileId = $p0['_id'].AsString }
$profileDoc['_id'] = [LiteDB.BsonValue][guid]::Parse($ProfileId)
}
Write-Host "Updating existing emulator '$EmuName' ($emuId)"
} else {
$emuId = [guid]::NewGuid().ToString()
$emuDoc = [LiteDB.BsonDocument]::new()
$emuDoc['_id'] = [LiteDB.BsonValue][guid]::Parse($emuId)
$profileDoc['_id'] = [LiteDB.BsonValue][guid]::Parse($ProfileId)
Write-Host "Creating emulator '$EmuName' ($emuId)"
}
Or re-run this script after the emulator exists (it reads EmulatorId from an existing pc_windows mapping).
$emuDoc['Name'] = $EmuName
$emuDoc['InstallDir'] = $installDir
$emuDoc['BuiltInConfigId'] = $null
$profilesList = New-Object 'System.Collections.Generic.List[LiteDB.BsonValue]'
$profilesList.Add($profileDoc)
$emuDoc['CustomProfiles'] = [LiteDB.BsonArray]::new($profilesList)
$col.Upsert($emuDoc) | Out-Null
} finally {
$db.Dispose()
'@
exit 1
}
$cfg = Get-Content -Raw $RommConfig | ConvertFrom-Json
$mapping = $cfg.Mappings | Where-Object { $_.PlatformId -eq $PlatformSpec } | Select-Object -First 1
if ($mapping) {
$mapping.EmulatorId = $emuId
$mapping.EmulatorProfileId = $ProfileId
$mapping.DestinationPath = $DestPath
$mapping.AutoExtract = $true
$mapping.Enabled = $true
$mapping.SyncSaves = $false
Write-Host 'Updated RomM mapping for pc_windows.'
} else {
$cfg.Mappings += [pscustomobject]@{
MappingId = [guid]::NewGuid()
Enabled = $true
AutoExtract = $true
UseM3u = $false
SyncSaves = $false
SaveStrategy = 0
SaveDirOverride = $null
EmulatorId = $emuId
EmulatorProfileId = $ProfileId
PlatformId = $PlatformSpec
DestinationPath = $DestPath
}
Write-Host 'Added RomM mapping for pc_windows.'
$m = $cfg.Mappings | Where-Object { $_.PlatformId -eq $PlatformSpec } | Select-Object -First 1
if (-not $m) {
Write-Host 'No pc_windows mapping yet — add it in RomM plugin settings after creating the emulator.'
exit 1
}
$m.DestinationPath = $DestPath
$m.AutoExtract = $true
$m.Enabled = $true
$m.SyncSaves = $false
New-Item -ItemType Directory -Force -Path $DestPath | Out-Null
$cfg | ConvertTo-Json -Depth 8 -Compress | Set-Content -Encoding UTF8 $RommConfig
Write-Host "Playnite configured. ROM download dir: $DestPath"
Write-Host "Open Playnite -> Menu -> Library -> Import RomM library (windows games)."
Write-Host "RomM pc_windows mapping updated (destination $DestPath, AutoExtract on)."