From a PS5 ELF to a Native Program
For AnyPS5, the first concrete problem is not “how do we launch the game?” but “what must change in the executable before Linux or Windows can load it?” This chapter follows that conversion boundary. The relinker reads the executable's program and dynamic metadata, reconstructs the imports and relocations that the host loader needs, applies only supported transformations, and emits a target-native executable. The useful result is a way to see conversion as a chain of validated contracts rather than as a file extension change.
What the relinker reads before it can rewrite
An ELF executable is more than a block of machine code. Its program headers tell the loader which parts of the file should become mapped segments and which segment contains executable instructions. Its dynamic section describes dependencies, symbols, strings, and relocation tables. A relocation is a record saying, in effect, “at this address, connect the code or data to this symbol according to this relocation rule.” If these tables are missing, inconsistent, or encoded in a form the relinker does not understand, there is no safe basis for producing a host executable.
AnyPS5 begins by reading the program headers and collecting executable load segments. It then locates the dynamic segment and validates the metadata it finds there. The pipeline expects the dynamic string and symbol tables, checks their entry sizes, reads the needed-library names, and extracts both ordinary RELA relocations and PLT relocations. The PLT—the procedure linkage table—is the part of an executable commonly used for calls whose final address is supplied by the loader. AnyPS5 also reads PS5-specific import-module information, because a symbol's name alone may not describe the title library relationship that must be preserved.
This is why the relinker can reject an input before producing any output. It checks that dynamic strings terminate within their declared bounds, relocation tables do not run past the file, symbol indexes point to real entries, and supported relocation types are used. It requires a dynamic segment and validates the relationship among the OS-specific and SysV forms of dynamic tags. These checks are not a general guarantee that every malformed executable will receive a perfect diagnosis. They are a deliberate refusal to invent loader metadata when the source does not supply a safe, recognizable structure.
The same principle applies to system calls. The relinker scans executable segments for calls into system services that it does not allow to pass through unchanged. A binary can have valid ELF tables and still rely on an operating-system boundary that the native process cannot honor. Conversion therefore examines both the static file structure and a portion of the behavior encoded in the machine code.
The diagram's ordering matters. The output patcher is near the end because it needs the analysis results: the rebuilt dynamic section, any safe code patches, the target operating system, and the converted guest-module artifacts. A patcher cannot compensate for an import table that was never understood.
The pipeline has gates, not just transformations
The top-level relinker coordinates the stages in a fixed order. An optional AMD-only conversion can run first for the main executable. The same kind of conversion can later be applied to bundled guest modules, but it is limited to supported instructions and safe code layouts; it is not an all-purpose CPU emulator. After that optional step, the executable bytes are handed to the relinking pipeline.
The pipeline reads the source representation, identifies the imported symbols and their relocations, scans for unsupported system calls, and records the references that will need to be represented in the output. At this point it can also apply one of three unused-import policies. Level zero keeps all imported NID references. Level one filters non-PLT imports using control-flow and global-offset-table analysis while preserving PLT imports. Level two applies stricter analysis and can compact the PLT, but unsupported analysis cases become errors.
Those modes illustrate a recurring design trade-off. Removing imports can make the output metadata smaller and can prevent unused dependencies from creating unnecessary obligations. But a PLT entry is not merely a row in a list: the original code may contain a hard-coded relationship to the relocation slot. AnyPS5's dynamic-section builder therefore preserves the correspondence between original PLT slots and their JUMP_SLOT relocations unless strict compaction has first produced the patches needed to make the relationship safe. If the original table has a slot with no corresponding retained relocation, the builder refuses to continue rather than allowing a call to bind unpredictably.
The filter levels are consequently different analyses, not three cosmetic output sizes. At level zero, the relinker keeps the input's imported NID references and lets the resulting dynamic section represent them all. At level one, PLT references are preserved while non-PLT references may be reduced through control-flow and GOT-access analysis. If the executable has multiple executable segments, the current implementation reports that this CFG/GOT model is not available and skips that filtering rather than pretending to know which references are dead. At level two, strict analysis has stronger requirements and can reject a multi-segment layout outright before compacting the PLT.
This is a useful example of how AnyPS5 treats uncertainty. An unused import is not proven dead because no obvious direct call was found in a quick scan. It is removed only when the selected analysis can account for the relevant control flow and memory accesses. For PLT entries, even a logically unused call slot may still be structurally significant until the code and relocation indexes are patched together. The resulting output may contain more metadata than an aggressive linker would prefer, but the extra entries preserve a known relationship instead of trading a smaller file for an unexplained runtime failure.
After filtering, the builder creates a host-facing SysV dynamic section. It emits the needed library names, dynamic symbols, PLT and non-PLT relocation records, and relative relocations that still matter after the transformation. Each imported reference carries more than a name: it is associated with a relocation address, relocation type, addend, and, where relevant, an import module. The output can later use that information to make the host loader resolve the call through the native library boundary described in the previous chapter.
This staged design also makes failure locations meaningful. A bad dynamic string is an input-structure failure. An unsupported relocation type is a binary-contract failure. A syscall rejected by the scanner is an operating-system boundary failure. A strict unused-import analysis that cannot model the executable is an analysis-boundary failure. These are different reasons for stopping, even if the user experiences each as “conversion failed.” The relinker prints an error and returns a failure code instead of producing an executable whose behavior would be difficult to interpret.
The distinction between these tables is worth making concrete. A program header describes a region that must be mapped or executed; the dynamic section describes how the mapped program refers to external objects. A dynamic string-table value is an offset into a bounded string area, not a free-standing pointer. A dynamic symbol-table entry supplies the name offset and symbol attributes, while a RELA record supplies a target address, an encoded symbol/type pair, and an addend. The relinker has to keep those relationships intact while changing the loader's view of the file. A record that points outside its table is not a missing library; it is evidence that the input cannot be interpreted safely.
The import-module tags add another layer of identity. A relocation can name a symbol and also carry the module relationship through which the title expects that symbol to be provided. AnyPS5 retains that association in its internal reference and later records it in the rebuilt dynamic representation. This is why “find every string that looks like a function name and replace it” would be inadequate. The relevant meaning is distributed across tables, addresses, relocation types, and module metadata. The relinker is reconstructing a loader contract from those linked structures.
That reconstruction also explains why the tool does not need to rewrite every call instruction. A call site can continue to reach its global-offset-table or PLT entry; the relocation at that entry is what the host loader resolves. The relinker changes the metadata and the exported names around the call, while preserving the code's existing control-flow shape wherever its supported format permits. This is a smaller and more inspectable transformation than replacing the entire executable with a new interpreter loop.
The output can include a registry describing the external PRX references. That registry is not the execution mechanism; it is an observability aid. It lets a later import audit compare what the converted title asks for with what the built native libraries actually export. Separating the registry from the dynamic loader is another useful boundary: diagnostics can describe the call surface without pretending that a report has executed the title.
The current implementation and usage documentation should be read together. The usage page gives the operator-facing contract—clean ELF input, target switches, filter levels, runtime layout—while the pipeline code defines the actual validation gates in this repository snapshot. If a high-level description and an implementation detail ever differ across versions, the versioned implementation's checks determine what the tool can safely accept; neither source should be silently generalized into a promise about arbitrary ELF files.
Linux and Windows are different output contracts
The target is selected explicitly. --windows requests a Windows PE executable. Without it, the output is Linux ELF, even if the output filename ends in .exe; the command-line program warns about that misleading suffix instead of treating the name as a target selector. This small rule prevents a common operational error: a file name is not a substitute for the output format that the patcher must construct.
The Linux and Windows patchers share the same preceding analysis but do not produce identical artifacts. Linux needs a host ELF with a SysV dynamic section and a loader-visible search path. Windows needs a PE image and Windows-specific handling for the converted guest modules. The relinker reports the target system and writes the selected executable only after the shared analysis has completed. The goal is not to make Linux and Windows look like the same operating system; it is to give the same title a native entry point on each supported target.
The runtime layout completes the conversion contract. The generated executable sits beside a libs/ directory containing AnyPS5's built native PRX libraries. Title resources and converted title-owned modules live under app0/. The distinction is essential. A title module is guest code that the relinker adapts so the host can load it; a native PRX in libs/ is an AnyPS5 implementation of a system boundary. Putting an original PS5 module in the Windows library directory makes Windows attempt to load it as a DLL, which can fail before AnyPS5 has a chance to provide any compatibility behavior.
The loader search path is part of this layout too. By default, the usage contract places system libraries below a path relative to the executable, so the converted program can find the PRX files beside it rather than depending on an unrelated global installation. A custom runtime path is possible, but it must obey the target platform's rules; on Linux, a guest module path must be absolute or use the executable-relative form the relinker understands. This makes the output portable as a bundle only when the executable, its app0/ content, and target-OS libraries are kept in the relationships the conversion step announced.
The distinction also protects debugging evidence. If a title-owned module is missing from app0/, the failure belongs to module discovery or runtime packaging. If a native PRX is missing from libs/, the host loader cannot satisfy a system dependency. If both files exist but an import is absent, the import audit can identify that more precisely than a generic startup crash. The converted executable is thus both a runnable artifact and a statement about which external pieces it still expects.
The relinker also preserves the relationship between the executable and its bundled modules. If module processing is enabled, the guest-module builder discovers the title's modules, converts them, and writes the resulting artifacts beneath the output's app0/ tree. The executable's dynamic dependencies are updated to point to that runtime arrangement. Native system libraries remain a separate set of host dependencies. This is why a successful main-executable patch is not the end of conversion: the host loader must eventually see a coherent graph containing the main program, its title-owned modules, and the system PRX libraries that satisfy external imports.
There is a practical implication for debugging. If the relinker fails while reading a dynamic table, the problem is in the input executable or in a feature outside the supported ELF contract. If conversion succeeds but the host loader reports a missing library, the runtime layout or native library build is the next place to inspect. If the loader finds a library but the title later throws, conversion and symbol resolution have already crossed their boundaries; the remaining problem is the native implementation's semantics. These are different stages and should not be collapsed into one vague notion of compatibility.
Conversion success is therefore a necessary but deliberately modest claim. It means AnyPS5 could understand enough of the executable's structure to produce a target-native artifact under its validation rules. It does not mean every imported function is implemented, every system call is supported, every bundled module behaves correctly, or the title will render on every host GPU. The next boundary is the symbol identity itself: the converted executable now contains host-facing import metadata, and those requested names must match the exports that native PRX libraries provide. That is where NIDs turn the relinker's reconstructed data into a loader-visible call contract.