A short fs-prefixed TLS access is patched with a 5-byte jump by
relocating the following instructions into the stub, but any branch
there was rejected outright. NHL 27 hits this on every stack-protector
epilogue: fs:xor rax,[rbx] immediately followed by jne __stack_chk_fail.
Allow relative conditional branches and short jumps: re-encode rel8
forms as their rel32 equivalents, rebase the displacement when the
stub is emitted, and keep rejecting loop/jrcxz, calls, returns, and
segment-prefixed followers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The macOS image initializer called a guest module's DT_INIT with no
arguments, so a module started with sceKernelLoadStartModule never saw
its args and argp, and its result was dropped. The Linux writer already
calls DT_INIT through libkernel's pending argument handoff. On the
macOS port, the PSN plugin of PPSA12544 refuses to start without its
argument block ("PSN is an old version that cannot be used by the
current player runtime") and the title then crashes.
A guest module with DT_INIT now gets two slots bound to
__aps5_get_pending_module_args_nid_no_patch and
__aps5_set_module_init_result_nid_no_patch, and its initializer calls
DT_INIT with the args and argp that sceKernelLoadStartModule records,
as on Linux, then hands the result back. The TechnicalDebt sentence
about the missing arguments on macOS is removed.
guest_module_args also relinks its fixture with --macos and checks the
two binds; on x86-64 macOS it loads the module and checks that DT_INIT
receives the arguments and that its result is returned.
AI-assisted: yes
The Linux desktop entry was written for every non-Windows output, macOS
included since --macos landed. It is now written only for Linux output,
and its paths go through Io::NativePath/Utf8Path like the rest of the
relinker. An end-to-end test relinks for Linux, macOS and Windows.
Co-Authored-By: Claude <noreply@anthropic.com>
The icon, title and output path are checked before the ELF is written,
so a malformed icon0.png or param.json leaves no output behind. Paths
with control characters are refused instead of producing an invalid
entry.
Co-Authored-By: Claude <noreply@anthropic.com>
ELF has no icon resource, so when sce_sys/icon0.png sits beside the input
the relinker copies it next to the output and writes <output>.desktop
(freedesktop Desktop Entry) with that icon. Name is the param.json title
without trademark signs or line breaks, or the output name without
param.json. Exec is quoted per the spec and the entry is marked executable
so file managers launch it. A malformed icon0.png or param.json fails the
relink; without icon0.png nothing extra is written.
Co-Authored-By: Claude <noreply@anthropic.com>
#698 taught the reader, the module builder and the Windows writer to
resolve relocations against SHN_ABS symbols and to leave them out of the
exports, but the macOS writer still threw "Unsupported guest export
section" for them. PPSA12544 bundles modules with such an export
(RKDbTdN8TO0), so --macos stopped before writing anything.
The macOS writer now resolves a relocation against an absolute symbol to
its value plus the addend, without a rebase, and skips absolute symbols
when exporting, as the Windows writer does. guest_absolute_symbols covers
the macOS output too.
AI-assisted: yes
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.
The converted thread pointer was set to the TLS block plus the size rounded to max(p_align, 16), while the guest reads its first variable at tp minus the size rounded to p_align. For a PT_TLS with memsz 0x21 and p_align 8 (Resident Evil 4) that is 0x30 against 0x28, so the first variable was read from the second slot. The thread pointer now uses the size rounded to p_align; the block layout is unchanged. WIP: the test for this is still missing.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
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.
The Windows startup stub stopped at the first DT_NEEDED library it could
not load, even when no import is searched in that library. Titles list
libraries they import nothing from (Road of Death needs libSceXml.prx,
Resident Evil 4 needs Party.prx), so they stopped with GetLastError 126
before running. A host library that no import's search list contains is
now skipped with a message when it is not found; its handle stays null
and is never read. Missing libraries that something imports from, other
load errors and guest modules still stop the start.
The --windows-diagnostics walker looked up modules and directories with
GetModuleHandleA, LoadLibraryExA, GetFileAttributesA,
GetModuleFileNameA and GetSystemDirectoryA, so under a directory outside
the ANSI code page it failed with "dependency diagnostics Windows API
failed" instead of naming the missing dependency.
Keep the walker's paths in UTF-8 and convert them at the Windows API
calls, which now use the wide functions. The entry stub passes it, and
prints, its paths as UTF-8 instead of the ANSI code page. The dependency
diagnostics test now builds its fixtures under a non-ASCII directory.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Windows entry stub read its path with GetModuleFileNameA, which
turns every character the ANSI code page cannot represent into '?', and
then used it with SetCurrentDirectoryA and LoadLibraryExA. A converted
executable in such a directory (Japanese on a Western-locale system, an
emoji, a script without an ANSI code page) stopped at start-up with
ERROR_INVALID_NAME.
Read the path with GetModuleFileNameW and enter the directory and load
the libraries with SetCurrentDirectoryW and LoadLibraryExW, with library
paths stored as UTF-16. Messages and the --windows-diagnostics walker
still get the paths in the ANSI code page, as recorded in TechnicalDebt.
Fixes#2640
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Linux patcher writes its own PT_PHDR and PT_INTERP but also copied
the input's. glibc takes the load bias from the last PT_PHDR, which was
the input's entry with the pre-relink address, so such an executable
crashed at start. Both copies also took program header slots, so an
input with PT_PHDR and PT_INTERP and no spare slots failed to relink.
WindowsPeWriter stored every PE section with SizeOfRawData equal to its
virtual size, so the zero-filled tail of a PT_LOAD segment (p_memsz above
p_filesz, the .bss) was written to the executable byte for byte. A segment
with 256 MiB of .bss produced a 269 MB .exe, while the Linux output of the
same image is under 5 KB.
Each section is now stored up to its last non-zero byte, aligned to the file
alignment, and VirtualSize keeps the full size. The PE loader zero-fills the
part of a section beyond SizeOfRawData (Microsoft PE format, section table:
SizeOfRawData). A section with no non-zero byte gets no raw data and a zero
PointerToRawData.
windows_bss relinks an image with 64 MiB of .bss, checks the section table
and the mapped image, and on Windows runs the executable, which reads the
last word of the .bss.
The executable is linked at a fixed base with a __PAGEZERO that fills the space below it, and
the guest assumes 16 KB pages. With MH_PIE set dyld slides the image by a multiple of 4 KB,
which breaks that assumption and ends in a SIGBUS (KERN_PROTECTION_FAILURE) in about half of
the launches under Rosetta 2. Clearing the flag keeps the image at its linked address.
test_macos_output.py now reads the header flags and checks that MH_PIE is clear.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The guest filesystem root and the save data directory are taken from the
current directory, so an executable started from another directory does
not find its title files and keeps saves elsewhere. Make the executable
directory the current directory in the Windows entry stub before the
system libraries are loaded.
The unused-filter=1 entry point collector translated DT_INIT_ARRAY and
DT_FINI_ARRAY through PT_DYNAMIC and skipped arrays placed before it, which is
the usual linker layout, so constructors and destructors were not roots and
imports used only by them were dropped. Arrays are now translated through the
PT_LOAD that holds them, slot targets come from RELATIVE relocations when
present, and DT_PREINIT_ARRAY is collected as in the strict filter.