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

> Discover how SharpEmu loads PRX files with ELF parsing and kernel registration. Understand the SelfLoader and KernelModuleRegistry processes for PS4 system modules.

- Repository: [Berk/sharpemu](https://github.com/par274/sharpemu)
- Tags: internals
- Published: 2026-07-13

---

**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`](https://github.com/par274/sharpemu/blob/main/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:

```csharp
// 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`](https://github.com/par274/sharpemu/blob/main/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`](https://github.com/par274/sharpemu/blob/main/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:

```csharp
// 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:

```csharp
// 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:

```csharp
// 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`:

```csharp
// 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:

```csharp
// 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:

```csharp
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:

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

```

## Summary

- **Extension Detection**: [`SharpEmuRuntime.cs`](https://github.com/par274/sharpemu/blob/main/SharpEmuRuntime.cs) identifies PRX/SPRX files using case-insensitive checks at lines 628‑629.
- **ELF Parsing**: [`SelfLoader.cs`](https://github.com/par274/sharpemu/blob/main/SelfLoader.cs) parses headers and relocations to determine memory layout and entry points.
- **Registration**: [`KernelModuleRegistry.cs`](https://github.com/par274/sharpemu/blob/main/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`](https://github.com/par274/sharpemu/blob/main/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.