/* ** binding-mri.cpp ** ** This file is part of mkxp. ** ** Copyright (C) 2013 Jonas Kulla ** ** 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 . */ #include "binding.h" #include "binding-util.h" #include "sharedstate.h" #include "eventthread.h" #include "filesystem.h" #include "util.h" #include "debugwriter.h" #include "graphics.h" #include #include #include #include #include #include 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(mriRgssStop); 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, "rgss_stop", mriRgssStop); _rb_define_module_function(rb_mKernel, "msgbox", mriPrint); _rb_define_module_function(rb_mKernel, "msgbox_p", mriP); rb_define_global_const("RGSS_VERSION", rb_str_new_cstr("3.0.1")); } 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(mriRgssStop) { RB_UNUSED_PARAM; while (true) shState->graphics().update(); 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_P(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 `
'"), 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_P(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_P(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(const_cast(decodeBuffer.c_str())); const unsigned char *sourcePtr = reinterpret_cast(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, " "); }