mirror of
https://github.com/mkxp-z/mkxp-z.git
synced 2025-04-29 19:44:49 +02:00

Setup active RGSS version at runtime. Desired version can be specified via config, or as default, auto detected from the game files. This removes the need to build specifically for each version, which should help packaging a lot. This also greatly reduces the danger of introducing code that wouldn't compile on all RGSS version paths (as certain code paths were completely ifdef'd out). This can be optimized more, eg. not compiling shaders that aren't needed in the active version.
473 lines
10 KiB
C++
473 lines
10 KiB
C++
/*
|
|
** binding-mri.cpp
|
|
**
|
|
** This file is part of mkxp.
|
|
**
|
|
** Copyright (C) 2013 Jonas Kulla <Nyocurio@gmail.com>
|
|
**
|
|
** 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 "binding.h"
|
|
#include "binding-util.h"
|
|
#include "sharedstate.h"
|
|
#include "eventthread.h"
|
|
#include "filesystem.h"
|
|
#include "util.h"
|
|
#include "debugwriter.h"
|
|
|
|
#include <ruby.h>
|
|
#include <ruby/encoding.h>
|
|
|
|
#include <assert.h>
|
|
#include <string>
|
|
#include <zlib.h>
|
|
|
|
#include <SDL_filesystem.h>
|
|
|
|
extern const char module_rpg1[];
|
|
extern const char module_rpg2[];
|
|
extern const char module_rpg3[];
|
|
|
|
static void mriBindingExecute();
|
|
static void mriBindingTerminate();
|
|
|
|
ScriptBinding scriptBindingImpl =
|
|
{
|
|
mriBindingExecute,
|
|
mriBindingTerminate
|
|
};
|
|
|
|
ScriptBinding *scriptBinding = &scriptBindingImpl;
|
|
|
|
void tableBindingInit();
|
|
void etcBindingInit();
|
|
void fontBindingInit();
|
|
void bitmapBindingInit();
|
|
void spriteBindingInit();
|
|
void viewportBindingInit();
|
|
void planeBindingInit();
|
|
void windowBindingInit();
|
|
void tilemapBindingInit();
|
|
void windowVXBindingInit();
|
|
void tilemapVXBindingInit();
|
|
|
|
void inputBindingInit();
|
|
void audioBindingInit();
|
|
void graphicsBindingInit();
|
|
|
|
void fileIntBindingInit();
|
|
|
|
RB_METHOD(mriPrint);
|
|
RB_METHOD(mriP);
|
|
RB_METHOD(mriDataDirectory);
|
|
RB_METHOD(mkxpPuts);
|
|
|
|
RB_METHOD(mriRgssMain);
|
|
RB_METHOD(_kernelCaller);
|
|
|
|
static void mriBindingInit()
|
|
{
|
|
tableBindingInit();
|
|
etcBindingInit();
|
|
fontBindingInit();
|
|
bitmapBindingInit();
|
|
spriteBindingInit();
|
|
viewportBindingInit();
|
|
planeBindingInit();
|
|
|
|
if (rgssVer == 1)
|
|
{
|
|
windowBindingInit();
|
|
tilemapBindingInit();
|
|
}
|
|
else
|
|
{
|
|
windowVXBindingInit();
|
|
tilemapVXBindingInit();
|
|
}
|
|
|
|
inputBindingInit();
|
|
audioBindingInit();
|
|
graphicsBindingInit();
|
|
|
|
fileIntBindingInit();
|
|
|
|
if (rgssVer >= 3)
|
|
{
|
|
_rb_define_module_function(rb_mKernel, "rgss_main", mriRgssMain);
|
|
|
|
_rb_define_module_function(rb_mKernel, "msgbox", mriPrint);
|
|
_rb_define_module_function(rb_mKernel, "msgbox_p", mriP);
|
|
}
|
|
else
|
|
{
|
|
_rb_define_module_function(rb_mKernel, "print", mriPrint);
|
|
_rb_define_module_function(rb_mKernel, "p", mriP);
|
|
|
|
rb_define_alias(rb_singleton_class(rb_mKernel), "_mkxp_kernel_caller_alias", "caller");
|
|
_rb_define_module_function(rb_mKernel, "caller", _kernelCaller);
|
|
}
|
|
|
|
if (rgssVer == 1)
|
|
rb_eval_string(module_rpg1);
|
|
else if (rgssVer == 2)
|
|
rb_eval_string(module_rpg2);
|
|
else if (rgssVer == 3)
|
|
rb_eval_string(module_rpg3);
|
|
else
|
|
assert(!"unreachable");
|
|
|
|
VALUE mod = rb_define_module("System");
|
|
_rb_define_module_function(mod, "data_directory", mriDataDirectory);
|
|
_rb_define_module_function(mod, "puts", mkxpPuts);
|
|
|
|
rb_gv_set("MKXP", Qtrue);
|
|
}
|
|
|
|
static void
|
|
showMsg(const std::string &msg)
|
|
{
|
|
shState->eThread().showMessageBox(msg.c_str());
|
|
}
|
|
|
|
RB_METHOD(mkxpPuts)
|
|
{
|
|
RB_UNUSED_PARAM;
|
|
|
|
const char *str;
|
|
rb_get_args(argc, argv, "z", &str RB_ARG_END);
|
|
|
|
Debug() << str;
|
|
|
|
return Qnil;
|
|
}
|
|
|
|
static void printP(int argc, VALUE *argv,
|
|
const char *convMethod, const char *sep)
|
|
{
|
|
VALUE dispString = rb_str_buf_new(128);
|
|
ID conv = rb_intern(convMethod);
|
|
|
|
for (int i = 0; i < argc; ++i)
|
|
{
|
|
VALUE str = rb_funcall2(argv[i], conv, 0, NULL);
|
|
rb_str_buf_append(dispString, str);
|
|
|
|
if (i < argc)
|
|
rb_str_buf_cat2(dispString, sep);
|
|
}
|
|
|
|
showMsg(RSTRING_PTR(dispString));
|
|
}
|
|
|
|
RB_METHOD(mriPrint)
|
|
{
|
|
RB_UNUSED_PARAM;
|
|
|
|
printP(argc, argv, "to_s", "");
|
|
|
|
return Qnil;
|
|
}
|
|
|
|
RB_METHOD(mriP)
|
|
{
|
|
RB_UNUSED_PARAM;
|
|
|
|
printP(argc, argv, "inspect", "\n");
|
|
|
|
return Qnil;
|
|
}
|
|
|
|
RB_METHOD(mriDataDirectory)
|
|
{
|
|
RB_UNUSED_PARAM;
|
|
|
|
const char *org, *app;
|
|
|
|
rb_get_args(argc, argv, "zz", &org, &app RB_ARG_END);
|
|
|
|
char *path = SDL_GetPrefPath(org, app);
|
|
|
|
VALUE pathStr = rb_str_new_cstr(path);
|
|
|
|
SDL_free(path);
|
|
|
|
return pathStr;
|
|
}
|
|
|
|
RB_METHOD(mriRgssMain)
|
|
{
|
|
RB_UNUSED_PARAM;
|
|
|
|
// TODO: Implement F12 reset
|
|
rb_yield(Qnil);
|
|
|
|
return Qnil;
|
|
}
|
|
|
|
RB_METHOD(_kernelCaller)
|
|
{
|
|
RB_UNUSED_PARAM;
|
|
|
|
VALUE trace = rb_funcall2(rb_mKernel, rb_intern("_mkxp_kernel_caller_alias"), 0, 0);
|
|
|
|
if (rb_type(trace) != RUBY_T_ARRAY)
|
|
return trace;
|
|
|
|
long len = RARRAY_LEN(trace);
|
|
|
|
if (len < 2)
|
|
return trace;
|
|
|
|
/* Remove useless "ruby:1:in 'eval'" */
|
|
rb_ary_pop(trace);
|
|
|
|
/* Also remove trace of this helper function */
|
|
rb_ary_shift(trace);
|
|
|
|
len -= 2;
|
|
|
|
if (len == 0)
|
|
return trace;
|
|
|
|
/* RMXP does this, not sure if specific or 1.8 related */
|
|
VALUE args[] = { rb_str_new_cstr(":in `<main>'"), rb_str_new_cstr("") };
|
|
rb_funcall2(rb_ary_entry(trace, len-1), rb_intern("gsub!"), 2, args);
|
|
|
|
return trace;
|
|
}
|
|
|
|
static VALUE newStringUTF8(const char *string, long length)
|
|
{
|
|
return rb_enc_str_new(string, length, rb_utf8_encoding());
|
|
}
|
|
|
|
struct evalArg
|
|
{
|
|
VALUE string;
|
|
VALUE filename;
|
|
};
|
|
|
|
static VALUE evalHelper(evalArg *arg)
|
|
{
|
|
VALUE argv[] = { arg->string, Qnil, arg->filename };
|
|
return rb_funcall2(Qnil, rb_intern("eval"), ARRAY_SIZE(argv), argv);
|
|
}
|
|
|
|
static VALUE evalString(VALUE string, VALUE filename, int *state)
|
|
{
|
|
evalArg arg = { string, filename };
|
|
return rb_protect((VALUE (*)(VALUE))evalHelper, (VALUE)&arg, state);
|
|
}
|
|
|
|
static void runCustomScript(const std::string &filename)
|
|
{
|
|
std::string scriptData;
|
|
|
|
if (!readFile(filename.c_str(), scriptData))
|
|
{
|
|
showMsg(std::string("Unable to open '") + filename + "'");
|
|
return;
|
|
}
|
|
|
|
evalString(newStringUTF8(scriptData.c_str(), scriptData.size()),
|
|
newStringUTF8(filename.c_str(), filename.size()), NULL);
|
|
}
|
|
|
|
VALUE kernelLoadDataInt(const char *filename);
|
|
|
|
static void runRMXPScripts()
|
|
{
|
|
const Config &conf = shState->rtData().config;
|
|
const std::string &scriptPack = conf.game.scripts;
|
|
|
|
if (scriptPack.empty())
|
|
{
|
|
showMsg("No game scripts specified (missing Game.ini?)");
|
|
return;
|
|
}
|
|
|
|
if (!shState->fileSystem().exists(scriptPack.c_str()))
|
|
{
|
|
showMsg("Unable to open '" + scriptPack + "'");
|
|
return;
|
|
}
|
|
|
|
VALUE scriptArray = kernelLoadDataInt(scriptPack.c_str());
|
|
|
|
if (rb_type(scriptArray) != RUBY_T_ARRAY)
|
|
{
|
|
showMsg("Failed to read script data");
|
|
return;
|
|
}
|
|
|
|
rb_gv_set("$RGSS_SCRIPTS", scriptArray);
|
|
|
|
long scriptCount = RARRAY_LEN(scriptArray);
|
|
|
|
std::string decodeBuffer;
|
|
decodeBuffer.resize(0x1000);
|
|
|
|
for (long i = 0; i < scriptCount; ++i)
|
|
{
|
|
VALUE script = rb_ary_entry(scriptArray, i);
|
|
|
|
if (rb_type(script) != RUBY_T_ARRAY)
|
|
continue;
|
|
|
|
VALUE scriptName = rb_ary_entry(script, 1);
|
|
VALUE scriptString = rb_ary_entry(script, 2);
|
|
|
|
int result = Z_OK;
|
|
unsigned long bufferLen;
|
|
|
|
while (true)
|
|
{
|
|
unsigned char *bufferPtr =
|
|
reinterpret_cast<unsigned char*>(const_cast<char*>(decodeBuffer.c_str()));
|
|
const unsigned char *sourcePtr =
|
|
reinterpret_cast<const unsigned char*>(RSTRING_PTR(scriptString));
|
|
|
|
bufferLen = decodeBuffer.length();
|
|
|
|
result = uncompress(bufferPtr, &bufferLen,
|
|
sourcePtr, RSTRING_LEN(scriptString));
|
|
|
|
bufferPtr[bufferLen] = '\0';
|
|
|
|
if (result != Z_BUF_ERROR)
|
|
break;
|
|
|
|
decodeBuffer.resize(decodeBuffer.size()*2);
|
|
}
|
|
|
|
if (result != Z_OK)
|
|
{
|
|
static char buffer[256];
|
|
snprintf(buffer, sizeof(buffer), "Error decoding script %ld: '%s'",
|
|
i, RSTRING_PTR(scriptName));
|
|
|
|
showMsg(buffer);
|
|
|
|
break;
|
|
}
|
|
|
|
rb_ary_store(script, 3, rb_str_new_cstr(decodeBuffer.c_str()));
|
|
}
|
|
|
|
/* Execute preloaded scripts */
|
|
for (size_t i = 0; i < conf.preloadScripts.size(); ++i)
|
|
runCustomScript(conf.preloadScripts[i]);
|
|
|
|
for (long i = 0; i < scriptCount; ++i)
|
|
{
|
|
VALUE script = rb_ary_entry(scriptArray, i);
|
|
VALUE scriptDecoded = rb_ary_entry(script, 3);
|
|
VALUE string = newStringUTF8(RSTRING_PTR(scriptDecoded),
|
|
RSTRING_LEN(scriptDecoded));
|
|
|
|
VALUE fname;
|
|
if (conf.useScriptNames)
|
|
{
|
|
fname = rb_ary_entry(script, 1);
|
|
}
|
|
else
|
|
{
|
|
char buf[32];
|
|
const char *format = rgssVer >= 3 ? "{%04ld}" : "Section%03ld";
|
|
int len = snprintf(buf, sizeof(buf), format, i);
|
|
fname = newStringUTF8(buf, len);
|
|
}
|
|
|
|
int state;
|
|
evalString(string, fname, &state);
|
|
if (state)
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void showExc(VALUE exc)
|
|
{
|
|
VALUE bt = rb_funcall2(exc, rb_intern("backtrace"), 0, NULL);
|
|
VALUE bt0 = rb_ary_entry(bt, 0);
|
|
VALUE name = rb_class_path(rb_obj_class(exc));
|
|
|
|
VALUE ds = rb_sprintf("%" PRIsVALUE ": %" PRIsVALUE " (%" PRIsVALUE ")",
|
|
bt0, exc, name);
|
|
/* omit "useless" last entry (from ruby:1:in `eval') */
|
|
for (long i = 1, btlen = RARRAY_LEN(bt) - 1; i < btlen; ++i)
|
|
rb_str_catf(ds, "\n\tfrom %" PRIsVALUE, rb_ary_entry(bt, i));
|
|
Debug() << StringValueCStr(ds);
|
|
|
|
ID id_index = rb_intern("index");
|
|
/* an "offset" argument is not needed for the first time */
|
|
VALUE argv[2] = { rb_str_new_cstr(":") };
|
|
long filelen = NUM2LONG(rb_funcall2(bt0, id_index, 1, argv));
|
|
argv[1] = LONG2NUM(filelen + 1);
|
|
VALUE tmp = rb_funcall2(bt0, id_index, ARRAY_SIZE(argv), argv);
|
|
long linelen = NUM2LONG(tmp) - filelen - 1;
|
|
VALUE file = rb_str_subseq(bt0, 0, filelen);
|
|
VALUE line = rb_str_subseq(bt0, filelen + 1, linelen);
|
|
VALUE ms = rb_sprintf("Script '%" PRIsVALUE "' line %" PRIsVALUE
|
|
": %" PRIsVALUE " occured.\n\n%" PRIsVALUE,
|
|
file, line, name, exc);
|
|
showMsg(StringValueCStr(ms));
|
|
}
|
|
|
|
static void mriBindingExecute()
|
|
{
|
|
ruby_setup();
|
|
rb_enc_set_default_external(rb_enc_from_encoding(rb_utf8_encoding()));
|
|
|
|
Config &conf = shState->rtData().config;
|
|
|
|
if (!conf.rubyLoadpaths.empty())
|
|
{
|
|
/* Setup custom load paths */
|
|
VALUE lpaths = rb_gv_get(":");
|
|
|
|
for (size_t i = 0; i < conf.rubyLoadpaths.size(); ++i)
|
|
{
|
|
std::string &path = conf.rubyLoadpaths[i];
|
|
|
|
VALUE pathv = rb_str_new(path.c_str(), path.size());
|
|
rb_ary_push(lpaths, pathv);
|
|
}
|
|
}
|
|
|
|
RbData rbData;
|
|
shState->setBindingData(&rbData);
|
|
|
|
mriBindingInit();
|
|
|
|
std::string &customScript = conf.customScript;
|
|
if (!customScript.empty())
|
|
runCustomScript(customScript);
|
|
else
|
|
runRMXPScripts();
|
|
|
|
VALUE exc = rb_errinfo();
|
|
if (!NIL_P(exc) && !rb_obj_is_kind_of(exc, rb_eSystemExit))
|
|
showExc(exc);
|
|
|
|
ruby_cleanup(0);
|
|
|
|
shState->rtData().rqTermAck = true;
|
|
}
|
|
|
|
static void mriBindingTerminate()
|
|
{
|
|
rb_raise(rb_eSystemExit, " ");
|
|
}
|