Catspeak and Code Execution - Skirlez/nubbys-forgery GitHub Wiki

Mods include Catspeak files. Catspeak, like GML, is a scripting language.

The modloader reads the Catspeak files that mods have, and executes them. If you know how to use GML, then Catspeak is very similar. This page goes over implementation details specific to the modloader. For a language overview see this page: https://www.katsaii.com/catspeak-lang/3.3.0/lan-overview.html

Nearly everything is exposed to you

Every single resource is exposed to you when running mods, except select functions. Here's a table with reasons as to why:

Functions Reason
keyboard_key_press, keyboard_key_release High malicious potential, as these press keys out of the game too
get_open_filename, get_save_filename, get_open_filename_ext, get_save_filename_ext These functions can bypass the save/load sandbox

Important! Global built-in variables, such as room, async_load, view_camera, etc. Can be obtained by adding _get() to the end of their name. So to get the current room, you'd call room_get() for example.

Executing your own code files

The modloader provides you with several functions to execute any code files belonging to your mod.

Let's say we have this file, at (your mod's folder)/folder/code.meow:

log_info("Hello World!")
my_function = fun {
    log_info("This is my function!")
}

We'll be using it as an example here.

mod_execute_code_file(path)

The simplest thing you can do is execute one of your code files, and that is what mod_execute_code_file(path) does.

-- Get and run code file
mod_execute_code_file("folder/code.meow") -- logs "Hello World!"

mod_get_code_file(path)

You can use mod_get_code_file(path) gets the code file at path that belongs to this mod. This is a relative path, meaning it starts from the mod folder.

After you get the file, you can run it like a function.

-- Get the file
file = mod_get_code_file("folder/code.meow")
-- Run it
file() -- logs "Hello World!"

catspeak_globals(code_file)

But say you want to access a function defined inside your code file. You need to access the file's global struct after it runs. This can be done like so, with Catspeak's own catspeak_globals function:

-- Get the file
file = mod_get_code_file("folder/code.meow")
-- Run it so the functions get defined, if you haven't ran it before
file() -- logs "Hello World!"
-- Get file globals
file_globals = catspeak_globals(file)
-- Call our function from the globals
file_globals.my_function() -- logs "This is my function!"

Important! You have to run the file before calling the function because the functions are not defined up to that point. It is only when you run the file, and the line my_function = fun { is reached, that the function exists. You can think of running a code file as initializing it.

mod_get_code_file_globals(path)

You can use mod_get_code_file_globals(path), which returns the globals of a file right away.

file_globals = mod_get_code_file_globals("folder/code.meow") -- logs "Hello World!"
file_globals.my_function() -- logs "This is my function!"

Important! In order to do this, mod_get_code_file_globals runs the file if its global struct is empty, i.e., it hasn't been run yet. This is why the first line logs "Hello World!" - subsequent calls will not.

mod_get_code_file_globals("folder/code.meow") -- logs "Hello World!"
mod_get_code_file_globals("folder/code.meow") -- does not log
mod_get_code_file_globals("folder/code.meow") -- does not log

A note on performance

When you get a file using any of these functions, it is read and compiled in real-time once, and then cached. Any retrieval after that does not read the file from disk or compile anything. It's essentially as costly as a few hashmap accesses (So almost none at all)

Additionally, you may set compile_all_code_on_load to true in your mod.json. This will cause all files to be read and compiled when the mod is loaded, so none of those functions will ever read from disk or compile. It will also prevent your mod from loading if any of your files fail to compile.