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,
`libretro-index.json` + `openvgdb-index.json` catalogs, `libretro-covers.zip` +
`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
loader when converting PS1 discs to `EBOOT.PBP` (git-ignored, copyrighted).
Prefer a 6.60/6.61-era classic; 3.xx-era loaders can fail on modern CFW.
- `data/base.PBP` — a PSN PS1 classic EBOOT (any game) supplying `STARTDAT`
+ a base `PARAM.SFO`/icon for PS1 conversion (git-ignored, copyrighted).
- `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`):
- `library.sqlite` — the catalog + library database.
- `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.
`SLUS-00001`) and writes it into `PARAM.SFO` + the PSAR header.
- **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**
at it. **Prefer a 6.60/6.61-era classic**: very old 3.xx-era classics ship a
POPS loader that fails to link on modern CFW (`80020148` "game could not be
started"). Any PS1 classic works structurally — the converter finds
`STARTDAT` at `PSAR + p1` regardless of PSAR alignment.
- **Output** — a folder `<out>/<Game ID>/` containing a single `EBOOT.PBP`
(the popstation way: raw-DEFLATE-compressed PSAR, no `KEYS.BIN`/`DOCUMENT.DAT`),
ready to copy to `<PSP>/PSP/GAME/<Game ID>/`. Compatible with CFW POPS
(ARK-4/ARK-5).
at it. Any PS1 classic works structurally — the converter finds `STARTDAT`
at `PSAR + p1` regardless of PSAR alignment.
- **POPS loader** — the `DATA.PSP` section uses a bundled plain-ELF loader
(`data/loader/DATA.PSP`, from iPoPS, MIT) so the EBOOT boots on modern CFW
without the base PBP's encrypted loader failing to link (`80020148`). The
base PBP's own loader is only used as a fallback when the bundle is missing.
- **Output** — a folder `<out>/<Game ID>/` containing `EBOOT.PBP` (raw-DEFLATE
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` /
`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,
ISO block index at +0x4000, disc data at +0x100000.
A ``base.pbp`` (any PSN PS1 classic ``EBOOT.PBP``) is REQUIRED to supply two
blobs that we cannot synthesise:
A ``base.pbp`` (any PSN PS1 classic ``EBOOT.PBP``) supplies the blobs that we
cannot synthesise:
* DATA.PSP - the encrypted POPS loader (header section 8).
* 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
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"
# 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).
FMT_U32 = 0x0004
FMT_STRING = 0x0204
@@ -518,6 +528,25 @@ def extract_data_psp(base_pbp: Path) -> bytes:
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:
"""Extract the trailing STARTDAT block (boot.png + PGD) from a base PBP.
@@ -865,7 +894,7 @@ def convert(
disc = DiscImage(None, source)
# 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)
_warn_old_base(base_pbp)
@@ -933,6 +962,13 @@ def convert(
except OSError:
pass
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
@@ -948,9 +984,9 @@ def convert_to_folder(
) -> Path:
"""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
popstation tool produces. No KEYS.BIN or DOCUMENT.DAT is written:
popstation doesn't emit them and CFW (ARK-4 POPS) doesn't need them.
Writes ``<out_dir>/<GAME_ID>/EBOOT.PBP`` plus a ``KEYS.BIN`` (the NPDRM
version key modern CFW uses to decrypt the STARTDAT PGD). No
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 = 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\Ω:�� �� �"