PS1: output PSN-style game folders (EBOOT.PBP + KEYS.BIN + DOCUMENT.DAT)

This commit is contained in:
2026-09-07 16:20:39 -04:00
parent 4d5c48cb94
commit a9c371c221
4 changed files with 77 additions and 27 deletions
+59 -11
View File
@@ -744,6 +744,49 @@ def convert(
return out_path
KEYS_BIN_SIZE = 0x10 # 16-byte boot key
def _document_dat() -> 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,
game_id: str,
base_pbp: Path,
out_dir: Path,
progress_cb=None,
cancel_event=None,
) -> Path:
"""Convert a PS1 disc into a PSN-style game folder ready for /PSP/GAME.
Writes ``<out_dir>/<GAME_ID>/`` 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.
"""
folder_name = re.sub(r"[^A-Za-z0-9]", "", game_id).upper() or "PS1GAME"
folder = Path(out_dir) / folder_name
folder.mkdir(parents=True, exist_ok=True)
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
# --------------------------------------------------------------------------
# Self-test / CLI
# --------------------------------------------------------------------------
@@ -829,7 +872,8 @@ def main(argv: Optional[List[str]] = None) -> int:
ap.add_argument("--title", help="game title shown on the PSP")
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")
ap.add_argument("--out", help="output EBOOT.PBP path (single file)")
ap.add_argument("--out-dir", help="output folder: writes <dir>/<GAME_ID>/EBOOT.PBP (+KEYS.BIN, DOCUMENT.DAT)")
ap.add_argument("--test", action="store_true", help="run self-test and exit")
args = ap.parse_args(argv)
@@ -837,18 +881,22 @@ def main(argv: Optional[List[str]] = None) -> int:
_self_test()
return 0
if not all([args.source, args.title, args.game_id, args.base_pbp, args.out]):
ap.error("--source --title --game-id --base-pbp and --out are all required (or use --test)")
if not all([args.source, args.title, args.game_id, args.base_pbp]) or (not args.out and not args.out_dir):
ap.error("--source --title --game-id --base-pbp and --out (or --out-dir) are all required (or use --test)")
try:
convert(
Path(args.source),
args.title,
args.game_id,
Path(args.base_pbp),
Path(args.out),
)
print(f"wrote {args.out}")
if args.out_dir:
folder = convert_to_folder(
Path(args.source), args.title, args.game_id,
Path(args.base_pbp), Path(args.out_dir),
)
print(f"wrote {folder}")
else:
convert(
Path(args.source), args.title, args.game_id,
Path(args.base_pbp), Path(args.out),
)
print(f"wrote {args.out}")
return 0
except ConversionError as exc:
print(f"error: {exc}")