fix NoPayStation PSP parsing + detail-panel UnboundLocalError

- PSP_GAMES.tsv and PSX_GAMES.tsv use different headers (PSP adds Type/RAP
  columns, PSX adds Original Name). Parse the header row and map columns by
  name instead of hardcoding positions — PSP names were showing as 'Minis'/
  'PC Engine' (the Type column) with broken download URLs.
- GameDetailsPanel.set_item referenced is_ps1 before it was assigned, raising
  UnboundLocalError and leaving the detail panel stale.
This commit is contained in:
2026-09-07 21:32:50 -04:00
parent efc8522117
commit d02152ca06
2 changed files with 37 additions and 20 deletions
+1 -1
View File
@@ -682,13 +682,13 @@ class GameDetailsPanel(QWidget):
self.system = item.get("system")
self._ps1_kind = item.get("ps1_kind")
path = item.get("path") or ""
is_ps1 = item.get("system") == "ps1"
if (is_ps1 and item.get("ps1_kind") == "eboot") or item.get("psp_kind") == "eboot":
self.filename.setText(Path(path).parent.name if path else "")
else:
self.filename.setText(Path(path).name if path else "")
self.filename.setToolTip(path)
self.filename.setVisible(bool(path))
is_ps1 = item.get("system") == "ps1"
is_ps1_disc = is_ps1 and item.get("ps1_kind") == "disc"
self.play_btn.setEnabled(
(item.get("system") == "games" and bool(path) and Path(path).suffix.lower() == ".iso")
+36 -19
View File
@@ -23,11 +23,24 @@ log = logging.getLogger("nopaystation")
_TSV_NAMES = {"psp": "PSP_GAMES.tsv", "psx": "PSX_GAMES.tsv"}
# TSV columns (tab-separated), in order.
_COLUMNS = (
"title_id", "region", "name", "pkg_url", "zrif", "content_id",
"date", "size", "sha256",
)
# NoPayStation TSVs are tab-separated with a header row whose exact columns
# differ per platform (PSP has "Type"/"RAP" columns, PSX has "Original Name").
# Map header names to canonical keys instead of relying on fixed positions.
_HEADER_ALIASES = {
"title id": "title_id",
"region": "region",
"name": "name",
"pkg direct link": "pkg_url",
"content id": "content_id",
"last modification date": "date",
"file size": "size",
"sha256": "sha256",
"zrif": "zrif",
"rap": "rap",
"download .rap file": "rap_url",
"type": "content_kind",
"original name": "original_name",
}
def tsv_url(content_type: str, base: str = "") -> str:
@@ -57,29 +70,32 @@ def _parse_date(value: str) -> str:
def parse_tsv(text: str, content_type: str) -> List[dict]:
"""Parse a NoPayStation TSV into store item dicts.
Each item carries the fields the Store page expects (``name``, ``size``,
The header row drives column mapping (PSP and PSX TSVs differ). Each item
carries the fields the Store page expects (``name``, ``size``,
``release_date``, ``mtime_ns``) plus ``title_id``, ``pkg_url``, ``sha256``,
``content_type`` and the cover ``system`` ("games" for PSP, "ps1" for PSX).
"""
system = "games" if content_type == "psp" else "ps1"
items: List[dict] = []
lines = text.splitlines()
for line in lines:
header = None
for line in text.splitlines():
line = line.rstrip("\r\n")
if not line.strip():
continue
cols = line.split("\t")
# Skip the header row.
if cols and cols[0].strip().lower() == "title id":
if header is None:
header = [c.strip().lower() for c in cols]
continue
if len(cols) < 5:
continue
row = dict(zip(_COLUMNS, cols))
name = (row.get("name") or "").strip()
pkg_url = (row.get("pkg_url") or "").strip()
title_id = (row.get("title_id") or "").strip()
row = {}
for i, key in enumerate(header):
canon = _HEADER_ALIASES.get(key)
if canon and i < len(cols):
row[canon] = cols[i].strip()
name = row.get("name") or ""
pkg_url = row.get("pkg_url") or ""
if not name or not pkg_url:
continue
title_id = row.get("title_id") or ""
try:
size = int(float(row.get("size") or 0))
except (TypeError, ValueError):
@@ -92,16 +108,17 @@ def parse_tsv(text: str, content_type: str) -> List[dict]:
items.append({
"name": name,
"title_id": title_id,
"region": (row.get("region") or "").strip(),
"region": row.get("region") or "",
"pkg_url": pkg_url,
"zrif": (row.get("zrif") or "").strip(),
"content_id": (row.get("content_id") or "").strip(),
"zrif": row.get("zrif") or "",
"content_id": row.get("content_id") or "",
"size": size,
"sha256": (row.get("sha256") or "").strip().lower(),
"release_date": date,
"mtime_ns": mtime_ns,
"content_type": content_type,
"system": system,
"content_kind": row.get("content_kind") or "",
})
return items