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.system = item.get("system")
self._ps1_kind = item.get("ps1_kind") self._ps1_kind = item.get("ps1_kind")
path = item.get("path") or "" 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": 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 "") self.filename.setText(Path(path).parent.name if path else "")
else: else:
self.filename.setText(Path(path).name if path else "") self.filename.setText(Path(path).name if path else "")
self.filename.setToolTip(path) self.filename.setToolTip(path)
self.filename.setVisible(bool(path)) self.filename.setVisible(bool(path))
is_ps1 = item.get("system") == "ps1"
is_ps1_disc = is_ps1 and item.get("ps1_kind") == "disc" is_ps1_disc = is_ps1 and item.get("ps1_kind") == "disc"
self.play_btn.setEnabled( self.play_btn.setEnabled(
(item.get("system") == "games" and bool(path) and Path(path).suffix.lower() == ".iso") (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_NAMES = {"psp": "PSP_GAMES.tsv", "psx": "PSX_GAMES.tsv"}
# TSV columns (tab-separated), in order. # NoPayStation TSVs are tab-separated with a header row whose exact columns
_COLUMNS = ( # differ per platform (PSP has "Type"/"RAP" columns, PSX has "Original Name").
"title_id", "region", "name", "pkg_url", "zrif", "content_id", # Map header names to canonical keys instead of relying on fixed positions.
"date", "size", "sha256", _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: 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]: def parse_tsv(text: str, content_type: str) -> List[dict]:
"""Parse a NoPayStation TSV into store item dicts. """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``, ``release_date``, ``mtime_ns``) plus ``title_id``, ``pkg_url``, ``sha256``,
``content_type`` and the cover ``system`` ("games" for PSP, "ps1" for PSX). ``content_type`` and the cover ``system`` ("games" for PSP, "ps1" for PSX).
""" """
system = "games" if content_type == "psp" else "ps1" system = "games" if content_type == "psp" else "ps1"
items: List[dict] = [] items: List[dict] = []
lines = text.splitlines() header = None
for line in lines: for line in text.splitlines():
line = line.rstrip("\r\n") line = line.rstrip("\r\n")
if not line.strip(): if not line.strip():
continue continue
cols = line.split("\t") cols = line.split("\t")
# Skip the header row. if header is None:
if cols and cols[0].strip().lower() == "title id": header = [c.strip().lower() for c in cols]
continue continue
if len(cols) < 5: row = {}
continue for i, key in enumerate(header):
row = dict(zip(_COLUMNS, cols)) canon = _HEADER_ALIASES.get(key)
name = (row.get("name") or "").strip() if canon and i < len(cols):
pkg_url = (row.get("pkg_url") or "").strip() row[canon] = cols[i].strip()
title_id = (row.get("title_id") or "").strip() name = row.get("name") or ""
pkg_url = row.get("pkg_url") or ""
if not name or not pkg_url: if not name or not pkg_url:
continue continue
title_id = row.get("title_id") or ""
try: try:
size = int(float(row.get("size") or 0)) size = int(float(row.get("size") or 0))
except (TypeError, ValueError): except (TypeError, ValueError):
@@ -92,16 +108,17 @@ def parse_tsv(text: str, content_type: str) -> List[dict]:
items.append({ items.append({
"name": name, "name": name,
"title_id": title_id, "title_id": title_id,
"region": (row.get("region") or "").strip(), "region": row.get("region") or "",
"pkg_url": pkg_url, "pkg_url": pkg_url,
"zrif": (row.get("zrif") or "").strip(), "zrif": row.get("zrif") or "",
"content_id": (row.get("content_id") or "").strip(), "content_id": row.get("content_id") or "",
"size": size, "size": size,
"sha256": (row.get("sha256") or "").strip().lower(), "sha256": (row.get("sha256") or "").strip().lower(),
"release_date": date, "release_date": date,
"mtime_ns": mtime_ns, "mtime_ns": mtime_ns,
"content_type": content_type, "content_type": content_type,
"system": system, "system": system,
"content_kind": row.get("content_kind") or "",
}) })
return items return items