Skip to main content

Overview

Ryujinx uses High-Level Emulation (HLE) to simulate Nintendo Switch system services and the Horizon operating system. Instead of emulating the entire OS at the binary level, HLE reimplements OS functionality in C#, providing:

Performance

Direct C# implementations are faster than emulating ARM OS code

Compatibility

Handles version differences and missing firmware gracefully

Features

Enhanced functionality beyond real hardware (save states, cheats, mods)

Horizon OS Emulation

System Architecture

The Horizon class (src/Ryujinx.HLE/HOS/Horizon.cs) is the core OS emulator:
public class Horizon : IDisposable
{
    internal KernelContext KernelContext { get; }
    internal Switch Device { get; private set; }
    internal ITickSource TickSource { get; }
    internal SurfaceFlinger SurfaceFlinger { get; private set; }
    
    public SystemStateMgr State { get; private set; }
    internal AppletStateMgr AppletState { get; private set; }
    internal SmRegistry SmRegistry { get; private set; }
    
    // System servers
    internal ServerBase SmServer { get; private set; }      // Service manager
    internal ServerBase FsServer { get; private set; }      // Filesystem
    internal ServerBase HidServer { get; private set; }     // Human interface devices
    internal ServerBase NvDrvServer { get; private set; }   // NVIDIA driver
    internal ServerBase TimeServer { get; private set; }    // Time services
    internal ServerBase ViServer { get; private set; }      // Visual/display
    
    // Shared memory regions
    internal KSharedMemory HidSharedMem { get; private set; }
    internal KSharedMemory FontSharedMem { get; private set; }
    internal KSharedMemory IirsSharedMem { get; private set; }
}
Key components:
Emulates Horizon kernel primitives:
  • Process/thread management
  • Memory management (virtual memory, page tables)
  • Synchronization objects (mutexes, events, semaphores)
  • Inter-process communication
Background threads handling service requests:
// Each server processes IPC messages on dedicated thread
SmServer = new ServerBase(KernelContext, "SmServer");
FsServer = new ServerBase(KernelContext, "FsServer");
HidServer = new ServerBase(KernelContext, "HidServer");
Direct memory sharing between guest and services:
// HID shared memory for controller input (256 KB)
HidSharedMem = CreateSharedMemory(hidPa, HidSize);

// Font shared memory for text rendering (17 MB)
FontSharedMem = CreateSharedMemory(fontPa, FontSize);

Service Implementation

Service Manager (sm)

The Service Manager is the cornerstone of Horizon’s service architecture:
public class SmRegistry
{
    private readonly Dictionary<string, Func<ServiceCtx, IpcService>> _services = new();
    
    public void RegisterService(string name, Func<ServiceCtx, IpcService> factory)
    {
        _services[name] = factory;
    }
    
    public IpcService GetService(ServiceCtx context, string name)
    {
        if (_services.TryGetValue(name, out var factory))
        {
            return factory(context);
        }
        return null;
    }
}
Common services:
// Core system functionality
"acc:u0"     // Account services
"am:u"       // Applet manager
"apm"        // Application performance management
"fsp-srv"    // Filesystem
"hid"        // Human interface devices
"irs"        // IR sensor
"lm"         // Logging
"nifm:u"     // Network interface
"ns:u"       // Nintendo Shell
"nv!nvdrv"   // NVIDIA driver
"pctl:u"     // Parental controls
"pl:u"       // Shared fonts
"set:sys"    // System settings
"time:u"     // Time services
"vi:u"       // Visual/display

IPC (Inter-Process Communication)

Ryujinx implements the Horizon IPC protocol for service communication:

IPC Message Structure

// From src/Ryujinx.HLE/HOS/Ipc/IpcMessage.cs
public class IpcMessage
{
    public IpcMessageType Type { get; set; }
    
    public List<IpcBuffDesc> BuffDescs { get; }           // Buffer descriptors
    public List<IpcPtrBuffDesc> PtrBuffDescs { get; }     // Pointer buffers
    public List<IpcRecvListBuffDesc> RecvListBuffs { get; } // Receive lists
    
    public List<int> ObjectIds { get; }                   // Domain object IDs
    public IpcHandleDesc HandleDesc { get; set; }         // Handle transfer
    
    public byte[] RawData { get; set; }                   // Command data
}
Message types:
public enum IpcMessageType
{
    Request          = 4,  // Standard service request
    Control          = 5,  // Control commands (convert to domain, etc.)
    CloseSession     = 6,  // Close service session
    RequestWithContext = 8 // Request with additional context
}

IPC Service Handler

Base class for all service implementations:
// From src/Ryujinx.HLE/HOS/Services/IpcService.cs
abstract class IpcService
{
    public IReadOnlyDictionary<int, MethodInfo> CmifCommands { get; }
    public IReadOnlyDictionary<int, MethodInfo> TipcCommands { get; }
    
    public IpcService(ServerBase server = null, bool registerTipc = false)
    {
        // Reflect and cache all [CommandCmif] and [CommandTipc] methods
        CmifCommands = GetType()
            .GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public)
            .SelectMany(methodInfo => methodInfo.GetCustomAttributes<CommandCmifAttribute>()
            .Select(command => (command.Id, methodInfo)))
            .ToDictionary(command => command.Id, command => command.methodInfo);
    }
    
    public void CallCmifMethod(ServiceCtx context)
    {
        // Dispatch to appropriate command handler
        int cmdId = context.Request.CommandId;
        if (CmifCommands.TryGetValue(cmdId, out MethodInfo method))
        {
            method.Invoke(this, new object[] { context });
        }
    }
}

Example Service Implementation

// Example: Time service implementation
class ITimeServiceManager : IpcService
{
    public ITimeServiceManager(ServiceCtx context) : base(context.Device.System.SmServer) { }
    
    [CommandCmif(0)] // Command ID 0
    // GetStandardUserSystemClock() -> object<nn::timesrv::detail::service::ISystemClock>
    public ResultCode GetStandardUserSystemClock(ServiceCtx context)
    {
        // Create new service instance
        MakeObject(context, new ISystemClock(
            context.Device.System.StandardUserSystemClock,
            context.Device.System.State.UserTimeOffset,
            false));
        
        return ResultCode.Success;
    }
    
    [CommandCmif(1)] // Command ID 1  
    // GetStandardNetworkSystemClock() -> object<nn::timesrv::detail::service::ISystemClock>
    public ResultCode GetStandardNetworkSystemClock(ServiceCtx context)
    {
        MakeObject(context, new ISystemClock(
            context.Device.System.StandardNetworkSystemClock,
            context.Device.System.State.NetworkTimeOffset,
            true));
        
        return ResultCode.Success;
    }
    
    [CommandCmif(2)]
    // GetStandardSteadyClock() -> object<nn::timesrv::detail::service::ISteadyClock>
    public ResultCode GetStandardSteadyClock(ServiceCtx context)
    {
        MakeObject(context, new ISteadyClock());
        return ResultCode.Success;
    }
}
Attribute-based dispatch: Methods are marked with [CommandCmif(id)] for automatic IPC routing

Domain Objects

Services can be converted to “domains” for efficient object management:
public int ConvertToDomain()
{
    if (_selfId == -1)
    {
        _selfId = _domainObjects.Add(this);
    }
    _isDomain = true;
    return _selfId;
}

public void CallCmifMethod(ServiceCtx context)
{
    IpcService service = this;
    
    if (_isDomain)
    {
        int domainWord0 = context.RequestData.ReadInt32();
        int domainObjId = context.RequestData.ReadInt32();
        
        int domainCmd = (domainWord0 >> 0) & 0xff;
        
        if (domainCmd == 1) // Close object
        {
            _domainObjects.Delete(domainObjId);
            return;
        }
        
        // Route to specific domain object
        service = _domainObjects.GetObject<IpcService>(domainObjId);
    }
    
    // Invoke command on service
    int cmdId = context.Request.CommandId;
    // ...
}
Benefits:
  • Multiple service objects per session
  • Efficient handle management
  • Reduced IPC overhead

System Call Interface

SVC (Supervisor Call) Handling

When guest code executes an SVC instruction:
// ARMeilleure emits call to SVC handler
// Guest: SVC #0x21 (SendSyncRequest)

// Routed to kernel:
public static ulong SendSyncRequest(ulong handle)
{
    KProcess currentProcess = KernelStatic.GetCurrentProcess();
    KThread currentThread = KernelStatic.GetCurrentThread();
    
    KClientSession session = currentProcess.HandleTable.GetObject<KClientSession>(handle);
    
    if (session != null)
    {
        Result result = session.SendSyncRequest();
        return (ulong)result.ErrorCode;
    }
    
    return KernelResult.InvalidHandle;
}
Common SVCs:
SetHeapSize              // svcSetHeapSize
MapMemory                // svcMapMemory  
UnmapMemory              // svcUnmapMemory
QueryMemory              // svcQueryMemory
MapSharedMemory          // svcMapSharedMemory
CreateTransferMemory     // svcCreateTransferMemory

Kernel Emulation

Process Management

public class KProcess : KSynchronizationObject
{
    public ulong Pid { get; }
    public KMemoryManager MemoryManager { get; }
    public KHandleTable HandleTable { get; }
    public KAddressArbiter AddressArbiter { get; }
    
    private readonly List<KThread> _threads = new();
    private readonly List<KSharedMemory> _sharedMemory = new();
    
    public CpuContext CpuContext { get; }
    public ProcessState State { get; set; }
    
    public string Name { get; set; }
    public ulong TitleId { get; set; }
    
    // Process creation
    public static KProcess Create(KernelContext context, 
                                   ProcessCreationInfo creationInfo)
    {
        KProcess process = new KProcess(context);
        process.Initialize(creationInfo);
        return process;
    }
}

Thread Scheduling

public class KThread : KSynchronizationObject
{
    public int ThreadId { get; }
    public KProcess Owner { get; }
    
    public ThreadState State { get; set; }
    public long LastScheduledTime { get; set; }
    
    private int _priority;
    public int Priority
    {
        get => _priority;
        set
        {
            if (_priority != value)
            {
                int oldPriority = _priority;
                _priority = value;
                Context.PriorityQueue.ChangePriority(this, oldPriority);
            }
        }
    }
    
    public ExecutionContext Context { get; }
    public ulong EntryPoint { get; set; }
}
Scheduler implementation:
  • Preemptive multitasking
  • Priority-based scheduling (0-63, lower is higher priority)
  • Round-robin within priority levels
  • Thread affinity to CPU cores

Memory Management

Kernel memory manager handles:
public class KMemoryManager
{
    private readonly KMemoryBlock[] _blocks;
    private readonly KPageTable _pageTable;
    
    public Result MapMemory(ulong src, ulong dst, ulong size, 
                           KMemoryPermission permission)
    {
        // Validate addresses
        // Update page table entries
        // Set memory permissions
        // Update memory block tracking
    }
    
    public Result AllocateOrMapMemory(ulong address, ulong pagesCount, 
                                      KMemoryPermission permission, 
                                      MemoryRegion region)
    {
        // Allocate physical pages from region
        // Map to virtual address
        // Set permissions
    }
}
Memory regions:
  • Application: Game code and data
  • Applet: System applets
  • System: System modules
  • NvServices: GPU/multimedia

Applet System

Emulates Switch’s overlay applet system:
public interface IApplet
{
    ResultCode Start(AppletSession normalSession, AppletSession interactiveSession);
    ResultCode GetResult();
}

public class AppletManager
{
    private readonly Dictionary<AppletId, Type> _applets = new()
    {
        { AppletId.PlayerSelect, typeof(PlayerSelectApplet) },
        { AppletId.SoftwareKeyboard, typeof(SoftwareKeyboardApplet) },
        { AppletId.Error, typeof(ErrorApplet) },
        { AppletId.Controller, typeof(ControllerApplet) },
        { AppletId.WebBrowser, typeof(BrowserApplet) },
    };
    
    public IApplet Create(AppletId appletId, Horizon system)
    {
        if (_applets.TryGetValue(appletId, out Type type))
        {
            return (IApplet)Activator.CreateInstance(type, system);
        }
        return null;
    }
}
Implemented applets:

Software Keyboard

On-screen keyboard for text input
  • Inline and full-screen modes
  • Text validation
  • Dictionary suggestions

Player Select

User account selection
  • Shows configured user profiles
  • Icon and nickname display

Error Display

Error message dialogs
  • Error code formatting
  • Custom error messages

Controller Config

Controller configuration
  • Button remapping
  • Controller order

File System Services

Virtual file system with multiple mount points:
public class VirtualFileSystem
{
    // Real file system paths
    public string BasePath { get; }
    public string SdCardPath { get; }
    
    // Virtual mounts
    public IFileSystem RomFs { get; set; }        // Game RomFS
    public IFileSystem SaveData { get; set; }     // Save data
    public IFileSystem SystemSaveData { get; set; } // System saves
    
    // Content management
    public void LoadRomFs(string path)
    {
        // Mount game RomFS from NSP/XCI/directory
    }
    
    public void CreateSaveData(ulong titleId, SaveDataType type)
    {
        // Create save data container
    }
}
Mount points:
  • @SystemContent: System firmware
  • @UserContent: User installed content
  • @SdCard: SD card access
  • @CalibFile: Calibration data
  • @User: User partition

Service Context

Passed to all service methods:
public class ServiceCtx
{
    public Switch Device { get; }
    public KProcess Process { get; }
    public IVirtualMemoryManager Memory { get; }
    public KThread Thread { get; }
    public IpcMessage Request { get; }
    public IpcMessage Response { get; }
    
    // Helper methods
    public BinaryReader RequestData { get; }
    public BinaryWriter ResponseData { get; }
    
    // Object creation
    public void MakeObject(IpcService service) { /* ... */ }
}

Result Codes

Horizon uses result codes for error handling:
public struct ResultCode
{
    public uint Value { get; }
    
    public int Module => (int)((Value >> 0) & 0x1FF);
    public int Description => (int)((Value >> 9) & 0x1FFF);
    
    public static ResultCode Success => new ResultCode(0);
    
    // Common results
    public static ResultCode ModuleNotFound => new ResultCode(0x202);
    public static ResultCode OutOfMemory => new ResultCode(0xC2);
    public static ResultCode InvalidAddress => new ResultCode(0xCC);
}

Performance Considerations

  • Method reflection cached at service creation
  • Direct C# method invocation (no marshaling)
  • Typical IPC latency: 1-5 microseconds
  • Zero-copy for shared memory regions
  • Direct pointer access for mapped buffers
  • Efficient for HID, graphics, audio data
  • Server threads handle IPC asynchronously
  • Guest threads scheduled by kernel emulator
  • Synchronization primitives map to host OS

ARMeilleure

How guest code interfaces with HLE services

Graphics Subsystem

NVDRV service and GPU command handling

Audio Subsystem

Audio renderer service implementation

Input System

HID service and controller emulation

Source Code Reference

  • src/Ryujinx.HLE/HOS/Horizon.cs:45 - Main Horizon OS class
  • src/Ryujinx.HLE/HOS/Services/IpcService.cs:14 - IPC service base
  • src/Ryujinx.HLE/HOS/Ipc/IpcMessage.cs - IPC message structure
  • src/Ryujinx.HLE/HOS/Kernel/ - Kernel emulation
  • src/Ryujinx.HLE/HOS/Services/ - All service implementations

Build docs developers (and LLMs) love