diff --git a/binding-sandbox/core.h b/binding-sandbox/core.h index 9c8969a8..be41542a 100644 --- a/binding-sandbox/core.h +++ b/binding-sandbox/core.h @@ -24,9 +24,11 @@ #include #include "../binding-sandbox/sandbox.h" +#include "filesystem.h" namespace mkxp_retro { extern boost::optional sandbox; + extern boost::optional fs; extern retro_log_printf_t log_printf; extern retro_video_refresh_t video_refresh; diff --git a/binding-sandbox/sandbox.cpp b/binding-sandbox/sandbox.cpp index 649c8915..ed9b4f5d 100644 --- a/binding-sandbox/sandbox.cpp +++ b/binding-sandbox/sandbox.cpp @@ -61,7 +61,7 @@ void sandbox::sandbox_free(usize ptr) { w2c_ruby_mkxp_sandbox_free(RB, ptr); } -sandbox::sandbox(const char *game_path) : ruby(new struct w2c_ruby), wasi(new wasi_t(ruby, game_path)), bindings(ruby), yielding(false) { +sandbox::sandbox() : ruby(new struct w2c_ruby), wasi(new wasi_t(ruby)), bindings(ruby), yielding(false) { try { // Initialize the sandbox wasm_rt_init(); diff --git a/binding-sandbox/sandbox.h b/binding-sandbox/sandbox.h index f3a18b71..e1b3c524 100644 --- a/binding-sandbox/sandbox.h +++ b/binding-sandbox/sandbox.h @@ -80,7 +80,7 @@ namespace mkxp_sandbox { public: inline struct mkxp_sandbox::bindings &operator*() noexcept { return *bindings; } inline struct mkxp_sandbox::bindings *operator->() noexcept { return &*bindings; } - sandbox(const char *game_path); + sandbox(); ~sandbox(); // Internal utility method of the `SANDBOX_YIELD` macro. diff --git a/binding-sandbox/wasi.cpp b/binding-sandbox/wasi.cpp index b246abda..3eb302e5 100644 --- a/binding-sandbox/wasi.cpp +++ b/binding-sandbox/wasi.cpp @@ -54,6 +54,14 @@ static inline size_t strlen_safe(const char *str, size_t max_length) { return ptr == NULL ? max_length : ptr - str; } +std::string *wasi_file_entry::dir_handle() { + return (std::string *)handle; +} + +struct FileSystem::File *wasi_file_entry::file_handle() { + return (struct FileSystem::File *)handle; +} + struct wasi_zip_handle *wasi_file_entry::zip_handle() { return (wasi_zip_handle *)handle; } @@ -119,8 +127,8 @@ wasi_zip_file_container::~wasi_zip_file_container() { } } -wasi_t::w2c_wasi__snapshot__preview1(std::shared_ptr ruby, const char *game_path) : ruby(ruby), dist(new wasi_zip_container(mkxp_retro_dist_zip, mkxp_retro_dist_zip_len, ZIP_RDONLY)), game(new wasi_zip_container(game_path, ZIP_RDONLY)) { - if (dist->zip == NULL || game->zip == NULL) { +wasi_t::w2c_wasi__snapshot__preview1(std::shared_ptr ruby) : ruby(ruby), dist(new wasi_zip_container(mkxp_retro_dist_zip, mkxp_retro_dist_zip_len, ZIP_RDONLY)) { + if (dist->zip == NULL) { throw SandboxTrapException(); } @@ -128,20 +136,8 @@ wasi_t::w2c_wasi__snapshot__preview1(std::shared_ptr ruby, cons fdtable.push_back({.type = wasi_fd_type::STDIN}); fdtable.push_back({.type = wasi_fd_type::STDOUT}); fdtable.push_back({.type = wasi_fd_type::STDERR}); - - struct wasi_zip_handle *dist_handle = (struct wasi_zip_handle *)std::malloc(sizeof(struct wasi_zip_handle)); - if (dist_handle == NULL) { - throw SandboxOutOfMemoryException(); - } - new(dist_handle) (struct wasi_zip_handle){.zip = dist, .path = "/mkxp-retro-dist"}; - fdtable.push_back({.type = wasi_fd_type::ZIP, .handle = dist_handle}); - - struct wasi_zip_handle *game_handle = (struct wasi_zip_handle *)std::malloc(sizeof(struct wasi_zip_handle)); - if (game_handle == NULL) { - throw SandboxOutOfMemoryException(); - } - new(game_handle) (struct wasi_zip_handle){.zip = game, .path = "/mkxp-retro-game"}; - fdtable.push_back({.type = wasi_fd_type::ZIP, .handle = game_handle}); + fdtable.push_back({.type = wasi_fd_type::ZIP, .handle = new (struct wasi_zip_handle){.zip = dist, .path = "/mkxp-retro-dist"}}); + fdtable.push_back({.type = wasi_fd_type::FS, .handle = new std::string("/mkxp-retro-game")}); } wasi_t::~w2c_wasi__snapshot__preview1() { @@ -270,6 +266,17 @@ static struct wasi_zip_stat wasi_zip_stat_entry(zip_t *zip, struct wasi_file_ent } } +struct fs_enumerate_data { + wasi_t *wasi; + u32 fd; + usize original_buf; + usize buf; + u32 buf_len; + u64 initial_cookie; + u64 cookie; + usize result; +}; + u32 wasi_t::allocate_file_descriptor(enum wasi_fd_type type, void *handle) { if (vacant_fds.empty()) { u32 fd = fdtable.size(); @@ -285,19 +292,25 @@ u32 wasi_t::allocate_file_descriptor(enum wasi_fd_type type, void *handle) { void wasi_t::deallocate_file_descriptor(u32 fd) { if (fdtable[fd].handle != NULL) { switch (fdtable[fd].type) { + case wasi_fd_type::FS: + case wasi_fd_type::FSDIR: + delete fdtable[fd].dir_handle(); + break; + case wasi_fd_type::FSFILE: + delete fdtable[fd].file_handle(); + break; case wasi_fd_type::ZIP: - fdtable[fd].zip_handle()->~wasi_zip_handle(); + delete fdtable[fd].zip_handle(); break; case wasi_fd_type::ZIPDIR: - fdtable[fd].zip_dir_handle()->~wasi_zip_dir_handle(); + delete fdtable[fd].zip_dir_handle(); break; case wasi_fd_type::ZIPFILE: - fdtable[fd].zip_file_handle()->~wasi_zip_file_handle(); + delete fdtable[fd].zip_file_handle(); break; default: break; } - std::free(fdtable[fd].handle); } if (!fdtable.empty() && fd == fdtable.size() - 1) { @@ -363,9 +376,12 @@ extern "C" u32 w2c_wasi__snapshot__preview1_fd_close(wasi_t *wasi, u32 fd) { case wasi_fd_type::STDIN: case wasi_fd_type::STDOUT: case wasi_fd_type::STDERR: + case wasi_fd_type::FS: case wasi_fd_type::ZIP: return WASI_EINVAL; + case wasi_fd_type::FSDIR: + case wasi_fd_type::FSFILE: case wasi_fd_type::ZIPDIR: case wasi_fd_type::ZIPFILE: wasi->deallocate_file_descriptor(fd); @@ -394,6 +410,7 @@ extern "C" u32 w2c_wasi__snapshot__preview1_fd_fdstat_get(wasi_t *wasi, u32 fd, case wasi_fd_type::STDIN: case wasi_fd_type::STDOUT: case wasi_fd_type::STDERR: + case wasi_fd_type::FSFILE: case wasi_fd_type::ZIPFILE: WASM_SET(u8, result, WASI_IFCHR); // fs_filetype WASM_SET(u16, result + 2, 0); // fs_flags @@ -401,6 +418,8 @@ extern "C" u32 w2c_wasi__snapshot__preview1_fd_fdstat_get(wasi_t *wasi, u32 fd, WASM_SET(u64, result + 16, 0); // fs_rights_inheriting return WASI_ESUCCESS; + case wasi_fd_type::FS: + case wasi_fd_type::FSDIR: case wasi_fd_type::ZIP: case wasi_fd_type::ZIPDIR: WASM_SET(u8, result, WASI_IFDIR); // fs_filetype @@ -427,6 +446,9 @@ extern "C" u32 w2c_wasi__snapshot__preview1_fd_fdstat_set_flags(wasi_t *wasi, u3 case wasi_fd_type::STDIN: case wasi_fd_type::STDOUT: case wasi_fd_type::STDERR: + case wasi_fd_type::FS: + case wasi_fd_type::FSDIR: + case wasi_fd_type::FSFILE: case wasi_fd_type::ZIP: case wasi_fd_type::ZIPDIR: case wasi_fd_type::ZIPFILE: @@ -460,6 +482,47 @@ extern "C" u32 w2c_wasi__snapshot__preview1_fd_filestat_get(wasi_t *wasi, u32 fd WASM_SET(u64, result + 56, 0); // ctim return WASI_ESUCCESS; + case wasi_fd_type::FS: + case wasi_fd_type::FSDIR: + { + PHYSFS_Stat stat; + if (!PHYSFS_stat(wasi->fdtable[fd].dir_handle()->c_str(), &stat)) { + return WASI_ENOENT; + } + if (stat.filetype != PHYSFS_FILETYPE_DIRECTORY) { + return WASI_EIO; + } + WASM_SET(u64, result, fd); // dev + WASM_SET(u64, result + 8, 0); // ino // TODO: generate a pseudorandom inode number + WASM_SET(u8, result + 16, WASI_IFDIR); // filetype + WASM_SET(u32, result + 24, 1); // nlink + WASM_SET(u64, result + 32, stat.filesize); // size + WASM_SET(u64, result + 40, stat.accesstime * 1000000000L); // atim + WASM_SET(u64, result + 48, stat.modtime * 1000000000L); // mtim + WASM_SET(u64, result + 56, stat.createtime * 1000000000L); // ctim + return WASI_ESUCCESS; + } + + case wasi_fd_type::FSFILE: + { + PHYSFS_Stat stat; + if (!PHYSFS_stat(wasi->fdtable[fd].file_handle()->path(), &stat)) { + return WASI_ENOENT; + } + if (stat.filetype != PHYSFS_FILETYPE_REGULAR) { + return WASI_EIO; + } + WASM_SET(u64, result, fd); // dev + WASM_SET(u64, result + 8, 0); // ino // TODO: generate a pseudorandom inode number + WASM_SET(u8, result + 16, WASI_IFREG); // filetype + WASM_SET(u32, result + 24, 1); // nlink + WASM_SET(u64, result + 32, stat.filesize); // size + WASM_SET(u64, result + 40, stat.accesstime * 1000000000L); // atim + WASM_SET(u64, result + 48, stat.modtime * 1000000000L); // mtim + WASM_SET(u64, result + 56, stat.createtime * 1000000000L); // ctim + return WASI_ESUCCESS; + } + case wasi_fd_type::ZIP: { struct wasi_zip_stat info = wasi_zip_stat(wasi->fdtable[fd].zip_handle()->zip->zip, "", 0); @@ -541,6 +604,10 @@ extern "C" u32 w2c_wasi__snapshot__preview1_fd_prestat_dir_name(wasi_t *wasi, u3 case wasi_fd_type::VACANT: return WASI_EBADF; + case wasi_fd_type::FS: + std::strncpy((char *)WASM_MEM(path), wasi->fdtable[fd].dir_handle()->c_str(), path_len); + return WASI_ESUCCESS; + case wasi_fd_type::ZIP: std::strncpy((char *)WASM_MEM(path), wasi->fdtable[fd].zip_handle()->path.c_str(), path_len); return WASI_ESUCCESS; @@ -548,6 +615,8 @@ extern "C" u32 w2c_wasi__snapshot__preview1_fd_prestat_dir_name(wasi_t *wasi, u3 case wasi_fd_type::STDIN: case wasi_fd_type::STDOUT: case wasi_fd_type::STDERR: + case wasi_fd_type::FSDIR: + case wasi_fd_type::FSFILE: case wasi_fd_type::ZIPDIR: case wasi_fd_type::ZIPFILE: return WASI_EINVAL; @@ -567,6 +636,11 @@ extern "C" u32 w2c_wasi__snapshot__preview1_fd_prestat_get(wasi_t *wasi, u32 fd, case wasi_fd_type::VACANT: return WASI_EBADF; + case wasi_fd_type::FS: + WASM_SET(u32, result, 0); + WASM_SET(u32, result + 4, wasi->fdtable[fd].dir_handle()->length()); + return WASI_ESUCCESS; + case wasi_fd_type::ZIP: WASM_SET(u32, result, 0); WASM_SET(u32, result + 4, wasi->fdtable[fd].zip_handle()->path.length()); @@ -575,6 +649,8 @@ extern "C" u32 w2c_wasi__snapshot__preview1_fd_prestat_get(wasi_t *wasi, u32 fd, case wasi_fd_type::STDIN: case wasi_fd_type::STDOUT: case wasi_fd_type::STDERR: + case wasi_fd_type::FSDIR: + case wasi_fd_type::FSFILE: case wasi_fd_type::ZIPDIR: case wasi_fd_type::ZIPFILE: return WASI_EINVAL; @@ -605,10 +681,26 @@ extern "C" u32 w2c_wasi__snapshot__preview1_fd_read(wasi_t *wasi, u32 fd, usize WASM_SET(u32, result, 0); return WASI_ESUCCESS; + case wasi_fd_type::FS: + case wasi_fd_type::FSDIR: case wasi_fd_type::ZIP: case wasi_fd_type::ZIPDIR: return WASI_EINVAL; + case wasi_fd_type::FSFILE: + { + u32 size = 0; + while (iovs_len > 0) { + PHYSFS_sint64 n = PHYSFS_readBytes(wasi->fdtable[fd].file_handle()->get(), WASM_MEM(WASM_GET(u32, iovs)), WASM_GET(u32, iovs + 4)); + if (n < 0) return WASI_EIO; + size += n; + iovs += 8; + --iovs_len; + } + WASM_SET(u32, result, size); + return WASI_ESUCCESS; + } + case wasi_fd_type::ZIPFILE: { u32 size = 0; @@ -641,9 +733,79 @@ extern "C" u32 w2c_wasi__snapshot__preview1_fd_readdir(wasi_t *wasi, u32 fd, usi case wasi_fd_type::STDIN: case wasi_fd_type::STDOUT: case wasi_fd_type::STDERR: + case wasi_fd_type::FSFILE: case wasi_fd_type::ZIPFILE: return WASI_EINVAL; + case wasi_fd_type::FS: + case wasi_fd_type::FSDIR: + { + struct fs_enumerate_data edata = { + .wasi = wasi, + .fd = fd, + .original_buf = buf, + .buf = buf, + .buf_len = buf_len, + .initial_cookie = cookie, + .cookie = 0, + .result = result, + }; + bool success = PHYSFS_enumerate( + wasi->fdtable[fd].dir_handle()->c_str(), + [](void *data, const char *path, const char *filename) { + struct fs_enumerate_data *edata = (struct fs_enumerate_data *)data; + wasi_t *wasi = edata->wasi; + + PHYSFS_Stat stat; + if (!PHYSFS_stat(edata->wasi->fdtable[edata->fd].dir_handle()->c_str(), &stat)) { + return PHYSFS_ENUM_OK; + } + if (stat.filetype != PHYSFS_FILETYPE_DIRECTORY && stat.filetype != PHYSFS_FILETYPE_REGULAR) { + return PHYSFS_ENUM_OK; + } + + if (edata->cookie++ < edata->initial_cookie) { + return PHYSFS_ENUM_OK; + } + + if (edata->buf - edata->original_buf + 8 > edata->buf_len) { + return PHYSFS_ENUM_STOP; + } + WASM_SET(u64, edata->buf, edata->cookie); + edata->buf += 8; + + if (edata->buf - edata->original_buf + 8 > edata->buf_len) { + return PHYSFS_ENUM_STOP; + } + WASM_SET(u64, edata->buf, 0); // TODO: generate a pseudorandom inode number + edata->buf += 8; + + if (edata->buf - edata->original_buf + 4 > edata->buf_len) { + return PHYSFS_ENUM_STOP; + } + WASM_SET(u32, edata->buf, std::strlen(filename)); + edata->buf += 4; + + if (edata->buf - edata->original_buf + 4 > edata->buf_len) { + return PHYSFS_ENUM_STOP; + } + WASM_SET(u8, edata->buf, stat.filetype); + edata->buf += 4; + + u32 len = std::min(std::strlen(filename), (size_t)(edata->original_buf + edata->buf_len - edata->buf)); + std::memcpy(WASM_MEM(edata->buf), filename, std::strlen(filename)); + edata->buf += len; + return PHYSFS_ENUM_OK; + }, + (void *)&edata + ); + if (success) { + WASM_SET(u32, result, edata.buf - edata.original_buf); + return WASI_ESUCCESS; + } + return success ? WASI_ESUCCESS : WASI_ENOENT; + } + case wasi_fd_type::ZIP: case wasi_fd_type::ZIPDIR: { @@ -744,11 +906,14 @@ extern "C" u32 w2c_wasi__snapshot__preview1_fd_renumber(wasi_t *wasi, u32 fd, u3 case wasi_fd_type::STDIN: case wasi_fd_type::STDOUT: case wasi_fd_type::STDERR: + case wasi_fd_type::FS: case wasi_fd_type::ZIP: return WASI_EINVAL; - case wasi_fd_type::ZIPFILE: + case wasi_fd_type::FSDIR: + case wasi_fd_type::FSFILE: case wasi_fd_type::ZIPDIR: + case wasi_fd_type::ZIPFILE: break; } @@ -765,9 +930,12 @@ extern "C" u32 w2c_wasi__snapshot__preview1_fd_renumber(wasi_t *wasi, u32 fd, u3 case wasi_fd_type::STDIN: case wasi_fd_type::STDOUT: case wasi_fd_type::STDERR: + case wasi_fd_type::FS: case wasi_fd_type::ZIP: return WASI_EINVAL; + case wasi_fd_type::FSDIR: + case wasi_fd_type::FSFILE: case wasi_fd_type::ZIPDIR: case wasi_fd_type::ZIPFILE: wasi->deallocate_file_descriptor(to); @@ -812,10 +980,16 @@ extern "C" u32 w2c_wasi__snapshot__preview1_fd_tell(wasi_t *wasi, u32 fd, usize case wasi_fd_type::STDIN: case wasi_fd_type::STDOUT: case wasi_fd_type::STDERR: + case wasi_fd_type::FS: + case wasi_fd_type::FSDIR: case wasi_fd_type::ZIP: case wasi_fd_type::ZIPDIR: return WASI_EINVAL; + case wasi_fd_type::FSFILE: + WASM_SET(u64, result, PHYSFS_tell(wasi->fdtable[fd].file_handle()->get())); + return WASI_ESUCCESS; + case wasi_fd_type::ZIPFILE: WASM_SET(u64, result, zip_ftell(wasi->fdtable[fd].zip_file_handle()->zip_file_handle.file)); return WASI_ESUCCESS; @@ -859,10 +1033,13 @@ extern "C" u32 w2c_wasi__snapshot__preview1_fd_write(wasi_t *wasi, u32 fd, usize return WASI_ESUCCESS; } + case wasi_fd_type::FS: + case wasi_fd_type::FSDIR: case wasi_fd_type::ZIP: case wasi_fd_type::ZIPDIR: return WASI_EINVAL; + case wasi_fd_type::FSFILE: case wasi_fd_type::ZIPFILE: return WASI_EROFS; } @@ -889,9 +1066,40 @@ extern "C" u32 w2c_wasi__snapshot__preview1_path_filestat_get(wasi_t *wasi, u32 case wasi_fd_type::STDIN: case wasi_fd_type::STDOUT: case wasi_fd_type::STDERR: + case wasi_fd_type::FSFILE: case wasi_fd_type::ZIPFILE: return WASI_EINVAL; + case wasi_fd_type::FS: + case wasi_fd_type::FSDIR: + { + PHYSFS_Stat stat; + std::string new_path(*wasi->fdtable[fd].dir_handle()); + new_path.push_back('/'); + new_path.append((const char *)WASM_MEM(path), strlen_safe((const char *)WASM_MEM(path), path_len)); + new_path = mkxp_retro::fs->normalize(new_path.c_str(), true, true); + if (std::strncmp(new_path.c_str(), wasi->fdtable[fd].dir_handle()->c_str(), wasi->fdtable[fd].dir_handle()->length()) != 0) { + return WASI_EPERM; + } + + if (!PHYSFS_stat(new_path.c_str(), &stat)) { + return WASI_ENOENT; + } + if (stat.filetype != PHYSFS_FILETYPE_DIRECTORY && stat.filetype != PHYSFS_FILETYPE_REGULAR) { + return WASI_EIO; + } + + WASM_SET(u64, result, fd); // dev + WASM_SET(u64, result + 8, 0); // ino // TODO: generate a pseudorandom inode number + WASM_SET(u8, result + 16, stat.filetype == PHYSFS_FILETYPE_DIRECTORY ? WASI_IFDIR : WASI_IFREG); // filetype + WASM_SET(u32, result + 24, 1); // nlink + WASM_SET(u64, result + 32, stat.filetype); // size + WASM_SET(u64, result + 40, stat.accesstime * 1000000000L); // atim + WASM_SET(u64, result + 48, stat.modtime * 1000000000L); // mtim + WASM_SET(u64, result + 56, stat.createtime * 1000000000L); // ctim + return WASI_ESUCCESS; + } + case wasi_fd_type::ZIP: { struct wasi_zip_stat info = wasi_zip_stat(wasi->fdtable[fd].zip_handle()->zip->zip, (char *)WASM_MEM(path), path_len); @@ -956,9 +1164,40 @@ extern "C" u32 w2c_wasi__snapshot__preview1_path_open(wasi_t *wasi, u32 fd, u32 case wasi_fd_type::STDIN: case wasi_fd_type::STDOUT: case wasi_fd_type::STDERR: + case wasi_fd_type::FSFILE: case wasi_fd_type::ZIPFILE: return WASI_EINVAL; + case wasi_fd_type::FS: + case wasi_fd_type::FSDIR: + { + PHYSFS_Stat stat; + std::string new_path(*wasi->fdtable[fd].dir_handle()); + new_path.push_back('/'); + new_path.append((const char *)WASM_MEM(path), strlen_safe((const char *)WASM_MEM(path), path_len)); + new_path = mkxp_retro::fs->normalize(new_path.c_str(), true, true); + if (std::strncmp(new_path.c_str(), wasi->fdtable[fd].dir_handle()->c_str(), wasi->fdtable[fd].dir_handle()->length()) != 0) { + return WASI_EPERM; + } + + if (!PHYSFS_stat(new_path.c_str(), &stat)) { + return WASI_ENOENT; + } + if (stat.filetype != PHYSFS_FILETYPE_DIRECTORY && stat.filetype != PHYSFS_FILETYPE_REGULAR) { + return WASI_EIO; + } + + if (stat.filetype == PHYSFS_FILETYPE_DIRECTORY) { + std::string *handle = new std::string(new_path); + WASM_SET(u32, result, wasi->allocate_file_descriptor(wasi_fd_type::FSDIR, handle)); + } else { + struct FileSystem::File *handle = new FileSystem::File(*mkxp_retro::fs, new_path.c_str(), FileSystem::OpenMode::Read); + WASM_SET(u32, result, wasi->allocate_file_descriptor(wasi_fd_type::FSFILE, handle)); + } + + return WASI_ESUCCESS; + } + case wasi_fd_type::ZIP: case wasi_fd_type::ZIPDIR: { @@ -980,11 +1219,7 @@ extern "C" u32 w2c_wasi__snapshot__preview1_path_open(wasi_t *wasi, u32 fd, u32 } if (info.filetype == WASI_IFDIR) { - struct wasi_zip_dir_handle *handle = (struct wasi_zip_dir_handle *)std::malloc(sizeof(struct wasi_zip_dir_handle)); - if (handle == NULL) { - throw SandboxOutOfMemoryException(); - } - new(handle) (struct wasi_zip_dir_handle){ + struct wasi_zip_dir_handle *handle = new (struct wasi_zip_dir_handle){ .index = info.inode, .path = info.normalized_path, .parent_fd = wasi->fdtable[fd].type == wasi_fd_type::ZIPDIR ? wasi->fdtable[fd].zip_dir_handle()->parent_fd : fd, @@ -992,11 +1227,7 @@ extern "C" u32 w2c_wasi__snapshot__preview1_path_open(wasi_t *wasi, u32 fd, u32 WASM_SET(u32, result, wasi->allocate_file_descriptor(wasi_fd_type::ZIPDIR, handle)); } else { - struct wasi_zip_file_handle *handle = (struct wasi_zip_file_handle *)std::malloc(sizeof(struct wasi_zip_file_handle)); - if (handle == NULL) { - throw SandboxOutOfMemoryException(); - } - new(handle) (struct wasi_zip_file_handle){ + struct wasi_zip_file_handle *handle = new (struct wasi_zip_file_handle){ .index = info.inode, .zip_file_handle = wasi_zip_file_container( wasi->fdtable[fd].type == wasi_fd_type::ZIP diff --git a/binding-sandbox/wasi.h b/binding-sandbox/wasi.h index 9b080155..d8513251 100644 --- a/binding-sandbox/wasi.h +++ b/binding-sandbox/wasi.h @@ -26,6 +26,7 @@ #include #include #include +#include "filesystem.h" #include "types.h" // Internal utility macros @@ -233,6 +234,9 @@ enum wasi_fd_type { STDIN, // This file descriptor is standard input. The `handle` field is null. STDOUT, // This file descriptor is standard output. The `handle` field is null. STDERR, // This file descriptor is standard error. The `handle` field is null. + FS, // This file descriptor is a preopened directory handled by the mkxp-z filesystem code. The `handle` field is a `std::string *` containing the path of the directory. + FSDIR, // This file descriptor is a directory handled by the mkxp-z filesystem code. The `handle` field is a `std::string *` containing the path of the directory. + FSFILE, // This file descriptor is a file handled by the mkxp-z filesystem code. The `handle` field is a `struct FileSystem::File *`. ZIP, // This file descriptor is a read-only zip file. The `handle` field is a `struct wasi_zip_handle *`. ZIPDIR, // This file descriptor is a directory inside of a zip file. The `handle` field is a `struct wasi_zip_dir_handle *`. ZIPFILE, // This file descriptor is a file inside of a zip file. The `handle` field is a `struct wasi_zip_file_handle *`. @@ -245,6 +249,8 @@ struct wasi_file_entry { // The file/directory handle that the file descriptor corresponds to. The exact type of this handle depends on the type of file descriptor. void *handle; + std::string *dir_handle(); + struct FileSystem::File *file_handle(); struct wasi_zip_handle *zip_handle(); struct wasi_zip_dir_handle *zip_dir_handle(); struct wasi_zip_file_handle *zip_file_handle(); @@ -263,7 +269,6 @@ typedef struct w2c_wasi__snapshot__preview1 { std::shared_ptr ruby; std::shared_ptr dist; - std::shared_ptr game; // WASI file descriptor table. Maps WASI file descriptors (unsigned 32-bit integers) to file handles. std::vector fdtable; @@ -271,7 +276,7 @@ typedef struct w2c_wasi__snapshot__preview1 { // List of vacant WASI file descriptors so that we can reallocate vacant WASI file descriptors in O(1) amortized time. std::vector vacant_fds; - w2c_wasi__snapshot__preview1(std::shared_ptr ruby, const char *game_path); + w2c_wasi__snapshot__preview1(std::shared_ptr ruby); ~w2c_wasi__snapshot__preview1(); u32 allocate_file_descriptor(enum wasi_fd_type type, void *handle = NULL); void deallocate_file_descriptor(u32 fd); diff --git a/meson.build b/meson.build index 3b2cdbbd..b6f52c8f 100644 --- a/meson.build +++ b/meson.build @@ -7,7 +7,7 @@ if get_option('retro') == false and host_system == 'darwin' error('\nThis Meson project no longer supports macOS. Please use the Xcode project instead.') endif -git_hash = run_command('git', 'rev-parse', '--short', 'HEAD').stdout().strip() +git_hash = run_command('git', 'rev-parse', '--short', 'HEAD', check: true).stdout().strip() compilers = {'cpp': meson.get_compiler('cpp')} @@ -203,10 +203,21 @@ if get_option('retro') == true include_directories: [ include_directories('.'), include_directories('src'), + include_directories('src/audio'), + include_directories('src/crypto'), include_directories('src/display'), include_directories('src/display/gl'), + include_directories('src/display/libnsgif'), + include_directories('src/display/libnsgif/utils'), include_directories('src/etc'), + include_directories('src/filesystem'), + include_directories('src/filesystem/ghc'), + include_directories('src/input'), + include_directories('src/net'), + include_directories('src/system'), include_directories('src/util'), + include_directories('src/util/sigslot'), + include_directories('src/util/sigslot/adapter'), include_directories(retro_phase1), include_directories(join_paths(retro_phase1, 'wasm2c')), include_directories(join_paths(retro_phase1, 'mkxp-retro-ruby')), @@ -215,12 +226,15 @@ if get_option('retro') == true sources: [ 'src/core.cpp', 'src/sharedstate.cpp', + 'src/crypto/rgssad.cpp', 'src/display/bitmap.cpp', 'src/display/viewport.cpp', 'src/display/window.cpp', 'src/display/gl/scene.cpp', 'src/etc/etc.cpp', 'src/etc/table.cpp', + 'src/filesystem/filesystem.cpp', + 'src/filesystem/filesystemImpl.cpp', 'binding-sandbox/binding-util.cpp', 'binding-sandbox/sandbox.cpp', 'binding-sandbox/wasi.cpp', diff --git a/src/core.cpp b/src/core.cpp index 6e8ac77a..95005576 100644 --- a/src/core.cpp +++ b/src/core.cpp @@ -19,6 +19,7 @@ ** along with mkxp. If not, see . */ +#include #include #include #include @@ -27,6 +28,7 @@ #include "../binding-sandbox/sandbox.h" #include "../binding-sandbox/binding-sandbox.h" #include "../binding-sandbox/core.h" +#include "filesystem.h" using namespace mkxp_retro; using namespace mkxp_sandbox; @@ -42,7 +44,8 @@ static void fallback_log(enum retro_log_level level, const char *fmt, ...) { static uint32_t *frame_buf; boost::optional mkxp_retro::sandbox; -static const char *game_path = NULL; +boost::optional mkxp_retro::fs; +static std::string game_path; static VALUE func(VALUE arg) { SANDBOX_COROUTINE(coro, @@ -84,10 +87,35 @@ SANDBOX_COROUTINE(main, static bool init_sandbox() { mkxp_retro::sandbox.reset(); + fs.reset(); + fs.emplace((const char *)NULL, false); + + { + std::string parsed_game_path(fs->normalize(game_path.c_str(), false, true)); + + // If the game path doesn't end with ".mkxp" or ".mkxpz", remove the last component from the path since we want to mount the directory that the file is in, not the file itself. + if ( + !(parsed_game_path.length() >= 5 && std::strcmp(parsed_game_path.c_str() + (parsed_game_path.length() - 5), ".mkxp") == 0) + && !(parsed_game_path.length() >= 5 && std::strcmp(parsed_game_path.c_str() + (parsed_game_path.length() - 5), ".MKXP") == 0) + && !(parsed_game_path.length() >= 6 && std::strcmp(parsed_game_path.c_str() + (parsed_game_path.length() - 6), ".mkxpz") == 0) + && !(parsed_game_path.length() >= 6 && std::strcmp(parsed_game_path.c_str() + (parsed_game_path.length() - 6), ".MKXPZ") == 0) + ) { + size_t last_slash_index = parsed_game_path.find_last_of('/'); + if (last_slash_index == std::string::npos) { + last_slash_index = 0; + } + parsed_game_path = parsed_game_path.substr(0, last_slash_index); + } + + fs->addPath(parsed_game_path.c_str(), "/mkxp-retro-game"); + } + + fs->createPathCache(); + SharedState::initInstance(NULL); try { - mkxp_retro::sandbox.emplace(game_path); + mkxp_retro::sandbox.emplace(); } catch (SandboxException) { log_printf(RETRO_LOG_ERROR, "Failed to initialize Ruby\n"); mkxp_retro::sandbox.reset(); @@ -225,7 +253,7 @@ extern "C" RETRO_API void retro_cheat_set(unsigned int index, bool enabled, cons } extern "C" RETRO_API bool retro_load_game(const struct retro_game_info *info) { - if (info == NULL) { + if (info == NULL || info->path == NULL) { log_printf(RETRO_LOG_ERROR, "This core cannot start without a game\n"); return false; } @@ -246,6 +274,8 @@ extern "C" RETRO_API bool retro_load_game_special(unsigned int type, const struc extern "C" RETRO_API void retro_unload_game() { mkxp_retro::sandbox.reset(); + + fs.reset(); } extern "C" RETRO_API unsigned int retro_get_region() { diff --git a/src/filesystem/filesystem.cpp b/src/filesystem/filesystem.cpp index 67c9be0c..38f8cad1 100644 --- a/src/filesystem/filesystem.cpp +++ b/src/filesystem/filesystem.cpp @@ -48,6 +48,7 @@ #include #endif +#ifndef MKXPZ_RETRO struct SDLRWIoContext { SDL_RWops *ops; std::string filename; @@ -211,6 +212,7 @@ static int SDL_RWopsCloseFree(SDL_RWops *ops) { return result; } +#endif // MKXPZ_RETRO /* Copies the first srcN characters from src into dst, * or the full string if srcN == -1. Never writes more @@ -245,6 +247,7 @@ static const char *findExt(const char *filename) { return 0; } +#ifndef MKXPZ_RETRO static void initReadOps(PHYSFS_File *handle, SDL_RWops &ops, bool freeOnClose) { ops.size = SDL_RWopsSize; ops.seek = SDL_RWopsSeek; @@ -259,6 +262,7 @@ static void initReadOps(PHYSFS_File *handle, SDL_RWops &ops, bool freeOnClose) { ops.type = SDL_RWOPS_PHYSFS; ops.hidden.unknown.data1 = handle; } +#endif // MKXPZ_RETRO static void strTolower(std::string &str) { for (size_t i = 0; i < str.size(); ++i) @@ -326,6 +330,8 @@ FileSystem::~FileSystem() { void FileSystem::addPath(const char *path, const char *mountpoint, bool reload) { /* Try the normal mount first */ int state = PHYSFS_mount(path, mountpoint, 1); + +#ifndef MKXPZ_RETRO if (!state) { /* If it didn't work, try mounting via a wrapped * SDL_RWops */ @@ -334,6 +340,8 @@ void FileSystem::addPath(const char *path, const char *mountpoint, bool reload) if (io) state = PHYSFS_mountIo(io, path, 0, 1); } +#endif // MKXPZ_RETRO + if (!state) { PHYSFS_ErrorCode err = PHYSFS_getLastErrorCode(); throw Exception(Exception::PHYSFSError, "Failed to mount %s (%s)", path, PHYSFS_getErrorByCode(err)); @@ -396,8 +404,10 @@ struct CacheEnumData { static PHYSFS_EnumerateCallbackResult cacheEnumCB(void *d, const char *origdir, const char *fname) { +#ifndef MKXPZ_RETRO if (shState && shState->rtData().rqTerm) throw Exception(Exception::MKXPError, "Game close requested. Aborting path cache enumeration."); +#endif // MKXPZ_RETRO CacheEnumData &data = *static_cast(d); char fullPath[512]; @@ -494,12 +504,14 @@ static PHYSFS_EnumerateCallbackResult fontSetEnumCB(void *data, const char *dir, if (!handle) return PHYSFS_ENUM_ERROR; +#ifndef MKXPZ_RETRO SDL_RWops ops; initReadOps(handle, ops, false); d->sfs->initFontSetCB(ops, filename); SDL_RWclose(&ops); +#endif // MKXPZ_RETRO return PHYSFS_ENUM_OK; } @@ -600,7 +612,10 @@ openReadEnumCB(void *d, const char *dirpath, const char *filename) { return PHYSFS_ENUM_ERROR; } + +#ifndef MKXPZ_RETRO initReadOps(phys, data.ops, false); +#endif // MKXPZ_RETRO const char *ext = findExt(filename); @@ -667,8 +682,11 @@ void FileSystem::openReadRaw(SDL_RWops &ops, const char *filename, if (!handle) throw Exception(Exception::NoFileError, "%s", filename); +#ifndef MKXPZ_RETRO initReadOps(handle, ops, freeOnClose); - return; +#endif // MKXPZ_RETRO + + return; } std::string FileSystem::normalize(const char *pathname, bool preferred, @@ -690,3 +708,7 @@ const char *FileSystem::desensitize(const char *filename) { return p->pathCache[fn_lower].c_str(); return filename; } + +bool FileSystem::enumerate(const char *path, PHYSFS_EnumerateCallback callback, void *data) { + return PHYSFS_enumerate(normalize(path, false, false).c_str(), callback, data) != 0; +} diff --git a/src/filesystem/filesystem.h b/src/filesystem/filesystem.h index 834a394b..947f1ff4 100644 --- a/src/filesystem/filesystem.h +++ b/src/filesystem/filesystem.h @@ -22,6 +22,7 @@ #ifndef FILESYSTEM_H #define FILESYSTEM_H +#include #include #include @@ -79,6 +80,54 @@ public: const char *desensitize(const char *filename); + enum OpenMode + { + Read, + Write, + Append, + }; + + struct File + { + private: + PHYSFS_File *inner; + std::string _path; + + public: + File(const struct File &) = delete; + inline File(FileSystem &fs, const char *filename, OpenMode mode) { + _path = fs.normalize(filename, false, false); + switch (mode) { + case OpenMode::Read: + inner = PHYSFS_openRead(_path.c_str()); + break; + case OpenMode::Write: + inner = PHYSFS_openWrite(_path.c_str()); + break; + case OpenMode::Append: + inner = PHYSFS_openAppend(_path.c_str()); + break; + } + } + inline ~File() { + PHYSFS_close(inner); + } + inline const char *path() { + return _path.c_str(); + } + inline PHYSFS_File *get() { + return inner; + } + inline PHYSFS_File *operator->() { + return get(); + } + inline PHYSFS_File &operator*() { + return *get(); + } + }; + + bool enumerate(const char *path, PHYSFS_EnumerateCallback callback, void *data); + private: FileSystemPrivate *p; }; diff --git a/src/filesystem/filesystemImpl.cpp b/src/filesystem/filesystemImpl.cpp index 1d79dee9..45707aa2 100644 --- a/src/filesystem/filesystemImpl.cpp +++ b/src/filesystem/filesystemImpl.cpp @@ -79,7 +79,7 @@ std::string filesystemImpl::normalizePath(const char *path, bool preferred, bool for (size_t i = 0; i < ret.length(); i++) { char sep; char sep_alt; -#ifdef __WIN32__ +#if !defined(MKXPZ_RETRO) && defined(__WIN32__) if (preferred) { sep = '\\'; sep_alt = '/'; @@ -98,8 +98,12 @@ std::string filesystemImpl::normalizePath(const char *path, bool preferred, bool } std::string filesystemImpl::getDefaultGameRoot() { +#ifdef MKXPZ_RETRO + return "/"; // TODO: implement +#else char *p = SDL_GetBasePath(); std::string ret(p); SDL_free(p); return ret; +#endif // MKXPZ_RETRO } diff --git a/src/util/debugwriter.h b/src/util/debugwriter.h index da128256..738a30b2 100644 --- a/src/util/debugwriter.h +++ b/src/util/debugwriter.h @@ -26,8 +26,10 @@ #include #include -#ifdef __ANDROID__ -#include +#ifdef MKXPZ_RETRO +# include "binding-sandbox/core.h" +#elif defined(__ANDROID__) +# include #endif @@ -61,7 +63,9 @@ public: ~Debug() { -#ifdef __ANDROID__ +#ifdef MKXPZ_RETRO + mkxp_retro::log_printf(RETRO_LOG_INFO, "%s\n", buf.str().c_str()); +#elif defined(__ANDROID__) __android_log_write(ANDROID_LOG_DEBUG, "mkxp", buf.str().c_str()); #else std::cerr << buf.str() << std::endl; diff --git a/src/util/sdl-util.h b/src/util/sdl-util.h index 839c161c..2f0fa345 100644 --- a/src/util/sdl-util.h +++ b/src/util/sdl-util.h @@ -5,7 +5,7 @@ #include #include -#include +#include #include #include @@ -121,7 +121,7 @@ private: if (eback() == base) { - memmove(base, egptr() - pbSize, pbSize); + std::memmove(base, egptr() - pbSize, pbSize); start += pbSize; }