diff --git a/.changeset/rich-seahorses-camp.md b/.changeset/rich-seahorses-camp.md new file mode 100644 index 00000000..768e5a8d --- /dev/null +++ b/.changeset/rich-seahorses-camp.md @@ -0,0 +1,5 @@ +--- +"nxjs-runtime": patch +--- + +Add `bigFile` option to `Switch.file()` diff --git a/docs/content/runtime/concepts/file-system.mdx b/docs/content/runtime/concepts/file-system.mdx index 8b6ebac0..4adf75d1 100644 --- a/docs/content/runtime/concepts/file-system.mdx +++ b/docs/content/runtime/concepts/file-system.mdx @@ -64,3 +64,26 @@ await writer.close(); > If the file path provided to `Switch.file()` does not yet exist, then a new file will be created > (as well as any necessary parent directories). + +### Big files + +If you need to write files larger than 4gb, can create a "big file" by passing +the `bigFile: true` option to `Switch.file()`: + +```typescript +const file = Switch.file('sdmc:/test.txt', { + bigFile: true +}); + +const writer = file.writable.getWriter(); +// … write more than 4gb worth of data to the file … +await writer.close(); +``` + +> [!NOTE] +> This works even on FAT32 partitions, which normally have a 4gb limit. +> How "big files" work under the hood is that a directory is created with the +> "archive" bit set, which causes the directory to be treated as a file +> containing the directory's concatenated contents. This is all handled +> transparently by the operating system, so your application code can treat +> it as if it were a normal file. diff --git a/packages/runtime/src/$.ts b/packages/runtime/src/$.ts index 911d09d9..c7800c7d 100644 --- a/packages/runtime/src/$.ts +++ b/packages/runtime/src/$.ts @@ -146,6 +146,7 @@ export interface Init { fopen(path: string, mode: string): Promise; fread(f: FileHandle, buf: ArrayBuffer): Promise; fwrite(f: FileHandle, data: ArrayBuffer): Promise; + fsCreateBigFile(path: string): void; mkdirSync(path: string, mode: number): number; readDirSync(path: string): string[] | null; readFile(path: string): Promise; diff --git a/packages/runtime/src/fs.ts b/packages/runtime/src/fs.ts index fb364826..5b4e4930 100644 --- a/packages/runtime/src/fs.ts +++ b/packages/runtime/src/fs.ts @@ -124,8 +124,19 @@ export function stat(path: PathLike) { return $.stat(pathToString(path)); } +/** + * Options object for the {@link File | `Switch.file()`} function. + */ export interface FsFileOptions { type?: string; + + /** + * Create a "big file", which is a directory with the "archive" bit set. + * This will cause HOS to treat the directory as if it were a file + * containing the directory's concatenated contents, allowing you to + * write file contents larger than 4GB. + */ + bigFile?: boolean; } /** @@ -139,15 +150,19 @@ export function file(path: PathLike, opts?: FsFileOptions) { export class FsFile extends File { constructor(path: PathLike, opts?: FsFileOptions) { + const { bigFile, ...rest } = opts ?? {}; super([], pathToString(path), { type: 'text/plain;charset=utf-8', - ...opts, + ...rest, }); Object.defineProperty(this, 'lastModified', { get(): number { return (statSync(this.name)?.mtime ?? 0) * 1000; }, }); + if (bigFile) { + $.fsCreateBigFile(this.name); + } } get size() { diff --git a/source/fs.c b/source/fs.c index 39df1faf..9e17ce0e 100644 --- a/source/fs.c +++ b/source/fs.c @@ -1,5 +1,6 @@ #include "fs.h" #include "async.h" +#include "error.h" #include #include #include @@ -685,11 +686,42 @@ JSValue nx_remove_sync(JSContext *ctx, JSValueConst this_val, int argc, return JS_UNDEFINED; } +JSValue nx_fs_create_big_file(JSContext *ctx, JSValueConst this_val, int argc, + JSValueConst *argv) { + const char *path = JS_ToCString(ctx, argv[0]); + if (!path) + return JS_EXCEPTION; + + char *protocol = js_strdup(ctx, path); + char *end_of_protocol = strstr(protocol, ":/"); + end_of_protocol[0] = '\0'; + char *name = end_of_protocol + 1; + + FsFileSystem *fs = fsdevGetDeviceFileSystem(protocol); + if (!fs) { + js_free(ctx, protocol); + JS_FreeCString(ctx, path); + return JS_ThrowTypeError(ctx, "Invalid protocol: %s", protocol); + } + + Result rc = fsFsCreateFile(fs, name, 0, FsCreateOption_BigFile); + if (R_FAILED(rc)) { + js_free(ctx, protocol); + JS_FreeCString(ctx, path); + return nx_throw_libnx_error(ctx, rc, "fsFsCreateFile()"); + } + + js_free(ctx, protocol); + JS_FreeCString(ctx, path); + return JS_UNDEFINED; +} + static const JSCFunctionListEntry function_list[] = { JS_CFUNC_DEF("fclose", 1, nx_fclose), JS_CFUNC_DEF("fopen", 1, nx_fopen), JS_CFUNC_DEF("fread", 1, nx_fread), JS_CFUNC_DEF("fwrite", 1, nx_fwrite), + JS_CFUNC_DEF("fsCreateBigFile", 1, nx_fs_create_big_file), JS_CFUNC_DEF("mkdirSync", 1, nx_mkdir_sync), JS_CFUNC_DEF("readDirSync", 1, nx_readdir_sync), JS_CFUNC_DEF("readFile", 2, nx_read_file),