Skip to content

Read NSO flags from 0xC so retail NSOs load - #18

Open
MeownsterHunter wants to merge 2 commits into
bgyss:mainfrom
MeownsterHunter:nso-flags-offset
Open

MeownsterHunter wants to merge 2 commits into
bgyss:mainfrom
MeownsterHunter:nso-flags-offset

Conversation

@MeownsterHunter

Copy link
Copy Markdown

Retail NSO files fail intake with a segment size mismatch. The parser reads the flags word from the wrong place in the header, so it thinks every segment is uncompressed. This PR reads the flags from the right offset and checks the segment hashes, so a wrong layout fails loudly instead of producing bad code.

What was wrong

The NSO header starts with the magic NSO0 at 0x0, the version at 0x4, a reserved word at 0x8, and the flags at 0xC (NSO format on switchbrew). crates/recomp-pipeline/src/homebrew/nso.rs read the flags from 0x8. Real files have zero there, so no segment looked compressed, and intake failed when the raw segment size did not match the decompressed size.

The test fixtures also wrote the flags at 0x8. The tests passed for that reason.

What this changes

  1. Read NSO flags from header offset 0xC. The fixtures in crates/recomp-pipeline/tests/homebrew_intake.rs, crates/recomp-pipeline/tests/xci_intake.rs, and samples/homebrew-intake/generate.py now write the flags there too.
  2. Verify NSO segment hashes when the header asks for it. Flag bits 3 to 5 request a check for the text, rodata, and data segments. The header stores the SHA-256 of each decompressed segment at 0xA0, 32 bytes per segment. The reader now compares them and returns an error on a mismatch.

Testing

  • cargo test --workspace passes: 80 tests.
  • I checked 8 NSO files from two retail games, 24 segments in total. With the flags read from 0xC, all 24 decompressed segments match the SHA-256 in their headers. With the flags read from 0x8, none match.

The NSO header is magic, version, a reserved word, then flags. The
parser read flags from the reserved word at 0x8. Real NSO files have
zero there, so every segment looked uncompressed and intake failed with
a segment size mismatch.

The test fixtures wrote flags at 0x8 as well, which is why the tests
passed. They now use 0xC.
An NSO header stores the SHA-256 of each decompressed segment, and flag
bits 3..5 say whether to check it. The reader now checks them, so a
segment that was decompressed wrongly, or read with the wrong layout,
is an error instead of silently wrong code.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant