Services
Interacting with system service modules via IPC
libnx, the core C library used by nx.js to interact with the Switch, contains a
vast number of functions for interacting with various system services in a myriad
of different ways. Many of these functions are higher level wrappers on top of
the Switch's IPC communication mechanism, also known as "service modules".
It would not be reasonable for nx.js to attempt to expose all of these functions directly, so instead nx.js provides a low-level API for interacting with these system modules via IPC, which allows functionality that is not directly exposed by nx.js to be implemented in userland.
Warning
This is a low-level API which is not recommended to be used directly in most cases. You should look for an npm package which provides a higher-level API for the service you are trying to interact with.
Porting libnx functions to nx.js
Example with output value
Let's say you want your application to retrieve the current system region code. In libnx,
this can be done using the setGetRegionCode() function, which is exposed by the
set service module.
However, nx.js does not directly expose this function, so your app can use the
Switch.Service class to interact with the set service module.
To do this correctly, you may need to reference the libnx source code to understand which command ID and parameters are required for the command you are attempting to port.
For example, the setGetRegionCode() function is implemented as follows:
Result setGetRegionCode(SetRegion *out) {
s32 code=0;
Result rc = _setCmdNoInOutU32(&g_setSrv, (u32*)&code, 4);
if (R_SUCCEEDED(rc) && out) *out = code;
return rc;
}Based on this implementation, we can see that:
setis the named service module- The command ID is
4 - There is no input data
- The output data is a region code, which is a
u32value
Porting this function to nx.js would look like this:
const setSrv = new Switch.Service('set');
function getRegionCode() {
const code = new Uint32Array(1);
setSrv.dispatchOut(4, code.buffer);
return code[0];
}
console.log(getRegionCode());Example with input value
Let's say you want to interact with a function which takes an input value, such as setsysSetRegionCode().
The setsysSetRegionCode() function is implemented as follows:
Result setsysSetRegionCode(SetRegion region) {
return _setCmdInU32NoOut(&g_setsysSrv, region, 57);
}set:sysis the named service module- The command ID is
57 - The input data is a region code, which is a
u32value - There is no output data
Porting this function to nx.js would look like this:
const setSysSrv = new Switch.Service('set:sys');
function setRegionCode(region: number) {
setSysSrv.dispatchIn(57, new Uint32Array([region]).buffer);
}
const regionCode = 1; // SetRegion_USA
setRegionCode(regionCode);Example with buffers
Some commands require use of input and/or output buffers, such as setGetAvailableLanguageCodes(). Let's take a look at how to port this function to nx.js.
The setGetAvailableLanguageCodes() function is implemented as follows (some parts are omitted for brevity):
Result setGetAvailableLanguageCodes(s32 *total_entries, u64 *LanguageCodes, size_t max_entries) {
return serviceDispatchOut(&g_setSrv, 5, *total_entries,
.buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_Out },
.buffers = { { LanguageCodes, max_entries*sizeof(u64) } },
);
}setis the named service module- The command ID is
5 - There is no input data
- The output data is a
s32number representing the number of entries that were returned - The output buffer is an array of
u64values, which represent the available language codes
Important
Additionally, looking at the Switchbrew wiki for LanguageCode,
we can see that the language codes are represented as a u64 value, which is actually a NUL-terminated string.
Porting this function to nx.js would look like this:
import { SfBufferAttr } from '@nx.js/constants';
const setSrv = new Switch.Service('set');
function getAvailableLanguageCodes() {
const totalEntriesArr = new Int32Array(1);
const languageCodesBuf = new ArrayBuffer(20 * 8);
setSrv.dispatchOut(5, totalEntriesArr.buffer, {
bufferAttrs: [SfBufferAttr.HipcMapAlias | SfBufferAttr.Out],
buffers: [languageCodesBuf],
});
const decoder = new TextDecoder();
const languageCodes: string[] = [];
// Process the output buffer into the list of language codes as strings
const totalEntries = totalEntriesArr[0];
for (let i = 0; i < totalEntries; i++) {
const data = languageCodesBuf.slice(i * 8, i * 8 + 8);
const nul = new Uint8Array(data).indexOf(0);
languageCodes.push(decoder.decode(data.slice(0, nul)));
}
return languageCodes;
}
console.log(getAvailableLanguageCodes());Example with output service object
In some cases, the result of a command is itself a new Switch.Service instance.
One such example is the clkrstOpenSession() function:
Result clkrstOpenSession(ClkrstSession* session_out, PcvModuleId module_id, u32 unk) {
const struct {
u32 module_id;
u32 unk;
} in = { module_id, unk };
return serviceDispatchIn(&g_clkrstSrv, 0, in,
.out_num_objects = 1,
.out_objects = &session_out->s,
);
}In this case, you can create an "uninitialized" Switch.Service instance (by
omitting the name from the constructor), and then pass the instance to the
outObjects option in the dispatch function:
const clkrstSrv = new Switch.Service('clkrst');
function openSession(moduleId: number) {
const sessionSrv = new Switch.Service();
const inArr = new Uint32Array([moduleId, 3]);
clkrstSrv.dispatchIn(0, inArr.buffer, {
outObjects: [sessionSrv],
});
return sessionSrv;
}
const moduleId = 0x40000001; // PcvModuleId_CpuBus
const session = openSession(moduleId);
// Dispatch additional commands to the session service instance