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:
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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.
@@ -0,0 +1 @@
|
|||||||
|
MB\Ω:���� �"
|
||||||
Reference in New Issue
Block a user