Skip to content

Commit 41fcbbb

Browse files
committed
Support string / URL initialization parameter to new Switch.Application()
1 parent e4c39c4 commit 41fcbbb

File tree

4 files changed

+79
-8
lines changed

4 files changed

+79
-8
lines changed

.changeset/slimy-goats-confess.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@nx.js/runtime": patch
3+
---
4+
5+
Support string / URL initialization parameter to `new Switch.Application()`

packages/runtime/src/$.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ export interface Init {
220220
// ns.c
221221
nsInitialize(): () => void;
222222
nsAppInit(c: ClassOf<Application>): void;
223-
nsAppNew(id: BigInt | ArrayBuffer | null): Application;
223+
nsAppNew(id: string | bigint | ArrayBuffer | null): Application;
224224
nsAppNext(index: number): bigint | null;
225225

226226
// software-keyboard.c

packages/runtime/src/switch/ns.ts

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
import { $ } from '../$';
2+
import { URL } from '../polyfills/url';
23
import { SaveData, SaveDataCreationInfoWithNacp } from './savedata';
34
import { inspect } from './inspect';
4-
import { readFileSync } from '../fs';
5-
import { FunctionPrototypeWithIteratorHelpers, proto, stub } from '../utils';
5+
import {
6+
FunctionPrototypeWithIteratorHelpers,
7+
pathToString,
8+
proto,
9+
stub,
10+
} from '../utils';
611
import type { Profile } from './profile';
712

813
let init = false;
@@ -75,6 +80,20 @@ export class Application {
7580
* @param id The ID of the installed application.
7681
*/
7782
constructor(id: bigint);
83+
/**
84+
* Creates an `Application` instance from the string path
85+
* containing the contents of a `.nro` homebrew application.
86+
*
87+
* @example
88+
*
89+
* ```typescript
90+
* const app = new Switch.Application('sdmc:/hbmenu.nro');
91+
* console.log(app.name);
92+
* ```
93+
*
94+
* @param path The path of the `.nro` file.
95+
*/
96+
constructor(path: string | URL);
7897
/**
7998
* Creates an `Application` instance from an `ArrayBuffer`
8099
* containing the contents of a `.nro` homebrew application.
@@ -90,9 +109,12 @@ export class Application {
90109
* @param nro The contents of the `.nro` file.
91110
*/
92111
constructor(nro: ArrayBuffer);
93-
constructor(v: bigint | ArrayBuffer) {
112+
constructor(v: string | bigint | ArrayBuffer | URL) {
94113
_init();
95-
return proto($.nsAppNew(v), Application);
114+
return proto(
115+
$.nsAppNew(v instanceof URL ? pathToString(v) : v),
116+
Application,
117+
);
96118
}
97119

98120
/**
@@ -167,11 +189,11 @@ export class Application {
167189
static get self(): Application {
168190
if (!self) {
169191
_init();
170-
let nro: ArrayBuffer | null = null;
192+
let p: string | null = null;
171193
if ($.argv.length) {
172-
nro = readFileSync($.argv[0]);
194+
p = $.argv[0];
173195
}
174-
self = proto($.nsAppNew(nro), Application);
196+
self = proto($.nsAppNew(p), Application);
175197
}
176198
return self;
177199
}

source/ns.c

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "ns.h"
22
#include "applet.h"
33
#include "error.h"
4+
#include <errno.h>
45

56
static JSClassID nx_app_class_id;
67

@@ -51,6 +52,49 @@ static JSValue nx_ns_app_new(JSContext *ctx, JSValueConst this_val, int argc,
5152
if (JS_ToBigUint64(ctx, &application_id, argv[0])) {
5253
return JS_EXCEPTION;
5354
}
55+
} else if (JS_IsString(argv[0])) {
56+
// Get app ID from opening the path specified at the string value, which
57+
// contains an NRO file.
58+
const char *path = JS_ToCString(ctx, argv[0]);
59+
if (!path) {
60+
return JS_EXCEPTION;
61+
}
62+
FILE *file = fopen(path, "rb");
63+
JS_FreeCString(ctx, path);
64+
65+
if (file == NULL) {
66+
return nx_throw_errno_error(ctx, errno, "fopen()");
67+
}
68+
69+
// Seek to offset 0x18 and read a u32 from the NRO file, which
70+
// contains the offset of the asset header.
71+
u32 asset_header_offset;
72+
fseek(file, 0x18, SEEK_SET);
73+
fread(&asset_header_offset, sizeof(u32), 1, file);
74+
75+
// Seek the file to the asset header offset and allocate buffer
76+
uint8_t *asset_header = malloc(0x28); // Size needed for header info
77+
fseek(file, asset_header_offset, SEEK_SET);
78+
fread(asset_header, 0x28, 1, file);
79+
80+
u32 icon_section_offset = *(u32 *)(asset_header + 0x8);
81+
u32 icon_section_size = *(u32 *)(asset_header + 0x10);
82+
u32 nacp_section_offset = *(u32 *)(asset_header + 0x18);
83+
u32 nacp_section_size = *(u32 *)(asset_header + 0x20);
84+
85+
// Seek to the icon section offset and read the icon data
86+
fseek(file, asset_header_offset + icon_section_offset, SEEK_SET);
87+
fread(&data->data.icon, icon_section_size, 1, file);
88+
89+
// Seek to the nacp section offset and read the nacp data
90+
fseek(file, asset_header_offset + nacp_section_offset, SEEK_SET);
91+
fread(&data->data.nacp, nacp_section_size, 1, file);
92+
93+
free(asset_header);
94+
fclose(file);
95+
96+
data->icon_size = icon_section_size;
97+
loaded = true;
5498
} else {
5599
// Get app ID from parsing the array buffer which contains an NRO file.
56100
// This is the case when running the NRO through hbmenu or a forwarder.

0 commit comments

Comments
 (0)