Hyeve's Cloud

< Reversing GML >

So, a while ago now, I was experimenting with Gamemaker Studio 2 - an all-in-one 2D game engine mainly aimed at beginners. It's honestly, pretty neat, though it's definitely lacking in some areas.

One of the big features it has is a custom scripting language - GML.

GML is quite interesting, because it's fully dynamically typed - and I got curious. What exactly are the extents of the dynamic typing? How is it actually implemented?

So, I decided to experiment with it a bit. The first thought was very simple - GML lets you treat functions as values and assign them to variables, fairly standard stuff, but what if we assign some other value to a variable and try and use it as a function?

> Invalid callv target #2

Well, that's interesting - we can immediately see that it did try and run our 'function', and that it has some kind of 'callv' instruction internally for function calls. What if we print out a variable that contains a function pointer?

> 1280

Ooh! That's promising - it's spit out a plain number. Is that a function pointer of sorts, perhaps? Let's see what happens if we...

> hello!

It works! Looks like GML might have a predefined lookup table of functions? At the very least, the function pointer seems consistent from run to run.

Now, this immediately opens up the possibility of doing absolutely incomprehensible obfuscation, since we have the ability to pass around function pointers and potentially do math on them, without ever even naming the actual functions they refer to.

But! What's more interesting would be finding out what function a given value points at. Is there a way to do that?

As it turns out, yes, GML has a script_get_name() function, which is supposed to be for looking up the name of a custom script given a reference to it, but if we're lucky, it might also work on built in function pointers.

> view_set_visible

It sure does! Apparently, script pointer 42 corresponds to view_set_visible (an api function). And with that, we now have the ability to print out an entire list of EVERYTHING that internally uses a script pointer...

I won't put the full list here, because it's Very Long (almost 3000 functions), but this spits out the index and name of everything that gamemaker considers to be a 'script', which includes the entire standard API, and - wait, what's this?!

2504 is @@NewGMLObject@@
2505 is @@NewGMLArray@@
2506 is @@This@@
2507 is @@Global@@
2508 is @@try_hook@@
2509 is @@try_unhook@@
2510 is @@throw@@
2511 is @@finish_catch@@
2512 is @@finish_finally@@
2513 is $PRINT
2514 is $FAIL
2515 is $ERROR
2516 is ERROR
2517 is testFailed
2518 is @@typeof@@
2519 is @@new@@
2520 is @@delete@@
2521 is exception_unhandled_handler
2522 is @@instanceof@@
2523 is @@Null@@
2524 is @@NullObject@@
2525 is @@Other@@
2526 is @@GetInstance@@
2527 is @@GlobalScope@@
2528 is @@NewObject@@
2529 is @@NewProperty@@
2530 is @@CopyStatic@@
2531 is @@SetStatic@@

Those are definitely not functions that we can normally access through GML.. it looks like we've uncovered some special internal operations that are invoked by certain keywords?

Well, since they have pointers, what if we assign those pointers to variables and call them manually anyway?

Neat! We can manually invoke $ERROR and the game pops up an error window with the text we passed in. What else can we do with this?

$PRINT seems to let us print directly to console without any automatic line breaks or formatting, $FAIL is just another error popup, @@typeof@@, @@new@@, and @@delete@@ all just act as their respective keywords (perhaps unsurprisingly)..

But I'm much more curious about the ones that seem related to try-catch keywords - can we write a custom try-catch block just by calling the hook and unhook functions appropriately?

Unfortunately... it's not that simple. This doesn't even spit out an error, it just crashes the game immediately :(

And, after much experimentation, I remain somewhat clueless about how the try/catch system works internally - @@try_hook@@ does initialize exception catching for a scope and catches things that are thrown.. but then execution just evaporates into the void.

Interestingly, @@try_hook@@ takes two number (pointer? handle?) parameters, but most values of the first seem to immediately crash, and it's unclear what effect the second has. (values of 0,0 cause execution to jump back to the start of the event after catching an exception, but that's the closest I've found to actual try-catch behaviour)

However! Gamemaker is free, and GML is easy, so if anyone else out there wants to dig deeper on this (or the numerous other internal APIs I didn't mention), I'd love to hear your findings! Send me a message through whatever contact option you prefer :)

try catch this