mkxp-z/src/core.cpp
刘皓 379c22833f
Store coroutine variables in the Ruby stack in libretro builds
To stop Ruby's garbage collector from freeing Ruby `VALUE`s while we're
in the middle of using them in libretro builds, we need to make sure all
the `VALUE`s we use are on the sandbox's stack.

Also, to allow Ruby to recognize `VALUE`s on the sandbox's stack on
big-endian targets, I've changed the serialization of `VALUE`s. Before,
any `VALUE`s returned by a sandbox function were always converted to the
target's endian, and any `VALUE`s passed to sandbox functions as
argument were then converted back to WebAssembly's endianness,
little-endian. Now, `VALUE`s are always little-endian; they are no
longer converted to the target's endianness. That should be fine since
`VALUE`s are supposed to be opaque values.
2025-01-19 19:08:03 -05:00

269 lines
7.4 KiB
C++

/*
** core.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 <cstdio>
#include <cstdlib>
#include <cstdarg>
#include <cstring>
#include "../binding-sandbox/sandbox.h"
#include "../binding-sandbox/binding-sandbox.h"
#include "../binding-sandbox/core.h"
using namespace mkxp_retro;
static void fallback_log(enum retro_log_level level, const char *fmt, ...) {
std::va_list va;
va_start(va, fmt);
std::vfprintf(stderr, fmt, va);
va_end(va);
}
static uint32_t *frame_buf;
std::unique_ptr<struct mkxp_sandbox::sandbox> mkxp_sandbox::sandbox;
static const char *game_path = NULL;
static VALUE my_cpp_func(void *_, VALUE self, VALUE args) {
SANDBOX_COROUTINE(coro,
void operator()(VALUE args) {
reenter (this) {
SANDBOX_AWAIT(mkxp_sandbox::rb_eval_string, "puts 'Hello from Ruby land!'");
SANDBOX_AWAIT(mkxp_sandbox::rb_p, args);
}
}
)
mkxp_sandbox::sandbox->bindings.bind<struct coro>()()(args);
return self;
}
static VALUE func(void *_, VALUE arg) {
SANDBOX_COROUTINE(coro,
void operator()() {
reenter (this) {
SANDBOX_AWAIT(mkxp_sandbox::rb_eval_string, "puts 'Hello, World!'");
SANDBOX_AWAIT(mkxp_sandbox::rb_eval_string, "require 'zlib'; p Zlib::Deflate::deflate('hello')");
SANDBOX_AWAIT(mkxp_sandbox::rb_define_global_function, "my_cpp_func", (VALUE (*)(void *, ANYARGS))my_cpp_func, -2);
SANDBOX_AWAIT(mkxp_sandbox::rb_eval_string, "my_cpp_func(1, nil, 3, 'this is a string', :symbol, 2)");
SANDBOX_AWAIT(mkxp_sandbox::rb_eval_string, "p Dir.glob '/mkxp-retro-game/*'");
SANDBOX_AWAIT(mkxp_sandbox::run_rmxp_scripts);
SANDBOX_AWAIT(mkxp_sandbox::rb_eval_string, "throw 'Throw an error on purpose to see if we can catch it'");
SANDBOX_AWAIT(mkxp_sandbox::rb_eval_string, "puts 'Unreachable code'");
}
}
)
mkxp_sandbox::sandbox->bindings.bind<struct coro>()()();
return arg;
}
static VALUE rescue(void *_, VALUE arg, VALUE exception) {
SANDBOX_COROUTINE(coro,
void operator()(VALUE exception) {
reenter (this) {
SANDBOX_AWAIT(mkxp_sandbox::rb_eval_string, "puts 'Entered rescue()'");
SANDBOX_AWAIT(mkxp_sandbox::rb_p, exception);
}
}
)
mkxp_sandbox::sandbox->bindings.bind<struct coro>()()(exception);
return arg;
}
static bool init_sandbox() {
struct main : boost::asio::coroutine {
void operator()() {
reenter (this) {
SANDBOX_AWAIT(mkxp_sandbox::rb_rescue, func, 0, rescue, 0);
}
}
};
mkxp_sandbox::sandbox.reset();
try {
mkxp_sandbox::sandbox.reset(new struct mkxp_sandbox::sandbox(game_path));
mkxp_sandbox::sandbox->run<struct main>();
} catch (SandboxException) {
log_printf(RETRO_LOG_ERROR, "Failed to initialize Ruby\n");
mkxp_sandbox::sandbox.reset();
return false;
}
return true;
}
extern "C" RETRO_API void retro_set_environment(retro_environment_t cb) {
environment = cb;
struct retro_log_callback log;
if (cb(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &log)) {
log_printf = log.log;
} else {
log_printf = fallback_log;
}
perf = {
.get_time_usec = nullptr,
.get_cpu_features = nullptr,
.get_perf_counter = nullptr,
.perf_register = nullptr,
.perf_start = nullptr,
.perf_stop = nullptr,
.perf_log = nullptr,
};
cb(RETRO_ENVIRONMENT_GET_PERF_INTERFACE, &perf);
}
extern "C" RETRO_API void retro_set_video_refresh(retro_video_refresh_t cb) {
video_refresh = cb;
}
extern "C" RETRO_API void retro_set_audio_sample(retro_audio_sample_t cb) {
audio_sample = cb;
}
extern "C" RETRO_API void retro_set_audio_sample_batch(retro_audio_sample_batch_t cb) {
audio_sample_batch = cb;
}
extern "C" RETRO_API void retro_set_input_poll(retro_input_poll_t cb) {
input_poll = cb;
}
extern "C" RETRO_API void retro_set_input_state(retro_input_state_t cb) {
input_state = cb;
}
extern "C" RETRO_API void retro_init() {
frame_buf = (uint32_t *)std::calloc(640 * 480, sizeof(uint32_t));
}
extern "C" RETRO_API void retro_deinit() {
std::free(frame_buf);
}
extern "C" RETRO_API unsigned int retro_api_version() {
return RETRO_API_VERSION;
}
extern "C" RETRO_API void retro_get_system_info(struct retro_system_info *info) {
std::memset(info, 0, sizeof *info);
info->library_name = "mkxp-z";
info->library_version = "rolling";
info->valid_extensions = "mkxp|mkxpz|json|ini|rxproj|rvproj|rvproj2";
info->need_fullpath = true;
info->block_extract = true;
}
extern "C" RETRO_API void retro_get_system_av_info(struct retro_system_av_info *info) {
std::memset(info, 0, sizeof *info);
info->timing = {
.fps = 60.0,
.sample_rate = 44100.0,
};
info->geometry = {
.base_width = 640,
.base_height = 480,
.max_width = 640,
.max_height = 480,
.aspect_ratio = 640.0f / 480.0f,
};
}
extern "C" RETRO_API void retro_set_controller_port_device(unsigned int port, unsigned int device) {
}
extern "C" RETRO_API void retro_reset() {
init_sandbox();
}
extern "C" RETRO_API void retro_run() {
input_poll();
video_refresh(frame_buf, 640, 480, 640 * 4);
audio_sample(0, 0);
}
extern "C" RETRO_API size_t retro_serialize_size() {
return 0;
}
extern "C" RETRO_API bool retro_serialize(void *data, size_t size) {
return true;
}
extern "C" RETRO_API bool retro_unserialize(const void *data, size_t size) {
return true;
}
extern "C" RETRO_API void retro_cheat_reset() {
}
extern "C" RETRO_API void retro_cheat_set(unsigned int index, bool enabled, const char *code) {
}
extern "C" RETRO_API bool retro_load_game(const struct retro_game_info *info) {
if (info == NULL) {
log_printf(RETRO_LOG_ERROR, "This core cannot start without a game\n");
return false;
}
game_path = info->path;
enum retro_pixel_format fmt = RETRO_PIXEL_FORMAT_XRGB8888;
if (!environment(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt)) {
log_printf(RETRO_LOG_ERROR, "XRGB8888 is not supported\n");
return false;
}
return init_sandbox();
}
extern "C" RETRO_API bool retro_load_game_special(unsigned int type, const struct retro_game_info *info, size_t num) {
return false;
}
extern "C" RETRO_API void retro_unload_game() {
mkxp_sandbox::sandbox.reset();
}
extern "C" RETRO_API unsigned int retro_get_region() {
return RETRO_REGION_NTSC;
}
extern "C" RETRO_API void *retro_get_memory_data(unsigned int id) {
return NULL;
}
extern "C" RETRO_API size_t retro_get_memory_size(unsigned int id) {
return 0;
}