From 02b4adb1c1e3ddde5777d6e665e8d0cf5ab58534 Mon Sep 17 00:00:00 2001 From: Mathieu Moran Date: Mon, 7 Sep 2026 16:07:51 -0400 Subject: [PATCH] PS1: match popstation PSAR header byte-for-byte; drop fake KEYS.BIN/DOCUMENT.DAT --- README.md | 7 +++--- app/ps1_convert.py | 60 ++++++++++++++++++++-------------------------- 2 files changed, 30 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 23b95b9..50b6848 100644 --- a/README.md +++ b/README.md @@ -190,9 +190,10 @@ PS1 discs (pending) and converted EBOOTs also show up in the **Library** tab - **base.pbp** — conversion needs a PSN PS1 classic `EBOOT.PBP` (any game) to supply Sony's encrypted `DATA.PSP` loader. Copy one to `data/base.PBP` and point **Settings → General → PS1 base.pbp** at it. -- **Output** — a PSN-style folder `//` containing `EBOOT.PBP`, - `KEYS.BIN` and `DOCUMENT.DAT`, ready to copy to - `/PSP/GAME//`. +- **Output** — a folder `//` containing a single `EBOOT.PBP` + (the popstation way: uncompressed level-0 PSAR, no `KEYS.BIN`/`DOCUMENT.DAT`), + ready to copy to `/PSP/GAME//`. Compatible with CFW POPS + (ARK-4). Configure the folders in **Settings → General** (or `PSP_PS1_ROOTS` / `PSP_PS1_OUTPUT_DIR`). Current limits: single-disc games only (no multi-disc diff --git a/app/ps1_convert.py b/app/ps1_convert.py index 8ab58b6..334cbf6 100644 --- a/app/ps1_convert.py +++ b/app/ps1_convert.py @@ -507,25 +507,35 @@ def build_toc(disc: "DiscImage") -> bytes: def _build_psar_header(disc: "DiscImage", game_id: str, title: str) -> bytes: - """Build the fixed PSAR header region 0x400..0x4000. + """Build the fixed PSAR header region 0x400..0x4000, byte-identical to the + popstation level-0 writer. - Layout mirrors the reference writer's data1/data2 templates: - * game id at +0x000 (11 bytes, "_SLUS_00594" form) - * TOC at +0x400 (i.e. absolute PSAR +0x800) - * title at data2 offset 8 (absolute PSAR +0x1228), 128 bytes max + Layout (offsets relative to the PSAR start): + * 0x400 data1 (0xE20 bytes): game id at +0, TOC at +0x400, 0x10 at +0x7FE + * 0x1220 p2 offset (u32) = padded ISO size + 0x100000 + 0x2D31 + * 0x1224 data2 (0x2DDC bytes): FF 07 at +4, title at +8, 0x03 at +0x88 """ - block = bytearray(PSAR_INDEX_OFFSET - PSAR_GAMEID_OFFSET) # 0x400..0x4000 + data1_size = 0xE20 + data2_size = 0x2DDC + p2_delta = 0x2D31 + + block = bytearray(data1_size + 4 + data2_size) # 0x3C00 = 0x400..0x4000 gid = _format_psar_game_id(game_id).encode("ascii")[:11] block[: len(gid)] = gid toc = build_toc(disc) - toc_off = PSAR_TOC_OFFSET - PSAR_GAMEID_OFFSET # 0x400 - block[toc_off: toc_off + len(toc)] = toc + block[0x400: 0x400 + len(toc)] = toc + block[0x7FE] = 0x10 + p2 = disc.padded_size() + PSAR_ISO_OFFSET + p2_delta + block[data1_size: data1_size + 4] = struct.pack(" bytes: - """Minimal placeholder manual (DOCUMENT.DAT). - - Blank is safe: POPS simply shows no manual when the file is empty. - """ - return b"" - - def convert_to_folder( source: Path, title: str, @@ -764,12 +763,12 @@ def convert_to_folder( progress_cb=None, cancel_event=None, ) -> Path: - """Convert a PS1 disc into a PSN-style game folder ready for /PSP/GAME. + """Convert a PS1 disc into a game folder ready for /PSP/GAME. - Writes ``//`` containing ``EBOOT.PBP``, ``KEYS.BIN`` and - ``DOCUMENT.DAT``. The PSAR is stored uncompressed/unencrypted (matching the - popstation/PSXPackager level-0 path), so KEYS.BIN is a zero placeholder that - keeps the folder structure identical to a real PSN classic. + Writes ``//EBOOT.PBP`` — the same single-file output the + popstation tool produces (uncompressed/unencrypted level-0 PSAR). No + KEYS.BIN or DOCUMENT.DAT is written: popstation doesn't emit them and CFW + (ARK-4 POPS) doesn't need them. """ folder_name = re.sub(r"[^A-Za-z0-9]", "", game_id).upper() or "PS1GAME" folder = Path(out_dir) / folder_name @@ -777,13 +776,6 @@ def convert_to_folder( convert(source, title, game_id, base_pbp, folder / "EBOOT.PBP", progress_cb=progress_cb, cancel_event=cancel_event) - - keys_path = folder / "KEYS.BIN" - if not keys_path.exists(): - keys_path.write_bytes(b"\x00" * KEYS_BIN_SIZE) - doc_path = folder / "DOCUMENT.DAT" - if not doc_path.exists(): - doc_path.write_bytes(_document_dat()) return folder @@ -873,7 +865,7 @@ def main(argv: Optional[List[str]] = None) -> int: ap.add_argument("--game-id", help="PS1 game id, e.g. SLUS00594") ap.add_argument("--base-pbp", help="path to a PSN PS1 classic EBOOT.PBP (required)") ap.add_argument("--out", help="output EBOOT.PBP path (single file)") - ap.add_argument("--out-dir", help="output folder: writes //EBOOT.PBP (+KEYS.BIN, DOCUMENT.DAT)") + ap.add_argument("--out-dir", help="output folder: writes //EBOOT.PBP") ap.add_argument("--test", action="store_true", help="run self-test and exit") args = ap.parse_args(argv)