D305 - A plain name is a NID nobody hashed yet
decided ยท 2026-08-26
D163 recorded that the ps5-payload-dev payloads have
no dynamic segment at all, so "there is no import list,
nothing for the NID resolver to match, and nothing the HLE layer can
intercept". On that basis the whole route was closed.
Measured again against twenty-three payload ELFs and it is
false. Every one carries PT_DYNAMIC,
DT_NEEDED, a real .dynsym, and between 3 and
207 named undefined symbols. Either the August test hit older builds or
the previous-generation variants; these link against
libkernel_web.sprx, libSceLibcInternal.sprx
and libSceNet.sprx and say so in their own tables.
orbistoun's refusal had already changed accordingly - from "no
PT_DYNAMIC segment" to "lacks a string table, symbol table, or hash
table" - and nobody read it.
Two things actually blocked it, and one of them was a silent wrong answer.
The count
DT_GNU_HASH does not state
Twenty-one of the twenty-three carry only
DT_GNU_HASH. The symbol count comes from
DT_HASH's nchain, and there is no equivalent
word here - the count has to be walked out of the bucket array and the
chain's stop bits. DT_HASH is still preferred where a
module carries both: it states the answer, and a stated answer cannot be
walked wrong.
The silent one: plain names were dropped
imports_from_symbols ended in
if let Some(decoded) = decode_symbol_name(name), and a name
that did not parse as NID#lib#mod was skipped without a
word. So the two payloads carrying DT_HASH got past
the first gate and reported
0 imports, 0 unresolved - a module needing
eighty-five things reporting that it needs none.
That is the exact claim principle 3 forbids an import list from
making, and the guard that exists for it
(Container::imports errors rather than returning an empty
list) did not cover this path. A guard nobody has watched reject
something is a guard nobody knows anything about.
Why hashing the name is the native answer and not a second scheme
The tempting reading is that these need a second resolver:
NIDs for vendor modules, strings for homebrew. They do not.
libSceNet.sprx exports socket, and the NID it
publishes is SHA-1("socket" + suffix). A
plain name is not a different kind of import. It is a NID nobody hashed
yet.
So RawImport now always carries a NID, computed where it
was not encoded, and everything downstream resolves one way.
Checked before it was built, because the whole design
rests on it: socket, bind,
listen, accept, malloc,
pthread_create, memcpy, sysctl,
kqueue and getifaddrs are all in this
repository's own hash-confirmed symbol database, which means each
already hashes to a NID that appears in real vendor modules. The
relation was established by the naming loop years of NIDs ago; this only
stops discarding it.
raw_imports therefore takes the hasher rather than
making one. The registry's own hasher is what callers pass, because a
name hashed with a different suffix resolves to nothing and does so
silently, as an unresolved import rather than as an
error.
What it does not carry
A vendor-encoded name carries its own attribution; a standard SysV
name carries none. So library_id and module_id
become one NameForm enum and the accessors return
Option. None is "the format does not record
this", not "library zero" - blending the two would attribute every
homebrew import to whichever library happened to be first.
Result
| payload | imports | orbistoun can name | registry answers |
|---|---|---|---|
| elfldr 0.25 | 24 | 24 | 8 |
| klogsrv 0.9 | 34 | 33 | 7 |
| shsrv 0.20 | 41 | 39 | 10 |
| ftpsrv 0.21.1 | 85 | 84 | 24 |
| pldmgr 0.5.1 | 160 | 159 | 42 |
Cross-checked against readelf, which
counts distinct undefined dynamic symbols independently: it agrees on
all five, exactly. That is the useful kind of confirmation - an external
tool that has never heard of this project arriving at the same number -
and it caught a first pass that was one too high on every row, from
counting a trailing blank line as an import.
The naming problem was already solved for these. One or two imports per payload are unnamed; the rest were nameable the moment the loader stopped throwing them away. What is left is implementation, which is a different and more ordinary kind of work.
Vendor modules are unaffected and were checked: a title's eboot still
attributes to libkernel with its ids intact, and the
conformance probe's own module still reads.