The package header is integrity-protected, and that is the wall (measured on hardware)
A package built here was refused by a console with
0x80f00101 from
scePlayGoCoreGetRawContentInfo. Chasing that produced five
real fixes to this crate, and then a result that explains why none of
them was enough.
The fixes, all measured against three real packages
| field | was | now |
|---|---|---|
content_type / drm_type
(0x74/0x70) |
0 |
0x1A / 0x0F - zero reads as "not a title
to register" |
content_flags (0x78) |
0 |
0x0A000000 |
sc_entry_count (0x14) |
the total entry count | 6 - it counts SC entries only, 3/3 |
main_entry_data_size (0x1C) |
~520000 (table-to-image gap) | summed SC entry sizes, 512-aligned |
promote_size (0x7C) |
0 |
the image offset, 3/3 |
manifest 0x1C |
0 |
0x6E, 3/3 |
| entry table offset | 0x1000, "only has to not collide" |
0x2A80, a fixed constant 3/3 |
playgo_chunk_sha (0x1002) |
digested from byte 0 | skips the header blocks; slots 0-7 zero, 3/3 |
Two further entries obSCEne was leaving empty are now generated from
measured structure: entry 0x200 (the NUL-separated entry
name table) and entry 0x1001 (plgo, 416 bytes,
content id at 0x40). derive::playgo_chunk is
the canonical writer for the latter.
What the bisect found
Copying our bytes into a known-good package one region at a time narrowed the rejection to the table offset - fixed above. With that corrected the same test moved on, and the decisive experiment was simpler: flip one byte of a working package and see what survives.
| byte flipped | result |
|---|---|
0x600, a reserved area nothing reads |
refused 0x80f00101 |
0x7000, inside an entry's data |
accepted, 0x00000000 |
0x80100, inside the filesystem image |
accepted, 0x00000000 |
The header region is integrity-protected; entry data and the image are not. A single changed byte in a region no field uses is enough to refuse a package that otherwise installs, and a real package truncated to a fraction of its size still installs as long as its header is untouched.
So 0x80f00101 is not a verdict on any field this crate
writes. Every value above can be correct - and now is - and the package
will still be refused, because the protection covers bytes rather than
meanings. Nothing this crate can compute produces that, and per
principle 6 nothing here will claim to be the vendor in order to
try.
This is worth having found: it converts "the package is wrong somewhere" into a bounded statement about which region is checked and which is not, and it is reproducible in three commands against any real package.