snx-service
Implement or fix a high-level-emulated Switch service or device (IPC commands, nvdrv ioctls, shared memory). Make it answer as the console does, as the SDK code of the game expects. Use it when a log
SKILL.md.claude/skills/snx-service/SKILL.md
GitHubServices
Where
A
.swipcfile declares the interfaces, and no code dispatches them by hand. Each service file has a.swipcbeside it, in the language of SwIPC. Examples insrc/nx/arehle/services/acc.cwithhle/services/acc.swipc,hle/services/network/bsd.swipcandhle/filesystem/fs.swipc.docs/IPC.mdhas the language, the layout that the generator applies, and the C side.tools/swipc/swipc.cgenerates<file>.swipc.hand<file>.swipc.incintobuild/<preset>/src/nx/snx_hle-swipc/. The C file of the service includes the.inc, which has the exact prototype of each handler. Other files include the.h.The handler of a command is
static int nn_ns_IFoo_Command(SvcObj* self, inputs..., outputs...). Scalars come by value, structs asconst T*and buffers asIpcBuf(address, size). Objects areSvcObj*in andSvcObj**out. Handles areu32in andu32*out. Outputs start zeroed. It returns 0 orRC(module, description), and a failure answers no outputs.I_new(data_size)makes an object of interfaceI, with zeroed state ino->data.I_register(ctor)registers a constructor under theisnames of the.swipc(inservices.c, or in the init of the service).@stubanswers success and zeros without a handler.@unsortedkeeps the raw arguments of a command in declaration order, for the few commands that the SDK lays out that way.@fallback(fn)selects the function that answers undeclared commands. The default isstub_ok, which logs.types.swipccomes first in everysnx_swipclist of the runtime files. It also comes first in the list of a test that uses its types.src/nx/hle/kernel/ipc.chas the HIPC/CMIF parser, the replies, sessions, domains and the registry.services_int.hdeclares what the service files share.In
services/arehid.c(input and its shared memory),vi.c(display, binder),nvdrv.c(nvhost devices),audren.c(audren_update),pl.c(shared fonts) andmisc.c(the small services). Also seesrc/nx/hle/filesystem/fs.c, andsrc/nx/gpu/video/(host1x channels, NVDEC, VIC).
Procedure
Find the command.
-vlogs every call. Usegrep "IAudioOut cmd" run1.log | sort | uniq -c. The name of the object is the name of the interface (nn::audio::detail::IAudioOut). But the objects of bsd and ldr have the name of the service (bsd:uandldr:ro).The fallback of the interface logs an undeclared command the first three times:
[undeclared] I cmd N -> okcomes fromstub_ok. It gives success with no outputs. For a command that gives an object or a handle, the game then calls through null.[unknown]comes fromipc_unknown.[not implemented]comes from the fallback of bsd.quiet_oklogs nothing.
[undeclared] NAME cmd N -> okalso comes from the stand-in of sm for a service that the runtime does not have. A[bad request] I cmd N: pid .., A .., ... -> InvalidCmifRequestmeans that the declaration does not agree with what the SDK sends (step 2). The game gets an error, and often aborts on it.[bad request] ... -> InvalidNumInObjectsis the same error for the objects of a domain request.... takes objects, on a session that isn't a domain -> NotSupportedshows a call on a plain session to a command that declares objects.The Switchbrew page for the service gives the name and layout of the command. The client struct of libnx shows the raw layout and the buffer attributes.
Declare it in the
.swipcwith the full signature of the console. Include the inputs and outputs that the runtime ignores. Give the exact buffer attributes, because they decide the A/B/X/C list.Never write offsets by hand. The generator sorts raw arguments by alignment, from the smallest to the largest, as the SDK does. Check a command with mixed alignment against the
InRawArgumentInfo<size, alignment, offset>templates of the SDK. A few commands use declaration order (@unsorted).pidis one 8-byte slot (write the(u64, pid)of SwIPC aspid).aruidis the ClientAppletResourceUserId slot of a command.Read the caller. The SDK wrapper of the game (
nn::audio::...,nn::hid::...) shows what it reads from the reply, and what it does with it. Usetools/symbols.pyandtools/disasm.pyon the wrapper, or thesdk-scoutagent. A field that the SDK reads and the runtime never writes is a bug, also when nothing crashes yet.Answer like the console in the handler. Give the same result codes, the same output values, and the same handle and event behavior. Where the console times something (audio buffers, vsync), keep the same times. Games sync against them.
Be careful with threads. Service calls come from game threads, which run in parallel on the host. State that calls share must have a lock (
PMutex,pm_lock). Events go throughkevent_signal.Some objects need a lock for every command. Give such an object its own dispatcher around
I_dispatch(file_disp,dir_dispandfs_dispin fs.c,ro_dispatchin ldr.c). Make it withsvcobj_newand that dispatcher, not withI_new.Verify with the scene that uses it (
snx-run). Grep the-vlog to see the new answers. A test that includes the C file of a service generates the dispatchers of that service itself (snx_swipcintests/CMakeLists.txt).
References and licenses
Switchbrew documents commands and structures. Ryujinx (MIT) is the best reference for behavior. You can port from it with its notice. You can also port from yuzu and its forks (GPL-2.0-or-later or GPL-3.0), because ScarletNX is GPL-3.0-or-later. Keep their copyright lines and say where the code came from. You cannot port GPL-2.0-only code: read Atmosphère, but never port it.
Known traps
- If the runtime reports more than one npad style for a controller, the SDK asserts in
GetVibrationDeviceHandles. - An
IAudioOutthat never releases buffers stalls CRI movies after a few frames. Clear the array of released buffers first, because the SDK reads every slot. WaitSynchronizationwith zero handles is legal (a plain sleep).- nvhost syncpoint ids above 127 overflow the tables of NvRmStream. Allocate them with
syncpt_alloc().