A deferred module that only another deferred module names (PSNCommon, needed by
PSNCore) started after the plugins that use it (SPFXUnityPlugin) and crashed
Soul Hackers 2 on Windows. The first title load of any deferred module now starts
every deferred module some module depends on, in dependency order, with that
load's start arguments, and logs the sweep. Boot is unchanged.
Since #12621 a Windows runtime module that another runtime module needs is deferred instead of started at boot, but loading a deferred module only started that module. A Unity plugin such as SPFXUnityPlugin that calls into a deferred module it needs (Il2CppUserAssemblies in Media/Modules) ran against code whose initializers had never run, and PPSA03743 crashed about 2.6 s in.
A deferred module's initialization table now lists the deferred modules it depends on (names, after the initializer array, behind a magic number so older output reads as having none). DynamicLoader starts those first, transitively and once each through the existing per-module state, with the start arguments of the load that began it, then starts the module itself. The module start result stays the module's own. Boot still starts no deferred module.
Linux is unchanged: its deferred modules still start only when loaded.
Homebrew built with the ps5-payload-dev SDK names its system
libraries with .sprx and imports by symbol name, while the libraries
in libs/ are .prx files that export NIDs. On Linux such an executable
converted but stopped at load time on the first .sprx dependency and,
once that was found, on the first symbol name.
The Linux patcher now loads a dependency named with .sprx, .suprx or
.debug_prx as the .prx library of the same stem. In an executable that
names such a dependency, an import without a module that is named by
symbol gets the NID of that name. A name that a bundled module exports
still binds to that module. There is no lookup by the name as written,
because the host loader also searches the host C library. Other
executables keep their unqualified imports as written.
In PS4 executables and modules the bytes between unwind functions are
16-byte slots of function tails and padding, and a tail can begin inside
an instruction, such as a lone REX byte before a 15-byte NOP. The
syscall and call-site scans decode the executable segment linearly, so
such a slot failed the relink with "Cannot decode instruction".
For images with a PT_SCE_DYNLIBDATA segment the scans now receive the
unwind function ranges. Outside every range an undecodable byte is
skipped and decoding resumes at the next byte, and a decoding that would
cross into a range ends at its start, so gap bytes cannot change how a
function is decoded. Inside a range a decode failure still fails the
relink, and images without unwind ranges are scanned as before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Converted guest modules never ran their DT_PREINIT_ARRAY entries: the
reader validated the array and the unused-NID filter kept its targets,
but no writer emitted or ran it. ld.so runs a shared object's preinit
array only for the main executable, so the Linux output runs the
entries from the DT_INIT stub before the module initializer; the macOS
and Windows outputs run them before the initializer in their startup
stubs, and the deferred Windows output exports a thunk that runs them
before the initializer for runtime-loaded modules. A module with a
preinit array and no initializer is still rejected, since the Linux
stub exists only with one.
Homebrew built with the ps5-payload-dev SDK imports by symbol name and
names its system libraries with .sprx, while the libraries in libs/
are .prx files that export NIDs. Quake from the websrv launcher
stopped with 457 unresolved imports such as strlen and malloc.
An import without a module that no searched library exports under its
name is now looked up again under the NID of that name. A dependency
named with .sprx, .suprx or .debug_prx loads the .prx library of the
same stem, and libkernel_web also loads libkernel.prx, as
libSceLibcInternal already loads libc.prx.
Domain::GuestImport had no weak flag, so WindowsPePatcher handed every
import of a bundled module to the entry stub as a strong one, and a
weak import that no library exports stopped the title with
"unresolved ELF import". The executable's own weak imports already
bind to null there. libOSMesa.so.8 of the Quake homebrew has ten weak
imports that nothing provides, such as ZSTD_trace_compress_begin and
the SDK's __dlopen.
The guest relocation writer now records the ELF weak binding, and the
stub binds such an import to null when no library exports it.
A weak import is optional: the entry stub leaves it unresolved and the loader sees null, so the title runs. The relinker now records the symbol binding in the call registry and import_audit skips weak imports when computing the current stop. regie5 (PPSA99520) weak-links sceLncUtilLaunchApp and was reported as stopping there while it runs past it.
With --trace-imports the entry stub resolves TraceImport_nid_no_patch
in libc.prx and passes each function import through it before writing
the slot: every PLT slot, and a GOT slot only when the original symbol
is a function, so data imports such as __stdoutp keep their address.
Without the option the output is unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Weak undefined imports in the input ELF became STB_GLOBAL in the relinked
output, so a title that weak-imports a function no module exports stopped
at startup instead of binding the import to null.
Keep STB_WEAK on the relinked dynamic symbols. On Windows, write 0 plus
the addend to the slot of a weak import that no searched module exports.
Fixes#4497
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The guest identity predicate accepted .debug_prx while Domain::ImportModule
did not: a dependency named libGuest.debug_prx normalised to
libGuest_debug_prx, so a qualified import declared in module libGuest was
left unscoped. When another guest module exported the same symbol, the
conversion failed with an ambiguous guest import, while .prx, .sprx and
.suprx resolved to the declaring module.
Module suffixes are now one list in the domain layer, used by the identity
predicate, import module normalisation and the default .prx suffix.
Needed-name stem aliasing keeps its own list.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Relinks the ELF modules of extra directories under the module parent directory. Modules the executable or another module needs start with the executable; the others are initialized when the title loads them with sceKernelLoadStartModule, with its start arguments (Windows: __aps5_guest_initialize export; Linux: left out of DT_NEEDED).
Squashed onto main by the reviewer (history had a merge commit).
Since #1454, a libSceLibcInternal import that no native library exports
on Windows falls back to the title's own libc.prx. The loader shim that
ps5-native-app-boilerplate builds into homebrew titles exports 1874
functions at three addresses whose code is `xor eax, eax; ret` or
`pxor xmm0, xmm0; xor eax, eax; ret`. Such an import then binds to a
function that returns 0, and the title starts instead of stopping with
`unresolved ELF import`.
The Windows guest module writer records the exported functions whose
code is one of these two sequences, and the entry stub leaves the guest
libc.prx out of the search for those names. A guest export with code of
its own still provides a missing libSceLibcInternal function, and native
libraries keep their priority.
An import whose module matches no needed library has no library name,
and the Windows startup stub counted it against every library, so a
missing library with nothing imported from it was still never skipped.
Every such import in the executables of Record of Lodoss War, Resident
Evil 4, PRAGMATA and Returnal declares a module that exists under
another file name (libSceJson for libSceJson2.prx, for example), and
none declares libSceXml or Party.
The relinker now keeps the declared module names of those imports, for
the executable and each guest module, and the stub counts a host
library as imported from when its name up to the first dot matches one
of them, ignoring case. A module declared as libGuest still keeps a
missing libGuest.xyz required, as guest_module_identity checks.
Imports without a module ID declare nothing and count against no
library. The rule and the title data are from xXPBSXx.
A qualified import whose module name matches no DT_NEEDED entry now uses
the unqualified search order instead of failing with "Import module is
not a dependency". Restore the Linux rejection of duplicate exports
within one guest module.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The unsupported-instruction error now lists the instruction bytes, and the relink failure names the input file, the main executable or the guest module, on an extra Input: line.