35 KiB
Relinker usage
Online relinker - files stay on your device.
Browser folder output
The online converter can save Linux output as an uncompressed folder in supported browsers. It does not overwrite an existing destination folder. If conversion fails or is cancelled, it aborts the active file and attempts to remove the newly created output folder. If the browser prevents cleanup, the error names the folder that may contain incomplete files.
Input and conversion
Use a clean ELF executable. Place its bundled ELF modules in sce_module/, sce_modules/, or prx/ beside the input executable, or under the parent directory selected with --sce-module-path <path>. The option accepts absolute paths and paths relative to the current working directory; do not include the sce_module directory itself. Only files directly inside the module directories are processed. SELF/fSELF containers are skipped with a warning; unwrap them before relinking if they need to be included. tools/unwrap_self.py <input.self> <output.elf> does that for a decrypted container: it places the segments at the offsets of the SELF segment records and restores p_offset % p_align == p_vaddr % p_align, and it refuses a container whose load segments all look encrypted. prx/ can coexist with either sce_module/ or sce_modules/. Both sce_module/ and sce_modules/ present, or all three absent, is an error.
source/
input.elf
sce_module/
<bundled ELF modules>
relinker [options] <input.elf> <output>
Linux output:
relinker source/input.elf app.elf
Windows output:
relinker --windows source/input.elf app.exe
macOS output (x86-64 Mach-O, which runs under Rosetta on Apple silicon):
relinker --macos source/input.elf eboot
Add --to-intel for Intel hosts, or --to-rosetta for Rosetta; a title that runs under Rosetta on Apple silicon is relinked with --macos --to-rosetta. --to-intel rewrites AMD-specific instructions found in the executable and bundled modules; it cannot translate AMD-specific instructions generated by a title at runtime, so such titles require an AMD host. The output format defaults to Linux ELF regardless of the filename; .exe alone does not select Windows.
Titles run natively, so the host CPU needs the extensions of the PS5's Zen 2 CPU that compiled code uses without checking, among them AVX2, FMA, F16C, BMI1, BMI2, LZCNT and MOVBE. On Linux and Windows a converted title logs the host CPU's extensions at start-up and stops before running any title code, naming the CPU and the missing extensions, if one is absent (list).
The executable, registry, Windows import diagnostics, icon and desktop entry output files must not refer to the input executable or a bundled module being converted, including through a hard link or symbolic link. These collisions fail before any output is written. An existing output file can be replaced if it is separate from those inputs.
Options
All switches are disabled by default. unused-filter defaults to 0; --rpath defaults to $ORIGIN/libs.
| Option | Effect |
|---|---|
--help |
Print usage to stdout and exit with code 0; input and output paths are not required. |
--windows |
Produce a Windows PE executable. |
--macos |
Produce an x86-64 Mach-O executable, with bundled modules as Mach-O libraries in app0/sce_module. Conflicts with --windows. |
--windows-diagnostics |
Include startup dependency diagnostics. Requires --windows. |
--windows-gui |
Select the Windows GUI subsystem instead of the console subsystem. Requires --windows. |
--windows-fixed-base <address> |
Map the lowest ELF load page at this fixed Windows address, given in decimal or hexadecimal (0x). Requires --windows, a 64 KiB-aligned address from 0x20000 through 0x7fffffff0000, and a complete image fitting in the user address space. The main executable has no ASLR or base relocations; an occupied address fails to load. Bundled modules keep their normal placement. |
--trace-imports |
Pass each function import through the libc.prx call tracer, see tracing system library calls. Requires --windows; conflicts with --lazy-binding. |
--linux-fixed-base <address> |
Load the Linux executable at this fixed address, given in decimal or hexadecimal (0x): every virtual address of the image is offset by it, so virtual address 0 is placed at the address, as the console places an executable. Requires a 64 KiB-aligned address from 0x10000 through 0x7fffffff0000 that is a multiple of every PT_LOAD alignment, and an image that ends in the user address space. The executable is written as ET_EXEC, without ASLR. Bundled modules keep their normal placement. Conflicts with --windows, --macos and --lazy-binding. |
--to-intel |
Convert supported AMD-only instructions in the executable and bundled modules. Unsupported instructions or unreachable conversion stubs cause an error. |
--to-rosetta |
Convert as --to-intel does and also lower what Rosetta lacks: RDSEED becomes RDRAND, CLWB a NOP, and RDPID returns processor 0. Implies --to-intel. Independent of --macos: --macos alone leaves these instructions as they are. Conflicts with --windows. |
unused-filter=0 |
Keep all imported NID references. |
unused-filter=1 |
Filter unused non-PLT imports using control-flow and GOT access analysis, treating address-taken code as reachable; preserve PLT imports. |
unused-filter=2 |
Apply strict unused-import analysis and compact the PLT. Unsupported analysis cases cause an error. |
--registry |
Write <output-stem>.registry.json beside the output executable, and <output-stem>.<module>.registry.json for each converted bundled module. |
--rpath <path> |
Set the system library search path. Quote $ORIGIN to prevent shell expansion, for example --rpath '$ORIGIN/libs' in Bash or PowerShell. Linux guest modules require an absolute path or a path beginning with $ORIGIN. Windows requires a nonempty ASCII path and supports $ORIGIN as the executable directory. |
--autorun |
Run the output after conversion, print its exit code, and wait for Enter. Adds executable permissions for Linux output. Requires the target OS and prepared runtime layout. |
--sce-module-path <path> |
Parent of the module directories, not the module directory itself. Default: input ELF parent; relative input paths resolve from the process working directory. Explicit absolute path: used directly. Explicit relative path (including . and ..): relative to the process working directory. Output path has no effect. Requires one nonempty value; repeats, nonexistent or nondirectory paths, access failures, and combination with --skip-sce-module are errors. |
--module-dir <dir> |
Relink the ELF modules of a directory under the module parent directory, besides sce_module/sce_modules/prx; outputs keep the relative path. A module the executable or another module needs is initialized at startup, any other when the title loads it with sceKernelLoadStartModule, with its start arguments (for example Unity plugins in Media/Plugins). Repeatable; . is the parent itself, without the input ELF. Conflicts with --skip-sce-module. |
--executable <path> |
Also convert another executable of the title, which it starts with sceSystemServiceLoadExec; the path is relative to the module parent directory, for example picker/picker.bin. It is written beside the output, named after the path with / replaced by . and given the output's extension (picker.picker.bin.exe), and uses the same libs/, app0/ and guest modules. It is an error if a guest module converts differently for it. Repeatable. Conflicts with --skip-sce-module. |
--skip-sce-module |
Deprecated. Disable bundled module processing and module directory checks. Conflicts with explicit --sce-module-path. |
--exclude-sce-module <file> |
Deprecated. Exclude a bundled module by exact filename, not path. Repeat for multiple files; a missing filename is an error. Conflicts with --skip-sce-module. |
--skip-syscall-check |
Deprecated. Disable syscall scanning in the executable and bundled modules. |
--trap-sdk-syscall-stubs |
Replace the exact ps5-payload-dev generic and numbered syscall wrapper stubs and the recognized inline mmap/mprotect sequences from SDK libc/mman.c with UD2, so unused wrappers do not block relinking. Other syscall instructions still cause an error. If a trapped wrapper runs, it raises an illegal-instruction exception. For debugging only. |
--lazy-binding |
Deprecated. Enable lazy symbol binding instead of eager binding. Incompatible with bundled ELF modules. |
Specify unused-filter=0|1|2 without --, at most once. Unknown options and extra positional arguments are errors. Use relinker --help to print the usage syntax to stdout and exit with code 0 without converting files. Invoking relinker without arguments prints the usage syntax to stderr and exits with code 1.
The --skip-sce-module, --exclude-sce-module <file>, --skip-syscall-check, and --lazy-binding flags are deprecated. If the application runs with these flags enabled, it will be extremely unstable and unsuitable for general use. These flags are only for debugging.
--trap-sdk-syscall-stubs is also for debugging. It preserves a runtime failure if a syscall wrapper is called; it does not implement FreeBSD syscalls or make these applications generally safe to run.
Runtime layout
For an executable that assumes its image starts at 0x400000, use relinker --windows --windows-fixed-base 0x400000 source/input.elf app.exe. This maps its lowest ELF load page there; the PE headers occupy the preceding 64 KiB. Use this option only when the executable requires an exact address. Without it, the Windows executable has no DYNAMIC_BASE and loads at its preferred base 0x140000000; relocations are kept, so Windows relocates it if that range is taken. Modules keep DYNAMIC_BASE. This option does not change macOS output.
For an executable that assumes its image starts at 0x400000, as Ruffle (PPSA68091), VLC (PPSA85300) and XPSemu (PPSA97358) do when they read their app0/imports.txt, use relinker --linux-fixed-base 0x400000 source/input.elf app.elf on Linux. Use this option only when the executable requires a fixed address. Without it, Linux output is a position-independent executable as before.
The same base is applied to executables selected with --executable; bundled PRX modules are not rebased.
Paths are relative to the output executable:
app.elf (Linux) or app.exe (Windows)
libs/
*.prx
app0/
<app resources>
eboot.bin
sce_module/
<converted modules>
Use sce_modules/ or prx/ instead of sce_module/ if that is the input directory name. Relinker preserves each module's directory under app0/ and prints its exact path. Place app resources in app0/ separately, including the title's original eboot.bin: titles test /app0/eboot.bin to find their own directory, as it is always present on the console (ProsperoTV, ProsperoRadio, ProsperoLight and ProsperoLichess fall back to an empty /data/homebrew/<title id> and fail to load their fonts without it). Only the converted executable runs. Copy the built system libraries from build/core/libs/libs/*.prx into libs/; use libraries built for the target OS. A custom --rpath changes the system library location.
Some applications also need their original app0/sce_sys/param.json at runtime. AnyPS5 reads this file for the title ID and display name; it must contain a non-empty titleId string and a non-empty localizedParameters object with a language entry that has a non-empty titleName. It selects the entry named by defaultLanguage (inside localizedParameters, or at the top level); if that entry is absent, it tries en-US, then the first object-valued language entry. Preserve the values from the title's own metadata and place the file under the same app0/ directory as the other app resources. At startup, its presence in the working directory creates data/; a non-zero downloadDataSize also creates download0/. Without the file, neither directory is created. If it is missing or invalid, metadata calls such as title/window setup can fail with a param.json error.
Use the generated files printed as Guest module: for bundled title modules. libs/ is for AnyPS5 system libraries, not the original PS5 .prx files. Placing an original PS5 module in libs/ on Windows makes Windows try to load it as a DLL and can fail with error 193 (not a valid Win32 application).
On Windows, a self-built libs/ also needs libgcc_s_seh-1.dll, libstdc++-6.dll and libwinpthread-1.dll from the mingw64/bin directory of the toolchain that built the libraries; the release archives already contain them. The libraries are loaded without searching PATH, so a copy elsewhere on the system is not used, and a library that needs one of them fails with error 126 (the specified module could not be found) although its .prx file is present.
On Windows, direct memory (sceKernelAllocateDirectMemory, up to 13824 MiB per title) is committed when the title maps it, not when the title allocates it or first uses its pages. The system commit limit (installed memory plus page file size, the second value of Committed in Task Manager) must cover the mapped direct memory together with all other committed memory. Otherwise sceKernelMapDirectMemory throws replace guest placeholder with resident direct memory with the Windows error (commit direct memory pages when a page is mapped at a second address, keep unmapped direct memory when it is unmapped); enlarge the page file or close other applications.
On Linux, when the Vulkan driver imports dma-buf memory (not the NVIDIA proprietary driver), shared direct memory is imported through /dev/udmabuf, and the user who runs the game needs read-write access to it. Many distributions create it as root:kvm with mode 0660: add the user to the kvm group and log in again (an ACL such as setfacl -m u:$USER:rw /dev/udmabuf lasts until the next reboot). Without access, startup prints [gpu] open /dev/udmabuf: Permission denied, these ranges are copied instead of imported, and GPU stores to them through FLAT/GLOBAL addresses fail with BDA access failed. A range larger than 64 MiB is imported as one sparse buffer over 64 MiB udmabuf buffers when the device binds sparse memory (RADV, ANV). Without sparse binding, or when the range does not start on the device's sparse block size in the backing file, ranges above udmabuf's size_limit_mb (64 MiB by default) are copied as well.
Linux:
chmod +x app.elf
./app.elf
Windows PowerShell:
.\app.exe
A Windows executable started without a standard output or error handle, such as a --windows-gui build started from Explorer, writes that output to anyps5.log beside it, replacing the previous run's file. Without write access to that directory it runs without the log.
Controller input for test runs
Set APS5_PAD_INPUT_FILE to an absolute path before launching the application to enable controller pulses from a local command file. Once the application is reading controller input, send a button with the helper from the repository root:
APS5_PAD_INPUT_FILE=/tmp/anyps5-pad ./app.elf
python3 tools/pad_input.py /tmp/anyps5-pad cross
python3 tools/pad_input.py /tmp/anyps5-pad down --hold-ms 500
python3 tools/pad_input.py /tmp/anyps5-pad release
python3 tools/pad_input.py /tmp/anyps5-pad release --left-stick 128 0 --hold-ms 2000
python3 tools/pad_input.py /tmp/anyps5-pad cross --left-stick 255 128 --right-stick 0 128
Run the helper in a second terminal. Commands are polled at most every 100 ms during controller reads, and their buttons add to physical controller input. A press lasts 300 ms by default, starting when the application reads it, and releases automatically; --hold-ms accepts 1–5000 ms. --left-stick X Y and --right-stick X Y hold the sticks for the same time: each axis is 0-255, 128 is the centre, X grows to the right and Y grows downward. While such a command is active both sticks come from it instead of the physical controller, and a stick that was not named stays centred. Wait for the application to observe a release before sending another press. The file holds only the latest command; rapid commands can replace one another. Commands left over when the application first reads the controller are ignored. Without the environment variable, file input is disabled.
System fonts
Games that open the console's system font sets (sceFontOpenFontSet) need font files in an anyps5-fonts/ directory beside the output executable; set ANYPS5_SYSTEM_FONTS to use another directory. Files dumped from the console are used under their own names (SST-Roman.otf, SST-Bold.otf, SSTJpPro-Regular.otf, ...). Without them, these openly licensed substitutes are used when present: NotoSans-{Light,Regular,Medium,Bold}.ttf and NotoSans-{LightItalic,Italic,MediumItalic,BoldItalic}.ttf (Latin and Vietnamese), NotoSansMono-{Light,Regular,Medium,Bold}.ttf (typewriter), NotoSansThai-{Light,Regular,Medium,Bold}.ttf (Thai) and NotoSansCJK-{Light,Regular,Medium,Bold}.ttc (Japanese and Chinese). Without either, opening a system font set fails and the game shows no text in those fonts.
Console language
Games ask for the console's language with sceSystemServiceParamGetInt and show their text in it when they have it. It is English (United States) unless ANYPS5_LANGUAGE is set to another console language number: 0 Japanese, 1 English (United States), 2 French, 3 Spanish, 4 German, 5 Italian, 6 Dutch, 7 Portuguese (Portugal), 8 Russian, 9 Korean, 10 Chinese (Traditional), 11 Chinese (Simplified), 12 Finnish, 13 Swedish, 14 Danish, 15 Norwegian, 16 Polish, 17 Portuguese (Brazil), 18 English (United Kingdom), 19 Turkish, 20 Spanish (Latin America), 21 Arabic, 22 French (Canada), 23 Czech, 24 Hungarian, 25 Greek, 26 Romanian, 27 Thai, 28 Vietnamese, 29 Indonesian, 30 Ukrainian. For example, ANYPS5_LANGUAGE=21 selects Arabic. An empty value is the same as an unset one; any other value stops the game at its first language query with an error that names the variable.
GPU selection
The game runs on the first Vulkan 1.1 device with graphics and compute queues and swapchain presentation, preferring a discrete GPU over an integrated one. Set ANYPS5_GPU to a part of a device name, compared without regard to case, to run on another device; the names are printed at start-up in the Physical device candidate lines. When no usable device contains the text, the start fails and the error lists the device names.
The window presents with vertical sync (fifo). Set ANYPS5_PRESENT_MODE to fifo_latest_ready, mailbox or immediate to keep the game from waiting for the display: fifo_latest_ready and mailbox still show whole frames at the display's refresh and drop the ones in between, immediate presents at once and can tear. The start fails on any other value and when the surface does not offer the mode (fifo_latest_ready needs VK_KHR_present_mode_fifo_latest_ready or its EXT form). Flips stay paced at 59.94 Hz in every mode. A Wayland compositor can stop delivering frames to a window it does not show, for example one on another workspace: on Hyprland with the NVIDIA driver a fifo swapchain then presents about once per second, while fifo_latest_ready and immediate keep the game at its normal rate.
On Windows, the OpenCL, OpenGL, and Vulkan Compatibility Pack can install Mesa Dozen, which exposes D3D12 devices through Vulkan. Some D3D12 drivers can crash while the Vulkan loader enumerates adapters, even when AnyPS5 would select a native Vulkan device. If this happens and the system has another Vulkan driver, disable Dozen for the current PowerShell session before launching AnyPS5:
$env:VK_LOADER_DRIVERS_DISABLE = '*dzn*'
.\app.exe
Do not disable Dozen if it is the only Vulkan driver available; the application would have no device to use. VK_LOADER_DRIVERS_DISABLE matches Vulkan driver manifest filenames and requires a Vulkan loader built with headers 1.3.234 or later (loader documentation).
On Windows, NVIDIA driver 610.47 can crash in its shader compiler while pipelines are created: the log ends in FATAL: unhandled exception 0xc0000005 with nvgpucomp64.dll in rip, on a driver thread. PPSA25344 on an RTX 3060 stopped this way in 3 of 3 New Game loads, also with APS5_NO_PIPELINE_LTO=1; with driver 617.42 the same build and scene ran for 40 minutes. Update the driver when the crash is in nvgpucomp64.dll.
HDR output
HDR output is available on Windows only. Titles see it when the monitor showing the window runs in HDR mode (Settings > Display > Use HDR), the window's Vulkan surface offers an HDR10 (VK_COLOR_SPACE_HDR10_ST2084_EXT) swapchain in a 10-bit format, the instance has VK_EXT_swapchain_colorspace and the device VK_EXT_hdr_metadata. The monitor's luminance comes from DXGI; a monitor that reports no peak luminance is taken as 1000 nits peak and full-frame, 0 nits black. On Linux and other hosts the output is always SDR, because the surface's formats do not show whether the display is in HDR. A title's BT.2100 PQ display buffers then reach the display unchanged; without HDR output they are converted to SDR.
Shader cache
Recompiled shaders and the Vulkan pipeline cache are kept in a shader_cache/ directory beside the output executable, so later runs skip recompilation. Set ANYPS5_SHADER_CACHE_DIR to use another directory, or set ANYPS5_NO_SHADER_CACHE to any non-empty value other than 0 to disable both caches. Shaders are stored in a subdirectory named after the recompiler's source version; subdirectories of other versions unused for 14 days are removed the first time a run looks up a shader. The pipeline cache is one vk-pipelines-*.bin file per GPU and driver version. Deleting the directory is safe: it is rebuilt on the next run.
Storing GPU results at each flip
GPU results in storage images and render targets stay on the GPU until something reads their memory. A title that frees such an image after waiting for its GPU work and reuses the memory can have its new data overwritten when the results are stored later (TechnicalDebt). APS5_STORE_AT_FLIP=1 stores every pending result when a display buffer is flipped, at the cost of one write-back per pending image and frame. Any other value stops the title at its first flip.
A title's own CPU reads of that memory are not seen, so a title that waits for a GPU label and then reads the results reads stale memory. APS5_STORE_AT_LABEL=1 stores every pending result before each label the GPU writes (RELEASE_MEM, WRITE_DATA, DMA_DATA), at the cost of a write-back of every pending image per label. Any other value stops the title at its first label.
Tracing system library calls
Windows only. An executable converted with --trace-imports records the calls it makes to the libraries named in APS5_TRACE_CALLS, a comma-separated list such as libkernel,libSceSaveData, or all. Without the variable nothing is recorded. The crash and abort reports then end with the last 64 calls: sequence number, thread, library, import name as the executable imports it (a NID) and the caller's address. They are followed by the 32 most called imports since start-up, with their number of calls.
last traced system library calls (APS5_TRACE_CALLS):
#1 thread 9384 libkernel.prx k+AXqu2-eBc from 0x0000000140010006 app.exe+0x10006
most called traced imports (1 calls in total):
1 libkernel.prx k+AXqu2-eBc
Limits are listed in the libc and relinker technical debt.
Windows crash diagnostics
Set APS5_CRASH_INDIRECT_MEMORY=1 before launching the executable to include up to 64 bytes from each pointer found in the register memory dump. The report labels each block with its register, pointer offset and target address. Inaccessible, incomplete and executable targets are skipped. This additional dump is disabled when the variable is unset or empty.
Exit codes
0: conversion succeeded or help was displayed. 1: invalid arguments. 2: conversion failed; the error is printed to stderr. With --autorun, successful conversion returns the launched application's exit code.
Import audit
tools/import_audit.py lists the system functions a converted game imports and whether the built libraries provide them, before the game is launched. It needs Python 3 and nothing else.
relinker --registry source/input.elf app.elf
python3 tools/import_audit.py app.registry.json app.libc.prx.guest.prx.registry.json --libs build/core/libs/libs --modules source/sce_module
--registry writes app.registry.json for the executable and one app.<module>.registry.json for each converted bundled module; pass all of them. --libs is the directory the libs target fills (build instructions); the exports are read from the built .prx files, so the result matches what the loader finds. Imports are counted once per NID and library, and every reference lands in exactly one class:
Module registries mark ELF weak imports with "weak": true, allowing the audit's current-stop selection to skip them. Strong imports omit the field. Older module registries do not preserve weak bindings; regenerate them to distinguish optional imports.
| Class | Meaning |
|---|---|
implemented |
A built library exports the NID and its function is not a throwing stub. |
stub |
Exported, but the whole function calls NotImplemented_nid_no_patch: the game loads and throws when it calls it. |
absent |
No built library exports the NID: the loader fails. |
module |
The import names a file found in --modules (the title's own library), which is trusted to provide it. |
ELF modules in --modules are audited regardless of filename: their undefined symbols are classified against the built libraries and the other modules' exports, with the module file named in the report, and the libraries they need must have a file in --libs or --modules. Ordinary non-module sidecars are ignored; malformed ELF, PE/SELF containers and non-ELF files with module extensions remain errors. The audit reads dynamic tables from ELF section headers or, for sectionless files, from PT_DYNAMIC and a SysV or GNU hash table.
Each registry has a companion with the same prefix and the suffix .needed.json, listing its host library dependencies, including declared DT_NEEDED entries with no imports. Dependencies supplied by converted bundled modules, including their aliases, are omitted. The executable's companion includes host dependencies of its bundled modules. The audit reads these companions automatically; keep them beside the registries. Older registries without companions still work, but cannot report dependencies that have no imports; regenerate them with --registry to include those dependencies.
The report also lists needed libraries that have no file in --libs or --modules, including dependencies with zero imports. These do not increase the import or reference counts. Windows can skip a missing dependency when no import needs it; Linux still needs the file. The audit also lists imports exported only by a library other than the one they name; those resolve on Linux and can fail on Windows.
| Option | Effect |
|---|---|
--libs <dir> |
Built .prx directory. Required; repeat for several. |
--modules <dir> |
Directory of the title's own modules; repeat for several. |
--source <dir> |
core/libs/prx tree that is searched for throwing stubs. Defaults to this repository's. |
--names <file> |
NID and name per line, as in aerolib.csv from tools/nid_names.py; names the absent imports. Never downloaded; a name that does not hash to its NID is marked. |
--json <file> |
Full result, stamped with the repository's commit and whether its tree was modified. |
Exit codes: 0 nothing blocks loading, 1 there are absent imports or needed libraries without a file, 2 an input could not be read; the message names the file and the value.
A module registry lists nid, library and targetOffset for each relocation against a function that no other bundled module exports, without the # suffix of the executable's NIDs.