PS1: output PSN-style game folders (EBOOT.PBP + KEYS.BIN + DOCUMENT.DAT)
This commit is contained in:
+59
-11
@@ -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}")
|
||||
|
||||
Reference in New Issue
Block a user