dfhack/plugins/ruby/ruby.cpp

1019 lines
33 KiB
C++

// blindly copied imports from fastdwarf
#include "Core.h"
#include "Console.h"
#include "Export.h"
#include "PluginManager.h"
#include "VersionInfo.h"
#include "MemAccess.h"
#include "DataDefs.h"
#include "df/global_objects.h"
#include "tinythread.h"
using namespace DFHack;
// DFHack stuff
static int df_loadruby(void);
static void df_unloadruby(void);
static void df_rubythread(void*);
static command_result df_rubyeval(color_ostream &out, std::vector <std::string> & parameters);
static void ruby_bind_dfhack(void);
// inter-thread communication stuff
enum RB_command {
RB_IDLE,
RB_INIT,
RB_DIE,
RB_EVAL,
};
tthread::mutex *m_irun;
tthread::mutex *m_mutex;
static volatile RB_command r_type;
static volatile command_result r_result;
static color_ostream *r_console; // color_ostream given as argument, if NULL resort to console_proxy
static const char *r_command;
static tthread::thread *r_thread;
static int onupdate_active;
static int onupdate_minyear, onupdate_minyeartick;
static color_ostream_proxy *console_proxy;
DFHACK_PLUGIN("ruby")
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
onupdate_active = 0;
// fail silently instead of spamming the console with 'failed to initialize'
// if libruby is not present, the error is still logged in stderr.log
if (!df_loadruby())
return CR_OK;
// the ruby thread sleeps trying to lock this
// when it gets it, it runs according to r_type
// when finished, it sets r_type to IDLE and unlocks
m_irun = new tthread::mutex();
// when any thread is going to request something to the ruby thread,
// lock this before anything, and release when everything is done
m_mutex = new tthread::mutex();
r_type = RB_INIT;
// create the dedicated ruby thread
// df_rubythread starts the ruby interpreter and goes to type=IDLE when done
r_thread = new tthread::thread(df_rubythread, 0);
// wait until init phase 1 is done
while (r_type != RB_IDLE)
tthread::this_thread::yield();
// ensure the ruby thread sleeps until we have a command to handle
m_irun->lock();
// check return value from rbinit
if (r_result == CR_FAILURE)
return CR_FAILURE;
commands.push_back(PluginCommand("rb_eval",
"Ruby interpreter. Eval() a ruby string.",
df_rubyeval));
return CR_OK;
}
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
// if dlopen failed
if (!r_thread)
return CR_OK;
// ensure ruby thread is idle
m_mutex->lock();
r_type = RB_DIE;
r_command = NULL;
// start ruby thread
m_irun->unlock();
// wait until ruby thread ends after RB_DIE
r_thread->join();
// cleanup everything
delete r_thread;
r_thread = 0;
delete m_irun;
// we can release m_mutex, other users will check r_thread
m_mutex->unlock();
delete m_mutex;
// dlclose libruby
df_unloadruby();
return CR_OK;
}
static command_result do_plugin_eval_ruby(color_ostream &out, const char *command)
{
command_result ret;
// ensure ruby thread is idle
m_mutex->lock();
if (!r_thread)
// raced with plugin_shutdown
return CR_OK;
r_type = RB_EVAL;
r_command = command;
r_console = &out;
// wake ruby thread up
m_irun->unlock();
// semi-active loop until ruby thread is done
while (r_type != RB_IDLE)
tthread::this_thread::yield();
ret = r_result;
r_console = NULL;
// block ruby thread
m_irun->lock();
// let other plugin_eval_ruby run
m_mutex->unlock();
return ret;
}
// send a single ruby line to be evaluated by the ruby thread
DFhackCExport command_result plugin_eval_ruby( color_ostream &out, const char *command)
{
// if dlopen failed
if (!r_thread)
return CR_FAILURE;
if (!strncmp(command, "nolock ", 7)) {
// debug only!
// run ruby commands without locking the main thread
// useful when the game is frozen after a segfault
return do_plugin_eval_ruby(out, command+7);
} else {
// wrap all ruby code inside a suspend block
// if we dont do that and rely on ruby code doing it, we'll deadlock in
// onupdate
CoreSuspender suspend;
return do_plugin_eval_ruby(out, command);
}
}
DFhackCExport command_result plugin_onupdate ( color_ostream &out )
{
if (!r_thread)
return CR_OK;
// ruby sets this flag when needed, to avoid lag running ruby code every
// frame if not necessary
if (!onupdate_active)
return CR_OK;
if (*df::global::cur_year < onupdate_minyear)
return CR_OK;
if (*df::global::cur_year == onupdate_minyear &&
*df::global::cur_year_tick < onupdate_minyeartick)
return CR_OK;
return plugin_eval_ruby(out, "DFHack.onupdate");
}
DFhackCExport command_result plugin_onstatechange ( color_ostream &out, state_change_event e)
{
if (!r_thread)
return CR_OK;
std::string cmd = "DFHack.onstatechange ";
switch (e) {
#define SCASE(s) case SC_ ## s : cmd += ":" # s ; break
SCASE(WORLD_LOADED);
SCASE(WORLD_UNLOADED);
SCASE(MAP_LOADED);
SCASE(MAP_UNLOADED);
SCASE(VIEWSCREEN_CHANGED);
SCASE(CORE_INITIALIZED);
// if we go through plugin_eval at BEGIN_UNLOAD, it'll
// try to get the suspend lock and deadlock at df exit
case SC_BEGIN_UNLOAD : return CR_OK;
SCASE(PAUSED);
SCASE(UNPAUSED);
#undef SCASE
}
return plugin_eval_ruby(out, cmd.c_str());
}
static command_result df_rubyeval(color_ostream &out, std::vector <std::string> & parameters)
{
if (parameters.size() == 1 && (parameters[0] == "help" || parameters[0] == "?"))
{
out.print("This command executes an arbitrary ruby statement.\n");
return CR_OK;
}
// reconstruct the text from dfhack console line
std::string full = "";
for (unsigned i=0 ; i<parameters.size() ; ++i) {
full += parameters[i];
if (i != parameters.size()-1)
full += " ";
}
return plugin_eval_ruby(out, full.c_str());
}
// ruby stuff
// - ruby-dev on windows is messy
// - ruby.h with gcc -m32 on linux 64 is broken
// so we dynamically load libruby with dlopen/LoadLibrary
// lib path is hardcoded here, and by default downloaded by cmake
// this code should work with ruby1.9, but ruby1.9 doesn't like running
// in a dedicated non-main thread, so use ruby1.8 binaries only for now
// these ruby definitions are invalid for windows 64bit (need long long)
typedef unsigned long VALUE;
typedef unsigned long ID;
#define Qfalse ((VALUE)0)
#define Qtrue ((VALUE)2)
#define Qnil ((VALUE)4)
#define INT2FIX(i) ((VALUE)((((long)i) << 1) | 1))
#define FIX2INT(i) (((long)i) >> 1)
#define RUBY_METHOD_FUNC(func) ((VALUE(*)(...))func)
void (*ruby_sysinit)(int *, const char ***);
void (*ruby_init)(void);
void (*ruby_init_loadpath)(void);
void (*ruby_script)(const char*);
void (*ruby_finalize)(void);
ID (*rb_intern)(const char*);
VALUE (*rb_funcall)(VALUE, ID, int, ...);
VALUE (*rb_define_module)(const char*);
void (*rb_define_singleton_method)(VALUE, const char*, VALUE(*)(...), int);
VALUE (*rb_gv_get)(const char*);
VALUE (*rb_str_new)(const char*, long);
char* (*rb_string_value_ptr)(VALUE*);
VALUE (*rb_eval_string_protect)(const char*, int*);
VALUE (*rb_ary_shift)(VALUE);
VALUE (*rb_float_new)(double);
double (*rb_num2dbl)(VALUE);
VALUE (*rb_int2inum)(long);
VALUE (*rb_uint2inum)(unsigned long);
unsigned long (*rb_num2ulong)(VALUE);
// end of rip(ruby.h)
DFHack::DFLibrary *libruby_handle;
// load the ruby library, initialize function pointers
static int df_loadruby(void)
{
const char *libpath =
#if defined(WIN32)
"./libruby.dll";
#elif defined(__APPLE__)
"/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/libruby.1.dylib";
#else
"hack/libruby.so";
#endif
libruby_handle = OpenPlugin(libpath);
if (!libruby_handle) {
fprintf(stderr, "Cannot initialize ruby plugin: failed to load %s\n", libpath);
return 0;
}
// ruby_sysinit is optional (ruby1.9 only)
ruby_sysinit = (decltype(ruby_sysinit))LookupPlugin(libruby_handle, "ruby_sysinit");
#define rbloadsym(s) if (!(s = (decltype(s))LookupPlugin(libruby_handle, #s))) return 0
rbloadsym(ruby_init);
rbloadsym(ruby_init_loadpath);
rbloadsym(ruby_script);
rbloadsym(ruby_finalize);
rbloadsym(rb_intern);
rbloadsym(rb_funcall);
rbloadsym(rb_define_module);
rbloadsym(rb_define_singleton_method);
rbloadsym(rb_gv_get);
rbloadsym(rb_str_new);
rbloadsym(rb_string_value_ptr);
rbloadsym(rb_eval_string_protect);
rbloadsym(rb_ary_shift);
rbloadsym(rb_float_new);
rbloadsym(rb_num2dbl);
rbloadsym(rb_int2inum);
rbloadsym(rb_uint2inum);
rbloadsym(rb_num2ulong);
#undef rbloadsym
return 1;
}
static void df_unloadruby(void)
{
if (libruby_handle) {
ClosePlugin(libruby_handle);
libruby_handle = 0;
}
}
static void printerr(const char* fmt, const char *arg)
{
if (r_console)
r_console->printerr(fmt, arg);
else
Core::printerr(fmt, arg);
}
// ruby thread code
static void dump_rb_error(void)
{
VALUE s, err;
err = rb_gv_get("$!");
s = rb_funcall(err, rb_intern("class"), 0);
s = rb_funcall(s, rb_intern("name"), 0);
printerr("E: %s: ", rb_string_value_ptr(&s));
s = rb_funcall(err, rb_intern("message"), 0);
printerr("%s\n", rb_string_value_ptr(&s));
err = rb_funcall(err, rb_intern("backtrace"), 0);
for (int i=0 ; i<8 ; ++i)
if ((s = rb_ary_shift(err)) != Qnil)
printerr(" %s\n", rb_string_value_ptr(&s));
}
// ruby thread main loop
static void df_rubythread(void *p)
{
int state, running;
if (ruby_sysinit) {
// ruby1.9 specific API
static int argc;
static const char *argv[] = { "dfhack", 0 };
ruby_sysinit(&argc, (const char ***)&argv);
}
// initialize the ruby interpreter
ruby_init();
ruby_init_loadpath();
// default value for the $0 "current script name"
ruby_script("dfhack");
// create the ruby objects to map DFHack to ruby methods
ruby_bind_dfhack();
console_proxy = new color_ostream_proxy(Core::getInstance().getConsole());
// ensure noone bothers us while we load data defs in the background
m_mutex->lock();
// tell the main thread our initialization is finished
r_result = CR_OK;
r_type = RB_IDLE;
// load the default ruby-level definitions in the background
state=0;
rb_eval_string_protect("require './hack/ruby/ruby'", &state);
if (state)
dump_rb_error();
// ready to go
m_mutex->unlock();
running = 1;
while (running) {
// sleep waiting for new command
m_irun->lock();
switch (r_type) {
case RB_IDLE:
case RB_INIT:
break;
case RB_DIE:
running = 0;
ruby_finalize();
break;
case RB_EVAL:
state = 0;
rb_eval_string_protect(r_command, &state);
if (state)
dump_rb_error();
break;
}
r_result = CR_OK;
r_type = RB_IDLE;
m_irun->unlock();
tthread::this_thread::yield();
}
}
#define BOOL_ISFALSE(v) ((v) == Qfalse || (v) == Qnil || (v) == INT2FIX(0))
// main DFHack ruby module
static VALUE rb_cDFHack;
// DFHack module ruby methods, binds specific dfhack methods
// df-dfhack version (eg "0.34.11-r2")
static VALUE rb_dfversion(VALUE self)
{
return rb_str_new(DFHACK_VERSION, strlen(DFHACK_VERSION));
}
// enable/disable calls to DFHack.onupdate()
static VALUE rb_dfonupdate_active(VALUE self)
{
if (onupdate_active)
return Qtrue;
else
return Qfalse;
}
static VALUE rb_dfonupdate_active_set(VALUE self, VALUE val)
{
onupdate_active = (BOOL_ISFALSE(val) ? 0 : 1);
return Qtrue;
}
static VALUE rb_dfonupdate_minyear(VALUE self)
{
return rb_uint2inum(onupdate_minyear);
}
static VALUE rb_dfonupdate_minyear_set(VALUE self, VALUE val)
{
onupdate_minyear = rb_num2ulong(val);
return Qtrue;
}
static VALUE rb_dfonupdate_minyeartick(VALUE self)
{
return rb_uint2inum(onupdate_minyeartick);
}
static VALUE rb_dfonupdate_minyeartick_set(VALUE self, VALUE val)
{
onupdate_minyeartick = rb_num2ulong(val);
return Qtrue;
}
static VALUE rb_dfprint_str(VALUE self, VALUE s)
{
if (r_console)
r_console->print("%s", rb_string_value_ptr(&s));
else
console_proxy->print("%s", rb_string_value_ptr(&s));
return Qnil;
}
static VALUE rb_dfprint_err(VALUE self, VALUE s)
{
printerr("%s", rb_string_value_ptr(&s));
return Qnil;
}
static VALUE rb_dfget_global_address(VALUE self, VALUE name)
{
return rb_uint2inum(Core::getInstance().vinfo->getAddress(rb_string_value_ptr(&name)));
}
static VALUE rb_dfget_vtable(VALUE self, VALUE name)
{
return rb_uint2inum((uint32_t)Core::getInstance().vinfo->getVTable(rb_string_value_ptr(&name)));
}
// read the c++ class name from a vtable pointer, inspired from doReadClassName
// XXX virtual classes only! dark pointer arithmetic, use with caution !
static VALUE rb_dfget_rtti_classname(VALUE self, VALUE vptr)
{
char *ptr = (char*)rb_num2ulong(vptr);
#ifdef WIN32
char *rtti = *(char**)(ptr - 0x4);
char *typeinfo = *(char**)(rtti + 0xC);
// skip the .?AV, trim @@ from end
return rb_str_new(typeinfo+0xc, strlen(typeinfo+0xc)-2);
#else
char *typeinfo = *(char**)(ptr - 0x4);
char *typestring = *(char**)(typeinfo + 0x4);
while (*typestring >= '0' && *typestring <= '9')
typestring++;
return rb_str_new(typestring, strlen(typestring));
#endif
}
static VALUE rb_dfget_vtable_ptr(VALUE self, VALUE objptr)
{
// actually, rb_dfmemory_read_int32
return rb_uint2inum(*(uint32_t*)rb_num2ulong(objptr));
}
// raw memory access
// used by the ruby class definitions
// XXX may cause game crash ! double-check your addresses !
static VALUE rb_dfmalloc(VALUE self, VALUE len)
{
char *ptr = (char*)malloc(FIX2INT(len));
if (!ptr)
return Qnil;
memset(ptr, 0, FIX2INT(len));
return rb_uint2inum((uint32_t)ptr);
}
static VALUE rb_dffree(VALUE self, VALUE ptr)
{
free((void*)rb_num2ulong(ptr));
return Qtrue;
}
// memory reading (buffer)
static VALUE rb_dfmemory_read(VALUE self, VALUE addr, VALUE len)
{
return rb_str_new((char*)rb_num2ulong(addr), rb_num2ulong(len));
}
// memory reading (integers/floats)
static VALUE rb_dfmemory_read_int8(VALUE self, VALUE addr)
{
return rb_int2inum(*(char*)rb_num2ulong(addr));
}
static VALUE rb_dfmemory_read_int16(VALUE self, VALUE addr)
{
return rb_int2inum(*(short*)rb_num2ulong(addr));
}
static VALUE rb_dfmemory_read_int32(VALUE self, VALUE addr)
{
return rb_int2inum(*(int*)rb_num2ulong(addr));
}
static VALUE rb_dfmemory_read_float(VALUE self, VALUE addr)
{
return rb_float_new(*(float*)rb_num2ulong(addr));
}
// memory writing (buffer)
static VALUE rb_dfmemory_write(VALUE self, VALUE addr, VALUE raw)
{
// no stable api for raw.length between rb1.8/rb1.9 ...
int strlen = FIX2INT(rb_funcall(raw, rb_intern("length"), 0));
memcpy((void*)rb_num2ulong(addr), rb_string_value_ptr(&raw), strlen);
return Qtrue;
}
// memory writing (integers/floats)
static VALUE rb_dfmemory_write_int8(VALUE self, VALUE addr, VALUE val)
{
*(char*)rb_num2ulong(addr) = rb_num2ulong(val);
return Qtrue;
}
static VALUE rb_dfmemory_write_int16(VALUE self, VALUE addr, VALUE val)
{
*(short*)rb_num2ulong(addr) = rb_num2ulong(val);
return Qtrue;
}
static VALUE rb_dfmemory_write_int32(VALUE self, VALUE addr, VALUE val)
{
*(int*)rb_num2ulong(addr) = rb_num2ulong(val);
return Qtrue;
}
static VALUE rb_dfmemory_write_float(VALUE self, VALUE addr, VALUE val)
{
*(float*)rb_num2ulong(addr) = rb_num2dbl(val);
return Qtrue;
}
// return memory permissions at address (eg "rx", nil if unmapped)
static VALUE rb_dfmemory_check(VALUE self, VALUE addr)
{
void *ptr = (void*)rb_num2ulong(addr);
std::vector<t_memrange> ranges;
Core::getInstance().p->getMemRanges(ranges);
unsigned i = 0;
while (i < ranges.size() && ranges[i].end <= ptr)
i++;
if (i >= ranges.size() || ranges[i].start > ptr || !ranges[i].valid)
return Qnil;
std::string perm = "";
if (ranges[i].read)
perm += "r";
if (ranges[i].write)
perm += "w";
if (ranges[i].execute)
perm += "x";
if (ranges[i].shared)
perm += "s";
return rb_str_new(perm.c_str(), perm.length());
}
// memory write (tmp override page permissions, eg patch code)
static VALUE rb_dfmemory_patch(VALUE self, VALUE addr, VALUE raw)
{
int strlen = FIX2INT(rb_funcall(raw, rb_intern("length"), 0));
bool ret;
ret = Core::getInstance().p->patchMemory((void*)rb_num2ulong(addr),
rb_string_value_ptr(&raw), strlen);
return ret ? Qtrue : Qfalse;
}
// stl::string
static VALUE rb_dfmemory_stlstring_new(VALUE self)
{
std::string *ptr = new std::string;
return rb_uint2inum((uint32_t)ptr);
}
static VALUE rb_dfmemory_stlstring_delete(VALUE self, VALUE addr)
{
std::string *ptr = (std::string*)rb_num2ulong(addr);
if (ptr)
delete ptr;
return Qtrue;
}
static VALUE rb_dfmemory_stlstring_init(VALUE self, VALUE addr)
{
// XXX THIS IS TERRIBLE
std::string *ptr = new std::string;
memcpy((void*)rb_num2ulong(addr), (void*)ptr, sizeof(*ptr));
return Qtrue;
}
static VALUE rb_dfmemory_read_stlstring(VALUE self, VALUE addr)
{
std::string *s = (std::string*)rb_num2ulong(addr);
return rb_str_new(s->c_str(), s->length());
}
static VALUE rb_dfmemory_write_stlstring(VALUE self, VALUE addr, VALUE val)
{
std::string *s = (std::string*)rb_num2ulong(addr);
int strlen = FIX2INT(rb_funcall(val, rb_intern("length"), 0));
s->assign(rb_string_value_ptr(&val), strlen);
return Qtrue;
}
// vector access
static VALUE rb_dfmemory_vec_new(VALUE self)
{
std::vector<uint8_t> *ptr = new std::vector<uint8_t>;
return rb_uint2inum((uint32_t)ptr);
}
static VALUE rb_dfmemory_vec_delete(VALUE self, VALUE addr)
{
std::vector<uint8_t> *ptr = (std::vector<uint8_t>*)rb_num2ulong(addr);
if (ptr)
delete ptr;
return Qtrue;
}
static VALUE rb_dfmemory_vec_init(VALUE self, VALUE addr)
{
std::vector<uint8_t> *ptr = new std::vector<uint8_t>;
memcpy((void*)rb_num2ulong(addr), (void*)ptr, sizeof(*ptr));
return Qtrue;
}
// vector<uint8>
static VALUE rb_dfmemory_vec8_length(VALUE self, VALUE addr)
{
std::vector<uint8_t> *v = (std::vector<uint8_t>*)rb_num2ulong(addr);
return rb_uint2inum(v->size());
}
static VALUE rb_dfmemory_vec8_ptrat(VALUE self, VALUE addr, VALUE idx)
{
std::vector<uint8_t> *v = (std::vector<uint8_t>*)rb_num2ulong(addr);
return rb_uint2inum((uint32_t)&v->at(FIX2INT(idx)));
}
static VALUE rb_dfmemory_vec8_insertat(VALUE self, VALUE addr, VALUE idx, VALUE val)
{
std::vector<uint8_t> *v = (std::vector<uint8_t>*)rb_num2ulong(addr);
v->insert(v->begin()+FIX2INT(idx), rb_num2ulong(val));
return Qtrue;
}
static VALUE rb_dfmemory_vec8_deleteat(VALUE self, VALUE addr, VALUE idx)
{
std::vector<uint8_t> *v = (std::vector<uint8_t>*)rb_num2ulong(addr);
v->erase(v->begin()+FIX2INT(idx));
return Qtrue;
}
// vector<uint16>
static VALUE rb_dfmemory_vec16_length(VALUE self, VALUE addr)
{
std::vector<uint16_t> *v = (std::vector<uint16_t>*)rb_num2ulong(addr);
return rb_uint2inum(v->size());
}
static VALUE rb_dfmemory_vec16_ptrat(VALUE self, VALUE addr, VALUE idx)
{
std::vector<uint16_t> *v = (std::vector<uint16_t>*)rb_num2ulong(addr);
return rb_uint2inum((uint32_t)&v->at(FIX2INT(idx)));
}
static VALUE rb_dfmemory_vec16_insertat(VALUE self, VALUE addr, VALUE idx, VALUE val)
{
std::vector<uint16_t> *v = (std::vector<uint16_t>*)rb_num2ulong(addr);
v->insert(v->begin()+FIX2INT(idx), rb_num2ulong(val));
return Qtrue;
}
static VALUE rb_dfmemory_vec16_deleteat(VALUE self, VALUE addr, VALUE idx)
{
std::vector<uint16_t> *v = (std::vector<uint16_t>*)rb_num2ulong(addr);
v->erase(v->begin()+FIX2INT(idx));
return Qtrue;
}
// vector<uint32>
static VALUE rb_dfmemory_vec32_length(VALUE self, VALUE addr)
{
std::vector<uint32_t> *v = (std::vector<uint32_t>*)rb_num2ulong(addr);
return rb_uint2inum(v->size());
}
static VALUE rb_dfmemory_vec32_ptrat(VALUE self, VALUE addr, VALUE idx)
{
std::vector<uint32_t> *v = (std::vector<uint32_t>*)rb_num2ulong(addr);
return rb_uint2inum((uint32_t)&v->at(FIX2INT(idx)));
}
static VALUE rb_dfmemory_vec32_insertat(VALUE self, VALUE addr, VALUE idx, VALUE val)
{
std::vector<uint32_t> *v = (std::vector<uint32_t>*)rb_num2ulong(addr);
v->insert(v->begin()+FIX2INT(idx), rb_num2ulong(val));
return Qtrue;
}
static VALUE rb_dfmemory_vec32_deleteat(VALUE self, VALUE addr, VALUE idx)
{
std::vector<uint32_t> *v = (std::vector<uint32_t>*)rb_num2ulong(addr);
v->erase(v->begin()+FIX2INT(idx));
return Qtrue;
}
// vector<bool>
static VALUE rb_dfmemory_vecbool_new(VALUE self)
{
std::vector<bool> *ptr = new std::vector<bool>;
return rb_uint2inum((uint32_t)ptr);
}
static VALUE rb_dfmemory_vecbool_delete(VALUE self, VALUE addr)
{
std::vector<bool> *ptr = (std::vector<bool>*)rb_num2ulong(addr);
if (ptr)
delete ptr;
return Qtrue;
}
static VALUE rb_dfmemory_vecbool_init(VALUE self, VALUE addr)
{
std::vector<bool> *ptr = new std::vector<bool>;
memcpy((void*)rb_num2ulong(addr), (void*)ptr, sizeof(*ptr));
return Qtrue;
}
static VALUE rb_dfmemory_vecbool_length(VALUE self, VALUE addr)
{
std::vector<bool> *v = (std::vector<bool>*)rb_num2ulong(addr);
return rb_uint2inum(v->size());
}
static VALUE rb_dfmemory_vecbool_at(VALUE self, VALUE addr, VALUE idx)
{
std::vector<bool> *v = (std::vector<bool>*)rb_num2ulong(addr);
return v->at(FIX2INT(idx)) ? Qtrue : Qfalse;
}
static VALUE rb_dfmemory_vecbool_setat(VALUE self, VALUE addr, VALUE idx, VALUE val)
{
std::vector<bool> *v = (std::vector<bool>*)rb_num2ulong(addr);
v->at(FIX2INT(idx)) = (BOOL_ISFALSE(val) ? 0 : 1);
return Qtrue;
}
static VALUE rb_dfmemory_vecbool_insertat(VALUE self, VALUE addr, VALUE idx, VALUE val)
{
std::vector<bool> *v = (std::vector<bool>*)rb_num2ulong(addr);
v->insert(v->begin()+FIX2INT(idx), (BOOL_ISFALSE(val) ? 0 : 1));
return Qtrue;
}
static VALUE rb_dfmemory_vecbool_deleteat(VALUE self, VALUE addr, VALUE idx)
{
std::vector<bool> *v = (std::vector<bool>*)rb_num2ulong(addr);
v->erase(v->begin()+FIX2INT(idx));
return Qtrue;
}
// BitArray
static VALUE rb_dfmemory_bitarray_length(VALUE self, VALUE addr)
{
DFHack::BitArray<int> *b = (DFHack::BitArray<int>*)rb_num2ulong(addr);
return rb_uint2inum(b->size*8); // b->size is in bytes
}
static VALUE rb_dfmemory_bitarray_resize(VALUE self, VALUE addr, VALUE sz)
{
DFHack::BitArray<int> *b = (DFHack::BitArray<int>*)rb_num2ulong(addr);
b->resize(rb_num2ulong(sz));
return Qtrue;
}
static VALUE rb_dfmemory_bitarray_isset(VALUE self, VALUE addr, VALUE idx)
{
DFHack::BitArray<int> *b = (DFHack::BitArray<int>*)rb_num2ulong(addr);
return b->is_set(rb_num2ulong(idx)) ? Qtrue : Qfalse;
}
static VALUE rb_dfmemory_bitarray_set(VALUE self, VALUE addr, VALUE idx, VALUE val)
{
DFHack::BitArray<int> *b = (DFHack::BitArray<int>*)rb_num2ulong(addr);
b->set(rb_num2ulong(idx), (BOOL_ISFALSE(val) ? 0 : 1));
return Qtrue;
}
// add basic support for std::set<uint32> used for passing keyboard keys to viewscreens
#include <set>
static VALUE rb_dfmemory_set_new(VALUE self)
{
std::set<unsigned long> *ptr = new std::set<unsigned long>;
return rb_uint2inum((uint32_t)ptr);
}
static VALUE rb_dfmemory_set_delete(VALUE self, VALUE set)
{
std::set<unsigned long> *ptr = (std::set<unsigned long>*)rb_num2ulong(set);
if (ptr)
delete ptr;
return Qtrue;
}
static VALUE rb_dfmemory_set_set(VALUE self, VALUE set, VALUE key)
{
std::set<unsigned long> *ptr = (std::set<unsigned long>*)rb_num2ulong(set);
ptr->insert(rb_num2ulong(key));
return Qtrue;
}
static VALUE rb_dfmemory_set_isset(VALUE self, VALUE set, VALUE key)
{
std::set<unsigned long> *ptr = (std::set<unsigned long>*)rb_num2ulong(set);
return ptr->count(rb_num2ulong(key)) ? Qtrue : Qfalse;
}
static VALUE rb_dfmemory_set_deletekey(VALUE self, VALUE set, VALUE key)
{
std::set<unsigned long> *ptr = (std::set<unsigned long>*)rb_num2ulong(set);
ptr->erase(rb_num2ulong(key));
return Qtrue;
}
static VALUE rb_dfmemory_set_clear(VALUE self, VALUE set)
{
std::set<unsigned long> *ptr = (std::set<unsigned long>*)rb_num2ulong(set);
ptr->clear();
return Qtrue;
}
/* call an arbitrary object virtual method */
#ifdef WIN32
__declspec(naked) static int raw_vcall(void *that, void *fptr, unsigned long a0,
unsigned long a1, unsigned long a2, unsigned long a3, unsigned long a4, unsigned long a5)
{
// __thiscall requires that the callee cleans up the stack
// here we dont know how many arguments it will take, so
// we simply fix esp across the funcall
__asm {
push ebp
mov ebp, esp
push a5
push a4
push a3
push a2
push a1
push a0
mov ecx, that
call fptr
mov esp, ebp
pop ebp
ret
}
}
#else
static int raw_vcall(void *that, void *fptr, unsigned long a0,
unsigned long a1, unsigned long a2, unsigned long a3, unsigned long a4, unsigned long a5)
{
int (*t_fptr)(void *me, int, int, int, int, int, int);
t_fptr = (decltype(t_fptr))fptr;
return t_fptr(that, a0, a1, a2, a3, a4, a5);
}
#endif
// call an arbitrary vmethod, convert args/ret to native values for raw_vcall
static VALUE rb_dfvcall(VALUE self, VALUE cppobj, VALUE fptr, VALUE a0, VALUE a1, VALUE a2, VALUE a3, VALUE a4, VALUE a5)
{
return rb_int2inum(raw_vcall((void*)rb_num2ulong(cppobj), (void*)rb_num2ulong(fptr),
rb_num2ulong(a0), rb_num2ulong(a1),
rb_num2ulong(a2), rb_num2ulong(a3),
rb_num2ulong(a4), rb_num2ulong(a5)));
}
// define module DFHack and its methods
static void ruby_bind_dfhack(void) {
rb_cDFHack = rb_define_module("DFHack");
rb_define_singleton_method(rb_cDFHack, "onupdate_active", RUBY_METHOD_FUNC(rb_dfonupdate_active), 0);
rb_define_singleton_method(rb_cDFHack, "onupdate_active=", RUBY_METHOD_FUNC(rb_dfonupdate_active_set), 1);
rb_define_singleton_method(rb_cDFHack, "onupdate_minyear", RUBY_METHOD_FUNC(rb_dfonupdate_minyear), 0);
rb_define_singleton_method(rb_cDFHack, "onupdate_minyear=", RUBY_METHOD_FUNC(rb_dfonupdate_minyear_set), 1);
rb_define_singleton_method(rb_cDFHack, "onupdate_minyeartick", RUBY_METHOD_FUNC(rb_dfonupdate_minyeartick), 0);
rb_define_singleton_method(rb_cDFHack, "onupdate_minyeartick=", RUBY_METHOD_FUNC(rb_dfonupdate_minyeartick_set), 1);
rb_define_singleton_method(rb_cDFHack, "get_global_address", RUBY_METHOD_FUNC(rb_dfget_global_address), 1);
rb_define_singleton_method(rb_cDFHack, "get_vtable", RUBY_METHOD_FUNC(rb_dfget_vtable), 1);
rb_define_singleton_method(rb_cDFHack, "get_rtti_classname", RUBY_METHOD_FUNC(rb_dfget_rtti_classname), 1);
rb_define_singleton_method(rb_cDFHack, "get_vtable_ptr", RUBY_METHOD_FUNC(rb_dfget_vtable_ptr), 1);
rb_define_singleton_method(rb_cDFHack, "print_str", RUBY_METHOD_FUNC(rb_dfprint_str), 1);
rb_define_singleton_method(rb_cDFHack, "print_err", RUBY_METHOD_FUNC(rb_dfprint_err), 1);
rb_define_singleton_method(rb_cDFHack, "malloc", RUBY_METHOD_FUNC(rb_dfmalloc), 1);
rb_define_singleton_method(rb_cDFHack, "free", RUBY_METHOD_FUNC(rb_dffree), 1);
rb_define_singleton_method(rb_cDFHack, "vmethod_do_call", RUBY_METHOD_FUNC(rb_dfvcall), 8);
rb_define_singleton_method(rb_cDFHack, "version", RUBY_METHOD_FUNC(rb_dfversion), 0);
rb_define_singleton_method(rb_cDFHack, "memory_read", RUBY_METHOD_FUNC(rb_dfmemory_read), 2);
rb_define_singleton_method(rb_cDFHack, "memory_read_int8", RUBY_METHOD_FUNC(rb_dfmemory_read_int8), 1);
rb_define_singleton_method(rb_cDFHack, "memory_read_int16", RUBY_METHOD_FUNC(rb_dfmemory_read_int16), 1);
rb_define_singleton_method(rb_cDFHack, "memory_read_int32", RUBY_METHOD_FUNC(rb_dfmemory_read_int32), 1);
rb_define_singleton_method(rb_cDFHack, "memory_read_float", RUBY_METHOD_FUNC(rb_dfmemory_read_float), 1);
rb_define_singleton_method(rb_cDFHack, "memory_write", RUBY_METHOD_FUNC(rb_dfmemory_write), 2);
rb_define_singleton_method(rb_cDFHack, "memory_write_int8", RUBY_METHOD_FUNC(rb_dfmemory_write_int8), 2);
rb_define_singleton_method(rb_cDFHack, "memory_write_int16", RUBY_METHOD_FUNC(rb_dfmemory_write_int16), 2);
rb_define_singleton_method(rb_cDFHack, "memory_write_int32", RUBY_METHOD_FUNC(rb_dfmemory_write_int32), 2);
rb_define_singleton_method(rb_cDFHack, "memory_write_float", RUBY_METHOD_FUNC(rb_dfmemory_write_float), 2);
rb_define_singleton_method(rb_cDFHack, "memory_check", RUBY_METHOD_FUNC(rb_dfmemory_check), 1);
rb_define_singleton_method(rb_cDFHack, "memory_patch", RUBY_METHOD_FUNC(rb_dfmemory_patch), 2);
rb_define_singleton_method(rb_cDFHack, "memory_stlstring_new", RUBY_METHOD_FUNC(rb_dfmemory_stlstring_new), 0);
rb_define_singleton_method(rb_cDFHack, "memory_stlstring_delete", RUBY_METHOD_FUNC(rb_dfmemory_stlstring_delete), 1);
rb_define_singleton_method(rb_cDFHack, "memory_stlstring_init", RUBY_METHOD_FUNC(rb_dfmemory_stlstring_init), 1);
rb_define_singleton_method(rb_cDFHack, "memory_read_stlstring", RUBY_METHOD_FUNC(rb_dfmemory_read_stlstring), 1);
rb_define_singleton_method(rb_cDFHack, "memory_write_stlstring", RUBY_METHOD_FUNC(rb_dfmemory_write_stlstring), 2);
rb_define_singleton_method(rb_cDFHack, "memory_vector_new", RUBY_METHOD_FUNC(rb_dfmemory_vec_new), 0);
rb_define_singleton_method(rb_cDFHack, "memory_vector_delete", RUBY_METHOD_FUNC(rb_dfmemory_vec_delete), 1);
rb_define_singleton_method(rb_cDFHack, "memory_vector_init", RUBY_METHOD_FUNC(rb_dfmemory_vec_init), 1);
rb_define_singleton_method(rb_cDFHack, "memory_vector8_length", RUBY_METHOD_FUNC(rb_dfmemory_vec8_length), 1);
rb_define_singleton_method(rb_cDFHack, "memory_vector8_ptrat", RUBY_METHOD_FUNC(rb_dfmemory_vec8_ptrat), 2);
rb_define_singleton_method(rb_cDFHack, "memory_vector8_insertat", RUBY_METHOD_FUNC(rb_dfmemory_vec8_insertat), 3);
rb_define_singleton_method(rb_cDFHack, "memory_vector8_deleteat", RUBY_METHOD_FUNC(rb_dfmemory_vec8_deleteat), 2);
rb_define_singleton_method(rb_cDFHack, "memory_vector16_length", RUBY_METHOD_FUNC(rb_dfmemory_vec16_length), 1);
rb_define_singleton_method(rb_cDFHack, "memory_vector16_ptrat", RUBY_METHOD_FUNC(rb_dfmemory_vec16_ptrat), 2);
rb_define_singleton_method(rb_cDFHack, "memory_vector16_insertat", RUBY_METHOD_FUNC(rb_dfmemory_vec16_insertat), 3);
rb_define_singleton_method(rb_cDFHack, "memory_vector16_deleteat", RUBY_METHOD_FUNC(rb_dfmemory_vec16_deleteat), 2);
rb_define_singleton_method(rb_cDFHack, "memory_vector32_length", RUBY_METHOD_FUNC(rb_dfmemory_vec32_length), 1);
rb_define_singleton_method(rb_cDFHack, "memory_vector32_ptrat", RUBY_METHOD_FUNC(rb_dfmemory_vec32_ptrat), 2);
rb_define_singleton_method(rb_cDFHack, "memory_vector32_insertat", RUBY_METHOD_FUNC(rb_dfmemory_vec32_insertat), 3);
rb_define_singleton_method(rb_cDFHack, "memory_vector32_deleteat", RUBY_METHOD_FUNC(rb_dfmemory_vec32_deleteat), 2);
rb_define_singleton_method(rb_cDFHack, "memory_vectorbool_new", RUBY_METHOD_FUNC(rb_dfmemory_vecbool_new), 0);
rb_define_singleton_method(rb_cDFHack, "memory_vectorbool_delete", RUBY_METHOD_FUNC(rb_dfmemory_vecbool_delete), 1);
rb_define_singleton_method(rb_cDFHack, "memory_vectorbool_init", RUBY_METHOD_FUNC(rb_dfmemory_vecbool_init), 1);
rb_define_singleton_method(rb_cDFHack, "memory_vectorbool_length", RUBY_METHOD_FUNC(rb_dfmemory_vecbool_length), 1);
rb_define_singleton_method(rb_cDFHack, "memory_vectorbool_at", RUBY_METHOD_FUNC(rb_dfmemory_vecbool_at), 2);
rb_define_singleton_method(rb_cDFHack, "memory_vectorbool_setat", RUBY_METHOD_FUNC(rb_dfmemory_vecbool_setat), 3);
rb_define_singleton_method(rb_cDFHack, "memory_vectorbool_insertat", RUBY_METHOD_FUNC(rb_dfmemory_vecbool_insertat), 3);
rb_define_singleton_method(rb_cDFHack, "memory_vectorbool_deleteat", RUBY_METHOD_FUNC(rb_dfmemory_vecbool_deleteat), 2);
rb_define_singleton_method(rb_cDFHack, "memory_bitarray_length", RUBY_METHOD_FUNC(rb_dfmemory_bitarray_length), 1);
rb_define_singleton_method(rb_cDFHack, "memory_bitarray_resize", RUBY_METHOD_FUNC(rb_dfmemory_bitarray_resize), 2);
rb_define_singleton_method(rb_cDFHack, "memory_bitarray_isset", RUBY_METHOD_FUNC(rb_dfmemory_bitarray_isset), 2);
rb_define_singleton_method(rb_cDFHack, "memory_bitarray_set", RUBY_METHOD_FUNC(rb_dfmemory_bitarray_set), 3);
rb_define_singleton_method(rb_cDFHack, "memory_stlset_new", RUBY_METHOD_FUNC(rb_dfmemory_set_new), 0);
rb_define_singleton_method(rb_cDFHack, "memory_stlset_delete", RUBY_METHOD_FUNC(rb_dfmemory_set_delete), 1);
rb_define_singleton_method(rb_cDFHack, "memory_stlset_set", RUBY_METHOD_FUNC(rb_dfmemory_set_set), 2);
rb_define_singleton_method(rb_cDFHack, "memory_stlset_isset", RUBY_METHOD_FUNC(rb_dfmemory_set_isset), 2);
rb_define_singleton_method(rb_cDFHack, "memory_stlset_deletekey", RUBY_METHOD_FUNC(rb_dfmemory_set_deletekey), 2);
rb_define_singleton_method(rb_cDFHack, "memory_stlset_clear", RUBY_METHOD_FUNC(rb_dfmemory_set_clear), 1);
}