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
SDK Version
The current version is 6
. Changes to SDK advance this number by one.
Platforms Support
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 GetIntParam
and SetIntParam
operate on signed 32-bit numbers, whereas on the 64-bit platform they operate on signed 64-bit numbers (declared as the type isize
).
It's recommended for 64-bit plugins to have 64
in their names (e.g. myplugin64.cleo
).
Plugin Lifetime
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 LoadLibrary
.
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/
orCLEO\\
gets resolved relative to the CLEO directory which is either{game}\CLEO
or{user}\AppData\Roaming\CLEO Redux\CLEO
-
a path starting with
./
or../
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
index.js
file.It is different from the resolution algorithm used by the
import
statement. Theimport
statement resolves paths relative to the current file's directory (that can be nested many levels deep), whereas theResolvePath
does not go deeper than theindex.js
directory. You can use the__dirname
variable 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)
String Arguments
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 0xDEADBEEF
.
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.
Unsafe commands
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: mem
, fs
, and dll
. They mark commands operating with the host process, user files and external libraries.
Command interface
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 (gta3.json
, vc.json
, 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).
Claiming Opcodes
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 1.pak1
, 2.pak1
, 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 *.txt
and *.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).