How SharpEmu Loads System Module (PRX) Files: ELF Parsing and Kernel Registration

SharpEmu loads PlayStation 4 system modules by detecting .prx or .sprx file extensions, parsing the ELF binary through SelfLoader, and registering the module in KernelModuleRegistry with support for synthetic entries and automatic extension resolution.

SharpEmu is an open-source PlayStation 4 emulator (available at par274/sharpemu) that treats system modules (PRX files) as standard ELF binaries mapped into the guest virtual address space. Understanding this loading pipeline is essential for developers working on compatibility layers and HLE implementations, as it bridges the gap between host file systems and the emulated kernel environment.

Detecting PRX and SPRX File Extensions

The loading process begins in SharpEmuRuntime.cs where the emulator identifies potential system modules by examining file extensions. The runtime recognizes both .prx and .sprx extensions as valid system module containers using a case-insensitive comparison at lines 628‑629:

// SharpEmuRuntime.cs – is‑PRX check
return string.Equals(extension, ".prx", StringComparison.OrdinalIgnoreCase) ||
       string.Equals(extension, ".sprx", StringComparison.OrdinalIgnoreCase);

This detection triggers the subsequent ELF parsing phase whenever a potential system module is encountered.

Parsing ELF Images with SelfLoader

Once detected, SharpEmu treats the PRX file as a regular ELF binary. The SelfLoader class in SharpEmu.Core/Loader/SelfLoader.cs reads the ELF header, program headers, and relocations to determine the module's base address, size, and entry point. This component handles the low-level binary parsing required to map the module into the guest's virtual address space.

The loader returns a SelfImage object containing the parsed metadata, which is then passed to the kernel registry for registration.

Registering Modules in the Kernel Registry

The KernelModuleRegistry class in KernelModuleRegistry.cs serves as the central authority for module tracking. It maintains handles, base addresses, and metadata for both loaded and synthetic modules.

Standard Module Registration

For normal modules loaded from disk, the registry uses the RegisterModule method (lines 50‑89). This creates a ModuleEntry record containing the module's handle, resolved name, path, memory range, and entry point:

// KernelModuleRegistry.cs – RegisterModule
var handle = _nextHandle++;
var name   = ResolveName(normalizedPath, handle);
var entry  = new ModuleEntry(
    Handle: handle,
    Name: name,
    Path: normalizedPath,
    BaseAddress: baseAddress,
    EndAddress: ComputeEnd(baseAddress, size),
    EntryPoint: entryPoint,
    IsMain: isMain,
    IsSystemModule: isSystemModule);

Synthetic Module Creation

When the emulator needs to represent a module that exists in the PlayStation 4 system but isn't present on the host disk, it creates a synthetic entry via RegisterSyntheticModule (lines 100‑115). This method generates fallback names for modules that haven't been loaded from disk:

// KernelModuleRegistry.cs – RegisterSyntheticModule
var fallbackName = string.IsNullOrWhiteSpace(moduleName)
    ? $"module_{handle:X4}.sprx"
    : moduleName;

System Module Tracking

System modules such as libSceFiber.prx or libSceIme.prx receive special handling through MarkSysmoduleLoaded (lines 130‑143). This method either resolves a known sysmodule handle or creates a synthetic entry flagged as a system module:

// KernelModuleRegistry.cs – MarkSysmoduleLoaded
var handle = TryResolveKnownSysmoduleHandleLocked(sysmoduleId, out var knownHandle)
    ? knownHandle
    : RegisterSyntheticModule($"sysmodule_0x{sysmoduleId:X4}.sprx", isSystemModule: true);

Alternate Extension Resolution

The registry provides flexible name resolution by automatically accepting the opposite extension when looking up modules. The TryResolveHandleByNameLocked method (lines 297‑300) swaps between .prx and .sprx extensions to ensure that a request for libSceIme.prx matches an entry stored as libSceIme.sprx:

// KernelModuleRegistry.cs – TryResolveHandleByNameLocked
var altName = moduleName.EndsWith(".sprx", StringComparison.OrdinalIgnoreCase)
    ? moduleName[..^5] + ".prx"
    : (moduleName.EndsWith(".prx", StringComparison.OrdinalIgnoreCase)
        ? moduleName[..^4] + ".sprx"
        : string.Empty);

Export Resolution and HLE Integration

After registration, the module's exported functions become available to the High-Level Emulation (HLE) layer via ModuleManager. This component scans assemblies for methods marked with [SysAbiExport], resolves the NID (Name Identifier) from the attribute or symbol catalog, and populates dispatch tables for guest code to invoke.

Practical Implementation Examples

Loading a PRX from Disk

The following simplified example demonstrates loading a system module from the host file system:

// Assume `fs` implements IFileSystem and points to a PRX file on the host.
var prxPath = "/games/MyGame/libSceIme.prx";
using var stream = fs.OpenRead(prxPath);
var loader   = new SelfLoader(stream);
var image    = loader.Load();                     // parses ELF, returns SelfImage
var handle   = KernelModuleRegistry.RegisterModule(
                   modulePath: prxPath,
                   baseAddress: image.BaseAddress,
                   size: image.Size,
                   entryPoint: image.EntryPoint,
                   isMain: false,
                   isSystemModule: true);

Marking a Known Sysmodule as Loaded

For modules that exist in the PlayStation 4 firmware but aren't present as files on disk, use the synthetic registration:

int sysmoduleId = 0x0095;                       // libSceIme
int handle = KernelModuleRegistry.MarkSysmoduleLoaded(sysmoduleId);
// `handle` now refers to a synthetic entry named "libSceIme.prx" flagged as a system module.

Resolving Exported Functions

Once registered, exports can be resolved for HLE dispatch:

var nid = "0x12345678";
if (ModuleManager.TryGetFunction(nid, out var fn))
{
    // `fn` can be invoked from the guest CPU context.
}

Summary

  • Extension Detection: SharpEmuRuntime.cs identifies PRX/SPRX files using case-insensitive checks at lines 628‑629.
  • ELF Parsing: SelfLoader.cs parses headers and relocations to determine memory layout and entry points.
  • Registration: KernelModuleRegistry.cs manages three registration types: standard modules (RegisterModule), synthetic placeholders (RegisterSyntheticModule), and system modules (MarkSysmoduleLoaded).
  • Flexible Resolution: The registry automatically resolves alternate extensions (.prx ↔ .sprx) via TryResolveHandleByNameLocked.
  • HLE Integration: ModuleManager bridges registered modules to the emulator's high-level emulation layer through NID resolution.

Frequently Asked Questions

What is the difference between PRX and SPRX files in SharpEmu?

SharpEmu treats both .prx and .sprx files identically as standard ELF binaries containing PlayStation 4 system modules. The emulator normalizes these extensions during lookup, allowing a request for either extension to resolve against the other automatically.

How does SharpEmu handle missing system module files?

When a system module is referenced but not present on disk, SharpEmu creates a synthetic module entry via RegisterSyntheticModule. This placeholder maintains the expected handle and metadata, allowing the emulator to continue execution while flagging the import as a synthetic system module.

What information does the ModuleEntry record contain?

According to KernelModuleRegistry.cs lines 50‑89, each ModuleEntry stores the module's unique handle, resolved name, file path, base address, end address, entry point, and boolean flags indicating whether it is the main executable or a system module.

How does SharpEmu resolve function exports from loaded PRX files?

After registration, ModuleManager scans assemblies for methods decorated with [SysAbiExport] attributes. It resolves the NID (Name Identifier) from these attributes or the symbol catalog, then populates dispatch tables that map guest import requests to the corresponding HLE implementations.

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 →