Skip to content

Commit 385bb10

Browse files
committed
Add support for "loading.jpg" splash screen image
1 parent d0ebd8b commit 385bb10

File tree

8 files changed

+122
-19
lines changed

8 files changed

+122
-19
lines changed

.changeset/rude-emus-scream.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+
Add support for "loading.jpg" splash screen image

docs/content/runtime/metadata.mdx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,3 +85,24 @@ To set the icon of your application, add an `icon.jpg` file to the root of your
8585
> [!NOTE]
8686
> The icon should be a square image. It will be resized to 256x256 pixels. If this file is
8787
> not present, then the default nx.js icon will be used.
88+
89+
## Loading Image
90+
91+
If your application takes a while to initialize, then you may want to include a loading (also known as a "splash screen") image.
92+
This image will be rendered to the screen while your application is loading.
93+
94+
To use a loading image in your application, add a `loading.jpg` file to the root of the `romfs` directory:
95+
96+
<Files>
97+
<File name="package.json" />
98+
<Folder name="romfs" defaultOpen>
99+
<File name="loading.jpg" />
100+
</Folder>
101+
<Folder name="src" defaultOpen>
102+
<File name="main.ts" />
103+
</Folder>
104+
</Files>
105+
106+
> [!NOTE]
107+
> The loading image is expected to be a JPEG image with a width of 1280 pixels and a height of 720 pixels.
108+
> If this file is not present, then nx.js will not render a loading screen.

source/image.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,8 @@ static JSValue nx_image_get_width(JSContext *ctx, JSValueConst this_val,
291291
static JSValue nx_image_get_height(JSContext *ctx, JSValueConst this_val,
292292
int argc, JSValueConst *argv) {
293293
nx_image_t *image = nx_get_image(ctx, this_val);
294+
if (!image)
295+
return JS_EXCEPTION;
294296
return JS_NewUint32(ctx, image->height);
295297
}
296298

source/image.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,7 @@ typedef struct {
1818

1919
nx_image_t *nx_get_image(JSContext *ctx, JSValueConst obj);
2020

21+
int decode_jpeg(uint8_t *jpegBuf, size_t jpegSize, uint8_t **output, int *width,
22+
int *height);
23+
2124
void nx_init_image(JSContext *ctx, JSValueConst init_obj);

source/main.c

Lines changed: 64 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#include "tls.h"
3434
#include "types.h"
3535
#include "url.h"
36+
#include "util.h"
3637
#include "wasm.h"
3738
#include "window.h"
3839

@@ -50,6 +51,13 @@ static NWindow *win = NULL;
5051
static Framebuffer *framebuffer = NULL;
5152
static uint8_t *js_framebuffer = NULL;
5253

54+
void nx_console_init(nx_context_t *nx_ctx) {
55+
nx_ctx->rendering_mode = NX_RENDERING_MODE_CONSOLE;
56+
if (print_console == NULL) {
57+
print_console = consoleInit(NULL);
58+
}
59+
}
60+
5361
void nx_console_exit() {
5462
if (print_console != NULL) {
5563
consoleExit(print_console);
@@ -166,10 +174,7 @@ static JSValue js_print(JSContext *ctx, JSValueConst this_val, int argc,
166174
nx_context_t *nx_ctx = JS_GetContextOpaque(ctx);
167175
if (nx_ctx->rendering_mode != NX_RENDERING_MODE_CONSOLE) {
168176
nx_framebuffer_exit();
169-
if (print_console == NULL) {
170-
print_console = consoleInit(NULL);
171-
}
172-
nx_ctx->rendering_mode = NX_RENDERING_MODE_CONSOLE;
177+
nx_console_init(nx_ctx);
173178
}
174179
const char *str = JS_ToCString(ctx, argv[0]);
175180
printf("%s", str);
@@ -514,6 +519,47 @@ void nx_process_pending_jobs(JSContext *ctx, nx_context_t *nx_ctx,
514519
}
515520
}
516521

522+
void nx_render_loading_image(nx_context_t *nx_ctx, const char *nro_path) {
523+
// Check if there is a `loading.jpg` file on the RomFS
524+
// and render that to the screen if present.
525+
size_t loading_image_size;
526+
const char *loading_image_path = "romfs:/loading.jpg";
527+
uint8_t *loading_image = (uint8_t *)read_file(loading_image_path, &loading_image_size);
528+
if (!loading_image && nro_path) {
529+
// RomFS loading_image image not found.
530+
// Try to load from SD card, relative to the path of the NRO.
531+
char *temp_loading_image_path = strdup(nro_path);
532+
if (temp_loading_image_path) {
533+
replace_file_extension(temp_loading_image_path, ".jpg");
534+
loading_image = (uint8_t *)read_file(temp_loading_image_path, &loading_image_size);
535+
free(temp_loading_image_path);
536+
}
537+
}
538+
if (loading_image != NULL) {
539+
win = nwindowGetDefault();
540+
int width = 1280;
541+
int height = 720;
542+
js_framebuffer = malloc(width * height * 4);
543+
framebuffer = malloc(sizeof(Framebuffer));
544+
framebufferCreate(framebuffer, win, width, height, PIXEL_FORMAT_BGRA_8888,
545+
2);
546+
framebufferMakeLinear(framebuffer);
547+
548+
decode_jpeg(loading_image, loading_image_size, &js_framebuffer, &width, &height);
549+
// TODO: ensure decompression was successful
550+
// TODO: ensure width and height are correct
551+
552+
u32 stride;
553+
u8 *framebuf = (u8 *)framebufferBegin(framebuffer, &stride);
554+
memcpy(framebuf, js_framebuffer, 1280 * 720 * 4);
555+
framebufferEnd(framebuffer);
556+
557+
free(js_framebuffer);
558+
free(loading_image);
559+
js_framebuffer = NULL;
560+
}
561+
}
562+
517563
static SocketInitConfig const s_socketInitConfig = {
518564
.tcp_tx_buf_size = 1 * 1024 * 1024,
519565
.tcp_rx_buf_size = 1 * 1024 * 1024,
@@ -533,14 +579,17 @@ static SocketInitConfig const s_socketInitConfig = {
533579
int main(int argc, char *argv[]) {
534580
Result rc;
535581

536-
print_console = consoleInit(NULL);
582+
nx_context_t *nx_ctx = malloc(sizeof(nx_context_t));
583+
memset(nx_ctx, 0, sizeof(nx_context_t));
537584

538-
rc = socketInitialize(&s_socketInitConfig);
585+
rc = romfsInit();
539586
if (R_FAILED(rc)) {
540587
diagAbortWithResult(rc);
541588
}
542589

543-
rc = romfsInit();
590+
nx_render_loading_image(nx_ctx, argc > 0 ? argv[0] : NULL);
591+
592+
rc = socketInitialize(&s_socketInitConfig);
544593
if (R_FAILED(rc)) {
545594
diagAbortWithResult(rc);
546595
}
@@ -555,9 +604,7 @@ int main(int argc, char *argv[]) {
555604
JSRuntime *rt = JS_NewRuntime();
556605
JSContext *ctx = JS_NewContext(rt);
557606

558-
nx_context_t *nx_ctx = malloc(sizeof(nx_context_t));
559-
memset(nx_ctx, 0, sizeof(nx_context_t));
560-
nx_ctx->rendering_mode = NX_RENDERING_MODE_CONSOLE;
607+
nx_ctx->rendering_mode = NX_RENDERING_MODE_INIT;
561608
nx_ctx->thpool = thpool_init(4);
562609
nx_ctx->frame_handler = JS_UNDEFINED;
563610
nx_ctx->exit_handler = JS_UNDEFINED;
@@ -595,20 +642,18 @@ int main(int argc, char *argv[]) {
595642
}
596643

597644
if (user_code == NULL && errno == ENOENT && argc > 0) {
598-
// If no `main.js`, then try the `.js file with the
599-
// matching name as the `.nro` file on the SD card
645+
// If no `main.js`, then try the `.js` file with the matching name
646+
// as the `.nro` file on the SD card
600647
user_path_needs_free = true;
601648
user_code_path = strdup(argv[0]);
602-
size_t js_path_len = strlen(user_code_path);
603-
char *dot_nro = strstr(user_code_path, ".nro");
604-
if (dot_nro != NULL && (dot_nro - user_code_path) == js_path_len - 4) {
605-
strcpy(dot_nro, ".js");
649+
if (user_code_path) {
650+
replace_file_extension(user_code_path, ".js");
651+
user_code = (char *)read_file(user_code_path, &user_code_size);
606652
}
607-
608-
user_code = (char *)read_file(user_code_path, &user_code_size);
609653
}
610654

611655
if (user_code == NULL) {
656+
nx_console_init(nx_ctx);
612657
printf("%s: %s\n", strerror(errno), user_code_path);
613658
if (user_path_needs_free) {
614659
free(user_code_path);
@@ -743,6 +788,7 @@ int main(int argc, char *argv[]) {
743788
}
744789
runtime_init_result = JS_EvalFunction(ctx, runtime_init_func);
745790
if (JS_IsException(runtime_init_result)) {
791+
nx_console_init(nx_ctx);
746792
printf("Runtime initialization failed\n");
747793
print_js_error(ctx);
748794
nx_ctx->had_error = 1;

source/types.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ struct nx_work_s {
7070
void *data;
7171
};
7272

73-
enum nx_rendering_mode { NX_RENDERING_MODE_CONSOLE, NX_RENDERING_MODE_CANVAS };
73+
enum nx_rendering_mode { NX_RENDERING_MODE_INIT, NX_RENDERING_MODE_CONSOLE, NX_RENDERING_MODE_CANVAS };
7474

7575
typedef struct nx_context_s {
7676
int had_error;

source/util.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,27 @@ u8 *NX_GetBufferSource(JSContext *ctx, size_t *size, JSValueConst obj) {
2727
*size = byte_length;
2828
return ptr + byte_offset;
2929
}
30+
31+
/**
32+
* @brief Replaces the file extension of a given path string.
33+
*
34+
* The modification is done in-place. The caller must ensure that the buffer
35+
* for `path` is large enough to hold the `new_extension`.
36+
*
37+
* This function will not add an extension if one does not already exist.
38+
* It also correctly handles filenames starting with a dot (e.g. ".profile")
39+
* and paths containing dots in directory names (e.g. "my.app/program").
40+
*
41+
* @param path The file path string to modify.
42+
* @param new_extension The new extension, which should include the leading dot.
43+
*/
44+
void replace_file_extension(char *path, const char *new_extension) {
45+
char *dot = strrchr(path, '.');
46+
char *slash = strrchr(path, '/');
47+
48+
// We only replace the extension if a dot is found,
49+
// and it appears after the last path separator.
50+
if (dot != NULL && (slash == NULL || dot > slash)) {
51+
strcpy(dot, new_extension);
52+
}
53+
}

source/util.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@
22
#include "types.h"
33

44
u8 *NX_GetBufferSource(JSContext *ctx, size_t *size, JSValue obj);
5+
6+
void replace_file_extension(char *path, const char *new_extension);

0 commit comments

Comments
 (0)