From e5bd2d5fb3f62e0dff58466c587104240e064960 Mon Sep 17 00:00:00 2001 From: Nathan Rajlich Date: Fri, 6 Dec 2024 13:15:33 -0800 Subject: [PATCH] Add `Switch.Service` class (#161) --- .changeset/afraid-rice-heal.md | 5 + .changeset/proud-dogs-explain.md | 5 + docs/content/runtime/concepts/services.mdx | 193 ++++++++++++++ packages/constants/src/index.ts | 1 + packages/constants/src/service.ts | 16 ++ packages/runtime/src/$.ts | 5 + packages/runtime/src/switch/index.ts | 1 + packages/runtime/src/switch/service.ts | 80 ++++++ source/main.c | 2 + source/service.c | 291 +++++++++++++++++++++ source/service.h | 4 + 11 files changed, 603 insertions(+) create mode 100644 .changeset/afraid-rice-heal.md create mode 100644 .changeset/proud-dogs-explain.md create mode 100644 docs/content/runtime/concepts/services.mdx create mode 100644 packages/constants/src/service.ts create mode 100644 packages/runtime/src/switch/service.ts create mode 100644 source/service.c create mode 100644 source/service.h diff --git a/.changeset/afraid-rice-heal.md b/.changeset/afraid-rice-heal.md new file mode 100644 index 00000000..985ee4b4 --- /dev/null +++ b/.changeset/afraid-rice-heal.md @@ -0,0 +1,5 @@ +--- +"@nx.js/runtime": patch +--- + +Add `Switch.Service` class diff --git a/.changeset/proud-dogs-explain.md b/.changeset/proud-dogs-explain.md new file mode 100644 index 00000000..eec068bd --- /dev/null +++ b/.changeset/proud-dogs-explain.md @@ -0,0 +1,5 @@ +--- +"@nx.js/constants": minor +--- + +Add `SfBufferAttr` and `SfOutHandleAttr` enums diff --git a/docs/content/runtime/concepts/services.mdx b/docs/content/runtime/concepts/services.mdx new file mode 100644 index 00000000..0ccf3eed --- /dev/null +++ b/docs/content/runtime/concepts/services.mdx @@ -0,0 +1,193 @@ +--- +title: Services +description: 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"](https://switchbrew.org/wiki/Services_API#Service_List). + +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`](https://switchbrew.org/wiki/Settings_services#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](https://github.com/switchbrew/libnx) +to understand which command ID and parameters are required for the command +you are attempting to port. + +For example, the [`setGetRegionCode()` function](https://github.com/switchbrew/libnx/blob/e79dd7ac52cc7fdc41134c9b55e6f55ec8b8799f/nx/source/services/set.c#L216-L221) is implemented as follows: + +```c title="libnx/source/services/set.c" +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: + +* [`set`](https://switchbrew.org/wiki/Settings_services#set) is the named service module +* The command ID is `4` +* There is no input data +* The output data is a [region code](https://switchbrew.org/wiki/Settings_services#RegionCode), which is a `u32` value + +Porting this function to nx.js would look like this: + +```typescript title="src/main.ts" +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](https://github.com/switchbrew/libnx/blob/e79dd7ac52cc7fdc41134c9b55e6f55ec8b8799f/nx/source/services/set.c#L546-L548) is implemented as follows: + +```c title="libnx/source/services/set.c" +Result setsysSetRegionCode(SetRegion region) { + return _setCmdInU32NoOut(&g_setsysSrv, region, 57); +} +``` + +* [`set:sys`](https://switchbrew.org/wiki/Settings_services#set:sys) is the named service module +* The command ID is `57` +* The input data is a [region code](https://switchbrew.org/wiki/Settings_services#RegionCode), which is a `u32` value +* There is no output data + +Porting this function to nx.js would look like this: + +```typescript title="src/main.ts" +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](https://github.com/switchbrew/libnx/blob/e79dd7ac52cc7fdc41134c9b55e6f55ec8b8799f/nx/source/services/set.c#L187-L204) is implemented as follows (some parts are omitted for brevity): + +```c title="libnx/source/services/set.c" +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) } }, + ); +} +``` + +* [`set`](https://switchbrew.org/wiki/Settings_services#set) is the named service module +* The command ID is `5` +* There is no input data +* The output data is a `s32` number representing the number of entries that were returned +* The output buffer is an array of `u64` values, which represent the available language codes + +> [!IMPORTANT] +> Additionally, looking at the [Switchbrew wiki for `LanguageCode`](https://switchbrew.org/wiki/Settings_services#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: + +```typescript title="src/main.ts" +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](https://github.com/switchbrew/libnx/blob/e79dd7ac52cc7fdc41134c9b55e6f55ec8b8799f/nx/source/services/clkrst.c#L25-L34): + +```c title="libnx/source/services/clkrst.c" +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: + +```typescript title="src/main.ts" +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 +``` diff --git a/packages/constants/src/index.ts b/packages/constants/src/index.ts index 67c43e5f..748d4221 100644 --- a/packages/constants/src/index.ts +++ b/packages/constants/src/index.ts @@ -4,4 +4,5 @@ export * from './errno'; export * from './fs'; export * from './gamepad'; export * from './hid'; +export * from './service'; export * from './swkbd'; diff --git a/packages/constants/src/service.ts b/packages/constants/src/service.ts new file mode 100644 index 00000000..29a394f5 --- /dev/null +++ b/packages/constants/src/service.ts @@ -0,0 +1,16 @@ +export enum SfBufferAttr { + In = 1 << 0, + Out = 1 << 1, + HipcMapAlias = 1 << 2, + HipcPointer = 1 << 3, + FixedSize = 1 << 4, + HipcAutoSelect = 1 << 5, + HipcMapTransferAllowsNonSecure = 1 << 6, + HipcMapTransferAllowsNonDevice = 1 << 7, +} + +export enum SfOutHandleAttr { + None = 0, + HipcCopy = 1, + HipcMove = 2, +} diff --git a/packages/runtime/src/$.ts b/packages/runtime/src/$.ts index c8ce74bf..b8922a3f 100644 --- a/packages/runtime/src/$.ts +++ b/packages/runtime/src/$.ts @@ -9,6 +9,7 @@ import type { ProfileUid, SaveData, SaveDataCreationInfo, + Service, Stats, Versions, } from './switch'; @@ -223,6 +224,10 @@ export interface Init { nsAppNew(id: string | bigint | ArrayBuffer | null): Application; nsAppNext(index: number): bigint | null; + // service.c + serviceInit(c: ClassOf): () => void; + serviceNew(name?: string): Service; + // software-keyboard.c swkbdCreate(fns: { onCancel: (this: VirtualKeyboard) => void; diff --git a/packages/runtime/src/switch/index.ts b/packages/runtime/src/switch/index.ts index 678359ed..81fe3243 100644 --- a/packages/runtime/src/switch/index.ts +++ b/packages/runtime/src/switch/index.ts @@ -22,6 +22,7 @@ export * from './irsensor'; export * from './profile'; export * from './album'; export * from './file-system'; +export * from './service'; export { Socket, Server }; export type PathLike = string | URL; diff --git a/packages/runtime/src/switch/service.ts b/packages/runtime/src/switch/service.ts new file mode 100644 index 00000000..369fdea7 --- /dev/null +++ b/packages/runtime/src/switch/service.ts @@ -0,0 +1,80 @@ +import { $ } from '../$'; +import { proto, stub } from '../utils'; + +export interface ServiceDispatchParams { + //Handle target_session; + targetSession?: number; + + //u32 context; + context?: number; + + //SfBufferAttrs buffer_attrs; + //SfBuffer buffers[8]; + bufferAttrs?: number[]; + buffers?: ArrayBuffer[]; + + //bool in_send_pid; + inSendPid?: boolean; + + //u32 in_num_objects; + //const Service* in_objects[8]; + inObjects?: Service[]; + + //u32 in_num_handles; + //Handle in_handles[8]; + inHandles?: number[]; + + //u32 out_num_objects; + //Service* out_objects; + // XXX: This seems to always be 1 (hence why its not an array?) + outObjects?: Service[]; + + //SfOutHandleAttrs out_handle_attrs; + //Handle* out_handles; + outHandleAttrs?: number[]; + outHandles?: number[]; +} + +export class Service { + constructor(name?: string) { + return proto($.serviceNew(name), Service); + } + + isActive() { + stub(); + } + + isDomain() { + stub(); + } + + isOverride() { + stub(); + } + + dispatch(rid: number, params?: ServiceDispatchParams) { + this.dispatchInOut(rid, undefined, undefined, params); + } + + dispatchIn(rid: number, inData: ArrayBuffer, parmas?: ServiceDispatchParams) { + this.dispatchInOut(rid, inData, undefined, parmas); + } + + dispatchOut( + rid: number, + outData: ArrayBuffer, + params?: ServiceDispatchParams, + ) { + this.dispatchInOut(rid, undefined, outData, params); + } + + dispatchInOut( + rid: number, + inData?: ArrayBuffer, + outData?: ArrayBuffer, + params?: ServiceDispatchParams, + ) { + stub(); + } +} +$.serviceInit(Service); diff --git a/source/main.c b/source/main.c index 71e03f4d..e23db32f 100644 --- a/source/main.c +++ b/source/main.c @@ -26,6 +26,7 @@ #include "nifm.h" #include "ns.h" #include "poll.h" +#include "service.h" #include "software-keyboard.h" #include "tcp.h" #include "tls.h" @@ -629,6 +630,7 @@ int main(int argc, char *argv[]) { nx_init_irs(ctx, nx_ctx->init_obj); nx_init_nifm(ctx, nx_ctx->init_obj); nx_init_ns(ctx, nx_ctx->init_obj); + nx_init_service(ctx, nx_ctx->init_obj); nx_init_tcp(ctx, nx_ctx->init_obj); nx_init_tls(ctx, nx_ctx->init_obj); nx_init_url(ctx, nx_ctx->init_obj); diff --git a/source/service.c b/source/service.c new file mode 100644 index 00000000..6654dae6 --- /dev/null +++ b/source/service.c @@ -0,0 +1,291 @@ +#include "service.h" +#include "error.h" + +static JSClassID nx_service_class_id; + +typedef struct { + Service service; +} nx_service_t; + +static void finalizer_service(JSRuntime *rt, JSValue val) { + nx_service_t *data = JS_GetOpaque(val, nx_service_class_id); + if (data) { + serviceClose(&data->service); + js_free_rt(rt, data); + } +} + +static JSValue nx_service_new(JSContext *ctx, JSValueConst this_val, int argc, + JSValueConst *argv) { + nx_service_t *data = js_mallocz(ctx, sizeof(nx_service_t)); + if (!data) { + return JS_EXCEPTION; + } + + // If a string is passed in, then it is the name of the service module + if (JS_IsString(argv[0])) { + const char *name = JS_ToCString(ctx, argv[0]); + if (!name) { + return JS_EXCEPTION; + } + + Result rc = smGetService(&data->service, name); + JS_FreeCString(ctx, name); + + if (R_FAILED(rc)) { + return nx_throw_libnx_error(ctx, rc, "smGetService()"); + } + } + + JSValue obj = JS_NewObjectClass(ctx, nx_service_class_id); + JS_SetOpaque(obj, data); + + return obj; +} + +static JSValue nx_service_is_active(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) { + nx_service_t *data = JS_GetOpaque2(ctx, this_val, nx_service_class_id); + if (!data) + return JS_EXCEPTION; + + return JS_NewBool(ctx, serviceIsActive(&data->service)); +} + +static JSValue nx_service_is_domain(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) { + nx_service_t *data = JS_GetOpaque2(ctx, this_val, nx_service_class_id); + if (!data) + return JS_EXCEPTION; + + return JS_NewBool(ctx, serviceIsDomain(&data->service)); +} + +static JSValue nx_service_is_override(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) { + nx_service_t *data = JS_GetOpaque2(ctx, this_val, nx_service_class_id); + if (!data) + return JS_EXCEPTION; + + return JS_NewBool(ctx, serviceIsOverride(&data->service)); +} + +static JSValue nx_service_dispatch_in_out(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) { + nx_service_t *data = JS_GetOpaque2(ctx, this_val, nx_service_class_id); + if (!data) + return JS_EXCEPTION; + + u32 rid; + if (JS_ToUint32(ctx, &rid, argv[0])) { + return JS_EXCEPTION; + } + + size_t in_data_size = 0; + void *in_data = NULL; + if (JS_IsArrayBuffer(argv[1])) { + in_data = JS_GetArrayBuffer(ctx, &in_data_size, argv[1]); + } + + size_t out_data_size = 0; + void *out_data = NULL; + if (JS_IsArrayBuffer(argv[2])) { + out_data = JS_GetArrayBuffer(ctx, &out_data_size, argv[2]); + } + + SfDispatchParams disp = {0}; + if (JS_IsObject(argv[3])) { + // disp.target_session + JSValue target_session_val = + JS_GetPropertyStr(ctx, argv[3], "targetSession"); + if (JS_IsNumber(target_session_val)) { + if (JS_ToUint32(ctx, &disp.target_session, target_session_val)) { + JS_FreeValue(ctx, target_session_val); + return JS_EXCEPTION; + } + } + JS_FreeValue(ctx, target_session_val); + + // disp.context + JSValue context_val = JS_GetPropertyStr(ctx, argv[3], "context"); + if (JS_IsNumber(context_val)) { + if (JS_ToUint32(ctx, &disp.context, context_val)) { + JS_FreeValue(ctx, context_val); + return JS_EXCEPTION; + } + } + JS_FreeValue(ctx, context_val); + + // disp.buffer_attrs + JSValue buffer_attrs_val = + JS_GetPropertyStr(ctx, argv[3], "bufferAttrs"); + if (JS_IsArray(ctx, buffer_attrs_val)) { + JSValue length_val = + JS_GetPropertyStr(ctx, buffer_attrs_val, "length"); + u32 length; + if (JS_ToUint32(ctx, &length, length_val)) { + JS_FreeValue(ctx, buffer_attrs_val); + JS_FreeValue(ctx, length_val); + return JS_EXCEPTION; + } + u32 attr; + JSValue buffer_attr_val; + +#define GET_BUFFER_ATTR(INDEX) \ + buffer_attr_val = JS_GetPropertyStr(ctx, buffer_attrs_val, #INDEX); \ + if (JS_IsNumber(buffer_attr_val)) { \ + if (JS_ToUint32(ctx, &attr, buffer_attr_val)) { \ + JS_FreeValue(ctx, buffer_attr_val); \ + JS_FreeValue(ctx, buffer_attrs_val); \ + JS_FreeValue(ctx, length_val); \ + return JS_EXCEPTION; \ + } \ + disp.buffer_attrs.attr##INDEX = attr; \ + } \ + JS_FreeValue(ctx, buffer_attr_val); + + GET_BUFFER_ATTR(0); + GET_BUFFER_ATTR(1); + GET_BUFFER_ATTR(2); + GET_BUFFER_ATTR(3); + GET_BUFFER_ATTR(4); + GET_BUFFER_ATTR(5); + GET_BUFFER_ATTR(6); + GET_BUFFER_ATTR(7); + +#undef GET_BUFFER_ATTR + + JS_FreeValue(ctx, length_val); + } + JS_FreeValue(ctx, buffer_attrs_val); + + // disp.buffers + JSValue buffers_val = JS_GetPropertyStr(ctx, argv[3], "buffers"); + if (JS_IsArray(ctx, buffers_val)) { + JSValue length_val = JS_GetPropertyStr(ctx, buffers_val, "length"); + u32 length; + if (JS_ToUint32(ctx, &length, length_val)) { + JS_FreeValue(ctx, buffers_val); + JS_FreeValue(ctx, length_val); + return JS_EXCEPTION; + } + for (u32 i = 0; i < length; i++) { + JSValue v = JS_GetPropertyUint32(ctx, buffers_val, i); + if (JS_IsArrayBuffer(v)) { + disp.buffers[i].ptr = + JS_GetArrayBuffer(ctx, &disp.buffers[i].size, v); + } + JS_FreeValue(ctx, v); + } + } + JS_FreeValue(ctx, buffers_val); + + // disp.in_send_pid + JSValue in_send_pid_val = JS_GetPropertyStr(ctx, argv[3], "inSendPid"); + if (JS_IsBool(in_send_pid_val)) { + disp.in_send_pid = JS_ToBool(ctx, in_send_pid_val); + } + JS_FreeValue(ctx, in_send_pid_val); + + // disp.in_num_objects + // disp.in_objects + JSValue in_objects_val = JS_GetPropertyStr(ctx, argv[3], "inObjects"); + if (JS_IsArray(ctx, in_objects_val)) { + JSValue length_val = + JS_GetPropertyStr(ctx, in_objects_val, "length"); + if (JS_ToUint32(ctx, &disp.in_num_objects, length_val)) { + JS_FreeValue(ctx, in_objects_val); + JS_FreeValue(ctx, length_val); + return JS_EXCEPTION; + } + + for (u32 i = 0; i < disp.in_num_objects; i++) { + JSValue v = JS_GetPropertyUint32(ctx, in_objects_val, i); + nx_service_t *v_data = + JS_GetOpaque2(ctx, v, nx_service_class_id); + if (!v_data) { + JS_FreeValue(ctx, v); + JS_FreeValue(ctx, in_objects_val); + JS_FreeValue(ctx, length_val); + return JS_EXCEPTION; + } + disp.in_objects[i] = &v_data->service; + JS_FreeValue(ctx, v); + } + + JS_FreeValue(ctx, length_val); + } + JS_FreeValue(ctx, in_objects_val); + + // disp.out_num_objects + // disp.out_objects + JSValue out_objects_val = JS_GetPropertyStr(ctx, argv[3], "outObjects"); + if (JS_IsArray(ctx, out_objects_val)) { + JSValue length_val = + JS_GetPropertyStr(ctx, out_objects_val, "length"); + if (JS_ToUint32(ctx, &disp.out_num_objects, length_val)) { + JS_FreeValue(ctx, out_objects_val); + JS_FreeValue(ctx, length_val); + return JS_EXCEPTION; + } + + for (u32 i = 0; i < disp.out_num_objects; i++) { + JSValue v = JS_GetPropertyUint32(ctx, out_objects_val, i); + nx_service_t *v_data = + JS_GetOpaque2(ctx, v, nx_service_class_id); + if (!v_data) { + JS_FreeValue(ctx, v); + JS_FreeValue(ctx, out_objects_val); + JS_FreeValue(ctx, length_val); + return JS_EXCEPTION; + } + // XXX: This seems to always be 1 in libnx (hence why its not an + // array?) + disp.out_objects = &v_data->service; + JS_FreeValue(ctx, v); + } + + JS_FreeValue(ctx, length_val); + } + JS_FreeValue(ctx, out_objects_val); + } + + Result rc = serviceDispatchImpl(&data->service, rid, in_data, in_data_size, + out_data, out_data_size, disp); + + if (R_FAILED(rc)) { + return nx_throw_libnx_error(ctx, rc, "serviceDispatchOut()"); + } + + return JS_UNDEFINED; +} + +static JSValue nx_service_init(JSContext *ctx, JSValueConst this_val, int argc, + JSValueConst *argv) { + JSValue proto = JS_GetPropertyStr(ctx, argv[0], "prototype"); + NX_DEF_FUNC(proto, "isActive", nx_service_is_active, 0); + NX_DEF_FUNC(proto, "isDomain", nx_service_is_domain, 0); + NX_DEF_FUNC(proto, "isOverride", nx_service_is_override, 0); + NX_DEF_FUNC(proto, "dispatchInOut", nx_service_dispatch_in_out, 3); + JS_FreeValue(ctx, proto); + return JS_UNDEFINED; +} + +static const JSCFunctionListEntry function_list[] = { + JS_CFUNC_DEF("serviceInit", 3, nx_service_init), + JS_CFUNC_DEF("serviceNew", 3, nx_service_new), +}; + +void nx_init_service(JSContext *ctx, JSValueConst init_obj) { + JSRuntime *rt = JS_GetRuntime(ctx); + + JS_NewClassID(rt, &nx_service_class_id); + JSClassDef nx_service_class = { + "Service", + .finalizer = finalizer_service, + }; + JS_NewClass(rt, nx_service_class_id, &nx_service_class); + + JS_SetPropertyFunctionList(ctx, init_obj, function_list, + countof(function_list)); +} diff --git a/source/service.h b/source/service.h new file mode 100644 index 00000000..703093b9 --- /dev/null +++ b/source/service.h @@ -0,0 +1,4 @@ +#pragma once +#include "types.h" + +void nx_init_service(JSContext *ctx, JSValueConst init_obj);