ps1: bundle plain-ELF POPS loader + KEYS.BIN (iPoPS, MIT)

Fix 80020148 'game could not be started' on ARK-5: the base PBP's encrypted
~PSP loader fails to link on modern CFW. Use the bundled plain-ELF loader
(data/loader/DATA.PSP) as the DATA.PSP section instead, and write the 16-byte
NPDRM version key (data/loader/KEYS.BIN) alongside the EBOOT so CFW can
decrypt the STARTDAT PGD. Both assets are from iPoPS (MIT, attributed).

- load_data_psp: prefer the bundled ELF, fall back to the base ~PSP loader.
- load_keys_bin: return the bundled 16-byte key when present.
- convert: write KEYS.BIN next to the output EBOOT.
- README + data/loader/ATTRIBUTION.md: document the loader and its license.
This commit is contained in:
2026-09-07 17:44:54 -04:00
parent ea08f52d44
commit f61a77a31f
5 changed files with 89 additions and 19 deletions
+15 -12
View File
@@ -93,9 +93,10 @@ Discs are matched by hash first (most precise), then serial, then filename.
- `data/covers/` — checked-in assets: the UMD video/game cover templates, - `data/covers/` — checked-in assets: the UMD video/game cover templates,
`libretro-index.json` + `openvgdb-index.json` catalogs, `libretro-covers.zip` + `libretro-index.json` + `openvgdb-index.json` catalogs, `libretro-covers.zip` +
`openvgdb-covers.zip` offline cover bundles, and the legacy cover cache. `openvgdb-covers.zip` offline cover bundles, and the legacy cover cache.
- `data/base.PBP` — a PSN PS1 classic EBOOT (any game) used as the encrypted - `data/base.PBP` — a PSN PS1 classic EBOOT (any game) supplying `STARTDAT`
loader when converting PS1 discs to `EBOOT.PBP` (git-ignored, copyrighted). + a base `PARAM.SFO`/icon for PS1 conversion (git-ignored, copyrighted).
Prefer a 6.60/6.61-era classic; 3.xx-era loaders can fail on modern CFW. - `data/loader/DATA.PSP` + `data/loader/KEYS.BIN` — bundled plain-ELF POPS
loader and NPDRM version key (from iPoPS, MIT).
- State dir (`~/.local/state/psp-preserve/` by default, or `$XDG_STATE_HOME/psp-preserve`): - State dir (`~/.local/state/psp-preserve/` by default, or `$XDG_STATE_HOME/psp-preserve`):
- `library.sqlite` — the catalog + library database. - `library.sqlite` — the catalog + library database.
- `covers/` — downloaded cover cache. - `covers/` — downloaded cover cache.
@@ -189,16 +190,18 @@ PS1 discs (pending) and converted EBOOTs also show up in the **Library** tab
- **Serial detection** — reads `SYSTEM.CNF` to derive the game ID (e.g. - **Serial detection** — reads `SYSTEM.CNF` to derive the game ID (e.g.
`SLUS-00001`) and writes it into `PARAM.SFO` + the PSAR header. `SLUS-00001`) and writes it into `PARAM.SFO` + the PSAR header.
- **base.pbp** — conversion needs a PSN PS1 classic `EBOOT.PBP` (any game) to - **base.pbp** — conversion needs a PSN PS1 classic `EBOOT.PBP` (any game) to
supply Sony's encrypted `DATA.PSP` loader (POPS emulator) and `STARTDAT`. supply the `STARTDAT` block (boot logo + PGD) and a base `PARAM.SFO`/icon.
Copy one to `data/base.PBP` and point **Settings → General → PS1 base.pbp** Copy one to `data/base.PBP` and point **Settings → General → PS1 base.pbp**
at it. **Prefer a 6.60/6.61-era classic**: very old 3.xx-era classics ship a at it. Any PS1 classic works structurally — the converter finds `STARTDAT`
POPS loader that fails to link on modern CFW (`80020148` "game could not be at `PSAR + p1` regardless of PSAR alignment.
started"). Any PS1 classic works structurally — the converter finds - **POPS loader** — the `DATA.PSP` section uses a bundled plain-ELF loader
`STARTDAT` at `PSAR + p1` regardless of PSAR alignment. (`data/loader/DATA.PSP`, from iPoPS, MIT) so the EBOOT boots on modern CFW
- **Output** — a folder `<out>/<Game ID>/` containing a single `EBOOT.PBP` without the base PBP's encrypted loader failing to link (`80020148`). The
(the popstation way: raw-DEFLATE-compressed PSAR, no `KEYS.BIN`/`DOCUMENT.DAT`), base PBP's own loader is only used as a fallback when the bundle is missing.
ready to copy to `<PSP>/PSP/GAME/<Game ID>/`. Compatible with CFW POPS - **Output** — a folder `<out>/<Game ID>/` containing `EBOOT.PBP` (raw-DEFLATE
(ARK-4/ARK-5). compressed PSAR) plus a `KEYS.BIN` (NPDRM version key used to decrypt the
`STARTDAT` PGD), ready to copy to `<PSP>/PSP/GAME/<Game ID>/`. Compatible
with CFW POPS (ARK-4/ARK-5).
Configure the folders in **Settings → General** (or `PSP_PS1_ROOTS` / Configure the folders in **Settings → General** (or `PSP_PS1_ROOTS` /
`PSP_PS1_OUTPUT_DIR`). Current limits: single-disc games only (no multi-disc `PSP_PS1_OUTPUT_DIR`). Current limits: single-disc games only (no multi-disc
+43 -7
View File
@@ -15,11 +15,13 @@ Format reference (offsets are byte-exact):
b"PSISOIMG0000", game id at +0x400, TOC at +0x800, b"PSISOIMG0000", game id at +0x400, TOC at +0x800,
ISO block index at +0x4000, disc data at +0x100000. ISO block index at +0x4000, disc data at +0x100000.
A ``base.pbp`` (any PSN PS1 classic ``EBOOT.PBP``) is REQUIRED to supply two A ``base.pbp`` (any PSN PS1 classic ``EBOOT.PBP``) supplies the blobs that we
blobs that we cannot synthesise: cannot synthesise:
* DATA.PSP - the encrypted POPS loader (header section 8).
* STARTDAT - the trailing encrypted block (boot.png + PGD). * STARTDAT - the trailing encrypted block (boot.png + PGD).
* DATA.PSP - fallback only: the encrypted POPS loader, used when the
bundled plain-ELF loader (``data/loader/DATA.PSP``, from
iPoPS/MIT) is unavailable.
Without a base.pbp the resulting EBOOT will not boot on real hardware; the Without a base.pbp the resulting EBOOT will not boot on real hardware; the
converter refuses to run without one. converter refuses to run without one.
@@ -82,6 +84,14 @@ INDEX_ENTRY_SIZE = 32 # offset u32 + length u32 + 6 unused u32
SYNC_PATTERN = b"\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00" SYNC_PATTERN = b"\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00"
# Bundled plain-ELF POPS loader + NPDRM key (from iPoPS, MIT). Modern CFW
# (ARK/PRO/ME) loads this ELF directly as the DATA.PSP section, sidestepping
# the base PBP's encrypted ~PSP loader which frequently fails to link on
# 6.60/6.61 (error 80020148 "game could not be started").
_LOADER_DIR = Path(__file__).resolve().parent.parent / "data" / "loader"
BUNDLED_DATA_PSP = _LOADER_DIR / "DATA.PSP"
BUNDLED_KEYS_BIN = _LOADER_DIR / "KEYS.BIN"
# SFO field formats (PSP PARAM.SFO). # SFO field formats (PSP PARAM.SFO).
FMT_U32 = 0x0004 FMT_U32 = 0x0004
FMT_STRING = 0x0204 FMT_STRING = 0x0204
@@ -518,6 +528,25 @@ def extract_data_psp(base_pbp: Path) -> bytes:
return section[:prx_size] return section[:prx_size]
def load_data_psp(base_pbp: Path) -> bytes:
"""Return the POPS loader (DATA.PSP) to embed.
Prefers the bundled plain-ELF loader (MIT, from iPoPS), which modern CFW
can load without ~PSP decryption. Falls back to the base PBP's encrypted
~PSP loader when the bundle is absent.
"""
if BUNDLED_DATA_PSP.is_file():
return BUNDLED_DATA_PSP.read_bytes()
return extract_data_psp(base_pbp)
def load_keys_bin() -> Optional[bytes]:
"""Return the bundled NPDRM version key (KEYS.BIN), if available."""
if BUNDLED_KEYS_BIN.is_file():
return BUNDLED_KEYS_BIN.read_bytes()
return None
def extract_startdat(base_pbp: Path) -> bytes: def extract_startdat(base_pbp: Path) -> bytes:
"""Extract the trailing STARTDAT block (boot.png + PGD) from a base PBP. """Extract the trailing STARTDAT block (boot.png + PGD) from a base PBP.
@@ -865,7 +894,7 @@ def convert(
disc = DiscImage(None, source) disc = DiscImage(None, source)
# Resources required from the base PBP. # Resources required from the base PBP.
data_psp = extract_data_psp(base_pbp) data_psp = load_data_psp(base_pbp)
startdat = extract_startdat(base_pbp) startdat = extract_startdat(base_pbp)
_warn_old_base(base_pbp) _warn_old_base(base_pbp)
@@ -933,6 +962,13 @@ def convert(
except OSError: except OSError:
pass pass
raise raise
# Write the NPDRM version key next to the EBOOT (the popstation/iPoPS
# convention; ARK and other CFW read it to decrypt the STARTDAT PGD).
keys = load_keys_bin()
if keys is not None:
out_path.with_name("KEYS.BIN").write_bytes(keys)
return out_path return out_path
@@ -948,9 +984,9 @@ def convert_to_folder(
) -> Path: ) -> Path:
"""Convert a PS1 disc into a game folder ready for /PSP/GAME. """Convert a PS1 disc into a game folder ready for /PSP/GAME.
Writes ``<out_dir>/<GAME_ID>/EBOOT.PBP`` — the same single-file output the Writes ``<out_dir>/<GAME_ID>/EBOOT.PBP`` plus a ``KEYS.BIN`` (the NPDRM
popstation tool produces. No KEYS.BIN or DOCUMENT.DAT is written: version key modern CFW uses to decrypt the STARTDAT PGD). No
popstation doesn't emit them and CFW (ARK-4 POPS) doesn't need them. DOCUMENT.DAT is written; CFW (ARK POPS) doesn't need one.
""" """
folder_name = re.sub(r"[^A-Za-z0-9]", "", game_id).upper() or "PS1GAME" folder_name = re.sub(r"[^A-Za-z0-9]", "", game_id).upper() or "PS1GAME"
folder = Path(out_dir) / folder_name folder = Path(out_dir) / folder_name
+30
View File
@@ -0,0 +1,30 @@
# Loader assets
The files in this directory are bundled from [iPoPS](https://github.com/julianxhokaxhiu/iPoPS)
by Julian Xhokaxhiu, licensed under the MIT License:
- `DATA.PSP` — a plain-ELF PSP POPS loader (relocatable PRX, 19002 bytes).
- `KEYS.BIN` — a 16-byte NPDRM version key used by CFW to decrypt the
`STARTDAT` PGD.
MIT License
Copyright (c) 2020 Julian Xhokaxhiu
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
MB\Ω:�� �� �"