Skip to main content

Overview

Oboromi implements High-Level Emulation (HLE) of Nintendo’s firmware services through the nn:: namespace. Rather than emulating the Switch operating system at a low level, services are implemented directly in Rust, providing faster execution and easier debugging.

Architecture

The service framework consists of three layers:
┌──────────────────────────────────┐
│       Game Code (Guest)          │
└────────────┬─────────────────────┘
             │ Service Calls (SVC)

┌──────────────────────────────────┐
│    Service Dispatcher (HLE)      │  ◄── start_host_services()
└────────────┬─────────────────────┘


┌──────────────────────────────────┐
│  160 Individual Services         │  ◄── acc, hid, fs, gpu, etc.
│  (nn::service_name::State)       │
└──────────────────────────────────┘

Service Trait System

All services implement a common trait defined in core/src/nn/mod.rs:
pub trait ServiceTrait {
    fn run(_state: &mut sys::State) -> () {
        todo!();
    }
}

Service Registration Macro

Services are defined using a macro that generates boilerplate code:
macro_rules! define_service {
    ($($name:ident),* $(,)?) => {
        $(
            pub mod $name {
                use crate::nn::ServiceTrait;
                use crate::sys;
                
                pub struct State {}
                
                impl State {
                    pub fn new(_state: &mut sys::State) -> Self {
                        Self {}
                    }
                }
                
                impl ServiceTrait for State {
                    fn run(state: &mut sys::State) {
                        state.services.$name = Some(State::new(state));
                    }
                }
            }
        )*
    };
}
What this generates:
  • A module for each service (e.g., nn::acc, nn::hid)
  • A State struct to hold service-specific data
  • A constructor that receives global system state
  • A run() method that initializes and registers the service

Service Catalog

Oboromi currently defines 160 system services, organized by category:
define_service!(
    acc,         // Account management
    fs,          // Filesystem
    hid,         // Human Interface Devices (controller input)
    vi,          // Visual (display)
    nvdrv,       // NVIDIA driver interface
    // ...
);
acc, adraw, ahid, aoc, apm, applet_ae, applet_oe, arp, aud, audctl, auddebug, auddev, auddmg, audin, audout, audrec, audren, audsmx, avm, banana, batlog, bcat, bgtc, bpc, bpmpmr, bsd, bsdcfg, bt, btdrv, btm, btp, capmtp, caps, caps2, cec_mgr, chat, clkrst, codecctl, csrng, dauth, disp, dispdrv, dmnt, dns, dt, ectx, erpt, es, eth, ethc, eupld, fan, fatal, fgm, file_io, friend, fs, fsp_ldr, fsp_pr, fsp_srv, gds, gpio, gpuk, grc, gsv, hdcp, hid, hidbus, host1x, hshl, htc, htcs, hwopus, i2c, idle, ifcfg, imf, ins, irs, jit, lbl, ldn, ldr, led, lm, lp2p, lr, manu, mig, mii, miiimg, mm, mnpp, ncm, nd, ndd, ndrm, news, nfc, nfp, ngc, ngct, nifm, nim, notif, npns, ns, nsd, ntc, nvdbg, nvdrv, nvdrvdbg, nvgem, nvmemp, olsc, omm, ommdisp, ovln, pcie, pcm, pctl, pcv, pdm, pgl, pinmux, pl, pm, prepo, psc, psm, pwm, rgltr, ro, rtc, sasbus, set, sf_uds, sfdnsres, spbg, spi, spl, sprof, spsm, srepo, ssl, syncpt, tc, tcap, time, tma_log, tmagent, ts, tspm, uart, usb, vi, vi2, vic, wlan, xcd

System State Management

The global system state is defined in core/src/sys/mod.rs:
pub struct State {
    pub services: Services,
    pub gpu_state: gpu::State,
}

impl State {
    pub fn new() -> Self {
        Self {
            services: Services::default(),
            gpu_state: gpu::State::default()
        }
    }
}

Services Container

#[derive(Default)]
pub struct Services {
    pub acc: Option<nn::acc::State>,
    pub adraw: Option<nn::adraw::State>,
    pub ahid: Option<nn::ahid::State>,
    // ... 157 more services ...
    pub xcd: Option<nn::xcd::State>,
}
Each service is stored as an Option<State> to support lazy initialization.

Service Initialization

All services are initialized through a single entry point:
pub fn start_host_services(state: &mut sys::State) {
    let entries: [(&str, fn(&mut sys::State)); 160] = [
        ("acc", nn::acc::State::run),
        ("adraw", nn::adraw::State::run),
        ("ahid", nn::ahid::State::run),
        ("aoc", nn::aoc::State::run),
        // ... 156 more entries ...
        ("xcd", nn::xcd::State::run),
    ];
    for (_name, run_fn) in entries.iter() {
        run_fn(state);
    }
}
Key characteristics:
  • Services are initialized sequentially (no parallelism currently)
  • Service names are provided for debugging/logging
  • Each service can access and modify global state during initialization
  • Failed initializations propagate through todo!() panics

Service Implementation Pattern

Here’s how to implement a real service:
// Example: Simplified HID (Human Interface Device) service
pub mod hid {
    use crate::nn::ServiceTrait;
    use crate::sys;
    
    pub struct State {
        controller_state: ControllerState,
        touchscreen_state: TouchState,
    }
    
    #[derive(Default)]
    struct ControllerState {
        buttons: u32,
        left_stick: (i16, i16),
        right_stick: (i16, i16),
    }
    
    #[derive(Default)]
    struct TouchState {
        x: u32,
        y: u32,
        pressed: bool,
    }
    
    impl State {
        pub fn new(_state: &mut sys::State) -> Self {
            Self {
                controller_state: ControllerState::default(),
                touchscreen_state: TouchState::default(),
            }
        }
        
        pub fn update_buttons(&mut self, buttons: u32) {
            self.controller_state.buttons = buttons;
        }
        
        pub fn get_controller_state(&self) -> &ControllerState {
            &self.controller_state
        }
    }
    
    impl ServiceTrait for State {
        fn run(state: &mut sys::State) {
            state.services.hid = Some(State::new(state));
        }
    }
}

Service Communication

Services can interact through shared state:
// fs service provides file access
pub mod fs {
    impl State {
        pub fn open_file(&mut self, path: &str) -> Result<FileHandle, Error> {
            // Implementation
        }
    }
}

// acc service uses fs to load account data
pub mod acc {
    impl State {
        pub fn new(state: &mut sys::State) -> Self {
            // Access fs service
            if let Some(fs) = &mut state.services.fs {
                let accounts = fs.open_file("/save/accounts.dat");
                // Load account data
            }
            Self { /* ... */ }
        }
    }
}

Critical Services

Some services are essential for basic emulation:

fs - Filesystem

Provides file I/O for game assets, saves, and configuration. Implements RomFS, SaveData, and ContentStorage.

hid - Input

Handles controller input, touchscreen, gyroscope, and other human interface devices.

vi - Display

Manages display surfaces, layer composition, and vsync. Coordinates with GPU for frame presentation.

nvdrv - GPU Driver

Low-level NVIDIA GPU interface. Handles command buffer submission and memory management.

acc - Accounts

User account management. Required for save data isolation and online features.

audren - Audio

Audio rendering pipeline. Processes game audio and outputs to speakers/headphones.

Service Categories

Human Interface Devices

hid       // Controller, touchscreen, gyro
hidbus    // USB HID devices
irs       // IR camera
nfp       // Amiibo (NFC)

Graphics & Display

vi        // Visual interface (display)
vi2       // Visual interface v2
nvdrv     // NVIDIA driver
nvdrvdbg  // NVIDIA driver debug
disp      // Display control
dispdrv   // Display driver

Filesystem & Storage

fs        // Filesystem
fsp_srv   // Filesystem proxy server
fsp_ldr   // Filesystem loader
fsp_pr    // Filesystem program registry
ncm       // Nintendo Content Manager

Network Services

nifm      // Network Interface Manager
nim       // Network Installation Manager
bsd       // Berkeley sockets
sfdnsres  // DNS resolver
ssl       // TLS/SSL

Power & System

pm        // Process Manager
psc       // Power State Control
pcv       // Power Control Voltage
apm       // Application Power Management
fan       // Fan control

Current Implementation Status

Most services are currently stubs - they register themselves but don’t implement functionality. The todo!() macro is used as a placeholder:
impl ServiceTrait for State {
    fn run(_state: &mut sys::State) -> () {
        todo!();  // Not implemented yet
    }
}
Priority implementation roadmap:
  1. Phase 1: Core services (fs, hid, vi, nvdrv, acc)
  2. Phase 2: Audio pipeline (audren, audout)
  3. Phase 3: Network services (nifm, bsd, ssl)
  4. Phase 4: Advanced features (applet, friend, news)

Design Rationale

Why HLE over LLE?

High-Level Emulation:
  • Direct Rust implementation
  • No instruction-by-instruction emulation
  • Native code execution speeds
vs Low-Level Emulation:
  • Must emulate ARM64 firmware
  • Interprets or JITs every instruction
  • 10-100x slower for system calls

Hybrid Approach

Oboromi uses HLE by default but could support LLE for specific services:
enum ServiceMode {
    HighLevel(Box<dyn ServiceTrait>),
    LowLevel { firmware_addr: u64 },
}

impl Services {
    pub fn call_service(&mut self, service: &str, cmd: u32) {
        match self.mode(service) {
            ServiceMode::HighLevel(svc) => svc.handle_command(cmd),
            ServiceMode::LowLevel { firmware_addr } => {
                // JIT or interpret firmware service handler
            },
        }
    }
}

Service Discovery

Games discover services through the sm: (Service Manager) service:
// Pseudocode for service discovery
let sm = state.services.sm.as_ref().unwrap();
let handle = sm.get_service("hid").unwrap();

// Now game can call HID service functions
hid::get_controller_state(handle, &mut state);

Debugging Services

// Enable service call logging
if let Some(hid) = &state.services.hid {
    log::debug!("HID service initialized");
}

// Trace service calls
impl ServiceTrait for State {
    fn run(state: &mut sys::State) {
        log::info!("Initializing acc (Account) service");
        state.services.acc = Some(State::new(state));
        log::info!("acc service ready");
    }
}

Testing Services

#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn test_service_initialization() {
        let mut state = sys::State::new();
        nn::start_host_services(&mut state);
        
        // Verify all services registered
        assert!(state.services.acc.is_some());
        assert!(state.services.hid.is_some());
        assert!(state.services.fs.is_some());
    }
    
    #[test]
    fn test_hid_button_input() {
        let mut state = sys::State::new();
        nn::hid::State::run(&mut state);
        
        let hid = state.services.hid.as_mut().unwrap();
        hid.update_buttons(0x0001);  // A button pressed
        
        let controller = hid.get_controller_state();
        assert_eq!(controller.buttons & 0x0001, 0x0001);
    }
}

Source Files

  • Service Framework: core/src/nn/mod.rs:1-224
  • System State: core/src/sys/mod.rs:1-180
  • Module Definition: core/src/lib.rs:5

Build docs developers (and LLMs) love