diff --git a/src/3ds/runtime/process.c b/src/3ds/runtime/process.c index 7bc67708..f8e6e826 100644 --- a/src/3ds/runtime/process.c +++ b/src/3ds/runtime/process.c @@ -266,6 +266,17 @@ static bool mount(SnxCtrProcess* p, const char* directory, const SnxCtrCia* cia) const unsigned char* image = snx_ctr_cia_romfs(cia, &size); /* a CIA without a RomFS reads as an empty one, as its extraction's empty romfs/ folder does */ ok = ok && snx_ctr_romfs_open_image(image, size, snx_ctr_cia_mtime(cia), &p->romfs); + /* the ExeFS files SelfNCCH serves (services/fs.c), in the tree as an extraction's exefs/ has them: copies of + the CIA's sections, read-only and never on the host */ + static const char* const sections[] = {"icon", "logo", "banner"}; + pm_lock(&tree->lock); + for (unsigned i = 0; i < 3 && ok; ++i) { + const unsigned char* data = snx_ctr_cia_exefs(cia, sections[i], &size); + char path[16]; + snprintf(path, sizeof path, "exefs/%s", sections[i]); + if (data) ok = !fs_tree_add_virtual(tree, path, data, size, NULL); + } + pm_unlock(&tree->lock); } else if (!ok || !snx_ctr_romfs_open(tree, &p->romfs)) ok = false; FsNode* sd = NULL; diff --git a/src/3ds/services/file.c b/src/3ds/services/file.c index 079b0676..c0d2e522 100644 --- a/src/3ds/services/file.c +++ b/src/3ds/services/file.c @@ -150,7 +150,7 @@ static void open_link(SnxCtrIpc* c, CtrObject* object) { if (!snx_ctr_ipc_expect(c, 0, 0)) return; SnxCtrProcess* p = c->process; FILE* host = NULL; - if (object->data.file.kind == CTR_FILE_HOST) { + if (object->data.file.kind == CTR_FILE_HOST && object->data.file.host) { pm_lock(&p->tree->lock); host = fs_tree_open(p->tree, object->data.file.node, object->data.file.writable ? "r+b" : "rb"); pm_unlock(&p->tree->lock); @@ -232,6 +232,10 @@ void snx_ctr_file_dispatch(SnxCtrIpc* c) { snx_ctr_ipc_error_reply(c, 0xd8e01beeu); /* the RomFS view is immutable */ return; } + if (command == 0x803 && !object->data.file.writable) { /* opened without the write flag (Azahar's DiskFile) */ + snx_ctr_ipc_error_reply(c, 0xc92044e6u); /* InvalidOpenFlags */ + return; + } if (size > 16u * 1024 * 1024) { c->state = SNX_CTR_IPC_UNSUPPORTED; return; @@ -277,8 +281,12 @@ void snx_ctr_file_dispatch(SnxCtrIpc* c) { ok = !n || snx_ctr_romfs_read(p->romfs, offset, data, n); } else { FILE* host = object->data.file.host; + FsNode* node = object->data.file.node; pm_lock(&p->tree->lock); - ok = !n || (!fseek(host, (long)offset, SEEK_SET) && fread(data, 1, n, host) == n); + if (!host) /* a section the tree holds in memory (a CIA's ExeFS file) */ + ok = !n || (node->virtual_data && (memcpy(data, node->virtual_data + offset, n), true)); + else + ok = !n || (!fseek(host, (long)offset, SEEK_SET) && fread(data, 1, n, host) == n); pm_unlock(&p->tree->lock); } if (ok && n) ok = snx_ctr_write_ram(p->space, address, data, n) == SNX_CTR_MEM_OK; diff --git a/src/3ds/services/fs.c b/src/3ds/services/fs.c index 684d534c..7a47773a 100644 --- a/src/3ds/services/fs.c +++ b/src/3ds/services/fs.c @@ -162,19 +162,44 @@ static void open_host(SnxCtrIpc* c, FsNode* root, uint32_t flags, uint32_t fixed } if (!snx_ctr_fs_file_object(c, CTR_FILE_HOST, !!(flags & 2), fixed, host, node)) fclose(host); } -/* SelfNCCH, empty archive path, binary selector for this process' RomFS: its first word the type, 0 for the RomFS, - whose ExeFS file name after it means nothing (Azahar's SelfNCCHFilePath) */ -static void open_romfs(SnxCtrIpc* c, const unsigned char path[12]) { +/* SelfNCCH's 12-byte file path: its type, then an ExeFS file's name (Azahar's SelfNCCHFilePath). 0 is the RomFS; + 2 an ExeFS file, of which Azahar serves the icon, logo and banner (exefs/ in the tree: the extraction's files, or + the CIA's sections, process.c); the code (1) is not for this archive; 5, an update's RomFS, finds none, as no update + is loaded */ +static void open_self(SnxCtrIpc* c, const unsigned char path[12]) { SnxCtrProcess* p = c->process; - if (path[0] || path[1] || path[2] || path[3]) { - c->state = SNX_CTR_IPC_UNSUPPORTED; + uint32_t type = (uint32_t)path[0] | (uint32_t)path[1] << 8 | (uint32_t)path[2] << 16 | (uint32_t)path[3] << 24; + if (type == 0 || type == 5) { + if (type == 5 || !p->romfs) { + snx_ctr_ipc_error_reply(c, 0xc8804464u); /* RomFSNotFound */ + return; + } + snx_ctr_fs_file_object(c, CTR_FILE_VIEW, 0, 0, NULL, NULL); return; } - if (!p->romfs) { - snx_ctr_ipc_error_reply(c, 0xc8804464u); + if (type != 2) { + snx_ctr_ipc_error_reply(c, type == 1 ? 0xd9004676u : 0xe0e046beu); /* CommandNotAllowed, InvalidPath */ + return; + } + char name[9] = {0}, relative[16]; + memcpy(name, path + 4, 8); + if (strcmp(name, "icon") && strcmp(name, "logo") && strcmp(name, "banner")) { + snx_ctr_ipc_error_reply(c, 0xe0e046beu); + return; + } + snprintf(relative, sizeof relative, "exefs/%s", name); + FsNode* node = NULL; + FILE* host = NULL; + pm_lock(&p->tree->lock); + FsTreeResult rc = fs_tree_lookup(p->tree, NULL, relative, &node); + if (!rc && node->is_dir) rc = FS_TREE_NOTFOUND; + if (!rc && !node->is_virtual && !(host = fs_tree_open(p->tree, node, "rb"))) rc = FS_TREE_IO; + pm_unlock(&p->tree->lock); + if (rc) { + snx_ctr_ipc_error_reply(c, rc == FS_TREE_NOTFOUND ? 0xc8804637u : tree_result(rc)); /* ExeFSSectionNotFound */ return; } - snx_ctr_fs_file_object(c, CTR_FILE_VIEW, 0, 0, NULL, NULL); + if (!snx_ctr_fs_file_object(c, CTR_FILE_HOST, 0, 0, host, node) && host) fclose(host); } /* The NCCH archive (0x2345678A): a title's content by its ID, its archive path the title ID, a media type and a word, its file path the open type, the content index, the section (0 the RomFS, 1 the code, 2 an ExeFS file) and @@ -249,7 +274,7 @@ static void open_direct(SnxCtrIpc* c) { c->transport_result = 0xe0e01bf5u; return; } - open_romfs(c, path); + open_self(c, path); return; } if (c->in[2] == 0x2345678au) { @@ -307,7 +332,7 @@ static void open_file(SnxCtrIpc* c) { c->state = SNX_CTR_IPC_UNSUPPORTED; return; } - open_romfs(c, raw); + open_self(c, raw); return; } /* An ExtSaveData's files are made by CreateFile at their size, and opened read-write whatever the flags ask diff --git a/tests/3ds_fs.c b/tests/3ds_fs.c index 530dfa98..8876e165 100644 --- a/tests/3ds_fs.c +++ b/tests/3ds_fs.c @@ -458,6 +458,47 @@ static void ncch(SnxCtrProcess* p, uint32_t fs, bool absent) { request(p, file, close, 1, SNX_CTR_SVC_RETURN, 0); assert(!get(p, 1) && !snx_ctr_handle_close(p, file)); } +/* SelfNCCH's other file types: an ExeFS file (the extraction's exefs/icon), one it lacks, an unknown name, the code + and an update's RomFS */ +static void self_exefs(SnxCtrProcess* p, uint32_t fs) { + uint32_t a[] = {0x80c00c2, 3, 1, 1, 0x4002, 0x300020}; + request(p, fs, a, 6, SNX_CTR_SVC_RETURN, 0); + assert(!get(p, 1)); + uint32_t lo = get(p, 2), hi = get(p, 3); + static const struct { + uint32_t type; + char name[9]; + uint32_t result; + } cases[] = {{2, "banner", 0xc8804637}, {2, ".code", 0xe0e046be}, {1, "", 0xd9004676}, {5, "", 0xc8804464}}; + unsigned char path[12] = {2, 0, 0, 0, 'i', 'c', 'o', 'n'}; + uint32_t open[] = {0x80201c2, 0, lo, hi, 2, 12, 1, 0, 0x30002, 0x300000}; + for (unsigned i = 0; i < sizeof cases / sizeof *cases; ++i) { + unsigned char other[12] = {(unsigned char)cases[i].type}; + memcpy(other + 4, cases[i].name, 8); + assert(snx_ctr_write_ram(p->space, 0x300000, other, sizeof other) == SNX_CTR_MEM_OK); + request(p, fs, open, 10, SNX_CTR_SVC_RETURN, 0); + assert(get(p, 1) == cases[i].result); + } + assert(snx_ctr_write_ram(p->space, 0x300000, path, sizeof path) == SNX_CTR_MEM_OK); + request(p, fs, open, 10, SNX_CTR_SVC_RETURN, 0); + assert(get(p, 0) == 0x8020042 && !get(p, 1)); + uint32_t file = get(p, 3); + uint32_t read[] = {0x80200c2, 0, 0, 16, (16u << 4) | 12, 0x300100}; + request(p, file, read, 6, SNX_CTR_SVC_RETURN, 0); + assert(!get(p, 1) && get(p, 2) == 9); + unsigned char bytes[9]; + assert(snx_ctr_read_ram(p->space, 0x300100, bytes, 9) == SNX_CTR_MEM_OK && !memcmp(bytes, "SMDH-icon", 9)); + uint32_t write[] = {0x8030102, 0, 0, 4, 0, (4u << 4) | 10, 0x300100}; + request(p, file, write, 7, SNX_CTR_SVC_RETURN, 0); + assert(get(p, 1) == 0xc92044e6); /* read-only */ + uint32_t finish[] = {0x8080000}, close[] = {0x80e0080, lo, hi}; + request(p, file, finish, 1, SNX_CTR_SVC_RETURN, 0); + assert(!get(p, 1) && !snx_ctr_handle_close(p, file)); + request(p, fs, close, 3, SNX_CTR_SVC_RETURN, 0); + assert(!get(p, 1)); + static const unsigned char romfs_path[12] = {0}; /* the RomFS's path again, which later checks open */ + assert(snx_ctr_write_ram(p->space, 0x300000, romfs_path, sizeof romfs_path) == SNX_CTR_MEM_OK); +} int main(int argc, char** argv) { assert(argc == 4); g_settings.ctr_answer_unknown_commands = 0; /* an unknown command stops, as the tests expect */ @@ -494,6 +535,7 @@ int main(int argc, char** argv) { sd_area(p, fs, argv[1]); extdata(p, fs, argv[1]); ncch(p, fs, absent); + if (!absent) self_exefs(p, fs); if (absent) { request(p, fs, open, 13, SNX_CTR_SVC_RETURN, 0); assert(get(p, 1) == 0xc8804464 && objects(p) == before); diff --git a/tests/3ds_fs.py b/tests/3ds_fs.py index a5edfacd..a0758c20 100755 --- a/tests/3ds_fs.py +++ b/tests/3ds_fs.py @@ -70,6 +70,8 @@ def main(): path = root / 'romfs' / name path.parent.mkdir(parents=True, exist_ok=True) path.write_bytes(contents) + (root / 'exefs').mkdir() + (root / 'exefs' / 'icon').write_bytes(b'SMDH-icon') # what SelfNCCH's ExeFS type serves for mode in ['shared', *(['fallback'] if os.name == 'posix' else [])]: output = Path(temp) / f'{mode}.bin' subprocess.run([probe, str(root), str(output), mode], check=True)