mkxp-z/binding-sandbox/sandbox.cpp
刘皓 4ada800de3
Refactor libretro core initialization to not need RETRO_SERIALIZATION_QUIRK_MUST_INITIALIZE
This commit moves some of the initialization around so that the core can
handle save states immediately after initialization instead of needing
to run for one frame before save states will work.
2025-07-23 13:47:58 -04:00

163 lines
5.6 KiB
C++

/*
** sandbox.cpp
**
** This file is part of mkxp.
**
** Copyright (C) 2013 - 2021 Amaryllis Kulla <ancurio@mapleshrine.eu>
**
** mkxp is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 2 of the License, or
** (at your option) any later version.
**
** mkxp is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with mkxp. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string>
#include <wasm-rt.h>
#include "wasi.h"
#include <mkxp-sandbox-ruby.h>
#include "mkxp-polyfill.h"
#include "sandbox.h"
#define RB (ruby.get())
#define AWAIT(statement) do statement; while (w2c_ruby_mkxp_sandbox_yield(RB))
using namespace mkxp_sandbox;
wasm_ptr_t sandbox::sandbox_malloc(wasm_size_t size) {
wasm_ptr_t buf = w2c_ruby_mkxp_sandbox_malloc(RB, size);
// Verify that the returned pointer is non-null and the entire allocated buffer is in valid memory
wasm_ptr_t buf_end;
if (buf == 0 || (buf_end = buf + size) < buf || buf_end >= ruby->w2c_memory.size) {
MKXPZ_THROW(std::bad_alloc());
}
return buf;
}
void sandbox::sandbox_free(wasm_ptr_t ptr) {
w2c_ruby_mkxp_sandbox_free(RB, ptr);
}
sandbox::sandbox() : ruby(new struct w2c_ruby), wasi(new wasi_t(ruby)), bindings(ruby), movie(nullptr), yielding(false), trans_map(nullptr), transitioning(false) {
// Initialize the sandbox
wasm2c_ruby_instantiate(RB, wasi.get());
w2c_ruby_mkxp_sandbox_init(
RB,
0, // heap_free_slots
1.1, // growth_factor
0, // growth_max_slots
0, // heap_free_slots_min_ratio
0, // heap_free_slots_goal_ratio
0, // heap_free_slots_max_ratio
0, // uncollectible_wb_unprotected_objects_limit_ratio
0, // oldobject_limit_factor
1 * 0x100000, // malloc_limit_min
4 * 0x100000, // malloc_limit_max
1.1, // malloc_limit_growth_factor
4 * 0x100000, // oldmalloc_limit_min
8 * 0x100000, // oldmalloc_limit_max
1.1 // oldmalloc_limit_growth_factor
);
// Change the current working directory to the game directory
wasm_ptr_t chdir_buf = sandbox_malloc(sizeof("/Game"));
wasi->strcpy(chdir_buf, "/Game");
w2c_ruby_mkxp_sandbox_chdir(RB, chdir_buf);
sandbox_free(chdir_buf);
// Determine Ruby command-line arguments
std::vector<std::string> args{"mkxp-z"};
args.push_back("/Dist/bin/mkxp-z");
// Copy all the command-line arguments into the sandbox (sandboxed code can't access memory that's outside the sandbox!)
wasm_ptr_t argv_buf = sandbox_malloc(args.size() * sizeof(wasm_ptr_t));
for (wasm_size_t i = 0; i < args.size(); ++i) {
wasm_ptr_t arg_buf = sandbox_malloc(args[i].length() + 1);
wasi->strcpy(arg_buf, args[i].c_str());
wasi->ref<wasm_ptr_t>(argv_buf, i) = arg_buf;
}
// Pass the command-line arguments to Ruby
AWAIT(w2c_ruby_ruby_init_stack(RB, w2c_ruby_rb_wasm_get_stack_pointer(RB)));
AWAIT(w2c_ruby_ruby_init(RB));
wasm_ptr_t node;
AWAIT(node = w2c_ruby_ruby_options(RB, args.size(), argv_buf));
// Start up Ruby executable node
bool valid;
u32 state;
wasm_ptr_t state_buf = sandbox_malloc(sizeof(wasm_ptr_t));
AWAIT(valid = w2c_ruby_ruby_executable_node(RB, node, state_buf));
if (valid) {
AWAIT(state = w2c_ruby_ruby_exec_node(RB, node));
}
if (!valid || state) {
std::abort();
}
sandbox_free(state_buf);
// Set the default encoding to UTF-8
VALUE encoding;
AWAIT(encoding = w2c_ruby_rb_utf8_encoding(RB));
VALUE enc;
AWAIT(enc = w2c_ruby_rb_enc_from_encoding(RB, encoding));
AWAIT(w2c_ruby_rb_enc_set_default_internal(RB, enc));
AWAIT(w2c_ruby_rb_enc_set_default_external(RB, enc));
}
sandbox::~sandbox() {
set_movie(nullptr);
if (w2c_ruby_asyncify_get_state(ruby.get()) == 1) {
w2c_ruby_asyncify_stop_unwind(ruby.get());
}
bindings.reset(); // Destroy the bindings before destroying the runtime since the bindings destructor requires the runtime to be alive
wasm2c_ruby_free(RB);
}
bool sandbox::sandbox_serialize_wasi(void *&data, wasm_size_t &max_size) const {
return wasi->sandbox_serialize(data, max_size);
}
bool sandbox::sandbox_deserialize_wasi(const void *&data, wasm_size_t &max_size) {
return wasi->sandbox_deserialize(data, max_size);
}
Movie *sandbox::get_movie_from_main_thread() {
return movie.load(std::memory_order_relaxed); // No need for synchronization because we always set the movie from the main thread
}
Movie *sandbox::get_movie_from_audio_thread() {
return movie.load(std::memory_order_seq_cst);
}
void sandbox::set_movie(Movie *new_movie) {
Movie *old_movie = get_movie_from_main_thread();
if (old_movie == new_movie) {
return;
}
if (old_movie != nullptr) {
movie.store(nullptr, std::memory_order_seq_cst);
AudioMutexGuard guard(movie_mutex);
Graphics::stopMovie(old_movie);
}
if (new_movie != nullptr) {
movie.store(new_movie, std::memory_order_seq_cst);
}
}
struct sandbox_str_guard sandbox::getcwd() {
if (w2c_ruby_mkxp_sandbox_getcwd(ruby.get())) {
return bindings->str(ruby->w2c_mkxp_sandbox_cwd);
} else {
return "/Game";
}
}