82 Commits
Author SHA1 Message Date
mpratsakiandClaude Opus 5.5 763702c5ce fix(relinker): use the Windows file identity in module and executable checks
Guest module discovery skipped the input executable and the
--executable check rejected an executable converted twice through
std::filesystem::equivalent, which MinGW reports as false for hard links
whose names give different permissions, such as .bin and .exe. Move the
GetFileInformationByHandle comparison to Io::SameFile and use it for
these checks and for SameOutputFile.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 02:19:01 +03:00
mpratsakiandClaude Opus 5.5 c9731df4c9 fix(relinker): compare Windows file identities by handle
MinGW's std::filesystem::equivalent returns false for hard links whose
names give different permissions, such as .elf and .exe. The temporary
hard-link probe that worked around it failed on read-only files and on
directories that refuse the link. Compare the volume serial number and
file index from GetFileInformationByHandle instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 19:54:53 +03:00
Adrià 04078b1073 Merge pull request #6927 from jotitadev/fix/relinker-case-only-output-collisions
fix(relinker): detect output aliases with a filesystem probe outside Windows
2026-10-19 15:54:05 +02:00
Adrià 0fa6ddb137 Merge pull request #6802 from DavidSamir/feat/relinker-macos-fixed-base
feat(relinker): place macOS executables at a fixed base with --macos-fixed-base
2026-10-19 12:54:31 +02:00
Adrià 8816fd7720 Merge pull request #6715 from simonbronk/codex/windows-unicode-output-collisions
fix(relinker): reject Unicode output aliases on Windows
2026-10-19 11:06:08 +02:00
David 35fe448155 feat(relinker): place macOS executables at a fixed base with --macos-fixed-base
Ruffle (PPSA68091), VLC (PPSA85300) and XPSemu (PPSA97358) read
app0/imports.txt, whose import slot addresses assume the executable's
lowest ELF load page at 0x400000, and dereference them at once. macOS
output always placed that page at 0x140010000, so all three fault on
their first slot read. Linux and Windows have --linux-fixed-base and
--windows-fixed-base for this; macOS had no equivalent.

--macos-fixed-base <address> builds the load image so that the lowest
ELF load page is at the address, as --windows-fixed-base does. The
Mach-O header takes the 64 KiB below it and __PAGEZERO ends there. The
executable is MH_EXECUTE without MH_PIE, so dyld does not slide it. The
address must be 64 KiB aligned, from 0x20000 through 0x7fffffff0000,
and an image ending past the macOS user address space is refused.
2026-10-19 10:32:30 +02:00
Jota 502b55722a fix(relinker): detect output aliases with a filesystem probe outside Windows
Fresh outputs whose names differ only in case, or that the volume treats as the same name otherwise, were not detected outside Windows. On default macOS APFS, for example, Ä/ä, Ω/ω, Ж/ж and canonically equivalent NFC/NFD names refer to the same file, so one output silently overwrote another.

Outside Windows, create the first name in an identity directory next to the outputs and check whether the second name resolves to it. This lets the filesystem decide instead of folding text, so case-sensitive volumes still report distinct names.
2026-10-19 02:50:43 -03:00
Adrià d265c58e40 Merge pull request #6351 from thuanquang/fix/layout-relative-output
fix(relinker): list converted modules correctly for a relative output path
2026-10-19 02:40:55 +02:00
Adrià 64b57d1b02 Merge pull request #6328 from aaro56/fix/relinker-preserve-input-hardlinks
fix(relinker): preserve inputs behind Windows executable hard links
2026-10-19 01:17:35 +02:00
Eli Yarson 346b0a92ca feat(relinker): add conversion-time game patches
A per-game relinker-patches.toml (or --patches) replaces verified bytes in
the input executable and bundled modules before conversion. Entries are
checked against the expected bytes and against every in-place writer,
the ELF header range and relocation targets. An <output-stem>.patches.toml
manifest records the applied entries, and --verify-patches checks an input
or a converted output. With no patch file the output is unchanged.
2026-10-18 14:57:13 -03:00
Adrià 889cee5f9f Merge pull request #6115 from ArjenS-LA/fix/relinker-needed-output-collisions
fix(relinker): reject needed companion output collisions
2026-10-18 18:44:13 +02:00
Dean 696c5f3516 Merge pull request #6156 from MarcZX100/codex/relinker-guest-dir-identity
fix(relinker): compare guest module directories by identity
2026-10-17 18:00:53 +13:00
simonbronk 46cafca20f fix(relinker): reject Unicode output aliases on Windows 2026-10-16 08:50:15 +02:00
thuanquangandClaude Opus 5.5 f40acdb2c6 fix(relinker): list converted modules correctly for a relative output path
The runtime layout printed ../app0/sce_module/... under app0/ when the
output path contained . or .. components (./out.elf), because the module
paths are normal and the output directory was not.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 19:44:50 +07:00
Adrià 7a22c43027 Merge pull request #5695 from YossiYad/fix/relinker-executable-mode
fix(relinker): write Linux and macOS executables with execute permission
2026-10-15 14:15:08 +02:00
aaroandCodex 5054b67b42 fix(relinker): preserve inputs behind Windows executable hard links
Co-authored-by: Codex <noreply@openai.com>
2026-10-15 17:23:14 +05:30
Jota 006e176812 fix(relinker): compare guest module directories by identity
(cherry picked from commit 6e3b80d65bd47344b896af9cbc763033fe6d5be0)
2026-10-14 23:42:30 +00:00
ArjenS-LA 62b09018d4 fix(relinker): reject needed companion output collisions 2026-10-14 15:36:25 -07:00
Adrià 8f9240f067 Merge pull request #5512 from zaryabzip/fix/module-dir-executable-input
fix(relinker): skip the input executable when scanning modules for --executable
2026-10-15 00:02:56 +02:00
Kang, Harry Zixuan b2c9e91102 fix(relinker): audit dependencies without imported functions 2026-10-14 20:57:16 +02:00
Adrià 2f3adfb91b Merge pull request #4802 from ArjenS-LA/fix/relinker-output-collisions
fix(relinker): reject collisions between generated outputs
2026-10-14 19:33:36 +02:00
Renan Pires 75cfcec67c feat(relinker): load Linux executables at a fixed base with --linux-fixed-base 2026-10-14 12:35:36 +02:00
Yossi Yadgar ab94ee21d7 fix(relinker): write Linux and macOS executables with execute permission
The executables were written with the default file mode (0644 under a
022 umask). The documented chmod covered app.elf, but not the outputs of
--executable, so RetroArch (PPSA99169) converted with
--executable picker/picker.bin --executable es-de/es-de.bin stopped as
soon as it started its picker:

  sceSystemServiceLoadExec: execv failed: Permission denied

Every executable output now gets execute permission for owner, group and
others after it is written, as --autorun already did for app.elf. With
it RetroArch starts the picker, and the picker starts ES-DE.
2026-10-14 12:49:12 +03:00
ArjenS-LA 69375305f5 fix(relinker): reject collisions between generated outputs 2026-10-13 23:59:02 -07:00
zaryabzip 9954e58af9 fix(relinker): skip the input executable when scanning modules for --executable 2026-10-14 02:51:23 +05:00
thuanquangandClaude Opus 5.5 286cf28ec7 fix(relinker): reject a directory as the executable output path
An existing directory, or a path ending in a separator, was only
rejected when the executable was written, after the converted modules
had already been written to app0/ beside or inside it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 00:49:48 +07:00
codingreapermapsandClaude Opus 5.5 430e5c833e feat(relinker): add --trace-imports for Windows executables
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 <noreply@anthropic.com>
2026-10-13 16:55:31 +02:00
Kovalev Vladislav 319fa5779a feat(relinker): support fixed Windows guest image bases 2026-10-13 12:55:54 +02:00
boykopovar 04899cb0f7 fix(relinker): align console progress columns 2026-10-13 13:02:03 +03:00
boykopovar c9a94dd021 fix(relinker): remove layout padding and blank lines after headings 2026-10-13 12:59:04 +03:00
boykopovar 754b82163b fix(relinker): remove indentation from conversion summary 2026-10-13 12:58:48 +03:00
boykopovar 868d7a138d fix(relinker): separate console sections without spacing every message 2026-10-13 12:58:35 +03:00
boykopovar 56c2a53188 fix(relinker): redraw console progress and format runtime layout as a tree 2026-10-13 12:58:34 +03:00
boykopovar b42aa8c540 feat(relinker): expose in-memory conversion through a portable core library 2026-10-13 12:57:53 +03:00
boykopovar 37e25a2a78 refactor(relinker): separate native module discovery from prx conversion 2026-10-13 12:57:05 +03:00
boykopovar 02cd2e57e2 refactor(relinker): move windows icon and diagnostic io to native adapters 2026-10-13 12:56:42 +03:00
boykopovar cd0433fd82 feat(relinker): report syscall scanning and import analysis progress 2026-10-13 12:56:16 +03:00
p3nicillinandClaude Opus 5.5 99efb32c40 feat: convert a title's other executables and start them with sceSystemServiceLoadExec
The relinker's repeatable --executable converts another executable of the
title beside the output, sharing its libs, app0 and guest modules.
sceSystemServiceLoadExec maps /app0 paths to those executables, passes the
argument list on the new command line and finds the main executable through
APS5_EBOOT. RetroArch (PPSA99169) starts its frontend picker this way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 08:51:50 +01:00
Dean e305bc4e4a Merge pull request #4483 from fable-void/pr/relinker-jump-table-targets
fix(relinker): treat jump table targets as branch targets when moving instructions
2026-10-13 07:28:12 +13:00
Dean d3187dff7f Merge pull request #4346 from SandstromPL/fix/relinker-registry-after-patch
fix(relinker): write registries after the executable
2026-10-13 04:52:40 +13:00
Dean 57f5ec232c Merge pull request #4338 from ArjenS-LA/fix/diagnostic-input-preservation
fix(relinker): preserve inputs when writing Windows diagnostics
2026-10-13 04:52:18 +13:00
fablevoid 1541244b78 fix(relinker): treat jump table targets as branch targets when moving instructions
The AMD-only converter moves the instructions next to a short AMD-only
instruction into its stub, and refuses to move one that a branch
enters. It knew only direct jump and call targets, so a switch case
entered through a jump table could land inside the jump written over
the moved instructions.

The converter now also reads relative jump tables: a RIP-relative LEA
whose address lies in a loaded segment starts a table of 32-bit
offsets from that address, read while each target starts an
instruction of the segment being converted. To read them, Convert
takes the program headers and converts their executable load
segments; the relinker passes all headers, and
ElfReader::ReadCodeSegments, now unused, is removed.

The converter test builds a dispatch through such a table whose second
case enters the instruction after a 4-byte 256-bit store, and checks
that the store takes the instruction before it instead. It also checks
that reading stops at an entry that does not start an instruction,
that only a LEA forms a table address, that tables are read only
inside loaded segments, that only executable load segments are
converted, and that a segment past the end of the file fails the
conversion.

PPSA12544: one 256-bit store in Il2cppUserAssemblies moved the
instruction after it, which is case 1 of a switch; it now goes through
an island. The other relinked files are unchanged.

AI-assisted: yes
2026-10-12 13:06:23 +00:00
SandstromPLandClaude Opus 5.5 70f03dc5e5 fix(relinker): write registries after the executable
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 12:48:01 +05:30
ArjenS-LA 4b6bb2f059 fix(relinker): preserve inputs when writing Windows diagnostics 2026-10-12 00:01:25 -07:00
fablevoid c763a78b67 feat(relinker): split unaligned 256-bit stores for Rosetta
Rosetta 2 ends the process when an unaligned 256-bit store crosses into
a page the store may not write ("rosetta error: unexpectedly need to
EmulateForward on a synchronous exception") instead of raising the
fault. A store inside such a page, and a 128-bit store across one,
fault normally. The macOS write watch keeps watched pages read-only
between collections, so a guest that stores 32 bytes across the start
of a watched page ends the process.

--to-rosetta now lowers VMOVUPS, VMOVUPD and VMOVDQU with a 256-bit
memory destination to a stub that stores the low half with the same
instruction at VEX.L=0 and the high half with VEXTRACTF128 at the
operand plus 16, so each half faults like any other store. RIP-relative
operands are relocated from the instruction's place in the site. A
store whose upper half has no 32-bit displacement fails the conversion.
Aligned stores (VMOVAPS, VMOVAPD, VMOVDQA and the non-temporal forms)
cannot cross a page and are left alone.

Every store is lowered. A 4-byte store has no room for the jump to its
stub, so its stub also takes one neighbouring instruction. The
following one: a return ends the stub, a call pushes its original
return address and jumps, a jump or conditional branch is re-encoded
with a 32-bit displacement, and another 256-bit store is lowered in the
same stub. Otherwise the preceding one, unless it is a call, a jump, a
return or a trap. Register calls, LOOP, JRCXZ and branches with a
prefix that changes them do not move. When neither neighbour can move,
the store becomes a short jump to an island: a run of movable
instructions within reach is moved to its own stub, and the bytes it
frees hold the jump to the store's stub. A store with neither fails the
conversion.

The converter tests compare the halves with clang's encodings for base,
SIB, disp8 overflowing to disp32, disp32, RSP-relative, FS-absolute,
r13-based and RIP-relative operands, and check that aligned stores,
loads, register forms and masked stores are not split. They check the
stub of a short store followed by a return, a call (direct, with an
address-size prefix, and through a RIP-relative pointer), short, near
and RIP-relative jumps, short and near conditional branches and another
store; that a following register call, LOOP, JRCXZ, prefixed branch or
CLWB makes the store take the instruction before it; islands past
instructions already moved, a store that needs its own stub, a branch
target, a register call and the short-jump reach; and that a store with
no room fails.

PPSA12544: 55295 stores split in the executable, 278 of them in the
stub of the store before them; every module converts with none kept.

AI-assisted: yes
2026-10-12 02:48:33 +00:00
Adrià 66e87807d5 Merge pull request #3804 from MarcZX100/codex/relinker-trap-sdk-syscalls-2136
feat(relinker): trap exact SDK syscall stubs on request
2026-10-12 00:05:38 +02:00
MarcZX100 0a6247532e feat(relinker): trap exact SDK syscall stubs on request 2026-10-11 16:54:02 +00:00
Ondra97 1f34f6e8d4 fix(relinker): preserve inputs when writing desktop assets 2026-10-11 17:11:26 +02:00
lorkydey 0cfdc79e59 fix(relinker): preserve input binaries when writing registries 2026-10-11 13:38:27 +02:00
Adrià FranchandClaude 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