#!/usr/bin/env python3 """build_art_table.py - resolve Sony CDN artwork for a title list via TMDB. Runs on the BUILD host (console network blocks TMDB port 80) and emits orbisrpc/art_table.h: {titleId, name, icon_url} for every resolvable title. Also downloads each icon into icons/ for the art_base_url pack. Usage: python3 scripts/build_art_table.py titles.txt [--icons icons/] Title list: one TITLEID per line (CUSAxxxxx etc.); unknown IDs are skipped. """ import hashlib import hmac import json import os import sys import urllib.request KEY = bytes([ 0xF5, 0xDE, 0x66, 0xD2, 0x68, 0x0E, 0x25, 0x5B, 0x2D, 0xF7, 0x9E, 0x74, 0xF8, 0x90, 0xEB, 0xF3, 0x49, 0x26, 0x2F, 0x61, 0x8B, 0xCA, 0xE2, 0xA9, 0xAC, 0xCD, 0xEE, 0x51, 0x56, 0xCE, 0x8D, 0xF2, 0xCD, 0xF2, 0xD4, 0x8C, 0x71, 0x17, 0x3C, 0xDC, 0x25, 0x94, 0x46, 0x5B, 0x87, 0x40, 0x5D, 0x19, 0x7C, 0xF1, 0xAE, 0xD3, 0xB7, 0xE9, 0x67, 0x1E, 0xEB, 0x56, 0xCA, 0x67, 0x53, 0xC2, 0xE6, 0xB0, ]) TMDB = "http://tmdb.np.dl.playstation.net" def c_escape(s): return s.replace("\\", "\\\\").replace('"', '\\"') def resolve(tid): mac = hmac.new(KEY, (tid + "_00\x00").encode("latin1")[:12], hashlib.sha1).hexdigest().upper() url = f"{TMDB}/tmdb2/{tid}_00_{mac}/{tid}_00.json" req = urllib.request.Request(url, headers={"User-Agent": "orbisRPC-arttbl"}) d = json.loads(urllib.request.urlopen(req, timeout=20).read()) name = d["names"][0]["name"] icon = d["icons"][0]["icon"].replace("http://", "https://") return name, icon def main(): titles = [l.strip() for l in open(sys.argv[1]) if l.strip()] icons = sys.argv[3] if len(sys.argv) > 3 and sys.argv[2] == "--icons" else None if icons: os.makedirs(icons, exist_ok=True) rows = [] for tid in titles: try: name, icon = resolve(tid) except Exception as e: print(f"MISS {tid}: {str(e)[:60]}", flush=True) continue print(f"OK {tid} :: {name}", flush=True) rows.append((tid, name, icon)) if icons: try: img = urllib.request.urlopen(icon, timeout=25).read() open(os.path.join(icons, tid.lower() + ".png"), "wb").write(img) except Exception as e: print(f" icon fetch failed: {str(e)[:60]}", flush=True) with open("orbisrpc/art_table.h", "w") as f: f.write("/* art_table.h - GENERATED by scripts/build_art_table.py.\n") f.write(" * Do not edit: Sony CDN artwork baked at build time so the\n") f.write(" * console needs no TMDB network access for art.\n") f.write(f" * Titles: {len(rows)}. */\n") f.write("#ifndef ORBISRPC_ART_TABLE_H\n#define ORBISRPC_ART_TABLE_H\n") f.write("#include \n") f.write("typedef struct { const char *id; const char *name;" " const char *icon; } art_entry_t;\n") f.write("static const art_entry_t ORBISRPC_ART_TABLE[] = {\n") for tid, name, icon in rows: f.write(f' {{ "{tid}", "{c_escape(name)}", "{icon}" }},\n') f.write("};\n#define ORBISRPC_ART_TABLE_N " f"(sizeof ORBISRPC_ART_TABLE / sizeof ORBISRPC_ART_TABLE[0])\n") f.write("#endif\n") print(f"wrote art_table.h with {len(rows)} entries") if __name__ == "__main__": main()