How SharpEmu Handles SELFs and PRX Modules for Game Loading

SharpEmu’s SelfLoader class in src/SharpEmu.Core/Loader/SelfLoader.cs parses decrypted ELF or fake-signed SELF (fSELF) binaries, maps segments into virtual memory, resolves relocations, and creates import stubs to transform PlayStation binaries into runnable emulator modules.

SharpEmu, developed by par274, implements a specialized loading pipeline for PlayStation Executable and Linkable Format (SELF) and PRX modules that converts raw binary images into executable guest code. Understanding how SharpEmu handles SELFs and PRX modules for game loading requires examining the SelfLoader implementation, which orchestrates binary validation, memory mapping, and dynamic linking. The loader strictly requires pre-decrypted images—either plain ELF files or fake-signed SELFs (fSELFs)—and explicitly aborts if it encounters encrypted retail eboots.

Detecting and Validating Binary Formats

The loading process begins with ParseLayout, which inspects the first four bytes of the input file to determine the container format. If the magic value matches 0x4F153D1D, the loader identifies a SELF container and proceeds to read the SELF header and segment table to calculate the elfOffset. If the magic equals 0x7F454C46 (the ASCII sequence \x7fELF), the file is treated as a plain ELF. Any other magic value triggers an immediate abort, as SharpEmu does not implement runtime decryption for encrypted retail binaries.

Upon successful detection, the loader returns a LoadContext structure that flags whether the image originated as a SELF and contains the calculated ELF offset plus the segment list. The loader then reads the ElfHeader structure at the determined offset and validates it via ValidateElfHeader before proceeding to memory mapping.

Mapping Segments and Memory Allocation

The MapLoadSegments method walks every PT_LOAD program header in the ELF, calculating the guest virtual address by adding header.VirtualAddress to the imageBase. For each segment, the loader maps the file data directly into the emulator’s virtual memory system (conforming to IVirtualMemory), or allocates zero-filled memory for BSS sections. The implementation emits alignment warnings if the ELF-specified alignment does not match the file offset, ensuring that memory layout matches the binary’s expectations.

Handling Thread-Local Storage (TLS)

When the ELF contains a PT_TLS segment, SharpEmu registers a TLS template for the module via RegisterModuleTlsTemplate. The loader assigns a module-local TLS ID using the _nextTlsModuleId counter and records the static offset required for TLS-relative relocations. This mechanism ensures that thread-local variables from PlayStation binaries are correctly addressed during emulation.

Resolving Relocations and Imports

The ParseDynamicInfo method locates the PT_DYNAMIC segment and loads the string table, symbol table, and relocation tables (Rela and JmpRel). The loader builds a list of relocation descriptors and processes each entry through ComputeRelocationValue:

  • Relative relocations (R_X86_64_RELATIVE / R_X86_64_RELATIVE64) are patched with imageBase + addend.
  • TLS-module-id relocations (R_X86_64_DTPMOD64) are patched with the module’s assigned TLS ID.
  • Symbol-based relocations extract the symbol name, translate it to a PlayStation NID (Name Identifier) using ExtractNid, and determine if an import stub is required.

For each unique imported NID, CreateImportStubMapping allocates a small code stub at a predictable address calculated as ImportStubBaseAddress + stride * index. The stub initially contains a 0xCC trap opcode followed by a 0xC3 return opcode; the emulator can later replace the trap with a real function pointer when the import is resolved by the module manager (src/SharpEmu.HLE/ModuleManager.cs).

The final relocation values are written to guest memory via TryWriteRelocationValue, with the loader warning if a relocation would write a suspiciously small absolute address—often indicating an unresolved import. After resolving imports, RegisterRuntimeSymbolsAndHooks registers any exported symbols from the loaded module so that other modules can import them later.

Preparing Entry Points and Initialization

Before returning the loaded image, the loader extracts the module’s initialization entries from DT_INIT, DT_INIT_ARRAY, and DT_PREINIT_ARRAY. It resolves their guest addresses and stores them in the returned SelfImage object along with the final entry point (elfHeader.EntryPoint + imageBase). The emulator can then invoke these initializer functions before jumping to the main entry point to ensure proper runtime setup.

Loading a Game: Practical Example

The following examples demonstrate how to load a decrypted ELF or fSELF into SharpEmu and prepare it for execution:

// Load a decrypted ELF or fSELF from a file into the emulator
byte[] fileBytes = File.ReadAllBytes("mygame.elf");          // or .self
var vmem = new PhysicalVirtualMemory();                      // VM implementation
var loader = new SelfLoader();

// Simple load – no extra filesystem, no module manager
SelfImage image = loader.Load(fileBytes, vmem);

// Load with a custom module manager so the game can import symbols from other modules
IModuleManager modMgr = new MyModuleManager();
SelfImage image2 = loader.Load(fileBytes, vmem, modMgr);

To manually inspect or add import stubs during development:

// Example: Adding a runtime import stub manually (rarely needed)
ulong stubAddr = loader.CreateImportStubMapping(vmem, new[] { "sceAudioOutOpen" })[0];
Console.WriteLine($"Import stub for sceAudioOutOpen placed at 0x{stubAddr:X16}");

After loading, invoke initializers before starting execution:

// After loading, invoke all initializers before the main entry point
foreach (ulong init in image.InitializerFunctions)
    vmem.CallFunction(init);               // pseudo‑call into guest memory

// Finally jump to the main entry point
vmem.CallFunction(image.EntryPoint);

Summary

  • SharpEmu requires decrypted ELF or fSELF images and rejects encrypted retail binaries with a clear error message.
  • The SelfLoader class validates magic signatures (0x4F153D1D for SELF, 0x7F454C46 for ELF), maps PT_LOAD segments, and handles TLS templates via RegisterModuleTlsTemplate.
  • Relocations are resolved through ComputeRelocationValue, supporting R_X86_64_RELATIVE, R_X86_64_DTPMOD64, and symbol-based types.
  • Import stubs are generated at ImportStubBaseAddress with 0xCC trap instructions for unresolved NIDs.
  • Runtime initializers (DT_INIT, DT_INIT_ARRAY) are invoked before jumping to the final EntryPoint stored in the SelfImage.

Frequently Asked Questions

What file formats does SharpEmu support for game loading?

SharpEmu supports decrypted ELF files and fake-signed SELF (fSELF) binaries. It explicitly rejects encrypted retail SELF files (eboots) because it does not implement runtime decryption, instructing users to provide decrypted images instead.

How does SharpEmu resolve imports from PRX modules?

The loader extracts symbol names from relocation entries, converts them to PlayStation NIDs using ExtractNid, and creates import stubs via CreateImportStubMapping. These stubs are placed at calculated addresses starting at ImportStubBaseAddress and initially contain trap instructions (0xCC) that can be replaced with actual function pointers when resolved by the module manager.

What is the purpose of TLS handling in SharpEmu’s loader?

When loading modules containing PT_TLS segments, SharpEmu registers a TLS template via RegisterModuleTlsTemplate and assigns a module-local TLS ID (_nextTlsModuleId). This enables correct computation of TLS-relative relocation offsets for thread-local variables in PlayStation binaries.

Where does SharpEmu place loaded segments in memory?

The MapLoadSegments method calculates guest virtual addresses by adding the program header’s VirtualAddress to the imageBase, then maps file data or zero-filled memory for BSS sections into the emulator's virtual memory system (IVirtualMemory).

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →