CLEO Redux SDK
SDK provides a way to create new script commands for any game that CLEO Redux supports. It is agnostic to the game title and the underlying runtime (CS or JS). At this moment CLEO provides SDK for the C++ and Rust languages.
- SDK Version
- Platforms Support
- Plugin Lifetime
- Path Resolution Convention
- String Arguments
- Developing New Commands
- Developing File Loaders
The current version is
6. Changes to SDK advance this number by one.
CLEO Redux provides SDK for both 32-bit and 64-bit games. There is one notable change between the two: on the 32-bit platform the SDK functions
SetIntParam operate on signed 32-bit numbers, whereas on the 64-bit platform they operate on signed 64-bit numbers (declared as the type
It's recommended for 64-bit plugins to have
64 in their names (e.g.
Each plugin is a dynamic-link library (DLL) with the
.cleo extension that must be placed in
CLEO\CLEO_PLUGINS. CLEO Redux scans this directory on startup and loads all
.cleo files using WinAPI's function
Path Resolution Convention
String arguments representing a path to the directory or file must be normalized using SDK's function
ResolvePath. This function takes a path and returns the absolute path resolved according to the following rules:
an absolute path gets resolved as is
a path starting with
CLEO\\gets resolved relative to the CLEO directory which is either
a path starting with
../gets resolved relative to the script directory.
What is the script directory?
For CS scripts it is always the CLEO directory.
For JS scripts it could be the CLEO directory (if the JS file is located there). Or if the script is located in a subfolder inside the CLEO directory, then the script directory is the one that contains its
It is different from the resolution algorithm used by the
importstatement resolves paths relative to the current file's directory (that can be nested many levels deep), whereas the
ResolvePathdoes not go deeper than the
index.jsdirectory. You can use the
__dirnamevariable to get the directory of the current file:
DynamicLibrary.Load(__dirname + "\\mylib.dll");
all other paths get resolved relative to the current working directory (the game directory)
Strings passed in and out of the SDK methods are UTF-8 encoded.
If the script uses an integer value where a string is expected SDK treats this number as a pointer to a null-terminated UTF-8 character sequence to read, or to a large enough buffer to store the result to:
IniFile.WriteString(0xdeadbeef, "my.ini", "section", "key");
SDK will read a string from the address
0xDEADBEEF and write it to the ini file.
0AF4: read_string_from_ini_file 'my.ini' section 'section' key 'key' store_to 0xDEADBEEF
SDK will read a string from the ini file and write it at the address
Developing New Commands
To register custom command handlers the plugin must call
RegisterCommand in the DllMain function. Once a user script encounters this command CLEO Redux invokes the handler with the one argument which is a pointer to the current context. This pointer must be used for calling other SDK methods. See C++ SDK guide or Rust SDK guide for examples.
New commands that use low-level WinAPI and can potentially damage user environment must be explicitly registered with a permission token (third argument to the
RegisterCommand). User can disallow usage of unsafe commands in the scripts using permission config. At the moment three permission tokens are used:
dll. They mark commands operating with the host process, user files and external libraries.
CLEO Redux uses Sanny Builder Library to know an interface of any command. For a new command to become available in the scripts, the JSON file (
sa.json) must have the command definition, including the name that matches with the value that the plugin uses
RegisterCommand with. E.g. if the plugin registers
SHOW_MESSAGE command, the JSON file must have a command with the
name property set to
SHOW_MESSAGE. The number and order of the input and output parameters in the definition must match the order of methods used by the plugin (i.e.
GetXXXParam for each input argument and
SetXXXParam for each output argument).
Opcodes get assigned to new commands in Sanny Builder Library based on the availability, similarity with existing commands in other games, and other factors. To claim an opcode reach out to Sanny Builder Library maintainers on GitHub.
Why use command names and not an id for the command lookup?
One of the common issues with CLEO Library plugins was that commands authored by different people often had id collisions. If two plugins add commands with the same id, it is impossible to use them both. Using string names minimizes the collisions with custom plugins as well as with native opcodes. The library's definitions ensure each command claims only an available id. Also it helps to track and document plugins in a single place.
Developing File Loaders
A plugin can associate itself with a particular glob pattern (or in other word a file name with wildcard characters in it, e.g.
*.txt) and provide a handler to be called when a script imports a file matching the pattern.
The glob pattern can be used to match an arbitrary file name using wildcards to represent any characters (
*.pak* would match
3.pak3, etc) or a specific file name (
gta.dat would match only a
gta.dat file). Be mindful about using wildcards to avoid matching unnecessary files. The plugin can associate itself with many globs (for example TextLoader uses
*.text), but it's recommended to support only related formats in one plugin.
The handler gets a full path to the file as an input and returns a pointer to the buffer with the content of the file that has been serialized into JSON. The serialized JSON content should be a valid null-terminated UTF-8 string.
To allocate a memory buffer for the serialized string the plugin MUST call SDK's method
AllocMem. This memory will be released by CLEO Redux once it reads the data from it. Allocating the memory inside the plugin will lead to an error.
If the serialization has failed or the file does not have the expected format (for example due to a glob matching too many files), the handler should return a null pointer. In this case CLEO proceeds to another loader that could handle this file.
See examples of the file loaders implemented for Text files (in C++) and IDE files (in Rust).