# How SharpEmu Implements AMPR Exports: PS5 libSceAmpr Emulation Guide

> Discover how SharpEmu implements AMPR exports using annotated HLE functions and a command-buffer model for efficient PS5 libSceAmpr emulation. Learn about file reads and kernel event processing.

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

---

**SharpEmu implements AMPR exports as annotated HLE functions in [`AmprExports.cs`](https://github.com/par274/sharpemu/blob/main/AmprExports.cs) using a command-buffer model that queues file reads and kernel events before processing them in bulk via `CompleteCommandBuffer`.**

SharpEmu is a PlayStation 5 emulator that provides high-level emulation (HLE) of the `libSceAmpr` library through a set of exported functions decorated with `[SysAbiExport]`. The implementation centers on a **command-buffer model** that batches asynchronous file I/O and kernel event queue operations. All AMPR exports are defined in [`src/SharpEmu.Libs/Ampr/AmprExports.cs`](https://github.com/par274/sharpemu/blob/main/src/SharpEmu.Libs/Ampr/AmprExports.cs) and interact with supporting registries for file path mapping and PACK archive handling.

## Understanding the AMPR Command-Buffer Architecture

The SharpEmu AMPR implementation revolves around a **command-buffer model** that mimics the PlayStation 5 ABI. This design allows the guest to queue multiple operations—file reads, kernel event notifications, and completion addresses—before dispatching them atomically.

### The Core Components

The architecture relies on four primary data structures managed within [`AmprExports.cs`](https://github.com/par274/sharpemu/blob/main/AmprExports.cs):

- **`_commandBuffers`** – A thread-safe `ConcurrentDictionary` that maps a guest buffer address to its runtime state, tracking the buffer, its size, and the current write offset.
- **`_hostFileCache`** – A cache of opened host files that minimizes file-open overhead when the guest repeatedly reads the same file.
- **`PakDirectoryTracker`** – Handles sequential reads of PACK archives using the "-1" offset protocol to infer the next logical chunk.
- **`AmprFileRegistry`** – Maps guest paths to host paths and supplies deterministic file IDs using **FNV-1a** hashing.

### Command Buffer Lifecycle

Every command buffer passes through three distinct phases. First, the guest calls `sceAmprCommandBufferConstructor` to initialize the buffer state. Then, the guest appends records—such as read requests or kernel event queue entries—using various append methods. Finally, the guest calls `sceAmprCompleteCommandBuffer` (internal logic at lines 69-127 in [`AmprExports.cs`](https://github.com/par274/sharpemu/blob/main/AmprExports.cs)) to walk the buffer, dispatch each record, and advance the write offset.

## Implementing the AMPR Export Functions

All exported functions in SharpEmu follow a consistent pattern: validate arguments, manipulate command-buffer state via helper methods, optionally trace the operation, and return an `OrbisGen2Result` code matching the PS5 ABI.

### Command Buffer Management

The constructor and destructor exports manage the lifecycle of command buffers in the `_commandBuffers` dictionary.

**`sceAmprCommandBufferConstructor`** (lines 63-82 in [`AmprExports.cs`](https://github.com/par274/sharpemu/blob/main/AmprExports.cs)) creates a new command buffer and optionally clears it. The corresponding destructor, **`sceAmprCommandBufferDestructor`** (lines 143-161), clears the buffer and removes it from the internal dictionary. For asynchronous buffers, **`sceAmprAprCommandBufferConstructor`** (lines 90-109) provides the same initialization logic under the APR (Audio-Path-Reader) namespace.

```csharp
// Example registration in the internal dictionary
_commandBuffers[bufferAddress] = new CommandBufferState 
{
    Buffer = bufferPtr,
    Size = bufferSize,
    CurrentOffset = 0
};

```

### File I/O Operations and Registry

The **`sceAmprCommandBufferReadFile`** export (implemented in `AprCommandBufferReadFile` at lines 54-124) reads a host file into guest memory. This function handles missing files, sequential reads, and PACK archives by consulting the `AmprFileRegistry` to resolve the file ID to a host path.

File IDs are generated using **FNV-1a** hashing in `AmprFileRegistry.ComputeFileId`. The registry maintains a `ConcurrentDictionary` mapping `fileId → hostPath`, allowing constant-time lookups during read operations.

```csharp
// From AmprFileRegistry.cs
uint fileId = AmprFileRegistry.Register("/guest/file.bin", "C:\\host\\file.bin");
if (AmprFileRegistry.TryGetHostPath(fileId, out var hostPath)) 
{
    // Proceed with file read
}

```

### Kernel Event Queue Integration

AMPR supports asynchronous notifications through the kernel event queue. The **`sceAmprCommandBufferWriteKernelEventQueue_04_00`** export (lines 14-40 in [`AmprExports.cs`](https://github.com/par274/sharpemu/blob/main/AmprExports.cs)) appends a kernel-event-queue record to the command buffer. This integrates with [`KernelEventQueueCompatExports.cs`](https://github.com/par274/sharpemu/blob/main/KernelEventQueueCompatExports.cs) to signal completion events to the guest kernel.

Additionally, **`sceAmprCommandBufferWriteAddressOnCompletion`** (lines 48-66) appends a write-address-on-completion record, allowing the guest to specify memory locations that should be updated when the buffer completes.

## Handling PACK Archives and Sequential Reads

SharpEmu handles sequential reads for PACK archives through the `PakDirectoryTracker` class. When the guest supplies `-1` as the file offset, the emulator infers the next logical chunk by tracking per-file state and parsing directory tables.

Key logic in [`PakDirectoryTracker.cs`](https://github.com/par274/sharpemu/blob/main/PakDirectoryTracker.cs) resolves the sequential offset:

```csharp
public static ulong ResolveSequentialOffset(uint fileId, ulong requestedSize)
{
    var state = _state.GetOrAdd(fileId, _ => new FileState());

    // If directory has been parsed, match entry by size
    if (state.Directory is { } entries)
    {
        foreach (var entry in entries)
        {
            if (!entry.Consumed && entry.FileLen == requestedSize) 
            { 
                entry.Consumed = true;
                return entry.FilePos; 
            }
        }
    }

    // Fallback to linear cursor
    return state.NextOffset;
}

```

The `OnReadCompleted` method updates the cursor, parses headers, or records the directory once the read operation passes the header boundary.

## Debugging AMPR Operations with Environment Variables

SharpEmu provides detailed tracing for AMPR operations through environment variables. Setting `SHARPEMU_LOG_AMPR=1` or `SHARPEMU_LOG_AMPR_READS=1` enables diagnostic output to `stderr`.

The tracing helpers prepend `[LOADER][TRACE] ampr.*` for easy filtering:

```csharp
if (_traceAmpr) 
{
    Console.Error.WriteLine($"[LOADER][TRACE] ampr.{operation}: cmd=0x{commandBuffer:X16}, fileId={fileId}");
}

```

This is particularly useful for debugging file path resolution issues or verifying that PACK archive sequential reads are advancing correctly.

## Code Example: Typical Guest Usage Pattern

The following pattern demonstrates how guest code typically interacts with SharpEmu's AMPR exports:

```csharp
// 1. Register a host file (done once on startup)
uint fileId = sceAmprRegisterFile("/game/data.bin", "C:/Games/PS5/data.bin");

// 2. Allocate a command buffer in guest memory (size 0x1000)
void* cmdBuf = malloc(0x1000);
sceAmprCommandBufferConstructor(&cmdBuf, cmdBuf, 0x1000);

// 3. Queue a read of the first 0x200 bytes (sequential offset = -1)
sceAmprAprCommandBufferReadFile(cmdBuf, fileId, dstPtr, 0x200);

// 4. Queue a kernel-event-queue notification
sceAmprCommandBufferWriteKernelEventQueue_04_00(cmdBuf, queueHandle, ident, token, userdata);

// 5. Complete the buffer – driver processes all queued records
sceAmprCompleteCommandBuffer(cmdBuf);

```

## Summary

- **SharpEmu AMPR exports** are implemented in [`AmprExports.cs`](https://github.com/par274/sharpemu/blob/main/AmprExports.cs) using `[SysAbiExport]` attributes to register functions with the HLE dispatcher.
- The **command-buffer model** batches operations in a guest-allocated structure, processing them atomically via `CompleteCommandBuffer`.
- **File identification** uses FNV-1a hashing through `AmprFileRegistry` to map guest paths to host paths with deterministic IDs.
- **PACK archive support** relies on `PakDirectoryTracker` to handle sequential reads when the guest specifies `-1` as the file offset.
- **Debugging** is enabled via the `SHARPEMU_LOG_AMPR` environment variable, which outputs detailed trace information to `stderr`.

## Frequently Asked Questions

### What is the purpose of the command buffer in SharpEmu's AMPR implementation?

The command buffer serves as a queue for asynchronous operations. It allows the guest to append multiple file read requests and kernel event notifications before dispatching them all at once through `sceAmprCompleteCommandBuffer`. This batching approach mimics the PlayStation 5's `libSceAmpr` ABI and reduces the overhead of individual system calls.

### How does SharpEmu handle file identification for AMPR exports?

SharpEmu uses the `AmprFileRegistry` class to map guest file paths to host file paths. When a file is registered, the system computes a deterministic file ID using the **FNV-1a** hash algorithm. This ID is used throughout the AMPR system to reference files without passing string paths, enabling efficient lookups in the `_hostFileCache` and `PakDirectoryTracker`.

### What is the difference between sceAmprCommandBufferConstructor and sceAmprAprCommandBufferConstructor?

Both functions initialize command buffers, but they serve different namespaces in the PlayStation 5 ABI. **`sceAmprCommandBufferConstructor`** (lines 63-82) handles standard AMPR buffers, while **`sceAmprAprCommandBufferConstructor`** (lines 90-109) specifically handles "APR" (Audio-Path-Reader) buffers. In SharpEmu's implementation, both methods perform identical initialization logic but are exported as separate symbols to match the PS5 library's expected interface.

### How can I debug AMPR file read operations in SharpEmu?

Set the environment variable `SHARPEMU_LOG_AMPR=1` or `SHARPEMU_LOG_AMPR_READS=1` before running the emulator. This enables detailed tracing that outputs to `stderr` with the prefix `[LOADER][TRACE] ampr.*`, showing each file read operation, command buffer address, file ID, and offset. This is particularly useful for verifying that `PakDirectoryTracker` is correctly resolving sequential offsets for PACK archives.