Reimplement WASI filesystem on top of existing mkxp-z filesystem implementation

This allows more flexibility when loading games in libretro builds,
since we can now load games either from a directory or from a ZIP or 7Z
archive. Also, the path cache is now active for all filesystem calls
made from inside Ruby.
This commit is contained in:
刘皓 2025-01-25 22:03:52 -05:00
parent 973b33e3e5
commit 84ca884f84
No known key found for this signature in database
GPG key ID: 7901753DB465B711
12 changed files with 407 additions and 46 deletions

View file

@ -24,9 +24,11 @@
#include <libretro.h>
#include "../binding-sandbox/sandbox.h"
#include "filesystem.h"
namespace mkxp_retro {
extern boost::optional<struct mkxp_sandbox::sandbox> sandbox;
extern boost::optional<FileSystem> fs;
extern retro_log_printf_t log_printf;
extern retro_video_refresh_t video_refresh;

View file

@ -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();

View file

@ -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.

View file

@ -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<struct w2c_ruby> 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<struct w2c_ruby> 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<struct w2c_ruby> 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

View file

@ -26,6 +26,7 @@
#include <string>
#include <vector>
#include <zip.h>
#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<struct w2c_ruby> ruby;
std::shared_ptr<struct wasi_zip_container> dist;
std::shared_ptr<struct wasi_zip_container> game;
// WASI file descriptor table. Maps WASI file descriptors (unsigned 32-bit integers) to file handles.
std::vector<wasi_file_entry> 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<u32> vacant_fds;
w2c_wasi__snapshot__preview1(std::shared_ptr<struct w2c_ruby> ruby, const char *game_path);
w2c_wasi__snapshot__preview1(std::shared_ptr<struct w2c_ruby> ruby);
~w2c_wasi__snapshot__preview1();
u32 allocate_file_descriptor(enum wasi_fd_type type, void *handle = NULL);
void deallocate_file_descriptor(u32 fd);

View file

@ -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',

View file

@ -19,6 +19,7 @@
** along with mkxp. If not, see <http://www.gnu.org/licenses/>.
*/
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstdarg>
@ -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<struct sandbox> mkxp_retro::sandbox;
static const char *game_path = NULL;
boost::optional<FileSystem> 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() {

View file

@ -48,6 +48,7 @@
#include <direct.h>
#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<CacheEnumData *>(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,7 +682,10 @@ void FileSystem::openReadRaw(SDL_RWops &ops, const char *filename,
if (!handle)
throw Exception(Exception::NoFileError, "%s", filename);
#ifndef MKXPZ_RETRO
initReadOps(handle, ops, freeOnClose);
#endif // MKXPZ_RETRO
return;
}
@ -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;
}

View file

@ -22,6 +22,7 @@
#ifndef FILESYSTEM_H
#define FILESYSTEM_H
#include <physfs.h>
#include <SDL_rwops.h>
#include <string>
@ -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;
};

View file

@ -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
}

View file

@ -26,8 +26,10 @@
#include <sstream>
#include <vector>
#ifdef __ANDROID__
#include <android/log.h>
#ifdef MKXPZ_RETRO
# include "binding-sandbox/core.h"
#elif defined(__ANDROID__)
# include <android/log.h>
#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;

View file

@ -5,7 +5,7 @@
#include <SDL_thread.h>
#include <SDL_rwops.h>
#include <string>
#include <cstring>
#include <iostream>
#include <unistd.h>
@ -121,7 +121,7 @@ private:
if (eback() == base)
{
memmove(base, egptr() - pbSize, pbSize);
std::memmove(base, egptr() - pbSize, pbSize);
start += pbSize;
}