From 20bb627e903fc16401bcda72a2d9ac31117f64b0 Mon Sep 17 00:00:00 2001 From: TotalJustice <47043333+ITotalJustice@users.noreply.github.com> Date: Tue, 10 Dec 2024 02:52:29 +0000 Subject: [PATCH] Nx add gc mount, Nx save zip upload, Nx romfs mount, Nx bis storage mount --- CMakeLists.txt | 3 + src/ftpsrv.c | 91 ++-- src/ftpsrv_vfs.h | 5 +- src/platform/nx/vfs/vfs_nx_fs.c | 30 +- src/platform/nx/vfs/vfs_nx_fs.h | 2 - src/platform/nx/vfs/vfs_nx_gc.c | 442 +++++++++++++++ src/platform/nx/vfs/vfs_nx_gc.h | 59 ++ src/platform/nx/vfs/vfs_nx_hdd.c | 89 +-- src/platform/nx/vfs/vfs_nx_hdd.h | 8 +- src/platform/nx/vfs/vfs_nx_root.c | 12 +- src/platform/nx/vfs/vfs_nx_save.c | 786 ++++++++++++++++++++++----- src/platform/nx/vfs/vfs_nx_save.h | 52 +- src/platform/nx/vfs/vfs_nx_stdio.c | 252 +++++++++ src/platform/nx/vfs/vfs_nx_stdio.h | 50 ++ src/platform/nx/vfs/vfs_nx_storage.c | 330 +++++++++++ src/platform/nx/vfs/vfs_nx_storage.h | 48 ++ src/platform/nx/vfs_nx.c | 213 +++++--- src/platform/nx/vfs_nx.h | 26 +- src/platform/stdio/vfs_stdio.c | 10 +- src/platform/unistd/vfs_unistd.c | 10 +- 20 files changed, 2113 insertions(+), 405 deletions(-) create mode 100644 src/platform/nx/vfs/vfs_nx_gc.c create mode 100644 src/platform/nx/vfs/vfs_nx_gc.h create mode 100644 src/platform/nx/vfs/vfs_nx_stdio.c create mode 100644 src/platform/nx/vfs/vfs_nx_stdio.h create mode 100644 src/platform/nx/vfs/vfs_nx_storage.c create mode 100644 src/platform/nx/vfs/vfs_nx_storage.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 738f844..4100e8a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -253,12 +253,15 @@ if (NINTENDO_SWITCH) src/platform/nx/vfs/vfs_nx_root.c src/platform/nx/vfs/vfs_nx_fs.c src/platform/nx/vfs/vfs_nx_save.c + src/platform/nx/vfs/vfs_nx_storage.c + src/platform/nx/vfs/vfs_nx_gc.c src/platform/nx/utils.c src/log/log.c ) add_executable(ftpexe src/platform/nx/main.c + src/platform/nx/vfs/vfs_nx_stdio.c src/platform/nx/vfs/vfs_nx_hdd.c ${NX_SRC} ) diff --git a/src/ftpsrv.c b/src/ftpsrv.c index eb06a56..eb0cb3a 100644 --- a/src/ftpsrv.c +++ b/src/ftpsrv.c @@ -605,29 +605,27 @@ static enum FTP_FILE_TRANSFER_STATE ftp_file_data_transfer_progress(struct FtpSe } else { n = socket_send(session->data_sock, g_ftp.data_buf, n, 0); if (n < 0) { - if (errno != EWOULDBLOCK && errno != EAGAIN) { - return FTP_FILE_TRANSFER_STATE_ERROR; - } else { - ftp_vfs_seek(&transfer->file_vfs, transfer->offset); + if (errno == EWOULDBLOCK || errno == EAGAIN) { + ftp_vfs_seek(&transfer->file_vfs, g_ftp.data_buf, 0, transfer->offset); return FTP_FILE_TRANSFER_STATE_BLOCKING; + } else { + return FTP_FILE_TRANSFER_STATE_ERROR; } } else { transfer->offset += (size_t)n; if (n != read) { - ftp_vfs_seek(&transfer->file_vfs, transfer->offset); + ftp_vfs_seek(&transfer->file_vfs, g_ftp.data_buf, n, transfer->offset); return FTP_FILE_TRANSFER_STATE_BLOCKING; - } else if (read < sizeof(g_ftp.data_buf)) { - return FTP_FILE_TRANSFER_STATE_FINISHED; } } } } else { n = socket_recv(session->data_sock, g_ftp.data_buf, sizeof(g_ftp.data_buf), 0); if (n < 0) { - if (errno != EWOULDBLOCK && errno != EAGAIN) { - return FTP_FILE_TRANSFER_STATE_ERROR; - } else { + if (errno == EWOULDBLOCK || errno == EAGAIN) { return FTP_FILE_TRANSFER_STATE_BLOCKING; + } else { + return FTP_FILE_TRANSFER_STATE_ERROR; } } else if (n == 0) { return FTP_FILE_TRANSFER_STATE_FINISHED; @@ -912,8 +910,13 @@ static void ftp_cmd_MODE(struct FtpSession* session, const char* data) { } } -// RETR | 125, 150, (110), 226, 250, 425, 426, 451, 450, 550, 500, 501, 421, 530 -static void ftp_cmd_RETR(struct FtpSession* session, const char* data) { +static void ftp_open_file(struct FtpSession* session, const char* data, enum FtpVfsOpenMode open_mode, enum FTP_TRANSFER_MODE transfer_mode, int error_code) { + session->transfer.offset = 0; + if (session->server_marker > 0) { + session->transfer.offset = session->server_marker; + session->server_marker = 0; + } + struct Pathname pathname = {0}; int rc = snprintf(pathname.s, sizeof(pathname), "%s", data); @@ -923,70 +926,40 @@ static void ftp_cmd_RETR(struct FtpSession* session, const char* data) { struct Pathname fullpath = {0}; rc = build_fullpath(session, &fullpath, pathname); if (rc < 0) { - ftp_client_msg(session, 550, "Requested action not taken."); + ftp_client_msg(session, error_code, "Requested action not taken."); } else { - rc = ftp_vfs_open(&session->transfer.file_vfs, fullpath.s, FtpVfsOpenMode_READ); + rc = ftp_vfs_open(&session->transfer.file_vfs, fullpath.s, open_mode); if (rc < 0) { - ftp_client_msg(session, 550, "Requested action not taken, %s Failed to open path: %s.", strerror(errno), fullpath.s); + ftp_client_msg(session, error_code, "Requested action not taken, %s Failed to open path: %s.", strerror(errno), fullpath.s); } else { - struct stat st = {0}; - rc = ftp_vfs_fstat(&session->transfer.file_vfs, fullpath.s, &st); + if (session->transfer.offset) { + rc = ftp_vfs_seek(&session->transfer.file_vfs, NULL, 0, session->transfer.offset); + } + if (rc < 0) { - ftp_client_msg(session, 550, "Requested action not taken, %s. Failed to fstat path: %s", strerror(errno), fullpath.s); + ftp_vfs_close(&session->transfer.file_vfs); + ftp_client_msg(session, 550, "Requested action not taken, %s. Failed to fseek path: %s", strerror(errno), fullpath.s); } else { - session->transfer.offset = 0; - session->transfer.size = st.st_size; - - if (session->server_marker > 0) { - session->transfer.offset = session->server_marker; - rc = ftp_vfs_seek(&session->transfer.file_vfs, session->transfer.offset); - session->server_marker = 0; - } - - if (rc < 0) { - ftp_client_msg(session, 550, "Requested action not taken, %s. Failed to fseek path: %s", strerror(errno), fullpath.s); - } else { - ftp_data_open(session, FTP_TRANSFER_MODE_RETR); - } + ftp_data_open(session, transfer_mode); } } } } } +// RETR | 125, 150, (110), 226, 250, 425, 426, 451, 450, 550, 500, 501, 421, 530 +static void ftp_cmd_RETR(struct FtpSession* session, const char* data) { + ftp_open_file(session, data, FtpVfsOpenMode_READ, FTP_TRANSFER_MODE_RETR, 550); +} + // STOR | 125, 150, (110), 226, 250, 425, 426, 451, 551, 552, 532, 450, 452, 553, 500, 501, 421, 530 static void ftp_cmd_STOR(struct FtpSession* session, const char* data) { - struct Pathname pathname = {0}; - int rc = snprintf(pathname.s, sizeof(pathname), "%s", data); - - if (rc <= 0 || rc >= sizeof(pathname)) { - ftp_client_msg(session, 501, "Syntax error in parameters or arguments."); - } else { - enum FtpVfsOpenMode flags = FtpVfsOpenMode_WRITE; - if (session->server_marker == -1) { - flags = FtpVfsOpenMode_APPEND; - session->server_marker = 0; - } - - struct Pathname fullpath = {0}; - rc = build_fullpath(session, &fullpath, pathname); - if (rc < 0) { - ftp_client_msg(session, 551, "Requested action aborted: page type unknown, %s.", strerror(errno)); - } else { - rc = ftp_vfs_open(&session->transfer.file_vfs, fullpath.s, flags); - if (rc < 0) { - ftp_client_msg(session, 551, "Requested action aborted: page type unknown, %s. Failed to open path: %s", strerror(errno), fullpath.s); - } else { - ftp_data_open(session, FTP_TRANSFER_MODE_STOR); - } - } - } + ftp_open_file(session, data, FtpVfsOpenMode_WRITE, FTP_TRANSFER_MODE_STOR, 551); } // APPE | 125, 150, (110), 226, 250, 425, 426, 451, 551, 552, 532, 450, 550, 452, 553, 500, 501, 502, 421, 530 static void ftp_cmd_APPE(struct FtpSession* session, const char* data) { - session->server_marker = -1; - ftp_cmd_STOR(session, data); + ftp_open_file(session, data, FtpVfsOpenMode_APPEND, FTP_TRANSFER_MODE_STOR, 551); } // ALLO | 200, 202, 500, 501, 504, 421, 530 diff --git a/src/ftpsrv_vfs.h b/src/ftpsrv_vfs.h index 86512c5..d738db3 100644 --- a/src/ftpsrv_vfs.h +++ b/src/ftpsrv_vfs.h @@ -23,14 +23,13 @@ struct FtpVfsDirEntry; int ftp_vfs_open(struct FtpVfsFile* f, const char* path, enum FtpVfsOpenMode mode); int ftp_vfs_read(struct FtpVfsFile* f, void* buf, size_t size); int ftp_vfs_write(struct FtpVfsFile* f, const void* buf, size_t size); -int ftp_vfs_seek(struct FtpVfsFile* f, size_t off); -int ftp_vfs_fstat(struct FtpVfsFile* f, const char* path, struct stat* st); +// buf and size is the amount of data sent. +int ftp_vfs_seek(struct FtpVfsFile* f, const void* buf, size_t size, size_t off); int ftp_vfs_close(struct FtpVfsFile* f); int ftp_vfs_isfile_open(struct FtpVfsFile* f); int ftp_vfs_opendir(struct FtpVfsDir* f, const char* path); const char* ftp_vfs_readdir(struct FtpVfsDir* f, struct FtpVfsDirEntry* entry); -int ftp_vfs_dirstat(struct FtpVfsDir* f, const struct FtpVfsDirEntry* entry, const char* path, struct stat* st); int ftp_vfs_dirlstat(struct FtpVfsDir* f, const struct FtpVfsDirEntry* entry, const char* path, struct stat* st); int ftp_vfs_closedir(struct FtpVfsDir* f); int ftp_vfs_isdir_open(struct FtpVfsDir* f); diff --git a/src/platform/nx/vfs/vfs_nx_fs.c b/src/platform/nx/vfs/vfs_nx_fs.c index 3e39658..38d0106 100644 --- a/src/platform/nx/vfs/vfs_nx_fs.c +++ b/src/platform/nx/vfs/vfs_nx_fs.c @@ -473,10 +473,6 @@ int vfs_fs_internal_seek(struct VfsFsFile* f, size_t off) { return 0; } -int vfs_fs_internal_fstat(FsFileSystem* fs, struct VfsFsFile* f, const char nxpath[static FS_MAX_PATH], struct stat* st) { - return fstat_internal(fs, &f->fd, nxpath, st); -} - int vfs_fs_internal_close(struct VfsFsFile* f) { if (!vfs_fs_internal_isfile_open(f)) { return -1; @@ -529,7 +525,7 @@ const char* vfs_fs_internal_readdir(struct VfsFsDir* f, struct VfsFsDirEntry* en return entry->buf.name; } -int vfs_fs_internal_dirstat(FsFileSystem* fs, struct VfsFsDir* f, const struct VfsFsDirEntry* entry, const char nxpath[static FS_MAX_PATH], struct stat* st) { +int vfs_fs_internal_dirlstat(FsFileSystem* fs, struct VfsFsDir* f, const struct VfsFsDirEntry* entry, const char nxpath[static FS_MAX_PATH], struct stat* st) { memset(st, 0, sizeof(*st)); st->st_nlink = 1; @@ -551,10 +547,6 @@ int vfs_fs_internal_dirstat(FsFileSystem* fs, struct VfsFsDir* f, const struct V return 0; } -int vfs_fs_internal_dirlstat(FsFileSystem* fs, struct VfsFsDir* f, const struct VfsFsDirEntry* entry, const char nxpath[static FS_MAX_PATH], struct stat* st) { - return vfs_fs_internal_dirstat(fs, f, entry, nxpath, st); -} - int vfs_fs_internal_closedir(struct VfsFsDir* f) { if (!vfs_fs_internal_isdir_open(f)) { return -1; @@ -668,21 +660,11 @@ static int vfs_fs_write(void* user, const void* buf, size_t size) { return vfs_fs_internal_write(f, buf, size); } -static int vfs_fs_seek(void* user, size_t off) { +static int vfs_fs_seek(void* user, const void* buf, size_t size, size_t off) { struct VfsFsFile* f = user; return vfs_fs_internal_seek(f, off); } -static int vfs_fs_fstat(void* user, const char* path, struct stat* st) { - struct VfsFsFile* f = user; - FsFileSystem* fs = NULL; - char nxpath[FS_MAX_PATH]; - if (fsdev_wrapTranslatePath(path, &fs, nxpath)) { - return -1; - } - return fstat_internal(fs, &f->fd, nxpath, st); -} - static int vfs_fs_isfile_open(void* user) { struct VfsFsFile* f = user; return vfs_fs_internal_isfile_open(f); @@ -712,7 +694,7 @@ const char* vfs_fs_readdir(void* user, void* user_entry) { return vfs_fs_internal_readdir(f, entry); } -static int vfs_fs_dirstat(void* user, const void* user_entry, const char* path, struct stat* st) { +static int vfs_fs_dirlstat(void* user, const void* user_entry, const char* path, struct stat* st) { struct VfsFsDir* f = user; const struct VfsFsDirEntry* entry = user_entry; FsFileSystem* fs = NULL; @@ -720,7 +702,7 @@ static int vfs_fs_dirstat(void* user, const void* user_entry, const char* path, if (fsdev_wrapTranslatePath(path, &fs, nxpath)) { return -1; } - return vfs_fs_internal_dirstat(fs, f, entry, nxpath, st); + return vfs_fs_internal_dirlstat(fs, f, entry, nxpath, st); } static int vfs_fs_isdir_open(void* user) { @@ -792,13 +774,11 @@ const FtpVfs g_vfs_fs = { .read = vfs_fs_read, .write = vfs_fs_write, .seek = vfs_fs_seek, - .fstat = vfs_fs_fstat, .close = vfs_fs_close, .isfile_open = vfs_fs_isfile_open, .opendir = vfs_fs_opendir, .readdir = vfs_fs_readdir, - .dirstat = vfs_fs_dirstat, - .dirlstat = vfs_fs_dirstat, + .dirlstat = vfs_fs_dirlstat, .closedir = vfs_fs_closedir, .isdir_open = vfs_fs_isdir_open, .stat = vfs_fs_stat, diff --git a/src/platform/nx/vfs/vfs_nx_fs.h b/src/platform/nx/vfs/vfs_nx_fs.h index 318a217..b665711 100644 --- a/src/platform/nx/vfs/vfs_nx_fs.h +++ b/src/platform/nx/vfs/vfs_nx_fs.h @@ -35,13 +35,11 @@ int vfs_fs_internal_open(FsFileSystem* fs, struct VfsFsFile* f, const char nxpat int vfs_fs_internal_read(struct VfsFsFile* f, void* buf, size_t size); int vfs_fs_internal_write(struct VfsFsFile* f, const void* buf, size_t size); int vfs_fs_internal_seek(struct VfsFsFile* f, size_t off); -int vfs_fs_internal_fstat(FsFileSystem* fs, struct VfsFsFile* f, const char nxpath[static FS_MAX_PATH], struct stat* st); int vfs_fs_internal_close(struct VfsFsFile* f); int vfs_fs_internal_isfile_open(struct VfsFsFile* f); int vfs_fs_internal_opendir(FsFileSystem* fs, struct VfsFsDir* f, const char nxpath[static FS_MAX_PATH]); const char* vfs_fs_internal_readdir(struct VfsFsDir* f, struct VfsFsDirEntry* entry); -int vfs_fs_internal_dirstat(FsFileSystem* fs, struct VfsFsDir* f, const struct VfsFsDirEntry* entry, const char nxpath[static FS_MAX_PATH], struct stat* st); int vfs_fs_internal_dirlstat(FsFileSystem* fs, struct VfsFsDir* f, const struct VfsFsDirEntry* entry, const char nxpath[static FS_MAX_PATH], struct stat* st); int vfs_fs_internal_closedir(struct VfsFsDir* f); int vfs_fs_internal_isdir_open(struct VfsFsDir* f); diff --git a/src/platform/nx/vfs/vfs_nx_gc.c b/src/platform/nx/vfs/vfs_nx_gc.c new file mode 100644 index 0000000..4abb328 --- /dev/null +++ b/src/platform/nx/vfs/vfs_nx_gc.c @@ -0,0 +1,442 @@ +/** + * Copyright 2024 TotalJustice. + * SPDX-License-Identifier: MIT + */ + +#include "ftpsrv_vfs.h" +#include "log/log.h" +#include +#include +#include +#include + +static FsDeviceOperator g_dev; +static FsGameCardHandle g_handle; +static FsFileSystem g_fs; +static NcmContentMetaDatabase g_db; +static NcmContentStorage g_cs; +static NcmApplicationContentMetaKey g_app_key; +static u8 g_cert[0x200]; + +static bool g_mounted = false; + +#define min(x, y) ((x) < (y) ? (x) : (y)) + +static bool gc_is_mounted(void) { + Result rc; + bool out; + if (R_FAILED(rc = fsDeviceOperatorIsGameCardInserted(&g_dev, &out))) { + return false; + } + return out; +} + +static void gc_unmount(void) { + if (g_mounted) { + ncmContentMetaDatabaseClose(&g_db); + ncmContentStorageClose(&g_cs); + fsFsClose(&g_fs); + g_mounted = false; + } +} + +static bool gc_mount(void) { + gc_unmount(); + + if (!gc_is_mounted()) { + log_file_fwrite("no gc mounted\n"); + return false; + } + + Result rc; + if (R_FAILED(rc = fsDeviceOperatorGetGameCardHandle(&g_dev,&g_handle))) { + log_file_fwrite("failed fsDeviceOperatorGetGameCardHandle(): 0x%X\n", rc); + return false; + } + + if (R_FAILED(rc = fsOpenGameCardFileSystem(&g_fs, &g_handle, FsGameCardPartition_Secure))) { + log_file_fwrite("failed fsOpenGameCardFileSystem(): 0x%X\n", rc); + return false; + } + + s64 out_size; + if (R_FAILED(rc = fsDeviceOperatorGetGameCardDeviceCertificate(&g_dev, &g_handle, g_cert, sizeof(g_cert), &out_size, sizeof(g_cert)))) { + log_file_fwrite("failed fsDeviceOperatorGetGameCardDeviceCertificate(): 0x%X\n", rc); + goto fail_close_fs; + } + + if (R_FAILED(rc = ncmOpenContentMetaDatabase(&g_db, NcmStorageId_GameCard))) { + log_file_fwrite("failed ncmOpenContentMetaDatabase(NcmStorageId_GameCard): 0x%X\n", rc); + goto fail_close_fs; + } + + if (R_FAILED(rc = ncmOpenContentStorage(&g_cs, NcmStorageId_GameCard))) { + log_file_fwrite("failed ncmOpenContentStorage(NcmStorageId_GameCard): 0x%X\n", rc); + goto fail_close_ncm_d; + } + + s32 entries_total; + s32 entries_written; + if (R_FAILED(rc = ncmContentMetaDatabaseListApplication(&g_db, &entries_total, &entries_written, &g_app_key, 1, NcmContentMetaType_Application))) { + log_file_fwrite("failed ncmContentMetaDatabaseListApplication(NcmStorageId_GameCard): 0x%X\n", rc); + goto fail_close_ncm_c; + } + + if (entries_written <= 0) { + log_file_fwrite("failed entries_written <= 0 (NcmStorageId_GameCard): 0x%X\n", rc); + goto fail_close_ncm_c; + } + + log_file_fwrite("gamecard is mounted\n"); + return g_mounted = true; + +fail_close_ncm_c: + ncmContentStorageClose(&g_cs); +fail_close_ncm_d: + ncmContentMetaDatabaseClose(&g_db); +fail_close_fs: + fsFsClose(&g_fs); + return false; +} + +static enum GcDirType get_type(const char* path) { + if (!strcmp(path, "gc:")) { + return GcDirType_Root; + } else if (!strncmp(path, "gc:/", strlen("gc:/"))) { + const char* dilem = strrchr(path, '['); + if (!dilem) { + return GcDirType_Invalid; + } + dilem++; + + char* end; + const u64 app_id = strtoull(dilem, &end, 0x10); + if (end == dilem || app_id != g_app_key.application_id) { + return GcDirType_Invalid; + } + + if (end[0] != ']') { + return GcDirType_Invalid; + } + + if (!strcmp(end, "].certificate")) { + return GcDirType_Cert; + } else { + return GcDirType_App; + } + } + + return GcDirType_Invalid; +} + +static void build_native_path(char out[static FS_MAX_PATH], const char* path) { + const char* dilem = strchr(path, ']'); + + if (dilem && strlen(dilem + 1)) { + snprintf(out, FS_MAX_PATH, "%s", dilem + 1); + } else { + strcpy(out, "/"); + } +} + +// returns true if mounted. +static bool gc_poll(void) { + if (g_mounted) { + if (!gc_is_mounted()) { + gc_unmount(); + return false; + } else { + FsGameCardHandle h; + if (R_FAILED(fsDeviceOperatorGetGameCardHandle(&g_dev, &h))) { + gc_unmount(); + return false; + } + + if (h.value != g_handle.value) { + return gc_mount(); + } + + return true; + } + } else { + log_file_fwrite("attempting to mount\n"); + return gc_mount(); + } +} + +static int vfs_gc_open(void* user, const char* path, enum FtpVfsOpenMode mode) { + if (!gc_poll()) { + return -1; + } + + if (mode != FtpVfsOpenMode_READ) { + return -1; + } + + struct VfsGcFile* f = user; + f->type = get_type(path); + switch (f->type) { + default: return -1; + + case GcDirType_Cert: + f->raw.ptr = g_cert; + f->raw.size = sizeof(g_cert); + f->raw.offset = 0; + break; + + case GcDirType_App: { + char nxpath[FS_MAX_PATH]; + build_native_path(nxpath, path); + if (vfs_fs_internal_open(&g_fs, &f->fs_file, nxpath, mode)) { + return -1; + } + } break; + } + + f->is_valid = 1; + return 0; +} + +static int vfs_gc_read(void* user, void* buf, size_t size) { + struct VfsGcFile* f = user; + switch (f->type) { + default: return -1; + + case GcDirType_Cert: + size = min(size, f->raw.size - f->raw.offset); + memcpy(buf, g_cert + f->raw.offset, size); + f->raw.offset += size; + return size; + + case GcDirType_App: + return vfs_fs_internal_read(&f->fs_file, buf, size); + } +} + +static int vfs_gc_write(void* user, const void* buf, size_t size) { + return -1; +} + +static int vfs_gc_seek(void* user, const void* buf, size_t size, size_t off) { + struct VfsGcFile* f = user; + switch (f->type) { + default: return -1; + + case GcDirType_Cert: + if (off >= f->raw.size) { + return -1; + } + f->raw.offset = off; + return 0; + + case GcDirType_App: + return vfs_fs_internal_seek(&f->fs_file, off); + } +} + +static int vfs_gc_isfile_open(void* user) { + struct VfsGcFile* f = user; + return f->is_valid; +} + +static int vfs_gc_close(void* user) { + struct VfsGcFile* f = user; + if (!vfs_gc_isfile_open(f)) { + return -1; + } + + switch (f->type) { + default: break; + + case GcDirType_App: + vfs_fs_internal_close(&f->fs_file); + break; + } + + f->is_valid = 0; + return 0; +} + +static int vfs_gc_opendir(void* user, const char* path) { + if (!gc_poll()) { + return -1; + } + + struct VfsGcDir* f = user; + f->type = get_type(path); + switch (f->type) { + default: return -1; + + case GcDirType_Root: + break; + + case GcDirType_App: { + char nxpath[FS_MAX_PATH]; + build_native_path(nxpath, path); + if (vfs_fs_internal_opendir(&g_fs, &f->fs_dir, nxpath)) { + return -1; + } + } break; + } + + f->index = 0; + f->is_valid = 1; + return 0; +} + +static const char* vfs_gc_readdir(void* user, void* user_entry) { + struct VfsGcDir* f = user; + struct VfsGcDirEntry* entry = user_entry; + + switch (f->type) { + default: return NULL; + + case GcDirType_Root: { + if (f->index >= 2) { + return NULL; + } + + Result rc; + NcmContentId id; + struct AppName name; + const char* ext = f->index ? ".certificate" : ""; + if (R_FAILED(rc = get_app_name2(g_app_key.application_id, &g_db, &g_cs, &id, &name))) { + snprintf(entry->name, sizeof(entry->name), "[%016lX]%s", g_app_key.application_id, ext); + } else { + snprintf(entry->name, sizeof(entry->name), "%s [%016lX]%s", name.str, g_app_key.application_id, ext); + } + f->index++; + return entry->name; + } + + case GcDirType_App: { + return vfs_fs_internal_readdir(&f->fs_dir, &entry->fs_entry); + } + } +} + +static int vfs_gc_dirlstat(void* user, const void* user_entry, const char* path, struct stat* st) { + struct VfsGcDir* f = user; + const struct VfsGcDirEntry* entry = user_entry; + memset(st, 0, sizeof(*st)); + + switch (f->type) { + default: return -1; + + case GcDirType_Root: { + st->st_nlink = 1; + if (strstr(path, ".certificate")) { + st->st_size = sizeof(g_cert); + st->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH; + } else { + st->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH; + } + return 0; + } + + case GcDirType_App: { + char nxpath[FS_MAX_PATH]; + build_native_path(nxpath, path); + return vfs_fs_internal_dirlstat(&g_fs, &f->fs_dir, &entry->fs_entry, nxpath, st); + } + } +} + +static int vfs_gc_isdir_open(void* user) { + struct VfsGcDir* f = user; + return f->is_valid; +} + +static int vfs_gc_closedir(void* user) { + struct VfsGcDir* f = user; + if (!vfs_gc_isdir_open(f)) { + return -1; + } + + switch (f->type) { + default: break; + + case GcDirType_App: + vfs_fs_internal_closedir(&f->fs_dir);; + break; + } + + f->is_valid = 0; + return 0; +} + +static int vfs_gc_stat(const char* path, struct stat* st) { + if (!gc_poll()) { + return -1; + } + + memset(st, 0, sizeof(*st)); + const enum GcDirType type = get_type(path); + + switch (type) { + default: return -1; + + case GcDirType_App: + if (strstr(path, "]/")) { + char nxpath[FS_MAX_PATH]; + build_native_path(nxpath, path); + return vfs_fs_internal_stat(&g_fs, nxpath, st); + } + case GcDirType_Root: + st->st_nlink = 1; + st->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH; + return 0; + + case GcDirType_Cert: + st->st_nlink = 1; + st->st_size = sizeof(g_cert); + st->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH; + return 0; + + } +} + +static int vfs_gc_mkdir(const char* path) { + return -1; +} + +static int vfs_gc_unlink(const char* path) { + return -1; +} + +static int vfs_gc_rmdir(const char* path) { + return -1; +} + +static int vfs_gc_rename(const char* src, const char* dst) { + return -1; +} + +Result vfs_gc_init(void) { + return fsOpenDeviceOperator(&g_dev); +} + +void vfs_gc_exit(void) { + gc_unmount(); + fsOpenDeviceOperator(&g_dev); +} + +const FtpVfs g_vfs_gc = { + .open = vfs_gc_open, + .read = vfs_gc_read, + .write = vfs_gc_write, + .seek = vfs_gc_seek, + .close = vfs_gc_close, + .isfile_open = vfs_gc_isfile_open, + .opendir = vfs_gc_opendir, + .readdir = vfs_gc_readdir, + .dirlstat = vfs_gc_dirlstat, + .closedir = vfs_gc_closedir, + .isdir_open = vfs_gc_isdir_open, + .stat = vfs_gc_stat, + .lstat = vfs_gc_stat, + .mkdir = vfs_gc_mkdir, + .unlink = vfs_gc_unlink, + .rmdir = vfs_gc_rmdir, + .rename = vfs_gc_rename, +}; diff --git a/src/platform/nx/vfs/vfs_nx_gc.h b/src/platform/nx/vfs/vfs_nx_gc.h new file mode 100644 index 0000000..cdde5c7 --- /dev/null +++ b/src/platform/nx/vfs/vfs_nx_gc.h @@ -0,0 +1,59 @@ +// Copyright 2024 TotalJustice. +// SPDX-License-Identifier: MIT +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include "vfs_nx_fs.h" + +enum GcDirType { + GcDirType_Invalid, + GcDirType_Root, + GcDirType_App, + GcDirType_Cert, +}; + +struct VfsGcFileRaw { + const u8* ptr; + u32 size; + u32 offset; +}; + +struct VfsGcFile { + enum GcDirType type; + union { + struct VfsFsFile fs_file; + struct VfsGcFileRaw raw; + }; + bool is_valid; +}; + +struct VfsGcDir { + enum GcDirType type; + union { + struct VfsFsDir fs_dir; + }; + size_t index; + bool is_valid; +}; + +struct VfsGcDirEntry { + union { + struct VfsFsDirEntry fs_entry; + char name[512 + 128]; + }; +}; + +struct FtpVfs; +const extern struct FtpVfs g_vfs_gc; + +Result vfs_gc_init(void); +void vfs_gc_exit(void); + +#ifdef __cplusplus +} +#endif diff --git a/src/platform/nx/vfs/vfs_nx_hdd.c b/src/platform/nx/vfs/vfs_nx_hdd.c index c9ddd7f..5c9776a 100644 --- a/src/platform/nx/vfs/vfs_nx_hdd.c +++ b/src/platform/nx/vfs/vfs_nx_hdd.c @@ -27,74 +27,37 @@ static void poll_usbhsfs(void) { static int vfs_hdd_open(void* user, const char* path, enum FtpVfsOpenMode mode) { poll_usbhsfs(); + struct VfsHddFile* f = user; path = fix_path(path); if (strncmp(path, "ums", strlen("ums"))) { return -1; } - - struct VfsHddFile* f = user; - int flags = 0, args = 0; - - switch (mode) { - case FtpVfsOpenMode_READ: - flags = O_RDONLY; - args = 0; - break; - case FtpVfsOpenMode_WRITE: - flags = O_WRONLY | O_CREAT | O_TRUNC; - args = 0666; - break; - case FtpVfsOpenMode_APPEND: - flags = O_WRONLY | O_CREAT | O_APPEND; - args = 0666; - break; - } - - f->fd = open(path, flags, args); - if (f->fd >= 0) { - f->valid = 1; - } - return f->fd; + return vfs_stdio_internal_open(&f->stdio_file, path, mode); } static int vfs_hdd_read(void* user, void* buf, size_t size) { struct VfsHddFile* f = user; - return read(f->fd, buf, size); + return vfs_stdio_internal_read(&f->stdio_file, buf, size); } static int vfs_hdd_write(void* user, const void* buf, size_t size) { struct VfsHddFile* f = user; - return write(f->fd, buf, size); + return vfs_stdio_internal_write(&f->stdio_file, buf, size); } -static int vfs_hdd_seek(void* user, size_t off) { +static int vfs_hdd_seek(void* user, const void* buf, size_t size, size_t off) { struct VfsHddFile* f = user; - return lseek(f->fd, off, SEEK_SET); -} - -static int vfs_hdd_fstat(void* user, const char* path, struct stat* st) { - struct VfsHddFile* f = user; - // fstat is not available with fatfs (fat32/exfat). - if (fstat(f->fd, st) && errno == ENOSYS) { - return stat(fix_path(path), st); - } - return 0; + return vfs_stdio_internal_seek(&f->stdio_file, off); } static int vfs_hdd_isfile_open(void* user) { struct VfsHddFile* f = user; - return f->valid && f->fd >= 0; + return vfs_stdio_internal_isfile_open(&f->stdio_file); } static int vfs_hdd_close(void* user) { struct VfsHddFile* f = user; - if (!vfs_hdd_isfile_open(f)) { - return -1; - } - int rc = close(f->fd); - f->fd = -1; - f->valid = 0; - return rc; + return vfs_stdio_internal_close(&f->stdio_file); } static int vfs_hdd_opendir(void* user, const char* path) { @@ -102,8 +65,7 @@ static int vfs_hdd_opendir(void* user, const char* path) { struct VfsHddDir* f = user; path = fix_path(path); if (!strncmp(path, "ums", strlen("ums"))) { - f->fd = opendir(path); - if (!f->fd) { + if (vfs_stdio_internal_opendir(&f->stdio_dir, path)) { return -1; } } @@ -117,12 +79,8 @@ static const char* vfs_hdd_readdir(void* user, void* user_entry) { struct VfsHddDir* f = user; struct VfsHddDirEntry* entry = user_entry; - if (f->fd) { - entry->d = readdir(f->fd); - if (!entry->d) { - return NULL; - } - return entry->d->d_name; + if (vfs_stdio_internal_isdir_open(&f->stdio_dir)) { + return vfs_stdio_internal_readdir(&f->stdio_dir, &entry->stdio_dir); } else { if (f->index >= g_count) { return NULL; @@ -131,11 +89,12 @@ static const char* vfs_hdd_readdir(void* user, void* user_entry) { } } -static int vfs_hdd_dirstat(void* user, const void* user_entry, const char* path, struct stat* st) { +static int vfs_hdd_dirlstat(void* user, const void* user_entry, const char* path, struct stat* st) { struct VfsHddDir* f = user; + const struct VfsHddDirEntry* entry = user_entry; path = fix_path(path); - if (f->fd) { - return stat(path, st); + if (vfs_stdio_internal_isdir_open(&f->stdio_dir)) { + return vfs_stdio_internal_dirlstat(&f->stdio_dir, &entry->stdio_dir, path, st); } else { memset(st, 0, sizeof(*st)); st->st_nlink = 1; @@ -154,8 +113,8 @@ static int vfs_hdd_closedir(void* user) { if (!vfs_hdd_isdir_open(f)) { return -1; } - if (f->fd) { - closedir(f->fd); + if (vfs_stdio_internal_isdir_open(&f->stdio_dir)) { + vfs_stdio_internal_closedir(&f->stdio_dir); } memset(f, 0, sizeof(*f)); return 0; @@ -174,7 +133,7 @@ static int vfs_hdd_stat(const char* path, struct stat* st) { st->st_mode = S_IFDIR | S_IRWXU | S_IRWXG | S_IRWXO; return 0; } else { - return stat(path, st); + return vfs_stdio_internal_stat(path, st); } } @@ -184,7 +143,7 @@ static int vfs_hdd_mkdir(const char* path) { if (strlen(path) <= 5 || strncmp(path, "ums", strlen("ums"))) { return -1; } - return mkdir(path, 0777); + return vfs_stdio_internal_mkdir(path); } static int vfs_hdd_unlink(const char* path) { @@ -193,7 +152,7 @@ static int vfs_hdd_unlink(const char* path) { if (strlen(path) <= 5 || strncmp(path, "ums", strlen("ums"))) { return -1; } - return unlink(path); + return vfs_stdio_internal_unlink(path); } static int vfs_hdd_rmdir(const char* path) { @@ -202,7 +161,7 @@ static int vfs_hdd_rmdir(const char* path) { if (strlen(path) <= 5 || strncmp(path, "ums", strlen("ums"))) { return -1; } - return rmdir(path); + return vfs_stdio_internal_rmdir(path); } static int vfs_hdd_rename(const char* src, const char* dst) { @@ -217,7 +176,7 @@ static int vfs_hdd_rename(const char* src, const char* dst) { return -1; } - return rename(path_src, path_dst); + return vfs_stdio_internal_rename(path_src, path_dst); } Result vfs_hdd_init(void) { @@ -233,13 +192,11 @@ const FtpVfs g_vfs_hdd = { .read = vfs_hdd_read, .write = vfs_hdd_write, .seek = vfs_hdd_seek, - .fstat = vfs_hdd_fstat, .close = vfs_hdd_close, .isfile_open = vfs_hdd_isfile_open, .opendir = vfs_hdd_opendir, .readdir = vfs_hdd_readdir, - .dirstat = vfs_hdd_dirstat, - .dirlstat = vfs_hdd_dirstat, + .dirlstat = vfs_hdd_dirlstat, .closedir = vfs_hdd_closedir, .isdir_open = vfs_hdd_isdir_open, .stat = vfs_hdd_stat, diff --git a/src/platform/nx/vfs/vfs_nx_hdd.h b/src/platform/nx/vfs/vfs_nx_hdd.h index 3c36e42..a716e8f 100644 --- a/src/platform/nx/vfs/vfs_nx_hdd.h +++ b/src/platform/nx/vfs/vfs_nx_hdd.h @@ -6,23 +6,23 @@ extern "C" { #endif +#include "vfs_nx_stdio.h" #include #include #include struct VfsHddFile { - int fd; - int valid; + struct VfsStdioFile stdio_file; }; struct VfsHddDir { - DIR* fd; + struct VfsStdioDir stdio_dir; size_t index; bool is_valid; }; struct VfsHddDirEntry { - struct dirent* d; + struct VfsStdioDirEntry stdio_dir; }; struct FtpVfs; diff --git a/src/platform/nx/vfs/vfs_nx_root.c b/src/platform/nx/vfs/vfs_nx_root.c index b8ab027..f8e7c5e 100644 --- a/src/platform/nx/vfs/vfs_nx_root.c +++ b/src/platform/nx/vfs/vfs_nx_root.c @@ -23,11 +23,7 @@ static int vfs_root_write(void* user, const void* buf, size_t size) { return -1; } -static int vfs_root_seek(void* user, size_t off) { - return -1; -} - -static int vfs_root_fstat(void* user, const char* path, struct stat* st) { +static int vfs_root_seek(void* user, const void* buf, size_t size, size_t off) { return -1; } @@ -55,7 +51,7 @@ static const char* vfs_root_readdir(void* user, void* user_entry) { } } -static int vfs_root_dirstat(void* user, const void* user_entry, const char* path, struct stat* st) { +static int vfs_root_dirlstat(void* user, const void* user_entry, const char* path, struct stat* st) { memset(st, 0, sizeof(*st)); st->st_nlink = 1; st->st_mode = S_IFDIR | S_IRWXU | S_IRWXG | S_IRWXO; @@ -113,13 +109,11 @@ const FtpVfs g_vfs_root = { .read = vfs_root_read, .write = vfs_root_write, .seek = vfs_root_seek, - .fstat = vfs_root_fstat, .close = vfs_root_close, .isfile_open = vfs_root_isfile_open, .opendir = vfs_root_opendir, .readdir = vfs_root_readdir, - .dirstat = vfs_root_dirstat, - .dirlstat = vfs_root_dirstat, + .dirlstat = vfs_root_dirlstat, .closedir = vfs_root_closedir, .isdir_open = vfs_root_isdir_open, .stat = vfs_root_stat, diff --git a/src/platform/nx/vfs/vfs_nx_save.c b/src/platform/nx/vfs/vfs_nx_save.c index 8c798c5..c63f097 100644 --- a/src/platform/nx/vfs/vfs_nx_save.c +++ b/src/platform/nx/vfs/vfs_nx_save.c @@ -11,6 +11,392 @@ #include #include +#define min(x, y) ((x) < (y) ? (x) : (y)) + +#define LOCAL_HEADER_SIG 0x4034B50 +#define FILE_HEADER_SIG 0x2014B50 +#define DATA_DESCRIPTOR_SIG 0x8074B50 +#define END_RECORD_SIG 0x6054B50 + +#pragma pack(push,1) +typedef struct mmz_LocalHeader { + uint32_t sig; + uint16_t version; + uint16_t flags; + uint16_t compression; + uint16_t modtime; + uint16_t moddate; + uint32_t crc32; + uint32_t compressed_size; + uint32_t uncompressed_size; + uint16_t filename_len; + uint16_t extrafield_len; +} mmz_LocalHeader; +#pragma pack(pop) + +#pragma pack(push,1) +typedef struct mmz_DataDescriptor { + uint32_t sig; + uint32_t crc32; + uint32_t compressed_size; + uint32_t uncompressed_size; +} mmz_DataDescriptor; +#pragma pack(pop) + +#pragma pack(push,1) +typedef struct mmz_FileHeader { + uint32_t sig; + uint16_t version; + uint16_t version_needed; + uint16_t flags; + uint16_t compression; + uint16_t modtime; + uint16_t moddate; + uint32_t crc32; + uint32_t compressed_size; + uint32_t uncompressed_size; + uint16_t filename_len; + uint16_t extrafield_len; + uint16_t filecomment_len; + uint16_t disk_start; + uint16_t internal_attr; + uint32_t external_attr; + uint32_t local_hdr_off; +} mmz_FileHeader; +#pragma pack(pop) + +#pragma pack(push,1) +typedef struct mmz_EndRecord { + uint32_t sig; + uint16_t disk_number; + uint16_t disk_wcd; + uint16_t disk_entries; + uint16_t total_entries; + uint32_t central_directory_size; + uint32_t file_hdr_off; + uint16_t comment_len; +} mmz_EndRecord; +#pragma pack(pop) + +struct mmz_FileInfoBuffer { + struct mmz_FileInfoMeta meta; + char path[FS_MAX_PATH]; +}; + +// struct mm +struct mmz_DataBuf { + s64 fbuf_size; // internal + FsDirectoryEntry entry; + char path[FS_MAX_PATH]; + char path_temp[FS_MAX_PATH]; +}; + +static u32 mmz_build_local_header(const struct mmz_Data* mz, struct mmz_LocalHeader* local) { + memset(local, 0, sizeof(*local)); + local->sig = LOCAL_HEADER_SIG; + local->flags = 1 << 3; // data descriptor + local->filename_len = mz->meta.string_len; + return sizeof(*local) + mz->meta.string_len; +} + +static u32 mmz_build_file_header(const struct mmz_Data* mz, struct mmz_FileHeader* file) { + memset(file, 0, sizeof(*file)); + file->sig = FILE_HEADER_SIG; + file->version = 3 << 8; // UNIX + file->flags = 1 << 3; // data descriptor + file->crc32 = mz->meta.crc32; + file->compressed_size = mz->meta.size; + file->uncompressed_size = mz->meta.size; + file->filename_len = mz->meta.string_len; + file->local_hdr_off = mz->local_hdr_off; + return sizeof(*file) + mz->meta.string_len; +} + +static u32 mmz_build_data_descriptor(const struct mmz_Data* mz, struct mmz_DataDescriptor* desc) { + memset(desc, 0, sizeof(*desc)); + desc->sig = DATA_DESCRIPTOR_SIG; + desc->crc32 = mz->meta.crc32; + desc->compressed_size = mz->meta.size; + desc->uncompressed_size = mz->meta.size; + return sizeof(*desc); +} + +static u32 mmz_build_end_record(const struct mmz_Data* mz, struct mmz_EndRecord* rec) { + memset(rec, 0, sizeof(*rec)); + rec->sig = END_RECORD_SIG; + rec->disk_entries = mz->file_count; + rec->total_entries = mz->file_count; + rec->central_directory_size = mz->central_directory_size; + rec->file_hdr_off = mz->local_hdr_off; + return sizeof(*rec); +} + +static Result mmz_add_file(struct mmz_Data* mz, struct mmz_DataBuf* db, const char* path) { + // skip leading root path, zip paths are relative. + if (path[0] == '/') { + path++; + } + + Result rc; + struct mmz_FileInfoBuffer buf = {0}; + buf.meta.string_len = strlen(path); + memcpy(buf.path, path, buf.meta.string_len); + const size_t buf_size = sizeof(buf.meta) + buf.meta.string_len; + + if (db->fbuf_size - mz->fbuf_off < buf_size) { + db->fbuf_size += 1024 * 64; + if (R_FAILED(rc = fsFileSetSize(&mz->fbuf_out, db->fbuf_size))) { + return rc; + } + } + + if (R_FAILED(rc = fsFileWrite(&mz->fbuf_out, mz->fbuf_off, &buf, buf_size, FsWriteOption_None))) { + return rc; + } + + mz->file_count++; + mz->fbuf_off += buf_size; + return rc; +} + +static Result mmz_add_dir(struct mmz_Data* mz, struct mmz_DataBuf* db, const char* path) { + Result rc; + FsDir dir; + snprintf(db->path, sizeof(db->path), path); + + if (R_FAILED(rc = fsFsOpenDirectory(mz->fs, db->path, FsDirOpenMode_ReadDirs|FsDirOpenMode_ReadFiles|FsDirOpenMode_NoFileSize, &dir))) { + return rc; + } + + if (!strcmp(db->path, "/")) { + db->path[0] = '\0'; + } + + s64 total_entries; + while ((R_SUCCEEDED(rc = fsDirRead(&dir, &total_entries, 1, &db->entry))) && total_entries == 1) { + snprintf(db->path_temp, sizeof(db->path_temp), "%s/%s", db->path, db->entry.name); + if (db->entry.type == FsDirEntryType_Dir) { + if (R_FAILED(rc = mmz_add_dir(mz, db, db->path_temp))) { + break; + } + strrchr(db->path, '/')[0] = '\0'; + } else { + if (R_FAILED(rc = mmz_add_file(mz, db, db->path_temp))) { + break; + } + } + } + + fsDirClose(&dir); + return rc; +} + +static void mzz_build_temp_path(char* out, u64 app_id, AccountUid uid, u8 type) { + snprintf(out, FS_MAX_PATH, "/~ftpsrv_mmzip_temp_%016lX_%016lX%016lX_%d", app_id, uid.uid[0], uid.uid[1], type); +} + +static Result mmz_build_zip(struct mmz_Data* mz, FsFileSystem* save_fs, u64 app_id, AccountUid uid, u8 type) { + memset(mz, 0, sizeof(*mz)); + struct mmz_DataBuf db = {0}; + mz->fs = save_fs; + db.fbuf_size = 1024 * 64; + + FsFileSystem* sdmc_fs = fsdev_wrapGetDeviceFileSystem("sdmc"); + mzz_build_temp_path(db.path_temp, app_id, uid, type); + fsFsDeleteFile(sdmc_fs, db.path_temp); + + Result rc; + if (R_FAILED(rc = fsFsCreateFile(sdmc_fs, db.path_temp, db.fbuf_size, 0))) { + return rc; + } + + if (R_FAILED(rc = fsFsOpenFile(sdmc_fs, db.path_temp, FsOpenMode_Read|FsOpenMode_Write|FsOpenMode_Append, &mz->fbuf_out))) { + goto end; + } + + if (R_FAILED(rc = mmz_add_dir(mz, &db, "/"))) { + goto end; + } + + if (!mz->file_count) { + rc = 0x339602; + goto end; + } + +end: + if (R_FAILED(rc)) { + fsFileClose(&mz->fbuf_out); + mzz_build_temp_path(db.path_temp, app_id, uid, type); + fsFsDeleteFile(sdmc_fs, db.path_temp); + } else { + mz->fbuf_off = 0; + } + + return rc; +} + +static Result mmz_read_buffer_info(struct mmz_Data* mz, struct mmz_FileInfoBuffer* buf) { + Result rc; + u64 bytes_read; + if (R_FAILED(rc = fsFileRead(&mz->fbuf_out, mz->fbuf_off, &buf->meta, sizeof(buf->meta), 0, &bytes_read))) { + return rc; + } + + if (R_FAILED(rc = fsFileRead(&mz->fbuf_out, mz->fbuf_off + sizeof(buf->meta), buf->path, buf->meta.string_len, 0, &bytes_read))) { + return rc; + } + + buf->path[buf->meta.string_len] = 0; + return rc; +} + +static int mmz_read(struct mmz_Data* mz, void* buf, size_t size) { + Result rc; + if (mz->pending) { + mz->off = 0; + mz->pending = false; + + switch (mz->state) { + case mmz_State_Local: { + struct mmz_FileInfoBuffer info_buf; + if (R_FAILED(rc = mmz_read_buffer_info(mz, &info_buf))) { + return vfs_fs_set_errno(rc); + } + + char file_path[FS_MAX_PATH]; + snprintf(file_path, sizeof(file_path), "/%s", info_buf.path); + if (R_FAILED(rc = fsFsOpenFile(mz->fs, file_path, FsOpenMode_Read, &mz->fin))) { + return vfs_fs_set_errno(rc); + } + + s64 size; + if (R_FAILED(rc = fsFileGetSize(&mz->fin, &size))) { + return vfs_fs_set_errno(rc); + } + + mz->meta.size = size; + mz->new_crc32 = 0; + mz->state = mmz_State_Data; + } break; + + case mmz_State_Data: { + fsFileClose(&mz->fin); + + // store crc32 and size + mz->meta.crc32 = mz->new_crc32; + if (R_FAILED(rc = fsFileWrite(&mz->fbuf_out, mz->fbuf_off, &mz->meta, sizeof(mz->meta), 0))) { + return vfs_fs_set_errno(rc); + } + + mz->state = mmz_State_Descriptor; + } break; + + case mmz_State_Descriptor: { + mz->index++; + if (mz->index == mz->file_count) { + mz->fbuf_off = 0; + mz->index = 0; + mz->state = mmz_State_File; + } else { + mz->fbuf_off += sizeof(mz->meta) + mz->meta.string_len; + mz->state = mmz_State_Local; + } + } break; + + case mmz_State_File: + mz->index++; + mz->central_directory_size += sizeof(mmz_FileHeader) + mz->meta.string_len; + mz->local_hdr_off += sizeof(mmz_LocalHeader) + mz->meta.string_len + mz->meta.size + sizeof(mmz_DataDescriptor); + if (mz->index == mz->file_count) { + mz->state = mmz_State_End; + } else { + mz->fbuf_off += sizeof(mz->meta) + mz->meta.string_len; + } + break; + + case mmz_State_End: + return 0; + } + } + + u32 total_size = 0; + + switch (mz->state) { + case mmz_State_Local: { + struct mmz_FileInfoBuffer info_buf; + if (R_FAILED(rc = mmz_read_buffer_info(mz, &info_buf))) { + return vfs_fs_set_errno(rc); + } + + mz->meta = info_buf.meta; + struct mmz_LocalHeader local_hdr; + total_size = mmz_build_local_header(mz, &local_hdr); + + if (mz->off < sizeof(local_hdr)) { + size = min(size, sizeof(local_hdr) - mz->off); + memcpy(buf, (u8*)&local_hdr + mz->off, size); + } else { + size = min(size, local_hdr.filename_len - (mz->off - sizeof(local_hdr))); + memcpy(buf, info_buf.path + mz->off - sizeof(local_hdr), size); + } + } break; + + case mmz_State_Data: { + mz->meta.crc32 = mz->new_crc32; + u64 bytes_read; + total_size = mz->meta.size; + size = min(size, total_size - mz->off); + if (R_FAILED(rc = fsFileRead(&mz->fin, mz->off, buf, size, 0, &bytes_read))) { + return vfs_fs_set_errno(rc); + } + + mz->new_crc32 = crc32CalculateWithSeed(mz->meta.crc32, buf, size); + } break; + + case mmz_State_Descriptor: { + mmz_DataDescriptor data_desc = {0}; + total_size = mmz_build_data_descriptor(mz, &data_desc); + size = min(size, total_size - mz->off); + memcpy(buf, (const u8*)&data_desc + mz->off, size); + } break; + + case mmz_State_File: { + struct mmz_FileInfoBuffer info_buf; + if (R_FAILED(rc = mmz_read_buffer_info(mz, &info_buf))) { + return vfs_fs_set_errno(rc); + } + + mz->meta = info_buf.meta; + struct mmz_FileHeader file_hdr; + total_size = mmz_build_file_header(mz, &file_hdr); + + if (mz->off < sizeof(file_hdr)) { + size = min(size, sizeof(file_hdr) - mz->off); + memcpy(buf, (const u8*)&file_hdr + mz->off, size); + } else { + size = min(size, file_hdr.filename_len - (mz->off - sizeof(file_hdr))); + memcpy(buf, info_buf.path + mz->off - sizeof(file_hdr), size); + } + } break; + + case mmz_State_End: { + struct mmz_EndRecord end_rec; + total_size = mmz_build_end_record(mz, &end_rec); + size = min(size, total_size - mz->off); + memcpy(buf, (const u8*)&end_rec + mz->off, size); + } break; + } + + mz->off += size; + mz->zip_off += size; + + if (mz->off == total_size) { + mz->pending = true; + } + + return size; +} + struct SaveAcc { AccountUid uid; char name[0x20]; @@ -31,8 +417,61 @@ static struct SaveAcc g_acc_profile[12]; static s32 g_acc_count; static bool g_writable; +// list of all characters that are invalid for fat, +// these are coverted to "_" +static const char INVALID_CHAR_TABLE[] = { + '<', + '>', + ':', + '"', + '/', + '\\', + '|', + '?', + '*', + '.', + ',', + ';', + '+', + '=', + '&', + '%', // probably invalid +}; + +static void make_zip_string_valid(char* str) { + for (int i = 0; str[i]; i++) { + const unsigned char c = str[i]; + const unsigned char c2 = str[i + 1]; + if (c < 0x20 || c >= 0x80) { + if (c == 195 && c2 == 169) { + str[i + 1] = 'e'; + memcpy(str + i, str + i + 1, strlen(str) - i); + } else if (c == 226 && c2 == 128 && (unsigned char)str[i + 2] == 153) { + str[i + 2] = '\''; + memcpy(str + i, str + i + 2, strlen(str) - i); + } else { + str[i] = '_'; + } + } else { + for (int j = 0; j < ARRAY_SIZE(INVALID_CHAR_TABLE); j++) { + if (c == INVALID_CHAR_TABLE[j]) { + // see what the next character is + if (str[i + 1] == '\0') { + str[i] = '\0'; + } else if (str[i + 1] != ' ') { + str[i] = '_'; + } else { + memcpy(str + i, str + i + 1, strlen(str) - i); + } + break; + } + } + } + } +} + static FsFileSystem* mount_save_fs(const struct SavePathData* d) { - for (int i = 0; i < 16; i++) { + for (int i = 0; i < ARRAY_SIZE(g_save_cache); i++) { struct SaveCacheEntry* entry = &g_save_cache[i]; if (entry->ref_count && entry->app_id == d->app_id && entry->type == d->data_type && !memcmp(&entry->uid, &d->uid, sizeof(d->uid))) { entry->ref_count++; @@ -41,7 +480,7 @@ static FsFileSystem* mount_save_fs(const struct SavePathData* d) { } // save is not currently mounted, find the next free slot - for (int i = 0; i < 16; i++) { + for (int i = 0; i < ARRAY_SIZE(g_save_cache); i++) { struct SaveCacheEntry* entry = &g_save_cache[i]; if (!entry->ref_count) { FsSaveDataAttribute attr = {0}; @@ -79,7 +518,7 @@ static FsFileSystem* mount_save_fs(const struct SavePathData* d) { } static void unmount_save_fs(const struct SavePathData* d) { - for (int i = 0; i < 16; i++) { + for (int i = 0; i < ARRAY_SIZE(g_save_cache); i++) { struct SaveCacheEntry* entry = &g_save_cache[i]; if (entry->ref_count && entry->app_id == d->app_id && entry->type == d->data_type && !memcmp(&entry->uid, &d->uid, sizeof(d->uid))) { entry->ref_count--; @@ -103,19 +542,19 @@ static struct SavePathData get_type(const char* path) { if (!strncmp(path, "save:/bcat", strlen("save:/bcat"))) { data.data_type = FsSaveDataType_Bcat; data.space_id = FsSaveDataSpaceId_User; - data.type = SaveDirType_User; + data.type = SaveDirType_User1; } else if (!strncmp(path, "save:/cache", strlen("save:/cache"))) { data.data_type = FsSaveDataType_Cache; data.space_id = FsSaveDataSpaceId_SdUser; - data.type = SaveDirType_User; + data.type = SaveDirType_User1; } else if (!strncmp(path, "save:/device", strlen("save:/device"))) { data.data_type = FsSaveDataType_Device; data.space_id = FsSaveDataSpaceId_User; - data.type = SaveDirType_User; + data.type = SaveDirType_User1; } else if (!strncmp(path, "save:/system", strlen("save:/system"))) { data.data_type = FsSaveDataType_System; data.space_id = FsSaveDataSpaceId_System; - data.type = SaveDirType_User; + data.type = SaveDirType_User1; } else if (dilem && strlen(dilem) >= 33) { dilem++; char uid_buf[2][17]; @@ -127,16 +566,27 @@ static struct SavePathData get_type(const char* path) { data.data_type = FsSaveDataType_Account; data.space_id = FsSaveDataSpaceId_User; - data.type = SaveDirType_User; + data.type = SaveDirType_User1; dilem = strchr(dilem, '['); } - if (data.type && dilem && strlen(dilem) >= 17) { - dilem++; - data.app_id = strtoull(dilem, NULL, 0x10); - data.type = SaveDirType_App; - dilem += 17; - data.path_off = dilem - path; + if (data.type == SaveDirType_User1) { + if (strstr(path, "/zips")) { + data.type = SaveDirType_Zip; + } else if (strstr(path, "/files")) { + data.type = SaveDirType_File; + } + + if (data.type == SaveDirType_File || data.type == SaveDirType_Zip) { + // will need to correctly handle this, its good enough for now. + if (dilem && strlen(dilem) >= 17) { + dilem++; + data.app_id = strtoull(dilem, NULL, 0x10); + data.type = data.type == SaveDirType_File ? SaveDirType_FileApp : SaveDirType_ZipApp; + dilem += 17; + data.path_off = dilem - path; + } + } } } @@ -152,14 +602,14 @@ static void build_native_path(char out[static FS_MAX_PATH], const char* path, co } static int vfs_save_open(void* user, const char* path, enum FtpVfsOpenMode mode) { - if (mode != FtpVfsOpenMode_READ && !g_writable) { + struct VfsSaveFile* f = user; + f->data = get_type(path); + if (mode != FtpVfsOpenMode_READ && (!g_writable || f->data.type != SaveDirType_ZipApp)) { errno = EROFS; return -1; } - struct VfsSaveFile* f = user; - f->data = get_type(path); - if (f->data.type != SaveDirType_App) { + if (f->data.type != SaveDirType_FileApp && f->data.type != SaveDirType_ZipApp) { return -1; } @@ -170,41 +620,65 @@ static int vfs_save_open(void* user, const char* path, enum FtpVfsOpenMode mode) char nxpath[FS_MAX_PATH]; build_native_path(nxpath, path, &f->data); - if (vfs_fs_internal_open(fs, &f->fs_file, nxpath, mode)) { - unmount_save_fs(&f->data); - return -1; - } - f->fs = *fs; - f->is_valid = 1; - return 0; + if (f->data.type == SaveDirType_FileApp) { + if (vfs_fs_internal_open(fs, &f->fs_file, nxpath, mode)) { + unmount_save_fs(&f->data); + return -1; + } + + f->fs = *fs; + f->is_valid = 1; + return 0; + } else { + Result rc; + if (R_FAILED(rc = mmz_build_zip(&f->mz, fs, f->data.app_id, f->data.uid, f->data.space_id))) { + unmount_save_fs(&f->data); + return vfs_fs_set_errno(rc); + } + + f->fs = *fs; + f->is_valid = 1; + return 0; + } } static int vfs_save_read(void* user, void* buf, size_t size) { struct VfsSaveFile* f = user; - return vfs_fs_internal_read(&f->fs_file, buf, size); + if (f->data.type == SaveDirType_ZipApp) { + return mmz_read(&f->mz, buf, size); + } else { + return vfs_fs_internal_read(&f->fs_file, buf, size); + } } static int vfs_save_write(void* user, const void* buf, size_t size) { - if (!g_writable) { + struct VfsSaveFile* f = user; + if (!g_writable || f->data.type == SaveDirType_ZipApp) { errno = EROFS; return -1; } - struct VfsSaveFile* f = user; return vfs_fs_internal_write(&f->fs_file, buf, size); } -static int vfs_save_seek(void* user, size_t off) { +static int vfs_save_seek(void* user, const void* buf, size_t size, size_t off) { struct VfsSaveFile* f = user; - return vfs_fs_internal_seek(&f->fs_file, off); -} -static int vfs_save_fstat(void* user, const char* path, struct stat* st) { - struct VfsSaveFile* f = user; - char nxpath[FS_MAX_PATH]; - build_native_path(nxpath, path, &f->data); - return vfs_fs_internal_fstat(&f->fs, &f->fs_file, nxpath, st); + if (f->data.type == SaveDirType_ZipApp) { + if (off > f->mz.zip_off) { + errno = ESPIPE; + return -1; + } + + f->mz.new_crc32 = crc32CalculateWithSeed(f->mz.meta.crc32, buf, size); + f->mz.pending = false; + f->mz.off -= f->mz.zip_off - off; + f->mz.zip_off = off; + return 0; + } else { + return vfs_fs_internal_seek(&f->fs_file, off); + } } static int vfs_save_isfile_open(void* user) { @@ -217,7 +691,17 @@ static int vfs_save_close(void* user) { if (!vfs_save_isfile_open(f)) { return -1; } - vfs_fs_internal_close(&f->fs_file); + + if (f->data.type == SaveDirType_FileApp) { + vfs_fs_internal_close(&f->fs_file); + } else { + fsFileClose(&f->mz.fbuf_out); + fsFileClose(&f->mz.fin); + char nxpath[FS_MAX_PATH]; + mzz_build_temp_path(nxpath, f->data.app_id, f->data.uid, f->data.space_id); + fsFsDeleteFile(fsdev_wrapGetDeviceFileSystem("sdmc"), nxpath); + } + unmount_save_fs(&f->data); f->is_valid = 0; return 0; @@ -227,36 +711,45 @@ static int vfs_save_opendir(void* user, const char* path) { struct VfsSaveDir* f = user; f->data = get_type(path); - if (f->data.type == SaveDirType_Invalid) { - return -1; - } else if (f->data.type == SaveDirType_User) { - FsSaveDataFilter filter = {0}; - filter.filter_by_save_data_type = true; - filter.attr.save_data_type = f->data.data_type; - - if (f->data.data_type == FsSaveDataType_Account) { - filter.filter_by_user_id = true; - filter.attr.uid = f->data.uid; - } + switch (f->data.type) { + default: return -1; - Result rc; - if (R_FAILED(rc = fsOpenSaveDataInfoReaderWithFilter(&f->r, f->data.space_id, &filter))) { - log_file_fwrite("failed: fsOpenSaveDataInfoReaderWithFilter() 0x%X\n", rc); - return -1; - } - } else if (f->data.type == SaveDirType_App) { - FsFileSystem* fs = mount_save_fs(&f->data); - if (!fs) { - return -1; - } - f->fs = *fs; + case SaveDirType_Root: + case SaveDirType_User1: + break; - char nxpath[FS_MAX_PATH] = {"/"}; - build_native_path(nxpath, path, &f->data); - if (vfs_fs_internal_opendir(&f->fs, &f->fs_dir, nxpath)) { - unmount_save_fs(&f->data); - return -1; - } + case SaveDirType_File: + case SaveDirType_Zip: { + FsSaveDataFilter filter = {0}; + filter.filter_by_save_data_type = true; + filter.attr.save_data_type = f->data.data_type; + + if (f->data.data_type == FsSaveDataType_Account) { + filter.filter_by_user_id = true; + filter.attr.uid = f->data.uid; + } + + Result rc; + if (R_FAILED(rc = fsOpenSaveDataInfoReaderWithFilter(&f->r, f->data.space_id, &filter))) { + log_file_fwrite("failed: fsOpenSaveDataInfoReaderWithFilter() 0x%X\n", rc); + return -1; + } + } break; + + case SaveDirType_FileApp: { + FsFileSystem* fs = mount_save_fs(&f->data); + if (!fs) { + return -1; + } + f->fs = *fs; + + char nxpath[FS_MAX_PATH] = {"/"}; + build_native_path(nxpath, path, &f->data); + if (vfs_fs_internal_opendir(&f->fs, &f->fs_dir, nxpath)) { + unmount_save_fs(&f->data); + return -1; + } + } break; } f->index = 0; @@ -270,8 +763,7 @@ static const char* vfs_save_readdir(void* user, void* user_entry) { Result rc; switch (f->data.type) { - default: case SaveDirType_Invalid: - return NULL; + default: return NULL; case SaveDirType_Root: { if (f->index >= g_acc_count) { @@ -287,7 +779,16 @@ static const char* vfs_save_readdir(void* user, void* user_entry) { return entry->name; } - case SaveDirType_User: { + case SaveDirType_User1: { + static const char* e[] = { "files","zips" }; + if (f->index >= sizeof(e)/sizeof(e[0])) { + return NULL; + } + return e[f->index++]; + } + + case SaveDirType_File: + case SaveDirType_Zip: { s64 total; if (R_FAILED(rc = fsSaveDataInfoReaderRead(&f->r, &entry->info, 1, &total))) { log_file_fwrite("failed: fsSaveDataInfoReaderRead() 0x%X\n", rc); @@ -302,37 +803,56 @@ static const char* vfs_save_readdir(void* user, void* user_entry) { // this can fail if the game is no longer installed. NcmContentId id; struct AppName name; - if (f->data.data_type == FsSaveDataType_System) { - snprintf(entry->name, sizeof(entry->name), "[%016lX]", entry->info.system_save_data_id); + const char* ext = f->data.type == SaveDirType_File ? "" : ".zip"; + if (entry->info.save_data_type == FsSaveDataType_System || entry->info.save_data_type == FsSaveDataType_SystemBcat) { + snprintf(entry->name, sizeof(entry->name), "[%016lX]%s", entry->info.system_save_data_id, ext); } else if (R_FAILED(rc = get_app_name(entry->info.application_id, &id, &name))) { - snprintf(entry->name, sizeof(entry->name), "[%016lX]", entry->info.application_id); + snprintf(entry->name, sizeof(entry->name), "[%016lX]%s", entry->info.application_id, ext); } else { - snprintf(entry->name, sizeof(entry->name), "%s [%016lX]", name.str, entry->info.application_id); + if (f->data.type == SaveDirType_Zip) { + make_zip_string_valid(name.str); + } + snprintf(entry->name, sizeof(entry->name), "%s [%016lX]%s", name.str, entry->info.application_id, ext); } + log_file_fwrite("read entry %s data: %s space: %s %u index: %u rank %u\n", name.str, entry->info.save_data_index, entry->info.save_data_rank); return entry->name; } - case SaveDirType_App: { + case SaveDirType_FileApp: { return vfs_fs_internal_readdir(&f->fs_dir, &entry->fs_buf); } } } -static int vfs_save_dirstat(void* user, const void* user_entry, const char* path, struct stat* st) { +static int vfs_save_dirlstat(void* user, const void* user_entry, const char* path, struct stat* st) { struct VfsSaveDir* f = user; const struct VfsSaveDirEntry* entry = user_entry; + memset(st, 0, sizeof(*st)); - if (f->data.type == SaveDirType_App) { - char nxpath[FS_MAX_PATH]; - build_native_path(nxpath, path, &f->data); - return vfs_fs_internal_dirstat(&f->fs, &f->fs_dir, &entry->fs_buf, nxpath, st); - } else { - memset(st, 0, sizeof(*st)); - st->st_nlink = 1; - st->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH; + switch (f->data.type) { + default: return -1; + + case SaveDirType_Root: + case SaveDirType_User1: + case SaveDirType_File: + st->st_nlink = 1; + st->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH; + return 0; + + case SaveDirType_Zip: + // random size for the client, hopefully they don't take it seriously ;) + st->st_nlink = 1; + st->st_size = entry->info.size; + st->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH; + return 0; + + case SaveDirType_FileApp: { + char nxpath[FS_MAX_PATH]; + build_native_path(nxpath, path, &f->data); + return vfs_fs_internal_dirlstat(&f->fs, &f->fs_dir, &entry->fs_buf, nxpath, st); + } } - return 0; } static int vfs_save_isdir_open(void* user) { @@ -346,11 +866,18 @@ static int vfs_save_closedir(void* user) { return -1; } - if (f->data.type == SaveDirType_User) { - fsSaveDataInfoReaderClose(&f->r); - } else if (f->data.type == SaveDirType_App) { - vfs_fs_internal_closedir(&f->fs_dir); - unmount_save_fs(&f->data); + switch (f->data.type) { + default: break; + + case SaveDirType_File: + case SaveDirType_Zip: + fsSaveDataInfoReaderClose(&f->r); + break; + + case SaveDirType_FileApp: + vfs_fs_internal_closedir(&f->fs_dir); + unmount_save_fs(&f->data); + break; } memset(f, 0, sizeof(*f)); @@ -358,38 +885,50 @@ static int vfs_save_closedir(void* user) { } static int vfs_save_stat(const char* path, struct stat* st) { + const struct SavePathData data = get_type(path); memset(st, 0, sizeof(*st)); st->st_nlink = 1; - const struct SavePathData data = get_type(path); - if (data.type != SaveDirType_App) { - memset(st, 0, sizeof(*st)); - st->st_nlink = 1; - st->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH; - } else { - FsFileSystem* fs = mount_save_fs(&data); - if (!fs) { - return -1; + + switch (data.type) { + default: return -1; + + case SaveDirType_Root: + case SaveDirType_User1: + case SaveDirType_File: + case SaveDirType_Zip: + st->st_nlink = 1; + st->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH; + return 0; + + case SaveDirType_FileApp: { + FsFileSystem* fs = mount_save_fs(&data); + if (!fs) { + return -1; + } + + char nxpath[FS_MAX_PATH]; + build_native_path(nxpath, path, &data); + int rc = vfs_fs_internal_stat(fs, nxpath, st); + unmount_save_fs(&data); + return rc; } - char nxpath[FS_MAX_PATH]; - build_native_path(nxpath, path, &data); - int rc = vfs_fs_internal_stat(fs, nxpath, st); - unmount_save_fs(&data); - return rc; + case SaveDirType_ZipApp: { + // random size for the client, hopefully they don't take it seriously ;) + st->st_size = 1024*1024*64; + st->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH; + return 0; + } } return 0; } static int vfs_save_mkdir(const char* path) { - if (!g_writable) { - errno = EROFS; - return -1; - } - const struct SavePathData data = get_type(path); - if (data.type != SaveDirType_App) { + if (!g_writable || data.type != SaveDirType_FileApp) { + errno = EROFS; return -1; } @@ -406,13 +945,9 @@ static int vfs_save_mkdir(const char* path) { } static int vfs_save_unlink(const char* path) { - if (!g_writable) { - errno = EROFS; - return -1; - } - const struct SavePathData data = get_type(path); - if (data.type != SaveDirType_App) { + if (!g_writable || data.type != SaveDirType_FileApp) { + errno = EROFS; return -1; } @@ -429,13 +964,9 @@ static int vfs_save_unlink(const char* path) { } static int vfs_save_rmdir(const char* path) { - if (!g_writable) { - errno = EROFS; - return -1; - } - const struct SavePathData data = get_type(path); - if (data.type != SaveDirType_App) { + if (!g_writable || data.type != SaveDirType_FileApp) { + errno = EROFS; return -1; } @@ -452,17 +983,14 @@ static int vfs_save_rmdir(const char* path) { } static int vfs_save_rename(const char* src, const char* dst) { - if (!g_writable) { - errno = EROFS; - return -1; - } - const struct SavePathData data_src = get_type(src); const struct SavePathData data_dst = get_type(dst); - if (data_src.type != SaveDirType_App) { + if (!g_writable || data_src.type != SaveDirType_FileApp || data_dst.type != SaveDirType_FileApp) { + errno = EROFS; return -1; } + if (data_src.app_id != data_dst.app_id || memcmp(&data_src.uid, &data_dst.uid, sizeof(data_src.uid))) { return -1; } @@ -515,7 +1043,7 @@ void vfs_save_init(bool save_writable) { } void vfs_save_exit(void) { - for (int i = 0; i < 16; i++) { + for (int i = 0; i < ARRAY_SIZE(g_save_cache); i++) { struct SaveCacheEntry* entry = &g_save_cache[i]; if (entry->ref_count) { if (g_writable) { @@ -531,13 +1059,11 @@ const FtpVfs g_vfs_save = { .read = vfs_save_read, .write = vfs_save_write, .seek = vfs_save_seek, - .fstat = vfs_save_fstat, .close = vfs_save_close, .isfile_open = vfs_save_isfile_open, .opendir = vfs_save_opendir, .readdir = vfs_save_readdir, - .dirstat = vfs_save_dirstat, - .dirlstat = vfs_save_dirstat, + .dirlstat = vfs_save_dirlstat, .closedir = vfs_save_closedir, .isdir_open = vfs_save_isdir_open, .stat = vfs_save_stat, diff --git a/src/platform/nx/vfs/vfs_nx_save.h b/src/platform/nx/vfs/vfs_nx_save.h index 62a1163..c429449 100644 --- a/src/platform/nx/vfs/vfs_nx_save.h +++ b/src/platform/nx/vfs/vfs_nx_save.h @@ -13,8 +13,11 @@ extern "C" { enum SaveDirType { SaveDirType_Invalid, SaveDirType_Root, - SaveDirType_User, - SaveDirType_App, + SaveDirType_User1, + SaveDirType_File, + SaveDirType_Zip, + SaveDirType_FileApp, + SaveDirType_ZipApp, }; struct SavePathData { @@ -26,9 +29,52 @@ struct SavePathData { size_t path_off; }; +enum mmz_State { + mmz_State_Local, + mmz_State_Data, + mmz_State_Descriptor, + mmz_State_File, + mmz_State_End, +}; + +struct mmz_FileInfoMeta { + u32 crc32; + u32 size; + u32 string_len; +}; + +struct mmz_Data { + FsFileSystem* fs; + FsFile fbuf_out; + u32 fbuf_off; + + // meta for file_hdr and end_record. + u32 file_count; + u32 local_hdr_off; + u32 central_directory_size; + + // meta for current file. + // crc32 is filled out in mmz_State_Data, and then + // written to file when completed. + struct mmz_FileInfoMeta meta; + + enum mmz_State state; // current transfer state. + FsFile fin; // file input, used in mmz_State_Data. + u32 new_crc32; + u32 index; // file index. + u32 off; // relative offset for transfer state. + u32 zip_off; // output offset. + bool pending; // pending write completion to change state. +}; + struct VfsSaveFile { struct SavePathData data; - struct VfsFsFile fs_file; + + union { + struct mmz_Data mz; + struct VfsFsFile fs_file; + }; + FsFileSystem fs; bool is_valid; }; diff --git a/src/platform/nx/vfs/vfs_nx_stdio.c b/src/platform/nx/vfs/vfs_nx_stdio.c new file mode 100644 index 0000000..4839552 --- /dev/null +++ b/src/platform/nx/vfs/vfs_nx_stdio.c @@ -0,0 +1,252 @@ +/** + * Copyright 2024 TotalJustice. + * SPDX-License-Identifier: MIT + */ + +#include "ftpsrv_vfs.h" +#include "log/log.h" +#include +#include +#include +#include +#include +#include + +static void build_native_path(char out[static FS_MAX_PATH], const char* path) { + const char* dilem = strchr(path, ':'); + + if (dilem && strlen(dilem + 1)) { + strcpy(out, path); + } else { + snprintf(out, FS_MAX_PATH, "%s/", path); + } +} + +static int vfs_stdio_open(void* user, const char* path, enum FtpVfsOpenMode mode) { + struct VfsStdioFile* f = user; + char nxpath[FS_MAX_PATH]; + build_native_path(nxpath, path); + return vfs_stdio_internal_open(f, nxpath, mode); +} + +static int vfs_stdio_read(void* user, void* buf, size_t size) { + struct VfsStdioFile* f = user; + return vfs_stdio_internal_read(f, buf, size); +} + +static int vfs_stdio_write(void* user, const void* buf, size_t size) { + struct VfsStdioFile* f = user; + return vfs_stdio_internal_write(f, buf, size); +} + +static int vfs_stdio_seek(void* user, const void* buf, size_t size, size_t off) { + struct VfsStdioFile* f = user; + return vfs_stdio_internal_seek(f, off); +} + +static int vfs_stdio_isfile_open(void* user) { + struct VfsStdioFile* f = user; + return vfs_stdio_internal_isfile_open(f); +} + +static int vfs_stdio_close(void* user) { + struct VfsStdioFile* f = user; + return vfs_stdio_internal_close(f); +} + +static int vfs_stdio_opendir(void* user, const char* path) { + struct VfsStdioDir* f = user; + char nxpath[FS_MAX_PATH]; + build_native_path(nxpath, path); + return vfs_stdio_internal_opendir(f, nxpath); +} + +static const char* vfs_stdio_readdir(void* user, void* user_entry) { + struct VfsStdioDir* f = user; + struct VfsStdioDirEntry* entry = user_entry; + return vfs_stdio_internal_readdir(f, entry); +} + +static int vfs_stdio_dirlstat(void* user, const void* user_entry, const char* path, struct stat* st) { + struct VfsStdioDir* f = user; + const struct VfsStdioDirEntry* entry = user_entry; + char nxpath[FS_MAX_PATH]; + build_native_path(nxpath, path); + return vfs_stdio_internal_dirlstat(f, entry, nxpath, st); +} + +static int vfs_stdio_isdir_open(void* user) { + struct VfsStdioDir* f = user; + return vfs_stdio_internal_isdir_open(f); +} + +static int vfs_stdio_closedir(void* user) { + struct VfsStdioDir* f = user; + return vfs_stdio_internal_closedir(f); +} + +static int vfs_stdio_stat(const char* path, struct stat* st) { + char nxpath[FS_MAX_PATH]; + build_native_path(nxpath, path); + return vfs_stdio_internal_stat(nxpath, st); +} + +static int vfs_stdio_mkdir(const char* path) { + char nxpath[FS_MAX_PATH]; + build_native_path(nxpath, path); + return vfs_stdio_internal_mkdir(nxpath); +} + +static int vfs_stdio_unlink(const char* path) { + char nxpath[FS_MAX_PATH]; + build_native_path(nxpath, path); + return vfs_stdio_internal_unlink(nxpath); +} + +static int vfs_stdio_rmdir(const char* path) { + char nxpath[FS_MAX_PATH]; + build_native_path(nxpath, path); + return vfs_stdio_internal_rmdir(nxpath); +} + +static int vfs_stdio_rename(const char* src, const char* dst) { + char nxpath_src[FS_MAX_PATH]; + char nxpath_dst[FS_MAX_PATH]; + build_native_path(nxpath_src, src); + build_native_path(nxpath_dst, dst); + return vfs_stdio_internal_rename(nxpath_src, nxpath_dst); +} + +int vfs_stdio_internal_open(struct VfsStdioFile* f, const char* path, enum FtpVfsOpenMode mode) { + int flags = 0, args = 0; + + switch (mode) { + case FtpVfsOpenMode_READ: + flags = O_RDONLY; + args = 0; + break; + case FtpVfsOpenMode_WRITE: + flags = O_WRONLY | O_CREAT | O_TRUNC; + args = 0666; + break; + case FtpVfsOpenMode_APPEND: + flags = O_WRONLY | O_CREAT | O_APPEND; + args = 0666; + break; + } + + f->fd = open(path, flags, args); + if (f->fd >= 0) { + f->valid = 1; + } + return f->fd; +} + +int vfs_stdio_internal_read(struct VfsStdioFile* f, void* buf, size_t size) { + return read(f->fd, buf, size); +} + +int vfs_stdio_internal_write(struct VfsStdioFile* f, const void* buf, size_t size) { + return write(f->fd, buf, size); +} + +int vfs_stdio_internal_seek(struct VfsStdioFile* f, size_t off) { + return lseek(f->fd, off, SEEK_SET); +} + +int vfs_stdio_internal_isfile_open(struct VfsStdioFile* f) { + return f->valid && f->fd >= 0; +} + +int vfs_stdio_internal_close(struct VfsStdioFile* f) { + if (!vfs_stdio_isfile_open(f)) { + return -1; + } + int rc = close(f->fd); + f->fd = -1; + f->valid = 0; + return rc; +} + +int vfs_stdio_internal_opendir(struct VfsStdioDir* f, const char* path) { + f->fd = opendir(path); + if (!f->fd) { + return -1; + } + + f->is_valid = 1; + return 0; +} + +const char* vfs_stdio_internal_readdir(struct VfsStdioDir* f, struct VfsStdioDirEntry* entry) { + entry->d = readdir(f->fd); + if (!entry->d) { + return NULL; + } + + return entry->d->d_name; +} + +int vfs_stdio_internal_dirlstat(struct VfsStdioDir* f, const struct VfsStdioDirEntry* entry, const char* path, struct stat* st) { + return lstat(path, st); +} + +int vfs_stdio_internal_isdir_open(struct VfsStdioDir* f) { + return f->is_valid; +} + +int vfs_stdio_internal_closedir(struct VfsStdioDir* f) { + if (!vfs_stdio_isdir_open(f)) { + return -1; + } + if (f->fd) { + closedir(f->fd); + } + memset(f, 0, sizeof(*f)); + return 0; +} + +int vfs_stdio_internal_stat(const char* path, struct stat* st) { + return stat(path, st); +} + +int vfs_stdio_internal_mkdir(const char* path) { + return mkdir(path, 0777); +} + +int vfs_stdio_internal_unlink(const char* path) { + return unlink(path); +} + +int vfs_stdio_internal_rmdir(const char* path) { + return rmdir(path); +} + +int vfs_stdio_internal_rename(const char* src, const char* dst) { + const char* dilem_src = strchr(src, ':'); + const char* dilem_dst = strchr(dst, ':'); + if (!dilem_src || !dilem_dst || dilem_src - src != dilem_dst - dst || strncmp(src, dst, dilem_dst - dst)) { + return -1; + } + return rename(src, dst); +} + +const FtpVfs g_vfs_stdio = { + .open = vfs_stdio_open, + .read = vfs_stdio_read, + .write = vfs_stdio_write, + .seek = vfs_stdio_seek, + .close = vfs_stdio_close, + .isfile_open = vfs_stdio_isfile_open, + .opendir = vfs_stdio_opendir, + .readdir = vfs_stdio_readdir, + .dirlstat = vfs_stdio_dirlstat, + .closedir = vfs_stdio_closedir, + .isdir_open = vfs_stdio_isdir_open, + .stat = vfs_stdio_stat, + .lstat = vfs_stdio_stat, + .mkdir = vfs_stdio_mkdir, + .unlink = vfs_stdio_unlink, + .rmdir = vfs_stdio_rmdir, + .rename = vfs_stdio_rename, +}; diff --git a/src/platform/nx/vfs/vfs_nx_stdio.h b/src/platform/nx/vfs/vfs_nx_stdio.h new file mode 100644 index 0000000..56825ac --- /dev/null +++ b/src/platform/nx/vfs/vfs_nx_stdio.h @@ -0,0 +1,50 @@ +// Copyright 2024 TotalJustice. +// SPDX-License-Identifier: MIT +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +struct VfsStdioFile { + int fd; + int valid; +}; + +struct VfsStdioDir { + DIR* fd; + bool is_valid; +}; + +struct VfsStdioDirEntry { + struct dirent* d; +}; + +int vfs_stdio_internal_open(struct VfsStdioFile* f, const char* path, enum FtpVfsOpenMode mode); +int vfs_stdio_internal_read(struct VfsStdioFile* f, void* buf, size_t size); +int vfs_stdio_internal_write(struct VfsStdioFile* f, const void* buf, size_t size); +int vfs_stdio_internal_seek(struct VfsStdioFile* f, size_t off); +int vfs_stdio_internal_close(struct VfsStdioFile* f); +int vfs_stdio_internal_isfile_open(struct VfsStdioFile* f); + +int vfs_stdio_internal_opendir(struct VfsStdioDir* f, const char* path); +const char* vfs_stdio_internal_readdir(struct VfsStdioDir* f, struct VfsStdioDirEntry* entry); +int vfs_stdio_internal_dirlstat(struct VfsStdioDir* f, const struct VfsStdioDirEntry* entry, const char* path, struct stat* st); +int vfs_stdio_internal_closedir(struct VfsStdioDir* f); +int vfs_stdio_internal_isdir_open(struct VfsStdioDir* f); + +int vfs_stdio_internal_stat(const char* path, struct stat* st); +int vfs_stdio_internal_lstat(const char* path, struct stat* st); +int vfs_stdio_internal_mkdir(const char* path); +int vfs_stdio_internal_unlink(const char* path); +int vfs_stdio_internal_rmdir(const char* path); +int vfs_stdio_internal_rename(const char* path_src, const char* path_dst); + +struct FtpVfs; +const extern struct FtpVfs g_vfs_stdio; + +#ifdef __cplusplus +} +#endif diff --git a/src/platform/nx/vfs/vfs_nx_storage.c b/src/platform/nx/vfs/vfs_nx_storage.c new file mode 100644 index 0000000..2348b47 --- /dev/null +++ b/src/platform/nx/vfs/vfs_nx_storage.c @@ -0,0 +1,330 @@ +#include "ftpsrv_vfs.h" +#include "log/log.h" +#include +#include +#include + +#define min(x, y) ((x) < (y) ? (x) : (y)) + +struct StorageCacheEntry { + FsStorage s; + FsBisPartitionId id; + u32 ref_count; +}; + +struct MountEntry { + const char* name; + FsBisPartitionId id; +}; + +static const struct MountEntry BIS_NAMES[] = { + { "BootPartition1Root.bin", FsBisPartitionId_BootPartition1Root }, + { "BootPartition2Root.bin", FsBisPartitionId_BootPartition2Root }, + { "UserDataRoot.bin", FsBisPartitionId_UserDataRoot }, + { "BootConfigAndPackage2Part1.bin", FsBisPartitionId_BootConfigAndPackage2Part1 }, + { "BootConfigAndPackage2Part2.bin", FsBisPartitionId_BootConfigAndPackage2Part2 }, + { "BootConfigAndPackage2Part3.bin", FsBisPartitionId_BootConfigAndPackage2Part3 }, + { "BootConfigAndPackage2Part4.bin", FsBisPartitionId_BootConfigAndPackage2Part4 }, + { "BootConfigAndPackage2Part5.bin", FsBisPartitionId_BootConfigAndPackage2Part5 }, + { "BootConfigAndPackage2Part6.bin", FsBisPartitionId_BootConfigAndPackage2Part6 }, + // this causes a fatal part way through the transfer. + // i assume this is due to cal being protected by ams? + // { "CalibrationBinary.bin", FsBisPartitionId_CalibrationBinary }, + { "CalibrationFile.bin", FsBisPartitionId_CalibrationFile }, + { "SafeMode.bin", FsBisPartitionId_SafeMode }, + { "User.bin", FsBisPartitionId_User }, + { "System.bin", FsBisPartitionId_System }, + { "SystemProperEncryption.bin", FsBisPartitionId_SystemProperEncryption }, + { "SystemProperPartition.bin", FsBisPartitionId_SystemProperPartition }, + { "SignedSystemPartitionOnSafeMode.bin", FsBisPartitionId_SignedSystemPartitionOnSafeMode }, + { "DeviceTreeBlob.bin", FsBisPartitionId_DeviceTreeBlob }, + { "System0.bin", FsBisPartitionId_System0 }, +}; + +static struct StorageCacheEntry g_storage_cache[ARRAY_SIZE(BIS_NAMES)]; + +static const struct MountEntry* find_mount_entry(const char* path) { + if (strncmp(path, "bis:/", strlen("bis:/"))) { + return NULL; + } + + path += strlen("bis:/"); + + for (int i = 0; i < ARRAY_SIZE(BIS_NAMES); i++) { + if (!strcmp(path, BIS_NAMES[i].name)) { + return &BIS_NAMES[i]; + } + } + + return NULL; +} + +static FsStorage* mount_storage(FsBisPartitionId id) { + for (int i = 0; i < ARRAY_SIZE(g_storage_cache); i++) { + struct StorageCacheEntry* entry = &g_storage_cache[i]; + if (entry->ref_count && entry->id == id) { + entry->ref_count++; + return &entry->s; + } + } + + // save is not currently mounted, find the next free slot + for (int i = 0; i < ARRAY_SIZE(g_storage_cache); i++) { + struct StorageCacheEntry* entry = &g_storage_cache[i]; + if (!entry->ref_count) { + Result rc; + if (R_FAILED(rc = fsOpenBisStorage(&entry->s, id))) { + vfs_fs_set_errno(rc); + log_file_fwrite("failed: fsOpenBisStorage(%u) 0x%X\n", id, rc); + return NULL; + } + + entry->id = id; + entry->ref_count++; + return &entry->s; + } + } + + return NULL; +} + +static void unmount_storage(FsBisPartitionId id) { + for (int i = 0; i < ARRAY_SIZE(g_storage_cache); i++) { + struct StorageCacheEntry* entry = &g_storage_cache[i]; + if (entry->ref_count && entry->id == id) { + entry->ref_count--; + if (!entry->ref_count) { + fsStorageClose(&entry->s); + } + } + } +} + +static int vfs_storage_open(void* user, const char* path, enum FtpVfsOpenMode mode) { + struct VfsStorageFile* f = user; + + const struct MountEntry* e = find_mount_entry(path); + if (!e) { + return -1; + } + + FsStorage* s = mount_storage(e->id); + if (!s) { + return -1; + } + + const int rc = vfs_storage_internal_open(s, f, path, mode); + if (rc) { + unmount_storage(e->id); + } else { + f->id = e->id; + } + + return rc; +} + +static int vfs_storage_read(void* user, void* buf, size_t size) { + struct VfsStorageFile* f = user; + return vfs_storage_internal_read(f, buf, size); +} + +static int vfs_storage_write(void* user, const void* buf, size_t size) { + return -1; +} + +static int vfs_storage_seek(void* user, const void* buf, size_t size, size_t off) { + struct VfsStorageFile* f = user; + return vfs_storage_internal_seek(f, off); +} + +static int vfs_storage_isfile_open(void* user) { + struct VfsStorageFile* f = user; + return vfs_storage_internal_isfile_open(f); +} + +static int vfs_storage_close(void* user) { + struct VfsStorageFile* f = user; + if (!vfs_storage_isfile_open(f)) { + return -1; + } + + vfs_storage_internal_close(f); + unmount_storage(f->id); + return 0; +} + +static int vfs_storage_opendir(void* user, const char* path) { + if (strcmp(path, "bis:")) { + return -1; + } + + struct VfsStorageDir* f = user; + f->index = 0; + f->is_valid = 1; + return 0; +} + +static const char* vfs_storage_readdir(void* user, void* user_entry) { + struct VfsStorageDir* f = user; + struct VfsStorageDirEntry* entry = user_entry; + + if (f->index < ARRAY_SIZE(BIS_NAMES)) { + const struct MountEntry e = BIS_NAMES[f->index++]; + entry->id = e.id; + return e.name; + } else { + return NULL; + } +} + +static int vfs_storage_dirlstat(void* user, const void* user_entry, const char* path, struct stat* st) { + const struct VfsStorageDirEntry* entry = user_entry; + + FsStorage* s = mount_storage(entry->id); + if (!s) { + return -1; + } + + const int rc = vfs_storage_internal_stat(s, path, st); + unmount_storage(entry->id); + return rc; +} + +static int vfs_storage_isdir_open(void* user) { + struct VfsStorageDir* f = user; + return f->is_valid; +} + +static int vfs_storage_closedir(void* user) { + struct VfsStorageDir* f = user; + if (!vfs_storage_isdir_open(f)) { + return -1; + } + memset(f, 0, sizeof(*f)); + return 0; +} + +static int vfs_storage_stat(const char* path, struct stat* st) { + const struct MountEntry* e = find_mount_entry(path); + if (!e) { + return -1; + } + + FsStorage* s = mount_storage(e->id); + if (!s) { + return -1; + } + + const int rc = vfs_storage_internal_stat(s, path, st); + unmount_storage(e->id); + return rc; +} + +static int vfs_storage_mkdir(const char* path) { + return -1; +} + +static int vfs_storage_unlink(const char* path) { + return -1; +} + +static int vfs_storage_rmdir(const char* path) { + return -1; +} + +static int vfs_storage_rename(const char* src, const char* dst) { + return -1; +} + +void vfs_storage_init(void) { +} + +void vfs_storage_exit(void) { + for (int i = 0; i < ARRAY_SIZE(g_storage_cache); i++) { + struct StorageCacheEntry* entry = &g_storage_cache[i]; + if (entry->ref_count) { + entry->ref_count = 0; + fsStorageClose(&entry->s); + } + } +} + +int vfs_storage_internal_open(FsStorage* s, struct VfsStorageFile* f, const char* path, enum FtpVfsOpenMode mode) { + if (mode != FtpVfsOpenMode_READ) { + return -1; + } + + Result rc; + if (R_FAILED(rc = fsStorageGetSize(s, &f->size))) { + return -1; + } + + f->s = *s; + f->is_valid = true; + f->off = 0; + return 0; +} + +int vfs_storage_internal_read(struct VfsStorageFile* f, void* buf, size_t size) { + size = min(size, f->size - f->off); + + Result rc; + if (R_FAILED(rc = fsStorageRead(&f->s, f->off, buf, size))) { + return -1; + } + + f->off += size; + return size; +} + +int vfs_storage_internal_seek(struct VfsStorageFile* f, size_t off) { + f->off = off; + return 0; +} + +int vfs_storage_internal_close(struct VfsStorageFile* f) { + f->is_valid = 0; + return 0; +} + +int vfs_storage_internal_isfile_open(struct VfsStorageFile* f) { + return f->is_valid; +} + +int vfs_storage_internal_stat(FsStorage* s, const char* path, struct stat* st) { + Result rc; + s64 size; + if (R_FAILED(rc = fsStorageGetSize(s, &size))) { + return -1; + } + + memset(st, 0, sizeof(*st)); + st->st_nlink = 1; + st->st_size = size; + st->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH; + return 0; +} + +int vfs_storage_internal_lstat(FsStorage* s, const char* path, struct stat* st) { + return vfs_storage_internal_stat(s, path, st); +} + +const FtpVfs g_vfs_storage = { + .open = vfs_storage_open, + .read = vfs_storage_read, + .write = vfs_storage_write, + .seek = vfs_storage_seek, + .close = vfs_storage_close, + .isfile_open = vfs_storage_isfile_open, + .opendir = vfs_storage_opendir, + .readdir = vfs_storage_readdir, + .dirlstat = vfs_storage_dirlstat, + .closedir = vfs_storage_closedir, + .isdir_open = vfs_storage_isdir_open, + .stat = vfs_storage_stat, + .lstat = vfs_storage_stat, + .mkdir = vfs_storage_mkdir, + .unlink = vfs_storage_unlink, + .rmdir = vfs_storage_rmdir, + .rename = vfs_storage_rename, +}; diff --git a/src/platform/nx/vfs/vfs_nx_storage.h b/src/platform/nx/vfs/vfs_nx_storage.h new file mode 100644 index 0000000..6b986d9 --- /dev/null +++ b/src/platform/nx/vfs/vfs_nx_storage.h @@ -0,0 +1,48 @@ +// Copyright 2024 TotalJustice. +// SPDX-License-Identifier: MIT +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +struct VfsStorageFile { + FsStorage s; + s64 off; + s64 size; + FsBisPartitionId id; + bool is_valid; +}; + +struct VfsStorageDir { + size_t index; + bool is_valid; +}; + +struct VfsStorageDirEntry { + FsBisPartitionId id; +}; + +int vfs_storage_set_errno(Result rc); +int vfs_storage_internal_open(FsStorage* s, struct VfsStorageFile* f, const char* path, enum FtpVfsOpenMode mode); +int vfs_storage_internal_read(struct VfsStorageFile* f, void* buf, size_t size); +int vfs_storage_internal_seek(struct VfsStorageFile* f, size_t off); +int vfs_storage_internal_close(struct VfsStorageFile* f); +int vfs_storage_internal_isfile_open(struct VfsStorageFile* f); + +int vfs_storage_internal_stat(FsStorage* s, const char* path, struct stat* st); +int vfs_storage_internal_lstat(FsStorage* s, const char* path, struct stat* st); + +void vfs_storage_init(void); +void vfs_storage_exit(void); + +struct FtpVfs; +const extern struct FtpVfs g_vfs_storage; + +#ifdef __cplusplus +} +#endif diff --git a/src/platform/nx/vfs_nx.c b/src/platform/nx/vfs_nx.c index de439d2..0a34110 100644 --- a/src/platform/nx/vfs_nx.c +++ b/src/platform/nx/vfs_nx.c @@ -37,11 +37,7 @@ static int vfs_none_write(void* user, const void* buf, size_t size) { return set_errno_and_return_minus1(); } -static int vfs_none_seek(void* user, size_t off) { - return set_errno_and_return_minus1(); -} - -static int vfs_none_fstat(void* user, const char* path, struct stat* st) { +static int vfs_none_seek(void* user, const void* buf, size_t size, size_t off) { return set_errno_and_return_minus1(); } @@ -61,7 +57,7 @@ static const char* vfs_none_readdir(void* user, void* user_entry) { return NULL; } -static int vfs_none_dirstat(void* user, const void* user_entry, const char* path, struct stat* st) { +static int vfs_none_dirlstat(void* user, const void* user_entry, const char* path, struct stat* st) { return set_errno_and_return_minus1(); } @@ -98,13 +94,11 @@ static const FtpVfs g_vfs_none = { .read = vfs_none_read, .write = vfs_none_write, .seek = vfs_none_seek, - .fstat = vfs_none_fstat, .close = vfs_none_close, .isfile_open = vfs_none_isfile_open, .opendir = vfs_none_opendir, .readdir = vfs_none_readdir, - .dirstat = vfs_none_dirstat, - .dirlstat = vfs_none_dirstat, + .dirlstat = vfs_none_dirlstat, .closedir = vfs_none_closedir, .isdir_open = vfs_none_isdir_open, .stat = vfs_none_stat, @@ -120,7 +114,10 @@ static const FtpVfs* g_vfs[] = { [VFS_TYPE_ROOT] = &g_vfs_root, [VFS_TYPE_FS] = &g_vfs_fs, [VFS_TYPE_SAVE] = &g_vfs_save, + [VFS_TYPE_STORAGE] = &g_vfs_storage, + [VFS_TYPE_GC] = &g_vfs_gc, #if USE_USBHSFS + [VFS_TYPE_STDIO] = &g_vfs_stdio, [VFS_TYPE_HDD] = &g_vfs_hdd, #endif }; @@ -176,12 +173,8 @@ int ftp_vfs_write(struct FtpVfsFile* f, const void* buf, size_t size) { return g_vfs[f->type]->write(&f->root, buf, size); } -int ftp_vfs_seek(struct FtpVfsFile* f, size_t off) { - return g_vfs[f->type]->seek(&f->root, off); -} - -int ftp_vfs_fstat(struct FtpVfsFile* f, const char* path, struct stat* st) { - return g_vfs[f->type]->fstat(&f->root, fix_path(path, f->type), st); +int ftp_vfs_seek(struct FtpVfsFile* f, const void* buf, size_t size, size_t off) { + return g_vfs[f->type]->seek(&f->root, buf, size, off); } int ftp_vfs_close(struct FtpVfsFile* f) { @@ -203,12 +196,8 @@ const char* ftp_vfs_readdir(struct FtpVfsDir* f, struct FtpVfsDirEntry* entry) { return g_vfs[f->type]->readdir(&f->root, &entry->root); } -int ftp_vfs_dirstat(struct FtpVfsDir* f, const struct FtpVfsDirEntry* entry, const char* path, struct stat* st) { - return g_vfs[f->type]->dirstat(&f->root, &entry->root, fix_path(path, f->type), st); -} - int ftp_vfs_dirlstat(struct FtpVfsDir* f, const struct FtpVfsDirEntry* entry, const char* path, struct stat* st) { - return ftp_vfs_dirstat(f, entry, path, st); + return g_vfs[f->type]->dirlstat(&f->root, &entry->root, fix_path(path, f->type), st); } int ftp_vfs_closedir(struct FtpVfsDir* f) { @@ -294,64 +283,79 @@ static const u32 g_nacpLanguageTable[15] = { static u8 g_lang_index; -Result get_app_name(u64 app_id, NcmContentId* id, struct AppName* name) { +Result get_app_name2(u64 app_id, NcmContentMetaDatabase* db, NcmContentStorage* cs, NcmContentId* id, struct AppName* name) { Result rc; + NcmContentMetaKey key; + s32 entries_total; + s32 entries_written; + if (R_FAILED(rc = ncmContentMetaDatabaseList(db, &entries_total, &entries_written, &key, 1, NcmContentMetaType_Application, app_id, 0, UINT64_MAX, NcmContentInstallType_Full))) { + return rc; + } - for (int i = 0; i < NCM_SIZE; i++) { - NcmContentMetaKey key; - s32 entries_total; - s32 entries_written; - if (R_FAILED(rc = ncmContentMetaDatabaseList(&g_db[i], &entries_total, &entries_written, &key, 1, NcmContentMetaType_Application, app_id, 0, UINT64_MAX, NcmContentInstallType_Full))) { - continue; - } - - if (R_FAILED(rc = ncmContentMetaDatabaseGetContentIdByType(&g_db[i], id, &key, NcmContentType_Control))) { - continue; - } + if (R_FAILED(rc = ncmContentMetaDatabaseGetContentIdByType(db, id, &key, NcmContentType_Control))) { + return rc; + } - char nxpath[FS_MAX_PATH] = {0}; - if (R_FAILED(rc = ncmContentStorageGetPath(&g_cs[i], nxpath, sizeof(nxpath), id))) { - continue; - } + char nxpath[FS_MAX_PATH]; + if (R_FAILED(rc = ncmContentStorageGetPath(cs, nxpath, sizeof(nxpath), id))) { + return rc; + } - FsFileSystem fs; - if (R_FAILED(rc = fsOpenFileSystemWithId(&fs, key.id, FsFileSystemType_ContentControl, nxpath, FsContentAttributes_All))) { - continue; - } + FsFileSystem fs; + if (R_FAILED(rc = fsOpenFileSystemWithId(&fs, key.id, FsFileSystemType_ContentControl, nxpath, FsContentAttributes_All))) { + return rc; + } - strcpy(nxpath, "/control.nacp"); - FsFile file; - if (R_FAILED(rc = fsFsOpenFile(&fs, nxpath, FsOpenMode_Read, &file))) { - fsFsClose(&fs); - continue; - } + strcpy(nxpath, "/control.nacp"); + FsFile file; + if (R_FAILED(rc = fsFsOpenFile(&fs, nxpath, FsOpenMode_Read, &file))) { + fsFsClose(&fs); + return rc; + } - name->str[0] = '\0'; - s64 off = g_lang_index * sizeof(NacpLanguageEntry); - u64 bytes_read; - rc = fsFileRead(&file, off, name->str, sizeof(name->str), 0, &bytes_read); - if (name->str[0] == '\0') { - for (int i = 0; i < 16; i++) { - off = i * sizeof(NacpLanguageEntry); - rc = fsFileRead(&file, off, name->str, sizeof(name->str), 0, &bytes_read); - if (name->str[0] != '\0') { - break; - } + name->str[0] = '\0'; + s64 off = g_lang_index * sizeof(NacpLanguageEntry); + u64 bytes_read; + rc = fsFileRead(&file, off, name->str, sizeof(name->str), 0, &bytes_read); + if (name->str[0] == '\0') { + for (int i = 0; i < 16; i++) { + off = i * sizeof(NacpLanguageEntry); + rc = fsFileRead(&file, off, name->str, sizeof(name->str), 0, &bytes_read); + if (name->str[0] != '\0') { + break; } } - fsFileClose(&file); - fsFsClose(&fs); + } - if (R_FAILED(rc) || bytes_read != sizeof(name->str)) { - continue; - } + fsFileClose(&file); + fsFsClose(&fs); + return rc; +} - return rc; +Result get_app_name(u64 app_id, NcmContentId* id, struct AppName* name) { + Result rc; + + for (int i = 0; i < NCM_SIZE; i++) { + if (R_SUCCEEDED(rc = get_app_name2(app_id, &g_db[i], &g_cs[i], id, name))) { + return rc; + } } return rc; } +struct MountEntry { + const char* name; + FsBisPartitionId id; +}; + +static const struct MountEntry BIS_NAMES[] = { + { "bis_calibration_file", FsBisPartitionId_CalibrationFile }, + { "bis_safe_mode", FsBisPartitionId_SafeMode }, + { "bis_user", FsBisPartitionId_User }, + { "bis_system", FsBisPartitionId_System }, +}; + void vfs_nx_init(bool enable_devices, bool save_writable, bool mount_bis) { g_enabled_devices = enable_devices; if (g_enabled_devices) { @@ -374,41 +378,84 @@ void vfs_nx_init(bool enable_devices, bool save_writable, bool mount_bis) { vfs_nx_add_device("sdmc", VFS_TYPE_FS); - if (!fsdev_wrapMountImage("image_nand", FsImageDirectoryId_Nand)) { - vfs_nx_add_device("image_nand", VFS_TYPE_FS); + if (!fsdev_wrapMountImage("album_nand", FsImageDirectoryId_Nand)) { + vfs_nx_add_device("album_nand", VFS_TYPE_FS); } - if (!fsdev_wrapMountImage("image_sd", FsImageDirectoryId_Sd)) { - vfs_nx_add_device("image_sd", VFS_TYPE_FS); + if (!fsdev_wrapMountImage("album_sd", FsImageDirectoryId_Sd)) { + vfs_nx_add_device("album_sd", VFS_TYPE_FS); } + // bis storage + vfs_storage_init(); + vfs_nx_add_device("bis", VFS_TYPE_STORAGE); + + // bis fs if (mount_bis) { - if (!fsdev_wrapMountBis("bis_system", FsBisPartitionId_System)) { - vfs_nx_add_device("bis_system", VFS_TYPE_FS); - } - if (!fsdev_wrapMountBis("bis_safe", FsBisPartitionId_SafeMode)) { - vfs_nx_add_device("bis_safe", VFS_TYPE_FS); - } - if (!fsdev_wrapMountBis("bis_user", FsBisPartitionId_User)) { - vfs_nx_add_device("bis_user", VFS_TYPE_FS); + for (int i = 0; i < ARRAY_SIZE(BIS_NAMES); i++) { + if (!fsdev_wrapMountBis(BIS_NAMES[i].name, BIS_NAMES[i].id)) { + vfs_nx_add_device(BIS_NAMES[i].name, VFS_TYPE_FS); + } } } + // content storage + FsFileSystem fs; + if (R_SUCCEEDED(fsOpenContentStorageFileSystem(&fs, FsContentStorageId_System))) { + fsdev_wrapMountDevice("content_system", NULL, fs, true); + vfs_nx_add_device("content_system", VFS_TYPE_FS); + } + if (R_SUCCEEDED(fsOpenContentStorageFileSystem(&fs, FsContentStorageId_User))) { + fsdev_wrapMountDevice("content_user", NULL, fs, true); + vfs_nx_add_device("content_user", VFS_TYPE_FS); + } + if (R_SUCCEEDED(fsOpenContentStorageFileSystem(&fs, FsContentStorageId_SdCard))) { + fsdev_wrapMountDevice("content_sdcard", NULL, fs, true); + vfs_nx_add_device("content_sdcard", VFS_TYPE_FS); + } + if (R_SUCCEEDED(fsOpenContentStorageFileSystem(&fs, FsContentStorageId_System0))) { + fsdev_wrapMountDevice("content_system0", NULL, fs, true); + vfs_nx_add_device("content_system0", VFS_TYPE_FS); + } + + // custom storage + if (R_SUCCEEDED(fsOpenCustomStorageFileSystem(&fs, FsCustomStorageId_System))) { + fsdev_wrapMountDevice("custom_system", NULL, fs, true); + vfs_nx_add_device("custom_system", VFS_TYPE_FS); + } + if (R_SUCCEEDED(fsOpenCustomStorageFileSystem(&fs, FsCustomStorageId_SdCard))) { + fsdev_wrapMountDevice("custom_sd", NULL, fs, true); + vfs_nx_add_device("custom_sd", VFS_TYPE_FS); + } + // add some shortcuts. FsFileSystem* sdmc = fsdev_wrapGetDeviceFileSystem("sdmc"); if (sdmc) { if (!fsdev_wrapMountDevice("switch", "/switch", *sdmc, false)) { vfs_nx_add_device("switch", VFS_TYPE_FS); } - if (!fsdev_wrapMountDevice("contents", "/atmosphere/contents", *sdmc, false)) { - vfs_nx_add_device("contents", VFS_TYPE_FS); + if (!fsdev_wrapMountDevice("atmosphere_contents", "/atmosphere/contents", *sdmc, false)) { + vfs_nx_add_device("atmosphere_contents", VFS_TYPE_FS); } } + if (R_SUCCEEDED(vfs_gc_init())) { + vfs_nx_add_device("gc", VFS_TYPE_GC); + } + vfs_save_init(save_writable); vfs_nx_add_device("save", VFS_TYPE_SAVE); #if USE_USBHSFS - vfs_hdd_init(); - vfs_nx_add_device("hdd", VFS_TYPE_HDD); + if (R_SUCCEEDED(romfsMountFromCurrentProcess("romfs"))) { + vfs_nx_add_device("romfs", VFS_TYPE_STDIO); + } + + if (R_SUCCEEDED(romfsMountDataStorageFromProgram(0x0100000000001000, "romfs_qlaunch"))) { + vfs_nx_add_device("romfs_qlaunch", VFS_TYPE_STDIO); + } + + if (R_SUCCEEDED(vfs_hdd_init())) { + vfs_nx_add_device("hdd", VFS_TYPE_HDD); + } #endif vfs_root_init(g_device, &g_device_count); @@ -428,11 +475,15 @@ void vfs_nx_init(bool enable_devices, bool save_writable, bool mount_bis) { void vfs_nx_exit(void) { if (g_enabled_devices) { + vfs_gc_exit(); + vfs_storage_exit(); vfs_save_exit(); + vfs_root_exit(); #if USE_USBHSFS + romfsUnmount("romfs_qlaunch"); + romfsUnmount("romfs"); vfs_hdd_exit(); #endif - vfs_root_exit(); for (int i = 0; i < NCM_SIZE; i++) { ncmContentStorageClose(&g_cs[i]); @@ -444,7 +495,7 @@ void vfs_nx_exit(void) { } void vfs_nx_add_device(const char* name, enum VFS_TYPE type) { - if (g_device_count >= 32) { + if (g_device_count >= DEVICE_NUM) { return; } diff --git a/src/platform/nx/vfs_nx.h b/src/platform/nx/vfs_nx.h index d2edaa1..8069f1a 100644 --- a/src/platform/nx/vfs_nx.h +++ b/src/platform/nx/vfs_nx.h @@ -13,7 +13,10 @@ extern "C" { #include "vfs/vfs_nx_root.h" #include "vfs/vfs_nx_fs.h" #include "vfs/vfs_nx_save.h" +#include "vfs/vfs_nx_storage.h" +#include "vfs/vfs_nx_gc.h" #if USE_USBHSFS +#include "vfs/vfs_nx_stdio.h" #include "vfs/vfs_nx_hdd.h" #endif @@ -21,9 +24,12 @@ enum VFS_TYPE { VFS_TYPE_NONE, VFS_TYPE_ROOT, // list root devices VFS_TYPE_FS, // list native fs devices - VFS_TYPE_SAVE, // list xci, uses ncm + VFS_TYPE_SAVE, // list saves, uses fs + VFS_TYPE_STORAGE, // list read-only bis storage + VFS_TYPE_GC, // list cert and secure partition #if USE_USBHSFS - VFS_TYPE_HDD, // list xci, uses ncm + VFS_TYPE_STDIO, // used for romfs and hdd + VFS_TYPE_HDD, // list hdd, uses unistd #endif }; @@ -33,7 +39,10 @@ struct FtpVfsFile { struct VfsRootFile root; struct VfsFsFile fs; struct VfsSaveFile save; + struct VfsStorageFile storage; + struct VfsGcFile gc; #if USE_USBHSFS + struct VfsStdioFile stdio; struct VfsHddFile usbhsfs; #endif }; @@ -45,7 +54,10 @@ struct FtpVfsDir { struct VfsRootDir root; struct VfsFsDir fs; struct VfsSaveDir save; + struct VfsStorageDir storage; + struct VfsGcDir gc; #if USE_USBHSFS + struct VfsStdioDir stdio; struct VfsHddDir usbhsfs; #endif }; @@ -57,7 +69,10 @@ struct FtpVfsDirEntry { struct VfsRootDirEntry root; struct VfsFsDirEntry fs; struct VfsSaveDirEntry save; + struct VfsStorageDirEntry storage; + struct VfsGcDirEntry gc; #if USE_USBHSFS + struct VfsStdioDirEntry stdio; struct VfsHddDirEntry usbhsfs; #endif }; @@ -72,15 +87,13 @@ typedef struct FtpVfs { int (*open)(void* user, const char* path, enum FtpVfsOpenMode mode); int (*read)(void* user, void* buf, size_t size); int (*write)(void* user, const void* buf, size_t size); - int (*seek)(void* user, size_t off); - int (*fstat)(void* user, const char* path, struct stat* st); + int (*seek)(void* user, const void* buf, size_t size, size_t off); int (*close)(void* user); int (*isfile_open)(void* user); // vfs_dir int (*opendir)(void* user, const char* path); const char* (*readdir)(void* user, void* user_entry); - int (*dirstat)(void* user, const void* user_entry, const char* path, struct stat* st); int (*dirlstat)(void* user, const void* user_entry, const char* path, struct stat* st); int (*closedir)(void* user); int (*isdir_open)(void* user); @@ -99,6 +112,9 @@ void vfs_nx_exit(void); void vfs_nx_add_device(const char* name, enum VFS_TYPE type); Result get_app_name(u64 app_id, NcmContentId* id, struct AppName* name); +Result get_app_name2(u64 app_id, NcmContentMetaDatabase* db, NcmContentStorage* cs, NcmContentId* id, struct AppName* name); + +#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) #ifdef __cplusplus } diff --git a/src/platform/stdio/vfs_stdio.c b/src/platform/stdio/vfs_stdio.c index 720bbe4..df351bc 100644 --- a/src/platform/stdio/vfs_stdio.c +++ b/src/platform/stdio/vfs_stdio.c @@ -44,14 +44,10 @@ int ftp_vfs_write(struct FtpVfsFile* f, const void* buf, size_t size) { return fwrite(buf, 1, size, f->fd); } -int ftp_vfs_seek(struct FtpVfsFile* f, size_t off) { +int ftp_vfs_seek(struct FtpVfsFile* f, const void* buf, size_t size, size_t off) { return fseek(f->fd, off, SEEK_SET); } -int ftp_vfs_fstat(struct FtpVfsFile* f, const char* path, struct stat* st) { - return fstat(fileno(f->fd), st); -} - int ftp_vfs_close(struct FtpVfsFile* f) { if (!ftp_vfs_isfile_open(f)) { return -1; @@ -81,10 +77,6 @@ const char* ftp_vfs_readdir(struct FtpVfsDir* f, struct FtpVfsDirEntry* entry) { return entry->buf->d_name; } -int ftp_vfs_dirstat(struct FtpVfsDir* f, const struct FtpVfsDirEntry* entry, const char* path, struct stat* st) { - return stat(path, st); -} - int ftp_vfs_dirlstat(struct FtpVfsDir* f, const struct FtpVfsDirEntry* entry, const char* path, struct stat* st) { return lstat(path, st); } diff --git a/src/platform/unistd/vfs_unistd.c b/src/platform/unistd/vfs_unistd.c index 7e300ed..e55aa3e 100644 --- a/src/platform/unistd/vfs_unistd.c +++ b/src/platform/unistd/vfs_unistd.c @@ -50,14 +50,10 @@ int ftp_vfs_write(struct FtpVfsFile* f, const void* buf, size_t size) { return write(f->fd, buf, size); } -int ftp_vfs_seek(struct FtpVfsFile* f, size_t off) { +int ftp_vfs_seek(struct FtpVfsFile* f, const void* buf, size_t size, size_t off) { return lseek(f->fd, off, SEEK_SET); } -int ftp_vfs_fstat(struct FtpVfsFile* f, const char* path, struct stat* st) { - return fstat(f->fd, st); -} - int ftp_vfs_close(struct FtpVfsFile* f) { int rc = 0; if (ftp_vfs_isfile_open(f)) { @@ -88,10 +84,6 @@ const char* ftp_vfs_readdir(struct FtpVfsDir* f, struct FtpVfsDirEntry* entry) { return entry->buf->d_name; } -int ftp_vfs_dirstat(struct FtpVfsDir* f, const struct FtpVfsDirEntry* entry, const char* path, struct stat* st) { - return stat(path, st); -} - int ftp_vfs_dirlstat(struct FtpVfsDir* f, const struct FtpVfsDirEntry* entry, const char* path, struct stat* st) { return lstat(path, st); }