Skip to main content

Overview

Oboromi implements High-Level Emulation (HLE) for Nintendo Switch 2 firmware services. Rather than running original firmware code, the emulator reimplements 160+ services in Rust that games call through system APIs.
HLE trades accuracy for performance and simplicity. Services are reimplemented based on reverse-engineered behavior rather than executing original firmware binaries.

Service Architecture

Service Organization

Services are organized into two main modules:
  1. core/src/nn/: Service implementations
  2. core/src/sys/: System state and service container

Service Definition

Macro-Based Generation

Services are defined using the define_service! macro to reduce boilerplate:
// core/src/nn/mod.rs:4
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));
                    }
                }
            }
        )*
    };
}
This macro generates a complete service module including:
  • State structure
  • Constructor
  • ServiceTrait implementation
  • Registration logic

Service Catalog

Complete Service List

Oboromi defines 160 services covering all system functionality:
// core/src/nn/mod.rs:29
define_service!(
    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,
);

Service Categories

Input Services

  • hid - Human Interface Devices
  • hidbus - HID bus management
  • irs - IR sensor
  • ahid - Auxiliary HID

Audio Services

  • aud - Audio core
  • audren - Audio renderer
  • audin - Audio input
  • audout - Audio output
  • audrec - Audio recording
  • hwopus - Hardware Opus codec

Graphics Services

  • vi - Visual interface
  • vi2 - Visual interface v2
  • nvdrv - NVIDIA driver
  • disp - Display management

Filesystem Services

  • fs - Filesystem
  • fsp_srv - Filesystem service
  • fsp_ldr - Filesystem loader
  • fsp_pr - Filesystem PR

Network Services

  • nifm - Network interface
  • bsd - BSD sockets
  • dns - DNS resolution
  • ssl - SSL/TLS
  • wlan - Wireless LAN

System Services

  • pm - Process manager
  • ldr - Loader
  • sm - Service manager
  • fatal - Fatal error handler

Service Container

Services Structure

All active services are stored in the Services struct:
// core/src/sys/mod.rs:5
pub struct Services {
    pub acc: Option<nn::acc::State>,
    pub adraw: Option<nn::adraw::State>,
    pub ahid: Option<nn::ahid::State>,
    // ... (160 service fields)
    pub xcd: Option<nn::xcd::State>,
}
Each service is an Option<T>, allowing lazy initialization:
  • None = Service not yet initialized
  • Some(state) = Service active and ready

System State

The global system state combines services with GPU state:
// core/src/sys/mod.rs:168
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()
        }
    }
}

Service Initialization

Startup Sequence

Services are registered at emulator startup:
// core/src/nn/mod.rs:57
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),
        // ... 157 more services
        ("xcd", nn::xcd::State::run),
    ];
    
    for (_name, run_fn) in entries.iter() {
        run_fn(state);
    }
}
This function:
  1. Creates a table of (name, init function) pairs
  2. Calls each service’s run() method
  3. Services register themselves in state.services

Service Registration

Each service registers itself when run() is called:
// Generated by macro for each service
impl ServiceTrait for State {
    fn run(state: &mut sys::State) {
        state.services.acc = Some(State::new(state));
    }
}

Service Trait

The ServiceTrait defines the interface all services implement:
// core/src/nn/mod.rs:51
pub trait ServiceTrait {
    fn run(_state: &mut sys::State) -> () {
        todo!();
    }
}
The default implementation uses todo!(). Services must override this method to provide actual functionality.

Service Implementation Pattern

Typical Service Structure

A fully implemented service would look like:
pub mod example_service {
    use crate::nn::ServiceTrait;
    use crate::sys;
    
    pub struct State {
        initialized: bool,
        internal_data: Vec<u8>,
    }
    
    impl State {
        pub fn new(state: &mut sys::State) -> Self {
            // Initialize service state
            Self {
                initialized: true,
                internal_data: Vec::new(),
            }
        }
        
        pub fn handle_command(&mut self, cmd: u32, input: &[u8]) -> Result<Vec<u8>, String> {
            match cmd {
                0 => self.initialize(),
                1 => self.process_data(input),
                _ => Err(format!("Unknown command: {}", cmd)),
            }
        }
        
        fn initialize(&mut self) -> Result<Vec<u8>, String> {
            // Implementation
            Ok(vec![])
        }
        
        fn process_data(&mut self, input: &[u8]) -> Result<Vec<u8>, String> {
            // Implementation
            Ok(input.to_vec())
        }
    }
    
    impl ServiceTrait for State {
        fn run(state: &mut sys::State) {
            state.services.example = Some(State::new(state));
        }
    }
}

Service Communication

Services communicate with games through system calls:

Call Flow

IPC Structure

Inter-Process Communication (IPC) messages contain:
  • Command ID: Which function to call
  • Input Buffer: Request data
  • Output Buffer: Response data
  • Handles: File descriptors, memory handles, etc.

Key Services

Filesystem Service (fs)

Handles all file I/O:
  • Open files from ROM, save data, SD card
  • Read/write operations
  • Directory enumeration
  • File metadata

HID Service (hid)

Manages input devices:
  • Controller input (buttons, analog sticks)
  • Touch screen
  • Accelerometer/gyroscope
  • Vibration/rumble

Audio Renderer (audren)

Processes game audio:
  • Mix audio streams
  • Apply effects (reverb, filters)
  • Output to speakers/headphones
  • Microphone input

NVIDIA Driver (nvdrv)

GPU interface:
  • Submit GPU commands
  • Allocate GPU memory
  • Create graphics contexts
  • Manage command buffers

Network Interface (nifm)

Network connectivity:
  • WiFi connection management
  • IP configuration
  • Network status monitoring
  • Internet connectivity checks

Development Status

Service Implementation Progress

Currently, all 160 services are registered but most contain only stub implementations. The macro-generated structure is in place, but actual functionality is being added incrementally.
StatusCountDescription
Registered160Service structure exists
Stubbed160Empty implementation with todo!()
Partially Implemented0Some commands work
Fully Implemented0All commands functional

Service Priorities

Implementation order based on importance:
  1. High Priority
    • fs - Required for loading games
    • hid - Essential for input
    • nvdrv - GPU functionality
    • vi - Display output
  2. Medium Priority
    • audren - Audio playback
    • nifm - Network (for online games)
    • pm - Process management
    • applet_ae - System applets
  3. Low Priority
    • Debug services (dmnt, nvdbg)
    • Manufacturing services (manu)
    • Specialized hardware (i2c, gpio)

Testing Services

Service Test Pattern

#[test]
fn test_service_registration() {
    let mut state = sys::State::new();
    nn::start_host_services(&mut state);
    
    // Verify services are registered
    assert!(state.services.fs.is_some());
    assert!(state.services.hid.is_some());
    assert!(state.services.nvdrv.is_some());
}

#[test]
fn test_service_functionality() {
    let mut state = sys::State::new();
    nn::start_host_services(&mut state);
    
    // Test specific service command
    let fs = state.services.fs.as_mut().unwrap();
    let result = fs.handle_command(0, &[]); // Initialize command
    assert!(result.is_ok());
}

Adding a New Service

Step-by-Step Guide

  1. Add to macro invocation:
define_service!(
    // ... existing services
    my_new_service,
);
  1. Add to Services struct:
pub struct Services {
    // ... existing fields
    pub my_new_service: Option<nn::my_new_service::State>,
}
  1. Add to initialization table:
let entries: [(&str, fn(&mut sys::State)); 161] = [
    // ... existing entries
    ("my-new-service", nn::my_new_service::State::run),
];
  1. Implement service logic (override macro-generated stub):
pub mod my_new_service {
    use crate::nn::ServiceTrait;
    use crate::sys;
    
    pub struct State {
        // Custom state fields
    }
    
    impl State {
        pub fn new(state: &mut sys::State) -> Self {
            // Custom initialization
            Self { /* ... */ }
        }
        
        // Add service-specific methods
    }
    
    impl ServiceTrait for State {
        fn run(state: &mut sys::State) {
            state.services.my_new_service = Some(State::new(state));
        }
    }
}

Best Practices

When implementing services:
  1. Start with stubs: Return success for all commands initially
  2. Log calls: Add logging to understand what games call
  3. Implement incrementally: Add one command at a time
  4. Match Nintendo behavior: Reverse-engineer exact return values
  5. Handle errors gracefully: Don’t crash on unknown commands

Future Enhancements

  1. IPC Layer: Proper message parsing and serialization
  2. Service Manager: Dynamic service lookup and handles
  3. Async Services: Non-blocking service calls
  4. Service Versioning: Support multiple firmware versions
  5. Plugin System: Allow external service implementations

Build docs developers (and LLMs) love