PS1: match popstation PSAR header byte-for-byte; drop fake KEYS.BIN/DOCUMENT.DAT
This commit is contained in:
+26
-34
@@ -507,25 +507,35 @@ def build_toc(disc: "DiscImage") -> bytes:
|
||||
|
||||
|
||||
def _build_psar_header(disc: "DiscImage", game_id: str, title: str) -> bytes:
|
||||
"""Build the fixed PSAR header region 0x400..0x4000.
|
||||
"""Build the fixed PSAR header region 0x400..0x4000, byte-identical to the
|
||||
popstation level-0 writer.
|
||||
|
||||
Layout mirrors the reference writer's data1/data2 templates:
|
||||
* game id at +0x000 (11 bytes, "_SLUS_00594" form)
|
||||
* TOC at +0x400 (i.e. absolute PSAR +0x800)
|
||||
* title at data2 offset 8 (absolute PSAR +0x1228), 128 bytes max
|
||||
Layout (offsets relative to the PSAR start):
|
||||
* 0x400 data1 (0xE20 bytes): game id at +0, TOC at +0x400, 0x10 at +0x7FE
|
||||
* 0x1220 p2 offset (u32) = padded ISO size + 0x100000 + 0x2D31
|
||||
* 0x1224 data2 (0x2DDC bytes): FF 07 at +4, title at +8, 0x03 at +0x88
|
||||
"""
|
||||
block = bytearray(PSAR_INDEX_OFFSET - PSAR_GAMEID_OFFSET) # 0x400..0x4000
|
||||
data1_size = 0xE20
|
||||
data2_size = 0x2DDC
|
||||
p2_delta = 0x2D31
|
||||
|
||||
block = bytearray(data1_size + 4 + data2_size) # 0x3C00 = 0x400..0x4000
|
||||
|
||||
gid = _format_psar_game_id(game_id).encode("ascii")[:11]
|
||||
block[: len(gid)] = gid
|
||||
|
||||
toc = build_toc(disc)
|
||||
toc_off = PSAR_TOC_OFFSET - PSAR_GAMEID_OFFSET # 0x400
|
||||
block[toc_off: toc_off + len(toc)] = toc
|
||||
block[0x400: 0x400 + len(toc)] = toc
|
||||
block[0x7FE] = 0x10
|
||||
|
||||
p2 = disc.padded_size() + PSAR_ISO_OFFSET + p2_delta
|
||||
block[data1_size: data1_size + 4] = struct.pack("<I", p2)
|
||||
|
||||
data2 = data1_size + 4
|
||||
block[data2 + 4: data2 + 6] = b"\xFF\x07"
|
||||
title_bytes = title.encode("ascii", "replace")[:0x80]
|
||||
title_off = 0x1220 - PSAR_GAMEID_OFFSET + 8 # data2 template offset 8
|
||||
block[title_off: title_off + len(title_bytes)] = title_bytes
|
||||
block[data2 + 8: data2 + 8 + len(title_bytes)] = title_bytes
|
||||
block[data2 + 0x88] = 0x03
|
||||
|
||||
return bytes(block)
|
||||
|
||||
@@ -744,17 +754,6 @@ 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,
|
||||
@@ -764,12 +763,12 @@ def convert_to_folder(
|
||||
progress_cb=None,
|
||||
cancel_event=None,
|
||||
) -> Path:
|
||||
"""Convert a PS1 disc into a PSN-style game folder ready for /PSP/GAME.
|
||||
"""Convert a PS1 disc into a 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.
|
||||
Writes ``<out_dir>/<GAME_ID>/EBOOT.PBP`` — the same single-file output the
|
||||
popstation tool produces (uncompressed/unencrypted level-0 PSAR). No
|
||||
KEYS.BIN or DOCUMENT.DAT is written: popstation doesn't emit them and CFW
|
||||
(ARK-4 POPS) doesn't need them.
|
||||
"""
|
||||
folder_name = re.sub(r"[^A-Za-z0-9]", "", game_id).upper() or "PS1GAME"
|
||||
folder = Path(out_dir) / folder_name
|
||||
@@ -777,13 +776,6 @@ def convert_to_folder(
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -873,7 +865,7 @@ def main(argv: Optional[List[str]] = None) -> int:
|
||||
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 (single file)")
|
||||
ap.add_argument("--out-dir", help="output folder: writes <dir>/<GAME_ID>/EBOOT.PBP (+KEYS.BIN, DOCUMENT.DAT)")
|
||||
ap.add_argument("--out-dir", help="output folder: writes <dir>/<GAME_ID>/EBOOT.PBP")
|
||||
ap.add_argument("--test", action="store_true", help="run self-test and exit")
|
||||
args = ap.parse_args(argv)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user