2026-09-02 - (/loop) Exports exist now, and the title's own module answers the wall

tests   1957  ->  1960

orbistoun-elf could read what a module needs and had no notion of what it provides. That is now RawExport, exports_from_symbols, Container::raw_exports, Service::exports_path and an orbistoun-cli exports verb - and pointing it at PPSA02664's own module settled the question the loader work exists to answer.

The measurement that matters

Il2CppUserAssemblies.prx exports 247 symbols. The eboot imports four NIDs from that library. All four are in the export table:

0x9162adc3c86893df  FOUND
0x9fdbed4fe0989d70  FOUND
0x6f8b9da539afc9af  FOUND      <- the NID PPSA02664 is stuck on
0x00e9dafedfe399fe  FOUND

0x6f8b9da539afc9af is the wall. It is answerable by a file that has been sitting in titles/PPSA02664-app0/Media/Modules/ the whole time. Nothing about it was unknown; nothing in the tree could read it.

What the reader is, and the one judgement in it

Imports and exports are the same dynamic symbol table read from opposite sides of SHN_UNDEF, so symbol_tables locates it once and both walks share it. Duplicating thirty lines of table location is how the two would eventually read different tables.

An export carries what an import has no room for: where it lives, as an offset from the module's own base. Not an address - nothing is placed when this is read, and the loader adds the base.

Binding and visibility are deliberately not consulted, and that is the judgement. A local or hidden symbol is unreachable in a real linker, and a general ELF reader should filter on it. This is not reading generally: the guest's imports name NIDs, and the only question is whether this module answers one. A spare row costs nothing; a symbol wrongly filtered out is an import that never binds and a guest that stops - which cannot be diagnosed from outside. It errs the other way and the code says so.

The encoding, which the tests pin

All 247 are vendor-encoded (CRJcH8CnPSI#I#A), not names. So the NID has to be decoded from the spelling, not hashed from it - hashing would produce 247 NIDs no importer asks for. The import path already had that logic, so exports reuse decode_symbol_name rather than growing a second decoder, and a test asserts the two answers differ.

Three unit tests, all running without any title data: imports and exports are complementary across one table, an export carries its offset, and an encoded name publishes the NID it contains rather than a hash of its spelling.

State

cargo test --workspace green - 117 suites, 1960 tests, 0 failures. clippy --tests clean, fmt clean, identity scan clean.

Nothing committed. The day holds worklogs 292-327 and D466-D481.

Next is D482, the load order, and it now has a concrete shape rather than an open question: the title's modules must be placed and relocated before the main executable, and their exports fed to the resolver. The decisions inside it are which module places first when one title module imports from another, how the case-variant library name (Il2cppUserAssemblies, four more imports) matches, and where modules are discovered - Media/Modules/ is the title's own choice, not a platform path, so it cannot be hardcoded.