Envision, Create, Share

Welcome to HBGames, a leading amateur game development forum and Discord server. All are welcome, and amongst our ranks you will find experts in their field from all aspects of video game design and development.

How to use RGSS native C/ruby interface functions

Sooo, i think noone has made research about this.
im going to explain some things with some examples and then someone searchs an utility for this. Sorry if my non native english fails somewhere, also, im not good at redactions, so this will look messy and redundant sometimes.

If anyone has made a C extension for ruby (the normal one, not RGSS),you know theres a lot of functions to write ruby in pure , like:
Code:
rb_define_class()

rb_define_method()

rb_new_str()
This is the native interface for the interpreter itself and every C extension for it.
Obviously all these functions are inside the RGSSX0XX.dll so it can setup and run the scripts.
Now im gonna explain how to use them, there are a lot of ways, using a custom Game.exe, injecting new code on the DLL itself, using another DLL, etc. Im gonna explain the first one using the chinese RGSS Player Ex rewrite as a base and using RGSS102J.dll.

First, the source for the new game.exe:
http://hi.baidu.com/tvga/blog/item/a4da ... 6e0a9.html

Now, lets see how it calls the exported function RGSSInitialize from the DLL:
Code:
hRgssCore = ::LoadLibraryA("RGSS102J.dll");

typedef void (*RGSSInitialize)  (HMODULE hRgssDll);

RGSSInitialize pRGSSInitialize = NULL;

pRGSSInitialize = ::GetProcAddress(hRgssCore, "RGSSInitialize");
First, the library is loaded into memory.
Then it defines a function prototype with the return value and the needed parameters.
Creates a function pointer and gets the address of the exported function from the DLL.

Roughly, a function is just a pointer that points to an address with instructions, so you can play with them like you do with normal variables. So, how do we get a pointer to a function that is not exported? we cant use GetProcAddress for this, time for imagination.

Now ill tell you how I make it, theres probably a simpler way that I dont know right now.
First, you need to find the function itself with a debugger (Load the dll on the debugger and find it, theres different ways that im not gonna cover here if noone wants) and note down the address, in this "tutorial" Ill use the rb_define_module function, which address in my DLL is 0x10020D70.
Now the cheap trick: since the memory addresses are not the same as the file addresses, we are going to use the address of an exported function as base for the rest, in this case RGSSGameMain. The address for RGSSGameMain in the file is 0x10003B00.
Time for some code!

Code:
typedef void (*RGSSGameMain) (HWND hWnd, const char* pScriptNames, char** pRgssadName);

typedef unsigned long (*rb_define_moduleF) (const char* name);

 

rb_define_moduleF rb_define_module = NULL;

RGSSGameMain pRGSSGameMain = NULL;

 

hRgssCore = ::LoadLibraryA("RGSS102J.dll");

pRGSSGameMain = ::GetProcAddress(hRgssCore, "RGSSGameMain");
Here we are declaring the function prototype for both functions, their pointers and getting the RGSSGameMain address, now what?
While the addresses change from the file to memory, the distance between two instructions is always the same (at least in XP), this means we just need to substract our function address (got it from the debugger) with the exported function address:
Code:
rb_define_module address -> 0x10020D70

RGSSGameMain address ->    0x10003B00

Distance between them ->    0x1D270
Now we this new number like this:
Code:
rb_define_module     = (rb_define_moduleF)  ((int)pRGSSGameMain + 0x1D270);
Yay! now we have our pointer pointing to the correct memory address, this mean we can now use the function without problems:
Code:
unsigned long rb_mDahrkael = rb_define_module("Dahrkael");
If we compile it and run the game, we will find that the module Dahrkael is defined by default.

And thats it. this can be used to avoid the Win32API overhead, to make some secret code or who knows (i dont know lol).

Another example:
Code:
 

VALUE rb_mLife = rb_define_module("Life");

rb_define_const(rb_mLife, "MEANING", INT2FIX(42));

 
also found an useful pair of functions, rb_funcall2 and rb_intern.
This mean you can call any method of from any class (if you have the class ID)
Code:
    

VALUE testing()

{

       VALUE string = rb_str_new("The answer is 42", 16);

    rb_funcall2(rb_cObject, rb_intern("print"), 1, string);

    return 2;

}

 

rb_define_singleton_function(rb_mLife, "testing", testing, 0);

 
Then in ruby:
=> Life.testing
=> Message Box saying "The answer is 42"


Ill leave the DLL i used plus some basic function here so someone can try to do something more useful.
http://bb.ohsk.net/uploads/RGSS102J.zip
Code:
 

typedef VALUE       (*rb_define_moduleF)                (const char* name);

typedef void        (*rb_define_constF)                 (VALUE parent, const char* name, VALUE value);

typedef VALUE       (*rb_f_requireF)                    (VALUE obj, VALUE fname);

typedef VALUE       (*rb_str_newF)                      (char *ptr, long len);

typedef ID          (*rb_internF)                       (const char* name);

typedef VALUE       (*rb_funcall2F)                     (VALUE recv, ID mid, int argc, int argv);

typedef void        (*rb_define_singleton_functionF)    (VALUE parent,const char* name,VALUE(*)(),int argc);

 

rb_define_module                    = (rb_define_moduleF)                   ((int)pRGSSGameMain + 0x1D270);

rb_define_const                     = (rb_define_constF)                    ((int)pRGSSGameMain + 0x7EC00);

rb_f_require                        = (rb_f_requireF)                       ((int)pRGSSGameMain + 0x35430);

rb_str_new                          = (rb_str_newF)                         ((int)pRGSSGameMain + 0x73680);

rb_define_singleton_function        = (rb_define_singleton_functionF)       ((int)pRGSSGameMain + 0x1DA10);

rb_funcall2                         = (rb_funcall2F)                        ((int)pRGSSGameMain + 0x266F0);

rb_intern                           = (rb_internF)                          ((int)pRGSSGameMain + 0x5330A);
aaaand now the functions I labeled with the file address, to convert them to memory address see above:
Code:
rb_define_class 0x10020AB0 

rb_define_class_under 0x10020BB0

rb_define_module 0x10020D70 

rb_define_method 0x10021330

rb_define_singleton_method 0x10021510

rb_scan_args 0x10021620 

rb_warn 0x100257A0 

rb_name_error 0x10025D60 

rb_raise 0x10026090 

rb_notimplement 0x10026170 

rb_check_type 0x10026940 

rb_secure 0x10027030 

rb_print_undef_str 0x10027180 

rb_funcall2 0x1002A1F0  

rb_f_caller 0x1002A4E0 

load_failed 0x1002ABA0 

rb_f_throw 0x1002D780 

eval_string_with_cref 0x10032680

rb_f_eval 0x10032BB0 

rb_eval_cmd 0x10032F00 

rb_f_catch 0x10036730 

raise_method_missing 0x10036DE0 

method_missing 0x10036F10 

rb_search_method_entry 0x10037860

rb_class_inherited 0x10037B20 

rb_require_safe 0x100381A0

rb_require 0x10038460

rb_f_require 0x10038F30

rb_obj_clone 0x10051CF0 

rb_obj_dup 0x10051D80 

rb_intern 0x10056E0A

rb_id2name 0x10057040

str_new 0x100770F0 

rb_str_new 0x10077180

mod_av_set 0x100825F0

rb_define_const 0x10082700 

rb_cvar_set 0x10083010

rb_cvar_get 0x10083110 

rb_cv_set 0x10083190

rb_cv_get 0x100831D0

rb_mod_remove_cvar 0x10083210 

 

Final note: I hope this will reduce all those ugly Win32API+pure ruby scripts to just a couple of calls, because everything is done internally.
Someone wants to rewrite the Graphics.snap_to_bitmap script this way? :box:

Now, questions, feedback, threats, go ahead.
 
With a dissasembler (Cheat Engine is good for the RGSS) and the Ruby 1.8.6 source code you can find a lot of functions. (Some use C strings so you can search the pointer of the string and search push string_ptr in the dissassembly, others are nearby to some functions.)

Btw, I saw you did some scripts that modify the RGSS memory. Be aware that the RGSS start pointer isn't always 0x10000000. (Some player have things there so the RGSS is stored somewhere else and your script can cause Memory failure, you've to check where the RGSS is before patching it. LoadLibrary).
 
I tried but RGSS301.dll is build with specific options that erase unused functions... (Or that change the calling convention of functions like rb_str_new)

Xilef has done something with the RGSS3 but I don't know any more if it's Win32API calling or RGSS injections : viewtopic.php?f=316&t=78477
 
I, conceptually, understand what is going on here, but this is out of my usual element - the setting up part of this, not the Ruby in C part. Is this something I can do with RMXP out of the box or do I need a modified game.exe? If so, how would I go about it (just a rough outline) - using this, or another, method; or, if that's involved, where can I read more?

My biggest hurdle is that I'm not really sure what to read to better understand how all of this works - or where to find pertinent examples. I don't come from a programming background, but a mathematics background, so all of the concepts are clear when I encounter them, but I'm not sure what terms I should be looking for (in general, if that makes sense...).

Thank you (anyone who responds) for the assistance, hacking together dll's and passing pointers to Ruby objects is inelegant and I'd much prefer something along these lines, if reasonably possible.
 
At the end of the day, your goal is probably to call native code from RPG Maker and there's two ways to do that (and both ways work on all RPG Maker versions);

  • Modified Game.exe - This is where you write additional Ruby APIs to be called from RPG Maker, this is more complex and requires finding addresses in the RGSS DLL and binding them to function pointers (which is what has been discussed the last couple of posts)
  • Win32API - This is the easier and more "standard" method. RGSS still has the Win32API Ruby class shoved in it, so you can write a DLL with public facing function pointers and call them. This has a bit more of a performance hit compared to a custom Game.exe, but it is okay for calling a single, complex function.

Modifying the Game.exe allows you to get greater control on the Win32 control that RPG Maker uses as you have to manually open the Window yourself. Saying that; through the second method you can still hook the calling process and modify the Window's settings after it is created (and outright create a new one even), so it is still possible, but in that particular scenario (which is very specific) I'd probably recommend the first method, but most games and most users won't even need to do this.

For super advanced stuff like my OpenGL 3D rendering API a DLL is good enough, but working around the performance hit is difficult. I don't think I quite mastered that myself, if you're experienced with C/C++ I would recommend having all DLL actions running on a second thread and using IPC to queue parallel operations rather than using the single-threaded, blocking calls that the Win32API module does.
 
Looking over old Ruby source, it shouldn't be overly difficult to make a win32api call that loads C written methods/classes in as if you had written an extension. You have to do some weird goofiness with sym_table and rb_intern stuff, need to be conscious of GC, rewrite some Ruby source* to make it fit (especially the goofy node stuff), but it isn't impossible (just time consuming - and seemingly pointless since other editors exist). If you were looking to just do computations and could store parameters/outputs as float arrays (with an eye for how shared rgenerics work so nothing goofy happens), it probably wouldn't take more than an afternoon, or two (famous last words...) - especially if you didn't need objects/classes, just faster methods (or could work with passed in by pointer ruby objects).

If I get some free time, I may work this all out for the hell of it and see how much of a kludgy mess I can come up with:p

*As in use rewritten versions in your DLL, not as in rewrite the source used by RGSS (which you cannot do, obviously).
 
Yeah I was actually thinking about this after writing my last reply above.

It is certainly possible, you can most likely just get the function pointers from within the DLL in the same way that a modified Game.exe will do it. A lot of work has already been done there, so getting something working shouldn't be too hard.
 
I'm, actually, not sure you would need any of that, depending on what exactly you are doing. Gettings things visible to ruby isn't so bad if you mod already existing RClass types (make a shell in ruby, pass it in). The only tedious part is replacing methods with c code, but that can all be done by your own code, honestly. The symbol table, and some other stuff, can, actually, be manipulated in Ruby, so you may not even need shell methors.

Of course, if you want to make more than a "does computations fast" object, you would need more, most likely; but I don't see the point, al that jazz can be Ruby handled, really.

Of course, my suggestion would create a more fragile ecosystem, you would have to be aware that you weren't dealing with a normal ruby object and that a few things will behave different, but that's not so bad.

I'm going to toy with this more next week, I'll share anything that comes of it.
 

Thank you for viewing

HBGames is a leading amateur video game development forum and Discord server open to all ability levels. Feel free to have a nosey around!

Discord

Join our growing and active Discord server to discuss all aspects of game making in a relaxed environment. Join Us

Content

  • Our Games
  • Games in Development
  • Emoji by Twemoji.
    Top