Skip to content
Nikita Krapivin edited this page Apr 22, 2021 · 16 revisions

Welcome to the libLassebq wiki!

libLassebq is an experimental modding SDK for YYC games, featuring a GMLua backend to execute Lua code inside object events.

Russische sprache версия находится вот здесь: https://github.com/nkrapivin/libLassebq/wiki/Russian-Home

For those who don't speak Russian, this is the English version. :)

Supported Games (Name, Version, OS/Platform, Developer/Publisher)

  • Katana ZERO, v1.0.5, Windows Steam, Askiisoft.
  • Katana ZERO, v1.0.5, Windows GOG, Askiisoft.
  • The Swords of Ditto, 1.17.05-205 STM, Windows Steam, OneBitBeyond & Devolver Digital.

Installation

Extract the libLassebq package to your game folder, then open the game's data.win in UndertaleModTool and run the llbqInjector.csx script.

Then run the game and observe the magic.

GMLua

GMLua is a backend that runs inside libLassebq and provides Lua scripts access to GameMaker's instances, object events, scripts, constants, and asset indexes.

Using GMLua's API may be a bit tricky, but once you get it, it becomes really powerful.

Your libLassebq package should come with a test.lua file with some very basic explanation of the functions. But if you still don't get how to use it, here's the information about GMLua functions:

GMLua_with

GMLua_with(inst_or_object:GMLInstance or integer, thing:function)

inst_or_object - can be an instance id, an object index, all keyword, or a GMLInstance.

thing - function to execute in the GMLInstance's context

The function has to take two arguments, _pSelf and _pOther, just like an object event.

Since with() is a loop in GM, when an object index or all is passed, it will execute this function in a loop.

If you wish to break out of it, simply return any value, not just return, but return 0 for example.

Please please please do not call instance_destroy or any other functions that affect the instance order for god's sake.

GMLua_getvar

GMLua_getvar(inst:lightuserdata, varid:GMLVar, [arrayIndex:integer])

inst - _pSelf/_pOther/_pGlobal or any other GMLInstance. (for example one returned by GMLua_inst)

varid - a GMLVar_ constant.

arrayIndex - only used if GMLVar_ is a builtin variable, accesses a builtin array variable.

Returns the value of the variable.

GMLua_setvar

GMLua_setvar(inst:lightuserdata, varid:integer, Rvalue, [arrayIndex:integer])

inst - _pSelf/_pOther/_pGlobal or any other GMLInstance. (for example one returned by GMLua_inst)

varid - a GMLVar_ constant.

Rvalue - can be any type, the value you want to assign to the variable.

arrayIndex - only used if GMLVar_ is a builtin variable, accesses a builtin array variable.

Sets the variable that has an id varid to the value Rvalue.

Returns nothing.

GMLua_inst

GMLua_inst(instid:integer or number)

instid - can be an integer (if you already know the instance id), or a number, if you got one from a GM function (they all treat instance ids as numbers).

This function is very useful if you want to loop through all instances and want to get variable values from them e.g. by using GMLua_getvar.

Returns GMLInstance which id variable is equal to instid.

GMLua_ignoreArgc

GMLua_ignoreArgc(boolean:doIgnore)

doIgnore - ignore argument count? (true) or don't? (false)

This function was designed as a hack for some built-ins such as gif_add_surface or game_get_speed that accept extra arguments, but the argument count doesn't reflect that, or their argument count is wrong, so libLassebq throws an error, because there are more (or less) arguments than expected, but in the GM documentation and the GM IDE all is fine.

Please don't use this function for anything else, it is only for cursed built-in runtime functions.

This function is global, meaning it will affect all functions and all Lua scripts.

Returns nothing.

Metamethods

GMLInstances have two metamethods, __index and __newindex. Also __tostring is defined but it's useless.

With these, you can access instance variables as if you were accessing an array, e.g.:

local myX = _pSelf.x
local myY = _pSelf.y

Or global/builtin variables:

local myFPS = _pGlobal.fps
local myDir = _pGlobal.program_directory
local myGlistenerX = _pGlobal.listener_x

Or assign values to instance variables:

_pSelf.x = 222.0
_pSelf.y = 137 + _pSelf.x
_pGlobal.version_string = _pGlobal.version_string .. "/libLassebq!"

Also since libLassebq v2.0, alarms and other built-in array variables are properly supported.

Runtime functions

They all have the GML_ prefix, so if you want to call draw_set_halign, you'd call GML_draw_set_halign:

GML_window_set_caption("whatever")
GML_room_goto_next()

Scripts

Same as runtime functions, except the prefix is GMLScript_, so for room_transition you'd call GMLScript_room_transition.

Keep in mind that in Lua you can assign methods to variables, so you don't have to retype the function name every time, you can just do:

Grt = GMLScript_room_transition
Grt(room_of_the_pug, 1) -- will transition to `room_of_the_pug` with mode `1`.

Script hooking

You can define two hooks on a GML script, Before and After.

When at least one of them is defined, the script is being detoured with a special Lua routine that works like this:

If Before is defined, it will be executed, if Before returned something, the routine will return that value without executing the original script and After.

Then if Before didn't return anything, the original script is executed.

Then if After hook is defined, it will be executed and the original script's return value will be provided as the fourth argument.

If After returned something, the result will be replaced with After's return value.

Then the Lua routine returns.

That means you can prevent the original script from executing in the first place by returning something from Before.

And you can also hijack the return value by returning from After.

Also, _args is the arguments that were provided to the script, it's just a regular Lua array.

Lua example:

local scr_test = { }
local obj_some = { }

function obj_some.Create(_pSelf, _pOther)
    -- do some stuff.
end

function scr_test.Before(_pSelf, _pOther, _args)
    for i,v in ipairs(_args) do
        print(i,v)
    end
end

function scr_test.After(_pSelf, _pOther, _args, _ret)
    return _ret -- don't have to do this actually.
end

return { obj_some=obj_some, scr_test=scr_test }

Also, if you want to get the current index of your script, use the global variable GMLua_Detours_Index.

That variable is an integer asset index of your script and IT IS ONLY VALID INSIDE Before and After HOOKS, IT IS nil AT ALL OTHER TIMES.

You can get the script's name like so:

local myname = GML_script_get_name(GMLua_Detours_Index)
print("i am script: ", myname)

Oh, and keep in mind that if you call your script via GMLScript_ while being inside Before or After, this will cause an infinite loop. (because recursion!)

Constants and asset indexes

They are exposed to you as global variables, constants are all numbers, asset indexes are all integers.

They do not have any prefix or suffix, use them as you would use them in GM.

The only constants that are not exposed is self (-1.0), and local (-7.0), because they conflict with Lua keywords.

It is advised that you don't use them anyway, use the instance id as self, and don't use local, you have Lua variables why would you use local?

Configuration file

GMLua will load the file GMLuaConfig.txt and loop through every line of the file.

So yeah, the config file is just a bunch of lines for now.

Valid lines:

  • addCollisionEvents - Enable generation of collision event names, will slow down the loading times drastically.

  • ignoreLuaErrors - will ignore Lua errors, this is very evil and should not be used unless you really have to. (only Lua errors are ignored, if an error happens in the GameMaker engine itself, the game WILL close itself, as this cannot be handled by libLassebq!)

  • noConsole - won't allocate the console window when starting up. Useful if you want to stay silent hehe.

  • waitForDebugger - will wait for the Visual Studio's debugger after loading the config in the config loader function. Only I use this when I need to, just ignore it :p

  • enableBeforeEvents - enable support for execution of Lua code before events, name the function Before[Event name here].

  • addScripts - hook GML scripts in the first place or not? if your computer has problems with handling Detours (weird AV?), consider removing it.

  • iWannaMakeMyOwnObjectsKthx - enables parsing of the GMLuaNewObjects.txt file used to define new objects (see below).

Invalid or empty lines are simply ignored.

Adding new objects (entierly new objects, not just adding code)

Sometimes to componentize your mod, you may want to add your own very new objects into the game.

To do that, add the appropriate config line which enables that, and in GMLuaNewObjects.txt type names of the objects you want to add.

All of them will be created with default properties, depth = 0, visible = true, solid = false, persistent = false, etc.

They will not be placed into any room, unless you desire so (GML_instance_create_depth/layer), you can also change various properties of your new objects via GML_object_set_* functions. (except for the name!)

If your name conflicts with an existing asset, or an object, it will not be added and a message will be printed into the console.

Example file:

obj_mod_controller
obj_mod_playertrack
obj_mod_hud

Empty lines or the ones that only contain spaces are not read at all.

Adding new scripts (Loadlist file)

To add your scripts to GMLua, you edit the loadlist.txt file, this file is just a list of paths to .lua scripts.

Compiled lua scripts are also supported just fine. Specify them exactly as you would specify a text lua script.

Keep in mind that the script is first executed to determine which object events and scripts it's hooking, then libLassebq will patch these events/scripts and continue to the next script in the list.

That means, you cannot patch new object events or hook new GML scripts after the game has started, for that, you need to run the game again.

Kinda sucks if you LOVE live-coding, but that's the best I can do.

Replacing or adding object events.

To do anything meaningful, you'd want to replace or append your code to an object event.

By default, libLassebq will provide an empty persistent object named obj_libLassebq added to the first room of the game, you are safe to replace any events in it, Create and Game Start will work too. (DOES NOT WORK ON DITTO BECAUSE IT'S A WEIRD GAME! USE obj_controller!)

Although you can use any object in the game.

To replace an event, you declare a table named however you want, for example:

local what = { }

Then you declare a function which's name matches the name of the event you want to replace, for example:

function what.Step(_pSelf, _pOther)
    --[[
        _pSelf is the pointer to your current instance (here, it's a pointer to an instance of obj_libLassebq).
        _pOther is only used in Collision events, points to an instance that collided with _pSelf,
        at any other times it points to any random instance (or even the same as _pSelf) and should NOT be used.
    ]]--
end

And, VERY IMPORTANT, you MUST return a table AT THE END of the script (NOT IN A FUNCTION), where key is the object/script's name, and value is the table with events (or Before/After hooks), the final script would look like this:

local what = { }

function what.Step(_pSelf, _pOther)
    --[[
        _pSelf is the pointer to your current instance (here, it's a pointer to an instance of obj_libLassebq).
        _pOther is only used in Collision events, points to an instance that collided with _pSelf,
        at any other times it points to any random instance (or even the same as _pSelf) and should NOT be used.
    ]]--
end

return { obj_libLassebq=what } -- t["obj_libLassebq"] = what

This will define a new Step event in obj_libLassebq.

If an event already exists (already used by the game), your Lua function will be executed after it. However GML scripts can define Before and After hooks, more on that above.

The order of execution is defined by the order of the scripts in loadlist.txt.

Here's a list of valid event names: https://github.com/nkrapivin/libLassebq/blob/master/libLassebq/Data.cpp#L10

Collision events are named Collision_ObjectNameHere, e.g. Collision_obj_libLassebq.

Game Specific

Katana ZERO

libLassebq will overwrite the game version in the main menu, to notify that this game is modified.

And it will also add a huge moving rectangle with bloody red text on it to the speedrun results room, notifying that this speedrun should not be trusted, as the game has been modified.

Also, any replacements to obj_speedrun_results_screen are ignored, that means any Lua functions referencing that object will not be executed.

This is intentional and will not be removed.

The Swords of Ditto

This game doesn't like when a new object is added, as such you cannot use obj_libLassebq in your mods,

please use any other persistent object.