83 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.
Browser batch conversion
Select or drop multiple app ZIP archives to convert them into one ZIP bundle. The converter inspects each app separately and puts its executable, libraries and app0/ files in a numbered folder, so archives with the same filename remain separate. Batch output uses ZIP mode; saving to an uncompressed folder remains available for one Linux app at a time. The 256 MiB memory-download limit applies to the whole bundle, so use Save ZIP to disk for larger batches. If a batch is cancelled or any app fails, the converter aborts the incomplete bundle; any cleanup failure is reported instead of offering it as a completed result.
Input and conversion
The SELF unwrapper refuses containers requiring more than 64 MiB of total alignment padding before allocating the output buffer. This limit counts padding across file-backed and empty segments, not segment data; a refusal preserves the input and any existing output file.
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. The relinker, on the command line and in the online converter, skips an ELF module as a backup copy, with a message, when its file name extends the name of another ELF module in the same directory and both have the same SONAME, such as a dumper's libX.prx.esbak or libX.prxbak beside an ELF libX.prx; a single backup beside a SELF container is still relinked. prx/ can coexist with either sce_module/ or sce_modules/. Both sce_module/ and sce_modules/ present is an error. All three absent is an error unless an explicit --module-dir is supplied.
source/
input.elf
sce_module/
<bundled ELF modules>
relinker [options] <input.elf> <output>
An explicit --module-dir also works when the module parent has no sce_module, sce_modules or prx directory. For a homebrew bundle with shared libraries beside its executable, use --module-dir .; no empty standard directory is required. Without an explicit module directory, the standard-directory requirement remains.
A module the executable needs that is not converted is looked for under the module parent directory, outside the module directories. Each SELF container or ET_SCE_DYNAMIC ELF found there whose name matches, up to the .prx, .sprx, .suprx or .debug_prx suffix (ignoring case with --windows, as the guest module match does), is reported after the conversion with the --module-dir that would include it; the output still refers to a host library of that name. A file that cannot be read is not reported.
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.
A converted title is an ordinary program: it runs with your user account's permissions and can read and write whatever your account can, with none of the console's sandbox. Convert and run only titles and modules you trust, and run them as a separate user or in a container or virtual machine to keep them away from your files. Its executable is not placed by address space layout randomization either: Linux loads it at 0x400000 or at the --linux-fixed-base address, unless the deprecated --lazy-binding keeps it position independent; Windows loads it at its preferred base 0x140000000 when that range is free, or at the --windows-fixed-base address, without DYNAMIC_BASE; and the macOS executable is written without MH_PIE.
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 instead of the preferred 0x400000, 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. Windows, Linux and macOS output; conflicts with --lazy-binding. |
--linux-fixed-base <address> |
Load the Linux executable at this fixed address instead of the default 0x400000, 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, as it is at the default address. Bundled modules keep their normal placement. Conflicts with --windows, --macos and --lazy-binding. |
--macos-fixed-base <address> |
Map the lowest ELF load page of the macOS executable at this fixed address, given in decimal or hexadecimal (0x). Requires --macos, a 64 KiB-aligned address from 0x20000 through 0x7fffffff0000, and an image that ends in the macOS user address space. __PAGEZERO ends 64 KiB below the address and the Mach-O header occupies those 64 KiB. The executable has no ASLR; an address the process cannot map fails to load. Bundled modules keep their normal placement. |
--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: ANDN becomes scalar NOT/AND, RDSEED becomes RDRAND, CLWB a NOP, XSAVEOPT becomes XSAVE, and RDPID and the ECX of RDTSCP return 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. On Linux every colon-separated entry must be nonempty, and with guest modules each must be absolute or begin 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. Needed modules are mapped at startup. On Linux, modules in these extra directories initialize only when explicitly loaded, with the arguments from sceKernelLoadStartModule (for example Unity plugins in Media/Plugins). A module another module needs initializes at startup on Windows and macOS; one only the executable needs initializes when explicitly loaded on Windows and at startup on macOS. Other modules initialize when loaded. 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. |
--patches <file> |
Apply the byte patches of this file during conversion, see game patches. Overrides relinker-patches.toml discovery. Conflicts with --no-patches. |
--no-patches |
Do not apply patches, and do not load relinker-patches.toml from the input directory. |
--verify-patches <file> |
Check a patch file against <path> without writing anything, see game patches. Takes one path and no output path. |
--verify-output |
With --verify-patches, treat <path> as a converted Linux output instead of an input executable. |
--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. |
--frame-red-zone |
Windows only: give leaf functions that keep values below RSP (the System V red zone) a frame, so an exception Windows delivers on a guest thread cannot overwrite them. Covers leaves with a push rbp; mov rbp, rsp; push ... prologue, leaves that only push registers, with or without a sub allocation after the pushes, and leaves with neither; a prologue or exit shorter than a jump takes in the instructions next to it. In leaves without a frame pointer, RSP is lowered only as far as the leaf reaches below it and their RSP operands are moved with it, in place or, when their encoding cannot hold the moved displacement, in a stub. Function bounds come from the executable's .eh_frame, and the relinker stops with an error when it has none. The relinker prints how many it framed and why it skipped the others, and refuses a game patch over a framed site. |
--lazy-binding |
Deprecated. Enable lazy symbol binding instead of eager binding. Incompatible with bundled ELF modules. The Linux executable stays position independent (ET_DYN) instead of being placed at 0x400000. |
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.
Game patches
A patch file changes bytes of the input executable or of a bundled module before conversion, for a title that hard-codes a value the host cannot meet. The relinker checks the bytes it replaces, so a patch for another version of the title fails instead of corrupting it. With no patch file the output is unchanged.
The advisor requires Python 3.11 or newer. To check a local patch catalog, run:
python3 tools/suggest_patches.py --catalog <directory> --param-json <sce_sys/param.json> --input <input.elf> --relinker <relinker> [--sce-module-path <directory>] [--module-dir <directory>]... [--executable <path>]... [--skip-sce-module] [--timeout-seconds <seconds>] [--list-verified]
The advisor reads titleId and contentVersion from the specified param.json, checks the SHA-256 of every file targeted by a candidate patch, then calls the relinker specified by --relinker to run --verify-patches. A relative path such as --relinker ./relinker resolves to that local file. The explicit metadata path also works when param.json and the unwrapped ELF files are stored separately. The catalog directory must contain an index.json with format: 1 and an entries array. Each entry has titleId, contentVersion, a relative patch path, and targets mapping every patch target to its SHA-256. Hexadecimal digests are case-insensitive. For example:
{
"format": 1,
"entries": [{
"titleId": "TEST00001",
"contentVersion": "01.000.000",
"patch": "patch.toml",
"targets": {"Media/Modules/module.prx": "<SHA-256 in hexadecimal>"}
}]
}
The advisor validates patch files only for entries matching the requested title and version. It reports and skips a malformed matching entry or a relinker verification error, so another valid entry can still be recommended. Verification times out after 120 seconds per entry by default; use --timeout-seconds <positive integer> to change the limit. For synthetic inputs without system modules, use --skip-sce-module if conversion also uses it; this option conflicts with --sce-module-path. No matching entry or no verified patch exits 1; invalid input or no verified patch after a relinker error exits 2. If multiple entries verify, the advisor recommends none because it cannot choose or combine them, and points to --list-verified to inspect the matches. The tool does not fetch or apply patches. A recommendation prints --patches and the module and executable options to use when converting. Keep catalog patch files outside the input executable's directory to avoid automatic relinker-patches.toml discovery.
With --list-verified, the advisor prints every entry that passes metadata, hash and relinker checks, in catalog order. Each result includes its catalog number, patch file, target hashes and conversion options. It exits 0 when at least one entry verifies, even if another entry was rejected; with no verified entries, the existing 1 or 2 exit status still applies. Without this option, a single verified entry is recommended and multiple verified entries remain ambiguous. The listing tells users to choose one patch file. It does not combine patch files or apply them during conversion; review the chosen patch before applying it.
The target hashes identify the exact unwrapped files changed by a patch, which might not include the main executable. Different dump tools could produce different bytes for the same title version; the advisor then rejects a patch even if it would work. It has not been confirmed that all dump tools produce identical files. The hashes do not authenticate a catalog or establish that a patch is safe or that the title is fully playable. Use catalogs from sources you trust and review the patch before applying it.
If the patch targets another title executable, pass --executable <path> to the advisor for each additional executable, using paths relative to the module parent directory, such as picker/picker.bin. The advisor hashes those target files and forwards the same options to relinker --verify-patches. Use the same --executable options when converting with --patches.
relinker-patches.toml in the directory of the input executable is loaded automatically and announced as Patches: <path>. --patches <file> names another file, and --no-patches loads none. Each applied patch is reported:
Patch: <target> [<name>] va=0x<address> off=0x<offset> <old> -> <new> (<description>)
The file is a subset of TOML; any other TOML syntax is an error that names the line. It is UTF-8, with blank lines, # comments outside strings, [[patch]] entries and key = value lines. Integers are decimal or 0x hexadecimal, strings are double-quoted without escapes, and keys match [A-Za-z0-9_]+.
format = 1
[[patch]]
name = "pool_size"
target = "eboot.elf"
va = 0x1234
type = "i32"
expected = 0x10000000
value = 0x18000000
max = 0x7f000000
align = 0x1000
description = "pool size"
format = 1 is required. The top-level keys relinker_version, relinker_commit, config, config_sha256, main_target, linux_base, settings and settings_sha256 are accepted in any file; only main_target and linux_base are used, by --verify-output. Any other top-level key is an error.
| Entry key | Meaning |
|---|---|
name |
Optional. A setting name of [a-z0-9_], unique within the file and not one of the reserved top-level key names (format and the eight keys above), used by a later --set option. Shown in the progress line, the manifest and the verify report. |
target |
Required. The main executable's file name, or for --executable files and bundled modules the path relative to the module parent directory, for example sce_module/libc.prx. |
va |
Required. The virtual address of the ELF as a disassembler shows it. |
type |
Optional: u8 u16 u32 u64 i8 i16 i32 i64. Then expected, value, min, max and align are integers, range-checked and written little-endian. Without it, expected and value are hexadecimal byte strings of the same length. |
expected |
Required. The bytes the input must hold. |
value |
Required. The bytes to write. |
min, max, align |
Optional, typed entries only; checked against value. |
description |
Optional. Reported and copied to the manifest. |
x86 mov r/m64, imm32 and add/sub/cmp r/m64, imm32 sign-extend their immediate. To patch a 64-bit field through an imm32 use type = "i32", and add min to keep negative values out.
Entries apply in file order. A patch range must lie in the file bytes of one PT_LOAD and outside the ELF header and program header table. The conversion fails before writing any output if bytes differ from expected, a range is not valid, two entries overlap, a target matches no file or more than one file, or a range overlaps bytes the relinker itself rewrites (--to-intel replacements and trampolines, relinker patches, syscall traps) or the 8-byte target of a relocation of the file.
When at least one patch is applied, <output-stem>.patches.toml is written beside the output. It lists every applied entry for every converted file, with main_target, linux_base (the address a Linux ET_EXEC output is placed at) and config (the patch file path in forward-slash form, reduced to its file name when the path holds a ", backslash or control character; the conversion fails before writing any output when even that cannot be held); typed entries keep their type, integers and min, max and align, and --patches accepts it to reproduce the conversion. The manifest must not replace the patch file it was read from.
--verify-patches <file> <path> writes nothing and exits 0 when no entry is a mismatch. Without --verify-output, <path> is the input executable of any type, modules are found as in a conversion (module options and --executable apply), and each entry is applicable (the input holds expected) or mismatch. A target that matches nothing is an error that suggests --verify-output.
With --verify-output, <path> is a converted Linux ELF; PE and Mach-O outputs are refused. The file must have main_target, so verify the generated manifest, not a hand-written file. Entries are checked at their va plus the manifest's linux_base in an ET_EXEC output, which requires linux_base; an ET_DYN output, from --lazy-binding, requires a manifest without it. The entry whose target is main_target is checked against <path>, and entries for the --executable strings given are checked against the output file derived as in a conversion; an entry holding value is applied. Every other entry is not-checked (module, or executable not named with --executable), including a mistyped module target. Two targets with the same name, such as an --executable equal to main_target, are an ambiguous patch target error. Module discovery options are rejected with --verify-output.
Verify mode rejects the conversion options (--windows, --macos, --to-intel, --rpath, --patches, --no-patches and the like) and a second path.
Runtime layout
The Windows executable has no DYNAMIC_BASE and prefers to load with its lowest ELF load page at 0x400000, where Ruffle (PPSA68091), VLC (PPSA85300) and XPSemu (PPSA97358) expect their image when they read their app0/imports.txt; the PE headers occupy the preceding 64 KiB (preferred base 0x3f0000). Relocations are kept, so Windows can relocate it if that range is taken. When the executable requires an exact address, use relinker --windows --windows-fixed-base <address> source/input.elf app.exe, which maps the lowest ELF load page at that address without relocations, so an occupied address fails to load. 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 --macos --macos-fixed-base 0x400000 source/input.elf app on macOS. Use this option only when the executable requires a fixed address; without it, macOS output places the lowest ELF load page at 0x140010000. Linux output places virtual address 0 at 0x400000 without an option, inside the 40-bit window that a title's own sceLibcMspaceCreate checks; --linux-fixed-base selects another address, and with --lazy-binding the executable stays position independent.
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. On Linux, a symbolic link inside the app0/ tree is listed with the type of the file or directory it points to, so a linked directory is walked like a real one. Links inside a linked directory whose target is outside app0/ keep the link type; for nested trees, link only the top-level entries or use copies or hard links. 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.
Resources can also be packed into app0.zar beside app0/, a ZArchive whose root is the app0/ directory. A file present in app0/ is read from there and any other file from the archive; the executable, sce_module/ and sce_sys/ stay in app0/. The archive is read only through sceKernelOpen/open, the read, seek and stat calls on its descriptors and sceKernelStat; other calls on an archived path, listing a directory that the archive also contains and opening a file in it for writing fail as described in technical debt. An archive that cannot be opened stops the title at its first file access.
For bundled title modules, use the .guest.prx files the relinker writes into app0/ and lists in the layout it prints. 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). A module the title loads while it runs that was not relinked, such as one in a directory not given to --module-dir, is not loaded either: the title gets an error and continues, and the runtime prints [module] dlopen: <path> was not relinked: ... once for each file. Relink that module's directory with --module-dir <dir>.
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. With APS5_MIMALLOC set, the title loads mimalloc.dll and mimalloc-redirect.dll from libs/ and allocates through mimalloc. Copy both from build/core/libs/libs; the release archives contain them. Without them the title keeps the C runtime's heap and prints nothing.
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 device's sparse block size is larger than 64 KiB, ranges above udmabuf's size_limit_mb (64 MiB by default) are copied as well.
Linux:
./app.elf
The relinker writes app.elf and each Executable file: it lists with execute permission. A copy that lost its file mode, such as a browser download, needs chmod +x on each of them first; without it a title that starts another executable stops with sceSystemServiceLoadExec: execv failed: Permission denied.
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.
Display resolution and window size
The game chooses its video-output resolution; AnyPS5 uses that mode for rendering. The display window can be resized or switched to fullscreen, which scales the presented image to fit the window. F11 switches between window and fullscreen; set ANYPS5_FULLSCREEN to a non-empty value other than 0 to open the window fullscreen from the start. Changing the window size does not change the game's render resolution. AnyPS5 does not currently provide a global render-resolution override; use a game's own display setting when it offers one.
VR compatibility
AnyPS5 does not currently support PS VR2 headsets or PC VR headsets connected through an OpenXR runtime. It does not provide headset tracking, stereoscopic output or reprojection. A VR title may skip its headset setup when no device is detected, but that does not mean its VR gameplay is supported.
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
python3 tools/pad_input.py /tmp/anyps5-pad touch-right
python3 tools/pad_input.py /tmp/anyps5-pad l1+r1
For timed button sequences, see Controller timelines.
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. touch-left and touch-right press the touchpad with a finger on its left or right half, as the TouchLeft and TouchRight keys do; in the command file they are bits 0x200000 and 0x400000 of the button field. Button names joined by +, such as l1+r1, are pressed together in one command; release cannot be joined. 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. On POSIX, replacing an existing command file retains its permission mode; a privileged helper also retains its owner and group so the game user can still read it. Commands left over when the application first reads the controller are ignored. Without the environment variable, file input is disabled.
Video-out and pad telemetry
Set APS5_VIDEOOUT_TELEMETRY_DIR to a directory before launching the application to write the run's presents and pad changes to <directory>/videoout.jsonl, one JSON object per line, appended when the file already exists. Every line starts with "v":1, the host's steady-clock nanoseconds in t_ns and the event name in ev. A run starts with a contract line, {"v":1,"t_ns":<ns>,"ev":"contract","contract":"v1-pad","commit":"<12 hex>","pid":<pid>,"prx":"libSceVideoOut"}: commit is the source commit the libraries were built from (unknown when Git was not found at configuration) and pid tells apart the runs that appended to the same file. A pad line is written when the pad state the title reads changes in its buttons, sticks or triggers, {"v":1,"t_ns":<ns>,"ev":"pad","n":<present>,"buttons":<bits>,"sticks":[<left x>,<left y>,<right x>,<right y>],"triggers":[<L2>,<R2>]}, where n is the number of presents completed when the change was published, the count the window title shows. buttons is the button bit field in decimal, stick axes run 0-255 with 128 at the centre, and both triggers run 0-255. Motion, touch and the device kind are not compared, so a run that only moves the controller writes no pad line. The state a pad line reports is the controller and keyboard/mouse input read through SDL; input fed by APS5_PAD_INPUT_FILE is applied later, in libScePad, so a scripted run writes no pad line. A present line is written after the first present and every 30 presents after that, {"v":1,"t_ns":<ns>,"ev":"present","n":<present>,"fps":<rate>}, with the count and rate the window title shows. A background thread writes the lines and the title is not waited for: when it falls behind, the line is dropped and a drops line with the number dropped so far, {"v":1,"t_ns":<ns>,"ev":"drops","dropped":<count>}, is written every ten seconds while the count grows, and once at a normal exit, only when lines were dropped; an exit that skips static destructors writes no last line. A pad line the full queue refused counts as dropped even though a later publish writes the change. Without the variable no file is opened and no thread is started; an empty value is the same as an unset one, and a directory that cannot be written stops the title when video output starts, with an error naming the file.
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), NotoSansArabic-{Light,Regular,Medium,Bold}.ttf (Arabic) and NotoSansCJK-{Light,Regular,Medium,Bold}.ttc (Japanese, Chinese and Korean). A set that covers several scripts also opens the files of its other scripts (SSTThai-*.otf, SSTArabic-*.otf, SSTJpPro-*.otf, DFHEI5-SONY.ttf, SCEPS4Yoongd-*.otf or their substitutes) for the characters its main file lacks; a missing one leaves those characters without a glyph. Without either, opening a system font set fails (the log shows sceFontOpenFontSet with file=(none)), and what happens next depends on the game: some show no text in those fonts, and others stop at start-up because they assert that the open succeeded. The substitutes come from the Noto project under the SIL Open Font License (the Noto Sans files are in fonts/NotoSans/full/ttf, the Arabic ones in fonts/NotoSansArabic/full/ttf); copy the files named above into anyps5-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.
Trophies
A game unlocks a trophy by posting a _UnlockTrophy event with an int32 _trophy_id property to libSceNpUniversalDataSystem, or through the statistics rules described below. The unlock is stored in _trophy/<user id>_<service label>.txt in the directory the game runs in (the one that holds app0/), one <trophy id> <unlock time> line per trophy; the time is an RTC tick, microseconds since 0001-01-01 UTC. Delete the file before starting the game to lock its trophies again. Each new unlock prints one line, such as [trophy] PPSA01556 unlocked trophy 12 for user 268435456; unlocking a trophy again prints nothing. sceNpTrophy2GetTrophyInfo, sceNpTrophy2GetTrophyInfoArray and sceNpTrophy2GetGameInfo report the stored unlocks and their times to the game. When the game's trophy data is loaded (see below), a posted unlock of a trophy that the data does not have, of the platinum, or of a progressive trophy is not stored and prints the reason, such as [trophy] ignored the posted unlock of trophy 0 for user 268435456: it is the platinum, which unlocks with the last trophy that names it; the platinum unlocks with the last trophy that names it, and a progressive trophy through its statistic. Without trophy data every posted id of 0 or more is stored, and the platinum is not unlocked when the other trophies are.
The trophy data comes from the game's own app0/sce_sys/trophy2/trophyNN.ucp, where NN is the service label (00 for most games). The trophy queries report its names, descriptions and rewards in the console language (ANYPS5_LANGUAGE, see above), or in English (United States) or the data's default language when it has no text in that language, and its grades, groups, hidden flags, number of trophies and grade counts. A platinum trophy unlocks as soon as every trophy that names it in platinumTrophyId is unlocked. The first trophy query or event prints what was read, such as [trophy] /app0/sce_sys/trophy2/trophy00.ucp: 52 trophies, 1 group, texts in en-US.
Many games unlock trophies through statistics instead: app0/sce_sys/uds/udsNN.ucp names, for each event, the property whose value becomes a statistic, and a trophy's unlockCondition compares a statistic with a target. The latest value of each statistic is stored in _trophy/<user id>_<service label>.stats.txt, one <stat id> <value> line per statistic, and is kept across runs; delete it together with the unlock file to start over. A progressive trophy reports its statistic as progress. Only statistics that keep the latest ("aggregation":"latest") or the largest value ("max") as uint32, uint64 or int32, and rules that copy one property of an event chosen by its name ($.name or $.object.name), optionally only when one property equals an integer (_UpdateTrophyProgress by its _trophy_id), are applied; the others are listed in [trophy] lines when the file is read.
APS5_TRACE_UDS=1 prints every posted event, such as [uds] context 1 posted _UnlockTrophy {"_trophy_id":12}, every statistic an event sets, such as [uds] stat 10 of user 268435456 is 10 (COLLECT $.counter), and a [uds] line for a missing trophyNN.ucp or udsNN.ucp. An event that is not used prints the reason in a [trophy] ignored line: an _UnlockTrophy event without an int32 _trophy_id of 0 or more, or posted to a context that does not exist; and, once per rule, an event whose property is missing, negative, not an integer or outside the statistic's range.
A missing or malformed trophyNN.ucp or udsNN.ucp turns off only what it provides, and a malformed one prints the reason once: without trophy data the queries report placeholders and statistics unlock nothing, and without statistics rules events change no statistic. Icons are not read: sceNpTrophy2GetGameIcon, sceNpTrophy2GetGroupIcon, sceNpTrophy2GetTrophyIcon and sceNpTrophy2GetRewardIcon stop the game with an error. So does an unlock or statistics file in _trophy/ that is damaged (a line that is not a new <id> <value> pair, such as a line an edit left blank or ended with a carriage return) or cannot be read or written. No notification is shown, and callbacks registered with sceNpTrophy2RegisterUnlockCallback are not called.
PlayGo dumps without metadata
When a dump has neither sce_sys/playgo-chunk.dat nor sce_sys/playgo-scenario.json, AnyPS5 cannot tell which nonzero PlayGo chunk IDs exist. Requests for those IDs stop with an error by default. Set APS5_PLAYGO_UNLISTED_CHUNKS to choose an answer for that dump:
| Value | Answer for an unlisted chunk ID | Observed title that needs it |
|---|---|---|
installed |
Report it as installed (LOCAL_FAST). |
High on Life (PPSA15388) needs this to load its saves. |
absent |
Return SCE_PLAYGO_ERROR_BAD_CHUNK_ID. |
Sifu (PPSA03001) and Demon Slayer: Kimetsu no Yaiba - The Hinokami Chronicles (PPSA03490) use this result to end their chunk-ID search. With installed, PPSA03490 keeps asking about higher chunk IDs forever, so its chunk list grows until startup fails with a Ran out of memory allocating 5565054976 bytes error. |
Leave the variable unset to keep the explicit error. Any other value also stops with an error. Dumps that include PlayGo metadata are unaffected. These title-specific results come from emulator runs and have not been verified against a console.
Screenshots
Press F12 in the game window to save the next displayed frame as a PNG at the resolution of the game's display buffer, without the window's scaling, letterbox or performance overlay. A BT.2100 PQ display buffer is saved converted to SDR, also while it is shown through HDR output. Files are named <title ID>-<YYYYMMDD-HHMMSS>.png and written to an anyps5-screenshots/ directory beside the output executable, which is created when needed; set ANYPS5_SCREENSHOTS to use another directory. The saved path is printed to stderr. Presses while the previous screenshot is still being written are merged into one. The key is the Screenshot action of the input mapping. A screenshot that cannot be written stops the game with the error.
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 in the same subdirectory, so it goes with the shaders it was built from. Entries of the current version are never removed and the directory has no size limit, so a title that compiles many shaders can add several GB within minutes. Deleting the directory is safe: it is rebuilt on the next run.
Pipeline journal
Besides the recompiled shaders, the shader cache directory holds pipelines.journal: every Vulkan graphics pipeline the pipeline store kept and every compute pipeline a run built, with the specialized shader modules they were built from. The next run of the title builds those pipelines on background threads while it loads, so a draw or dispatch the earlier run already made does not compile its pipeline when it is first reached. The journal belongs to the GPU and driver that wrote it, like the driver's pipeline cache file next to it: a run on another GPU or driver version starts it over, and the driver's own pipeline cache decides how fast each pipeline is rebuilt. Two runs sharing the directory take turns on pipelines.journal.lock; a run that cannot lock it records nothing. APS5_NO_PIPELINE_JOURNAL=1 disables recording and replay; APS5_PIPELINE_JOURNAL_THREADS sets the replay threads (half the CPU threads by default; 0 records without replaying), APS5_PIPELINE_JOURNAL_ENTRIES how many pipelines are replayed (4096, the last recorded) and APS5_PIPELINE_STORE_ENTRIES how many graphics pipelines stay resident (by default the store grows from 1024 up to 4096 to hold the replay and the pipelines that keep coming back), and APS5_PIPELINE_JOURNAL_MAX_MIB (1024) the file size at which recording stops. Deleting the file loses nothing but the head start. With APS5_PROFILE_DRAW=1 a [pipeline-journal] line reports what was loaded, replayed and recorded.
Draw pipeline
APS5_DRAW_PIPELINE=1 (off by default) moves more of each draw off the thread that reads the title's command buffers: a second thread finishes each draw while the next one is read, the thread that records draws skips work whose inputs did not change, and checks that a draw's resources are still current are reused until the title or the GPU writes memory. It is meant with APS5_PIPELINED_DRAWS=32 APS5_PIPELINE_DISPATCH=1. The picture does not change; if it does, that is a bug, and a run without the switch shows whether it is the cause. APS5_VERIFY_DRAW_PIPELINE=1 runs the full checks next to the reused ones, at a cost in speed, and prints [epoch-proofs] lines with how many differed (any nonzero count is a bug). APS5_PROFILE_DRAW=1 finishes every draw on the reading thread, so a run with it does not measure this switch. To see the hand-off without that, set APS5_PIPELINE_STATS (any value; 1 also turns on Vulkan pipeline statistics, which cost time): every 10 s a [finisher] line counts the draws handed to the second thread and those finished on the reading thread, and [finisher-held] lines count the draws held back by why their writes are not known.
Texture cache budget
Image and buffer memory allocations retry after releasing unused pooled buffers and reclaimable texture cache entries. Recovery preserves storage images with pending GPU results and resources retained by recorded work. [bufferpool] recovery and failure lines report the requested allocation, memory released and retry count. An allocation still fails if recovery cannot free enough memory.
Sampled textures and storage images stay in device memory in two caches that evict their least recently used entries when they grow past their budgets. The storage image budget is what the VK_EXT_memory_budget budget of the largest device-local heap leaves after the heap's use other than the cached storage images themselves and an eighth of the budget; it is capped at a quarter of that budget, at most a quarter of the heap's size or 2 GiB, whichever is larger, and never below 512 MiB. The sampled texture budget is what the reported budget leaves after the heap's use other than cached textures, a quarter of the heap's size (at least 2 GiB) and an eighth of the budget, and never below 2 GiB. As the heap's other use grows the sampled budget falls first, to that floor, then the storage budget; the storage budget also gives back what the sampled textures alive take as they grow. Each cache trims at its next insert. Both budgets are read again every second; [gpu] sampled texture cache budget lines report them at start-up and when either moves by more than a tenth, at most every 10 s. APS5_TEXTURE_CACHE_MIB=<n> fixes both budgets at n MiB, and the lines are then not printed.
A fixed budget above the automatic one can exceed what the card holds next to the rest of the frame. Allocations then go to host memory ([gpu] ... placed in host memory lines), where the GPU reads them over PCIe. PPSA16064 on an RTX 4060 8 GB, whose automatic storage image budget was 1797 MiB under the earlier rule, placed 868 and 878 MiB in host memory with APS5_TEXTURE_CACHE_MIB=3072 and walked through its first level at 4.46 and 4.83 FPS; with the variable unset it placed none and ran the same walk at 6.48 and 6.07 FPS. Leave it unset unless a measurement on the card shows otherwise.
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.
Benchmark reports
To measure how a title runs on a machine, start it with APS5_TRACE_SUMMARY_EVERY=30 APS5_TRACE_BENCH=1, play the scene for a few minutes and quit. Every 30 seconds the runtime rewrites aps5-bench-<commit>-<pid>.json.gz in APS5_TRACE_DIR or the working directory and appends a line to aps5-summary.jsonl. The file holds the time between every two presents, the commit, GPU, driver, OS, CPU name, thread count and physical memory, and, when app0/sce_sys/param.json is present, the title ID, name and content version. It holds no game data. The first interval includes shader compilation; compare later intervals. The interval still open when the title exits is not written. The fields are described under summaries for a long session.
Tracing system library calls
Windows, Linux and macOS. 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 (on Linux and macOS, which have no crash report, only an abort prints them): sequence number, thread, library, import name as the executable imports it (a NID) and the caller's address. The last 32 of these calls made by the reporting thread follow, taken from the 65536 most recent calls, so a thread whose calls were pushed out of the last 64 by a busier one still shows them. Then come 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
last traced calls of thread 9384:
#1 libkernel.prx k+AXqu2-eBc from 0x0000000140010006 app.exe+0x10006
most called traced imports (1 calls in total):
1 libkernel.prx k+AXqu2-eBc
To list every import a title calls during a run that ends without a crash, also set APS5_TRACE_CALLS_FILE to a file path. Every 10 seconds the file is rewritten with one line per import called so far, most called first: the number of calls, the library and the import name. It survives a run that is killed, up to its last rewrite.
245557 libc.prx gQX+4GDQjpM#guest
3866 libSceAgc.prx 57labkp+rSQ
Limits are listed in the libc and relinker technical debt.
Performance overlay
F10 cycles a performance overlay over the presented image through three states: off, page 1 (the table below), page 2 (a detailed page, the second table), then off again. ANYPS5_OVERLAY=1 shows page 1 from the first frame and ANYPS5_OVERLAY=2 page 2; F10 then goes on from there. ANYPS5_OVERLAY_POS puts it in a corner of the window: tl (top left, the default), tr, bl or br. For either variable an empty value is the same as an unset one, and any other value stops the game at its first frame with an error that names the variable. The key can be moved with TogglePerformanceOverlay in the input mapping. The text scales with the window height, at a whole multiple of its 5x7 pixel font; on an HDR10 swapchain the overlay is drawn at 203 nits.
| Line | Meaning |
|---|---|
PROFILED |
The first line while APS5_PROFILE_GPU=1 or APS5_PROFILE_DRAW=1 is set. Those switches add timestamp queries to every batch and timers to every packet, so the numbers of such a run aren't comparable with an unprofiled one. |
FPS |
Frames presented per second, over the last second. When the last second holds fewer than two presents (below 1 fps), the rate of the last frame instead, 1000 divided by its time in ms: a frame every 2 s shows 0.50. Every present counts, including frames a title presents cleared, for example while it loads. |
FRAME |
Time between the last two presented frames, in ms. |
| graph | The last 120 frame times, the newest on the right. The full height is the longest of them or twice their average, whichever is larger. |
MIN/AVG/MAX |
The shortest, average and longest of the 120 frame times, in ms. |
GPU |
GPU time per frame over the last second, or of the last frame below 1 fps, in ms: the span of each recorded batch from its first to its last command, from the timestamp queries of APS5_PROFILE_GPU=1. Shown only with that variable. Batches are counted when they complete, so the line trails a sudden change by up to a second, or by a frame below 1 fps. Batches that overlap on different queues each count in full, so the line can exceed FRAME. |
DRIVER |
CPU time per frame over the last second, or of the last frame below 1 fps, in ms, that the packet workers spent in dispatch and draw packets (the dispatch and draw time of the [gpu] worker line of APS5_PROFILE_DRAW=1). Shown only with that variable. Every worker's time counts in full, so with several workers busy at once the line can exceed FRAME. |
DRAWS |
Draw packets per frame over the last second, or of the last frame below 1 fps, then dispatch packets per frame (DISPATCH), as the packet workers execute them; packets that predication skips are not counted. Shown once the title has submitted either. |
SHADERS |
Pipelines created in the last second, or during the last frame below 1 fps, then since the start (TOTAL): a graphics pipeline for each draw state the pipeline cache had not seen and a compute pipeline for each new dispatch shader. A rise while the game stutters points at pipeline compilation. Shown once a pipeline was created. |
VRAM |
Usage and budget of the GPU's largest device-local memory heap from VK_EXT_memory_budget, read once a second. Not shown when the driver lacks the extension. |
RAM |
The process's private memory, read once a second: PrivateUsage of GetProcessMemoryInfo on Windows, RssAnon plus VmSwap of /proc/self/status on Linux. The title's direct memory is shared memory on both systems and is not included. |
RES |
The display buffer's size, then the window's swapchain size, for example RES: 1920X1080 / 2560X1440. Not shown for frames without a display buffer. |
Times are shown with one decimal below 100 and as whole numbers above; anything above 9999 shows as 9999, so FRAME and MIN/AVG/MAX read 9999 for frames of 10 s or more. FPS has the same format, with two decimals below 1. Counts are whole numbers, in thousands (12K) from 10000 and in millions (3M) from a million. Memory is in GB of 2^30 bytes. While the overlay is hidden the presentation records nothing for it and the presented image is unchanged; while it is shown, one small render pass draws it over each presented frame. Each draw and dispatch packet and each pipeline creation is counted also while the overlay is hidden, with one increment of a counter that belongs to the packet worker or, for pipelines, a shared one; the memory and the counters are read only while the overlay is shown.
Page 2 is wider, 46 characters, and has no graph. It reads its sources once a second while it is shown, so its rates cover the last second; they appear a second after the page does. A line whose source isn't available in the run is left out, and so is a part of a line.
| Line | Meaning |
|---|---|
FPS, FRAME |
As on page 1. |
BUSY % |
The share of the last second each queue's packet worker (Q0 for queue 0, Q1 for queue 1, and so on) spent on packets, suspends, ends of submissions, rewinds and completions, as worker_busy_pct of the trace summary counts it. From the trace totals. |
| phases | The 5 phases with the most CPU time per frame over the last second, in ms, summed over the driver's threads and named as in the trace summary's top_phases_ms_per_frame: a timed scope, or a scope and one of its stages. A scope's time includes the scopes and stages within it, and threads that work at once each count, so the phases can add up to more than FRAME. From the trace totals. |
Q0 WAIT MS/FRAME |
Time per frame that queue 0's packet worker waited, in ms: MEM in packets that wait for guest memory or for rendering to finish, LOCK for the GPU lock, GPU in the recorder's syncs and serial waits and in command batches' fence waits. A memory wait that syncs the GPU counts in MEM and in GPU. From the trace totals. |
RENDER PASSES/FRAME |
Render passes begun per frame; a draw that continues the open pass begins none. From the trace totals; not shown while an APS5_TRACE_* variable is set. |
PIPELINES |
COMPILING: shader preparations and pipeline-library prebuilds queued or running. NEW/S: pipelines created in the last second, as SHADERS on page 1. |
HIT % |
DRAW CACHE: the share of the draw cache's lookups in the last second that found an entry whose stages all matched. PIPELINES: the share of the pipeline cache's lookups that found the pipeline. Each is left out for a second without lookups. |
FLIPS/S, PRESENTS/S |
The title's flips per second against the frames presented per second. |
DROPPED/S, REPEATED/S |
Per second: flips not presented because no swapchain image was free (the dropped count of the [present] line of APS5_PROFILE_DRAW=1), and presents of the same display buffer as the present before. |
COMMIT |
The system's commit charge against its commit limit, in GB: CommitTotal and CommitLimit of GetPerformanceInfo on Windows, Committed_AS and CommitLimit of /proc/meminfo on Linux, where the limit is enforced only with vm.overcommit_memory=2. Then FAULTS/S, the process's page faults per second, soft ones included: PageFaultCount of GetProcessMemoryInfo on Windows, minor plus major faults of getrusage on Linux. |
HOST IMPORTS |
Host memory imports in use, then their size against the import budget, in GB. Not shown on a device that can't import host memory. |
CPU COPIES |
Guest memory copied per second by address-based resource builds, for the ranges that no host import or mirror serves, in MB of 2^20 bytes. CAP: the largest of those builds in the last second against the copy cap, APS5_ADDRESS_COPY_MAX_MIB (64 MiB unless set), past which a build stops the game. |
UNIMPLEMENTED |
Calls per second to functions AnyPS5 doesn't implement, then their total since the start. Such a call throws an error, so a count above 0 means the title caught it and went on. |
REFUSED SHADERS |
Shaders whose draws are skipped. Shown only with APS5_SKIP_REFUSED_DRAWS=1; without it a refused shader stops the game. |
TITLE, PRESENT |
The title ID from the title's param.json, and the swapchain's present mode, as ANYPS5_PRESENT_MODE names it. |
The busy shares, the phases, the queue 0 waits and the render passes come from the totals the trace keeps per thread. Showing page 2 turns those totals on, without the event buffers of an APS5_TRACE_FRAMES trace: while page 2 is shown, each timed scope on the driver's threads reads the clock twice and adds its time to its thread's totals, and each render pass begun is counted there. Leaving page 2 turns the totals off again. A run with an APS5_TRACE_* variable keeps its trace as configured; page 2 then shows those lines only while that trace records. While the overlay is hidden or on page 1, page 2 adds a check per render pass begun (the same check each timed scope already makes), a per-thread count of the bytes each address-based build copies, a count of unimplemented-function calls and the display buffer's address per present; its sources are read only while it is shown.
Windows crash diagnostics
When a register points at readable memory that starts with at least four printable ASCII characters, either as UTF-16LE or as single bytes, the crash report prints that text after the register memory dump, as [r13] u"..." for UTF-16LE or [rdi] "..." for single bytes. The text ends at a NUL, at the first character that is not printable ASCII, a tab, a line feed or a carriage return, after 512 characters or at the end of readable memory. Tabs, line breaks, quotes and backslashes are escaped, and ... after the closing quote means that no NUL ended the text within those limits.
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.
Set APS5_CRASH_STACK_BYTES=<n> to print, after the stack return address candidates, the faulting thread's stack words from rsp for n bytes (at most 1048576, decimal or 0x hexadecimal), four words per line with their offset from rsp. Below them the report prints eight words at every distinct stack word that points at readable, non-executable memory outside the thread's stack, labelled with the word's offset and the address it holds, up to 256 addresses. This shows the objects the caller frames kept, such as the this of a destructor or the buffer of a container. The dump stops at the first unreadable stack page, and nothing is printed when the variable is unset, empty or 0.
The report lists every other thread of the process with its rip, rsp, rbp and the return addresses of its rbp frame chain. A thread whose chain yields fewer than two frames, such as one waiting inside ntdll.dll or in code built without frame pointers, also gets up to 24 stack return address candidates: the words in the 32 KiB above its rsp that point at executable memory, read while the thread is suspended. Like the candidates of the faulting thread they are a scan, not an unwind, so stale return addresses of finished calls can appear among them.
GPU submission rings and stall dumps
Each GPU queue keeps its last 16 command buffer submissions: when each was submitted, its serial, size and guest address, and a copy of its first 512 dwords after indirect buffers are inlined. APS5_PM4_RING=<n> keeps the last n instead, from 0 to 4096; 0 keeps none, and any other value stops the title at its first submission. The rings are printed to stderr, one line per submission and one per decoded packet, when the Windows crash report runs: an unhandled exception, abort(), an invalid CRT parameter or the timed dump of APS5_HANG_DUMP_SECS.
With APS5_STALL_WATCHDOG_SECS=<n>, a queue that has waited n seconds without progress in a WAIT_REG_MEM, a REWIND or behind queue 0 prints the rings once for that wait, and the title keeps running. A WAIT_REG_MEM progresses while any other packet runs or completes, as for APS5_GPU_WAIT_TIMEOUT_MS. n is from 0 to 86400 and 0, the default, disables the watchdog; any other value stops the title at its first submission, as for APS5_PM4_RING. While the rings are printed for a queue held behind queue 0, new submissions and the other queues wait.
Exit codes
0: conversion succeeded or help was displayed. 1: invalid arguments, or a --verify-patches entry that is a mismatch. 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:
| 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. |
An import that names a library requires the literal export name. If its implementation is found only by hashing the import name, it still counts as implemented or stub, but the report lists the name mismatch, sets symbol_mismatch on the JSON import record and counts it in the summary, and exits with code 1. A strong import with a name mismatch is a current stop. Imports without a library retain the audit's name-to-NID fallback.
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.
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.
The audit also walks the built libraries' own host dependencies: each .prx under --libs has its PE imports (or ELF DT_NEEDED entries) checked for .prx names with no file in --libs or --modules, following present libraries transitively. Gaps fail the audit and are reported as Importer: <root> Chain: <root> -> ... -> <missing> lines, since the loader fails on them even when every game import resolves.
| 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. |
Registry nid fields may contain either encoded NIDs or ordinary ELF symbol names such as strcmp. Imports are matched against literal exports first, then against their computed NIDs when the literal lookup misses. Reports retain the original import name. Export availability does not establish that every platform loader supports that lookup path.
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.
Patching imports of native applications
nid_patcher --exec <file> [file2 ...] patches imports in place and preserves the application's exports. It detects PE, ELF64 and Mach-O64 from file contents. No library name is required. Build a separate executable for each target platform.
By default, imports use the library export naming rules: _nid_no_patch and SDL_* stay unchanged; _nid_no_patch_cut is removed without hashing; _nid_postfix and numeric _nid_disambig suffixes are removed before computing the NID. When both foo and foo_nid_postfix are imported, foo stays unchanged. These rules also apply to host imports unless they are explicitly preserved.
nid_patcher --exec --only-sce <file> restricts renaming to names starting with sce (case-insensitive) or ending with _nid_postfix. Both modes accept --preserve-exports <unpatched-library> or --preserve-export-list <export-list> for explicit exclusions. The existing nid_patcher <library_name> <file> mode is unchanged.
Unsupported layouts fail before the input file is written.