Commit Graph

397 Commits

Author SHA1 Message Date
Adrià 03708282b3
Merge pull request #3495 from Lorkydey/fix/relinker-registry-input-preservation
fix(relinker): preserve input binaries when writing registries
2026-10-11 13:56:53 +02:00
lorkydey 0cfdc79e59 fix(relinker): preserve input binaries when writing registries 2026-10-11 13:38:27 +02:00
fablevoid 1b865fc3ea fix(relinker): pass module start arguments to macOS guest initializers
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
2026-10-11 11:22:38 +00:00
Adrià Franch 33a1ca5d33 fix(relinker): skip the desktop entry for macOS output and use native paths
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>
2026-10-11 12:55:22 +02:00
Adrià Franch ca330dfefb fix(relinker): validate the desktop entry before writing the executable
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>
2026-10-11 12:39:14 +02:00
Adrià Franch 1ff7b534fe feat(relinker): write a desktop entry with the game icon for Linux executables
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>
2026-10-11 12:39:14 +02:00
fablevoid 3462427519 fix(relinker): accept absolute symbols in macOS guest modules
#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
2026-10-11 10:16:59 +00:00
Adrià ce4263b1a6
Merge pull request #3423 from hawwwwwk/pr/relinker-instruction-length-current
fix(relinker): reject x86 instructions exceeding 15 bytes
2026-10-11 12:22:21 +02:00
boykopovar cfbd516c24
Merge pull request #3397 from boykopovar/feat/show-guest-prx-processing
feat(relinker): show guest prx processing
2026-10-11 13:07:42 +03:00
ethan f2ef0f9557 fix(relinker): reject x86 instructions exceeding 15 bytes
(cherry picked from commit 7243caa56ed79fab09edd23d92851e2508671775)
2026-10-11 03:02:05 -07:00
boykopovar 651edc39f5 test(relinker): expect self containers to be skipped 2026-10-11 12:56:17 +03:00
Adrià 76f2d72d40
Merge pull request #3384 from sypher69/fix/sce-dynlibdata-relocation-offsets
fix(relinker): resolve SCE relocation tables relative to dynlibdata
2026-10-11 11:55:28 +02:00
Dani cdfc11cf56 fix(relinker): resolve SCE relocation tables relative to dynlibdata 2026-10-11 11:34:59 +02:00
boykopovar 480e68a84d fix(relinker): format module size to two decimal places 2026-10-11 12:33:21 +03:00
Adrià Franch de2f7aca22 test(relinker): drop docstring from loader path check 2026-10-11 11:33:17 +02:00
MathiasDevelopes 3f5c6e8336 test(relinker): remove disallowed loader test comments 2026-10-11 11:33:13 +02:00
MathiasDevelopes c245e3abc4 test(relinker): verify unscoped Windows provider load paths 2026-10-11 11:33:13 +02:00
MathiasDevelopes 2faa5dd6ed fix(relinker): load needed providers for unscoped Windows imports 2026-10-11 11:33:13 +02:00
Adrià Franch 364c8936da test(relinker): pin executable import binding to the needed provider 2026-10-11 10:36:00 +02:00
exefer 144b1ea8f8 fix(relinker): bind Linux guest imports by declared module identity 2026-10-11 10:35:55 +02:00
Merijn 1c6eac425d feat(relinker): add --module-dir for modules loaded at run time
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).
2026-10-11 10:11:31 +02:00
Adrià c8facc79ff
Merge pull request #3325 from AchrafReyani/carry/windows-stub-orphan-failed-line
fix(relinker): print the Windows stub's ' -> FAILED' only with dependency diagnostics
2026-10-11 09:58:25 +02:00
Adrià 832c84298e
Merge pull request #2705 from DariusHQ/fix/windows-guest-libc-placeholders
fix(relinker): skip zero-return guest libc exports for internal libc imports
2026-10-11 09:56:33 +02:00
Achraf Reyani 62d6cbe42d test(relinker): check that a Windows load failure starts with its own message
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 16:41:25 +09:00
Badr d102e186c9 fix(relinker): print the Windows stub's ' -> FAILED' only with dependency diagnostics 2026-10-11 16:40:50 +09:00
Adrià cfcffebd0f
Merge pull request #3290 from AchrafReyani/carry/relinker-windows-tls-thread-pointer
fix(relinker): place the Windows thread pointer after the TLS size rounded to its own alignment
2026-10-11 09:26:24 +02:00
Adrià 6957bb4ae2
Merge pull request #2785 from tototomate123/fix/hfw-relinker-split
fix(relinker): preserve Linux executable exports and ELF section addresses
2026-10-11 09:20:45 +02:00
DariusHQ e720d3cd61 fix(relinker): skip zero-return guest libc exports for internal libc imports
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.
2026-10-11 09:20:00 +02:00
Adrià a3aca02c5a
Merge pull request #3273 from MarcZX100/codex/fix-relinker-gnu-hash-3224
fix(relinker): read GNU hash symbols during code analysis
2026-10-11 09:17:31 +02:00
Adrià a41bad9771
Merge pull request #3141 from MarcZX100/codex/carry-sce-dyn-3093
fix(relinker): read SCE dynamic tables from PT_SCE_DYNLIBDATA
2026-10-11 09:15:56 +02:00
Adrià 833e5eb507
Merge pull request #3142 from MarcZX100/codex/carry-sce-scan-3093
fix(relinker): stop code scans at the PLT for PS4 executables
2026-10-11 09:12:30 +02:00
Adrià bbb9419642
Merge pull request #3129 from CnRJay/fix/windows-start-without-unused-library
fix(relinker): start on Windows without a missing library nothing imports from
2026-10-11 09:11:54 +02:00
ArjenS-LA 4bc36b19b8 fix(relinker): read GNU hash symbols during code analysis
When the dynamic symbol count is absent from the SCE or SysV hash metadata, derive it from the GNU hash buckets and chain. Keep all table reads within mapped file-backed ranges.

Carried from issue #3224, commit 1a10126089882a2306318f18d19d05b04a764d6c; original author preserved.
2026-10-11 04:48:55 +00:00
MarcZX100 16adcd4871 fix(relinker): validate SCE dynamic data segment 2026-10-11 03:30:09 +00:00
Get-fix ef727569cc fix(relinker): read SCE dynamic tables from PT_SCE_DYNLIBDATA
DT_SCE_STRTAB, DT_SCE_SYMTAB, DT_SCE_RELA and DT_SCE_JMPREL are offsets
into the PT_SCE_DYNLIBDATA segment, as the code analysis and the guest
image reader already read them, but the pipeline read them as file
offsets. That only works when the segment starts at file offset 0, as in
the test fixtures. A PS4 executable (CUSA00900) keeps the segment at
0x53e9b40, so the relinker read its relocations from the code and failed
with "Dynamic string offset is outside DT_STRSZ". Without the segment the
values are still read as file offsets.

(cherry picked from commit 5dbf89a57e096f2891ae786f44c3f57935af2285)
2026-10-11 03:25:49 +00:00
Chad 7755e1d05c fix(relinker): leave needed libraries nothing imports from out of the start-up load list
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-10 20:14:23 -05:00
Adrià abb2f8e4fc
Merge pull request #2986 from MarcZX100/fix/relinker-windows-unicode-2640
fix(relinker): support Unicode Windows startup paths
2026-10-11 01:46:23 +02:00
Adrià aa79dbdfe3
Merge pull request #2885 from MarcZX100/fix/relinker-linux-input-phdr-interp
fix(relinker): drop input Linux PT_PHDR and PT_INTERP
2026-10-11 01:13:32 +02:00
xXPBSXx 272b9d1338 test(relinker): check Windows TLS thread pointer offset
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-11 00:56:01 +02:00
xXPBSXx 8e03bb0af8 fix(relinker): place the Windows thread pointer after the TLS size rounded to its own alignment
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>
2026-10-11 00:56:01 +02:00
Get-fix 9a13dc8c78 fix(relinker): stop code scans at the PLT in executables with PT_SCE_DYNLIBDATA
A PS4 executable (CUSA00900) keeps 37 MiB of read-only data in its
executable segment after the code. The syscall scan and the call-site
resolution decoded the whole segment linearly, reached a float table and
failed with "Unsupported 0F 79 opcode without SSE4A prefix". In
executables with a PT_SCE_DYNLIBDATA segment they now stop at the end of
the PLT that follows the last function of the unwind table. The PLT is
found by searching forward from that function for its first entry, which
pushes the GOT entry at PLTGOT + 8 and jumps through PLTGOT + 16, since
code without unwind entries can sit in between (680 bytes of start-up
code in an OpenOrbis-built sample). When the executable has PLT
relocations and no such entry follows, the relinker fails instead of
guessing. Executables without the segment are scanned as before.

(cherry picked from commit a21e8fe4c0e6c03189d559e992c0f037c076700d)
2026-10-10 22:53:48 +00:00
Abdulrahman Elkordy 87ca3a2cf1 fix(relinker): count unnamed imports only against the library they declare
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.
2026-10-10 17:49:47 -05:00
Abdulrahman Elkordy 8ebf892849 fix(relinker): start on Windows without a missing library nothing imports from
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.
2026-10-10 17:49:47 -05:00
simonbbby cfd61a5977 fix(relinker): treat address-taken code as reachable in unused-filter=1
The CFG used by unused-filter=1 followed only direct branches, so code reached
through lea, relative-relocated pointers or jump tables was unreachable and its
imports were dropped. It now roots RIP-relative lea targets, relative relocation
targets and relative jump tables, and counts any RIP-relative reference to a GOT
slot as an access. Address-taken targets are walked after the direct control
flow, and a target that does not decode as an instruction is not code.
2026-10-11 00:35:27 +02:00
StarMrGamer 1a45dee292 fix(relinker): resolve the syscall number through prefixed instructions
The lookback classifies preceding instructions at their decoded opcode
offset, the same place the forbidden-instruction check looks since #1935,
so a legacy-prefixed mov rax, imm or mov r10, rcx is recognized. The REX
byte stays part of the patterns, so mov r8 and mov r8d cannot masquerade
as rax loads. Anything unrecognized still stops the inference.

Adds prefixed fixtures to both test suites: a prefixed constant stub that
must report syscall number 9, and a prefixed clobbered stub that must
report no number.

AI-assisted: yes. The submitting author is responsible for every changed line.
2026-10-11 00:27:44 +02:00
StarMrGamer f7a9629e68 fix(relinker): report the syscall number of a forbidden syscall instruction
The syscall check names only the code offset, so a title that links a FreeBSD
libc (the ps5-payload-dev SDK statically links one into every homebrew binary)
stops with no indication of which call it reached. Those stubs load the number
with a constant before the instruction (mov rax, 9 for link), so decode the
nearest constant load into rax and add it to the message:

  Forbidden syscall instruction at code offset 0xa: syscall number 9

The number is reported only when it can be established. The constant load must
be the instruction directly before the syscall, or one mov r10, rcx (the SDK's
first-argument move, which does not write rax) in front of it; any other
instruction stops the inference instead of returning a stale value, so
mov rax, 9; add rax, 1; syscall reports no number rather than 9. Only the three
invocation opcodes report a number: sysret is a return and keeps the current
message, as does a stub that loads rax from a register, like the SDK's generic
syscall() wrapper. The check still throws; only the text changes.

Tested: relinker-only build, full ctest on Linux x86-64 (45/45). The C++
syscall_scanner test covers the opcode matrix and the review regression; the
Python test_syscall_number covers the same contract through the harness the
relinker lane already runs on Ubuntu, Windows and macOS, where the C++ fixture
helpers (sys/mman.h, posix_spawn) do not build.

AI-assisted: yes
2026-10-11 00:27:44 +02:00
Adrià 4d27834ff9
Merge pull request #2119 from mugurc/macos-rosetta
feat(relinker): convert for Rosetta with --to-rosetta
2026-10-11 00:26:25 +02:00
Adrià Franch 36ffda9a98 test(relinker): check the declared-host import by membership, not as the module's only import 2026-10-11 00:14:26 +02:00
john.maia 5f4a28c2bc fix(relinker): keep Linux imports declared from host modules off guest exports
On Linux every executable or guest-module import whose NID a guest
module exports was renamed to the guest-suffixed name, whatever module
the import declares. A title that bundles a module exporting system
NIDs then had those imports bound to it: the libc.prx loader shim of
ps5-native-app-boilerplate exports placeholder functions under the
libSceLibcInternal NIDs, so DOOM PPSA99666's snprintf, vsnprintf,
memcpy and malloc (declared from libSceLibcInternal) returned 0 and the
title never found its WAD (#1497).

An import now binds to a guest export only when the module it declares
is a guest module, as on Windows. Imports without import-module
metadata keep the previous behaviour, and a title's own libc.prx
imported from libc still binds to it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 00:14:19 +02:00
mugurc 92b20608e8 feat(relinker): convert for Rosetta with --to-rosetta
Rosetta runs AVX2, FMA, BMI1/2, F16C and MOVBE, but, unlike an Intel
host, it has neither RDSEED, CLWB nor RDPID. --to-rosetta converts
everything --to-intel does and lowers these three as well:

- RDSEED becomes RDRAND (the /6 form of the same opcode), which also
  returns a random value and sets CF.
- CLWB only writes a cache line back, so it becomes a NOP of the same
  length.
- RDPID becomes a load of 0 through an optional stub: Rosetta's TSC_AUX
  is a constant, not a processor index, so every thread reports
  processor 0. A site with no movable instruction after it for the jump
  is kept.

The flag implies --to-intel and conflicts with --windows. It is
independent of --macos: it only changes what the conversion produces, so
--macos alone leaves these instructions as they are, as on an Intel Mac,
and a title for Rosetta is relinked with --macos --to-rosetta. Intel
output is unchanged: the target is a parameter of the converter, Intel by
default.
2026-10-10 22:46:45 +03:00