# SharpEmu HLE System Architecture: Implementing PlayStation 5 System Calls in C#

> Explore SharpEmu's HLE system architecture. Discover how it implements PlayStation 5 system calls in C# using attribute-driven registration and a dispatch table for efficient emulation.

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

---

**SharpEmu's High-Level Emulation (HLE) layer implements the PlayStation 5 system-call interface using attribute-driven registration, a dispatch table, and a CPU context abstraction to route guest NIDs to C# host implementations.**

SharpEmu is an open-source PlayStation 5 emulator written in C# that relies on High-Level Emulation (HLE) to intercept and handle kernel functionality on the host side. The HLE system architecture centers on a metadata-driven approach where system calls are mapped to managed methods via numeric identifiers (NIDs), allowing the emulator to translate guest kernel requests into host execution without low-level virtualization overhead.

## Core Components of the HLE Layer

The architecture isolates guest-side ABI concerns from host-side implementation through several tightly-focused types defined under `src/SharpEmu.HLE`.

### SysAbiExportAttribute

The **`SysAbiExportAttribute`** class (defined in [`src/SharpEmu.HLE/SysAbiExportAttribute.cs`](https://github.com/par274/sharpemu/blob/main/src/SharpEmu.HLE/SysAbiExportAttribute.cs)) marks managed methods as HLE exports and supplies the metadata required for registration. Developers decorate kernel implementations with this attribute to specify the NID (numeric identifier), export name, and target firmware generation.

```csharp
[SysAbiExport(Nid = "0x1234ABCD", ExportName = "sceKernelOpen", Target = Generation.Gen2)]
public static int KernelOpen(CpuContext ctx) { /* ... */ }

```

### SysAbiFunction Delegate

All HLE methods must conform to the **`SysAbiFunction`** delegate signature defined in [`src/SharpEmu.HLE/SysAbiFunction.cs`](https://github.com/par274/sharpemu/blob/main/src/SharpEmu.HLE/SysAbiFunction.cs). This standardization allows the dispatcher to invoke every export uniformly without reflection at dispatch time.

```csharp
public delegate int SysAbiFunction(CpuContext context);

```

### CpuContext

The **`CpuContext`** class (located in [`src/SharpEmu.HLE/CpuContext.cs`](https://github.com/par274/sharpemu/blob/main/src/SharpEmu.HLE/CpuContext.cs)) represents the guest CPU state. It holds general-purpose registers (`Rax`, `Rbx`, etc.) and provides access to guest memory. HLE functions read arguments from and write return values to this context, mirroring the PlayStation 5 ABI directly.

### ModuleManager

The **`ModuleManager`** class in [`src/SharpEmu.HLE/ModuleManager.cs`](https://github.com/par274/sharpemu/blob/main/src/SharpEmu.HLE/ModuleManager.cs) serves as the central registry and dispatcher. It scans assemblies for methods bearing `SysAbiExportAttribute`, builds the dispatch table (mapping NIDs to delegates), and manages the lifecycle of HLE registration. The manager also performs "warming" operations—pre-JIT-compiling methods and triggering static constructors—to avoid fail-fast events when the guest thread invokes system calls.

### Supporting Data Structures

- **`ExportedFunction`** ([`src/SharpEmu.HLE/ExportedFunction.cs`](https://github.com/par274/sharpemu/blob/main/src/SharpEmu.HLE/ExportedFunction.cs)): A data holder storing library name, NID, export name, target generation, and the delegate reference.
- **`SysAbiSymbol`** ([`src/SharpEmu.HLE/SysAbiSymbol.cs`](https://github.com/par274/sharpemu/blob/main/src/SharpEmu.HLE/SysAbiSymbol.cs)): Represents symbol entries used by the catalog for lookup and resolution.
- **`Generation` Enum** ([`src/SharpEmu.HLE/Generation.cs`](https://github.com/par274/sharpemu/blob/main/src/SharpEmu.HLE/Generation.cs)): Encodes PlayStation 5 firmware generations (e.g., `Gen1`, `Gen2`), allowing exports to target specific system software versions.

## How the Dispatch Pipeline Works

The SharpEmu HLE architecture operates through four distinct phases that bridge the guest kernel and host implementation.

### Registration Phase

At emulator startup, `ModuleManager.RegisterFromAssembly(Assembly assembly, Generation generation)` reflects over the provided assembly, identifies methods decorated with `SysAbiExportAttribute`, and resolves export metadata. If a method specifies only an export name without a NID, the `ResolveExportInfo` routine consults an `ISymbolCatalog` (such as the pre-generated **Aerolib**) to map the name to the correct numeric identifier. Valid entries are stored in `_dispatchTable` as NID-to-delegate mappings.

### Freezing Phase

After all modules load, calling `ModuleManager.Freeze()` locks the registration to prevent further mutations. This phase "warms" type initializers by triggering static constructors and JIT-compiles all exported methods. This preparation ensures that the Common Language Runtime (CLR) will not attempt compilation or initialization on the guest thread, which could trigger fail-fast events or deadlocks during emulation.

### Dispatch Phase

When the emulated PlayStation 5 kernel invokes a system call, the guest CPU places the target NID in a register (typically `Rax`) and transfers control to the HLE layer. The emulator core calls `ModuleManager.Dispatch(string nid, CpuContext context)`, which looks up the delegate in the frozen dispatch table, verifies that the target generation matches the current emulation context, and invokes the method. The HLE function writes its return value directly into `context.Rax` before control returns to the guest.

### Symbol Resolution

For exports defined by name only, the `ISymbolCatalog` interface (implemented by [`Aerolib.cs`](https://github.com/par274/sharpemu/blob/main/Aerolib.cs)) provides NID resolution without hardcoding values in the source. This separation allows the HLE layer to remain portable across firmware versions while maintaining accurate symbol mapping.

## Complete Implementation Example

The following example demonstrates defining an HLE export, registering it with the module manager, and dispatching a system call:

```csharp
// Defining an HLE export in src/SharpEmu.HLE/KernelFunctions.cs
[SysAbiExport(Nid = "0x2A3B4C5D", ExportName = "sceKernelPrint", Target = Generation.Gen2)]
public static int KernelPrint(CpuContext ctx)
{
    // Read a guest string pointer from Rdx
    var str = ctx.Memory.ReadString(ctx[CpuRegister.Rdx]);
    SharpEmu.Logging.ConsoleLogSink.WriteLine(str);
    return 0; // Success code written to guest Rax
}

// Registration during emulator initialization
var mgr = new ModuleManager();
int count = mgr.RegisterFromAssembly(typeof(KernelPrint).Assembly, Generation.Gen2);
mgr.Freeze(); // Lock and warm the module

// Dispatch from the CPU emulator core
// Assumes guest has placed NID 0x2A3B4C5D in Rax
string nid = ctx[CpuRegister.Rax].ToString("X8");
OrbisGen2Result result = mgr.Dispatch(nid, ctx);
// Return value already written to ctx.Rax by KernelPrint

```

## Key Source Files Reference

| File | Role |
|------|------|
| [`src/SharpEmu.HLE/SysAbiExportAttribute.cs`](https://github.com/par274/sharpemu/blob/main/src/SharpEmu.HLE/SysAbiExportAttribute.cs) | Attribute marking HLE functions and providing export metadata |
| [`src/SharpEmu.HLE/SysAbiFunction.cs`](https://github.com/par274/sharpemu/blob/main/src/SharpEmu.HLE/SysAbiFunction.cs) | Delegate type that all HLE functions must implement |
| [`src/SharpEmu.HLE/CpuContext.cs`](https://github.com/par274/sharpemu/blob/main/src/SharpEmu.HLE/CpuContext.cs) | Guest CPU state representation (registers and memory access) |
| [`src/SharpEmu.HLE/ModuleManager.cs`](https://github.com/par274/sharpemu/blob/main/src/SharpEmu.HLE/ModuleManager.cs) | Central registration, dispatch, and warm-up logic |
| [`src/SharpEmu.HLE/ExportedFunction.cs`](https://github.com/par274/sharpemu/blob/main/src/SharpEmu.HLE/ExportedFunction.cs) | Data holder for exported symbol metadata |
| [`src/SharpEmu.HLE/SysAbiSymbol.cs`](https://github.com/par274/sharpemu/blob/main/src/SharpEmu.HLE/SysAbiSymbol.cs) | Symbol entry structure for catalog lookups |
| [`src/SharpEmu.HLE/Generation.cs`](https://github.com/par274/sharpemu/blob/main/src/SharpEmu.HLE/Generation.cs) | Firmware generation enumeration |
| [`src/SharpEmu.HLE/Aerolib/Aerolib.cs`](https://github.com/par274/sharpemu/blob/main/src/SharpEmu.HLE/Aerolib/Aerolib.cs) | Default symbol catalog providing NID mappings |

## Summary

- **SharpEmu's HLE system** uses attribute-driven registration to map PlayStation 5 NIDs to C# methods without hardcoding dispatch logic.

- The **`ModuleManager`** handles the full lifecycle: scanning assemblies via `RegisterFromAssembly()`, locking state with `Freeze()`, and routing calls through `Dispatch()`.
- **`CpuContext`** abstracts the guest CPU state, allowing HLE functions to read arguments and write return values using the PlayStation 5 ABI.
- Pre-JIT compilation and static constructor warming during the freeze phase prevent runtime compilation on guest threads, ensuring stable emulation.
- The **`ISymbolCatalog`** interface decouples NID resolution from implementation, supporting multiple firmware generations through the `Generation` enum.

## Frequently Asked Questions

### What is the role of CpuContext in SharpEmu's HLE system?

The **`CpuContext`** class encapsulates the guest CPU state, including general-purpose registers and guest memory access. HLE functions receive this context as their sole parameter, allowing them to read system call arguments from specific registers (such as `Rdx` or `Rdi`) and write return values back to `Rax` according to the PlayStation 5 ABI. This abstraction keeps the emulator core decoupled from specific HLE implementations.

### How does ModuleManager prevent JIT compilation issues during guest execution?

Before entering the emulation loop, `ModuleManager.Freeze()` pre-JIT-compiles all registered HLE methods using `PrepareMethod` and triggers static constructors. This "warming" ensures that the CLR will not attempt to compile or initialize types when a guest thread invokes a system call, preventing potential deadlocks or fail-fast events that could occur if the runtime tried to acquire locks on the guest execution thread.

### What is the purpose of the Generation enum in HLE exports?

The **`Generation`** enum (defined in [`src/SharpEmu.HLE/Generation.cs`](https://github.com/par274/sharpemu/blob/main/src/SharpEmu.HLE/Generation.cs)) specifies which PlayStation 5 firmware versions an HLE export supports, such as `Gen1` or `Gen2`. During registration, `ModuleManager` filters exports based on the current target generation, allowing the emulator to maintain separate implementations for kernel functions that changed between system software updates without namespace collisions or runtime version checking.

### How does SharpEmu resolve export names to NIDs without hardcoded values?

When a method specifies only an export name in `SysAbiExportAttribute`, the `ModuleManager` consults an implementation of **`ISymbolCatalog`** (typically the `Aerolib` class) to resolve the symbolic name to its numeric NID. This lookup occurs during the registration phase, allowing the dispatch table to store direct NID-to-delegate mappings while keeping the source code readable with symbolic names rather than hexadecimal constants.