Skip to content

Lua patcher API

GDPatch mods use patchers written in the Lua programming language to modify game code. Before the game finishes loading, GDPatch loads the patcher.lua script in your mod folder, which calls functions provided by GDPatch to patch game files.

Since these scripts are loaded before the game finishes loading, patchers exist independently of the GDScript runtime and have no relation to the GDScript autoload. You cannot run GDScript or access the scene tree from patchers, as the game has not loaded yet. Conceptually, it is similar to modifying the game’s pack file on disk before starting it, except that it happens at runtime.

The Lua version in use is Lua 5.5. print statements will be redirected to the GDPatch log file.

We suggest using simple text patching if you’re new to GDPatch, or if your change is relatively trivial. Text-based patching is performed with the GDPatch.patch_script_as_text function.

We suggest you reference a project decompilation and print the script’s source code (print(src)) while working on patches.

TODO

These are available under the GDPatch global in the Lua runtime. Be careful not to confuse it with the GDScript autoload.

Returns a table containing information about the game’s pack file:

  • path: string
    • The path to the pack file on disk. If the game contains multiple pack files, you can differentiate them with this field.
  • files: string[]
    • A list of files in the pack file (without the res:// prefix).
Example
local pack = GDPatch.get_pack()
print("The pack at " .. pack.path .. "'s first file is " .. pack.files[1])
  • Arguments:
    • paths: string | string[]
    • callback: function(PatcherContext, string): string
    • options: PatcherCallbackOptions (optional)

Registers a callback function for the path(s) provided. The callback function receives a PatcherContext and the script’s source code, and returns the modified source code. paths can either be a string or table of strings, and the paths should not contain the res:// prefix.

Most game scripts are stored in GDScript binary form (.gdc), in which case GDPatch will transparently convert the script to text format before calling this function. When editing binary scripts, the file extension must match what is stored in the pack file (so the paths will likely end in .gdc instead of .gd).

The script source code passed to this function is normalized. Any special formatting (from the original game script or from other mods) is lost. This is done to improve compatibility of text-based patching from multiple mods at once.

We suggest using string.gsub with gdpatch.utils.escape to write most patches. Experienced developers can utilize Lua’s pattern system for advanced string matching. Consider using Lua’s [[ string notation for syntax-heavy snippets.

Example
local utils = require("gdpatch.utils")
GDPatch.patch_script_as_text("Scripts/Player.gdc", function(ctx, src)
return src:gsub(
utils.escape("const MAX_HEALTH = 10"),
utils.escape("const MAX_HEALTH = 100", true)
)
end)
  • Arguments:
    • paths: string | string[]
    • callback: function(PatcherContext, GDScriptAST): GDScriptAST
    • options: PatcherCallbackOptions (optional)

Registers a callback function for the path(s) provided. The callback function receives a PatcherContext and the script’s AST representation, and returns the modified AST. paths can either be a string or table of strings, and the paths should not contain the res:// prefix.

TODO

  • Arguments:
    • callback: function(ProjectSettings): ProjectSettings

Registers a callback function for the game’s project.binary. The callback function receives a table of key-value pairs (not a regular dictionary-like table, as to preserve item order).

As an example, this function is used by GDPatch’s builtin mod to install its autoload. Note that you do not need to add your own autoload as GDPatch will load your scene or script automatically.

Example
GDPatch.patch_project_settings(function(settings)
table.insert(settings, 1, {
"autoload/GDPatch",
"*res://gdpatch/GDPatch.gd"
})
return settings
end)
  • Arguments:
    • paths: string | string[]
    • callback: function(PatcherContext, string): string
    • options: PatcherCallbackOptions (optional)

Registers a callback function for the path(s) provided. The callback function receives a PatcherContext and the file’s string contents, and returns the modified contents. paths can either be a string or table of strings, and the paths should not contain the res:// prefix.

This is used for text-based resources included in a game like .json or .gdshader. You cannot edit scripts or binary resources (e.g. scenes) with this function.

Example
local utils = require("gdpatch.utils")
GDPatch.patch_file("Shaders/shader.gdshader", function(ctx, src)
return src:gsub(
utils.escape("pos = (floor(pos * res + off) + 0.5) / res;"),
""
)
end)

Returns an array containing all loaded mod manifests. You can access the mod ID via the id field.

Example
local mods = GDPatch.get_mods()
print("There are " .. #mod_ids .. " mods loaded")

Returns the root directory used by GDPatch (usually next to the game install).

Example
local root_directory = GDPatch.get_root_directory()
-- usually something like /path/to/game/GDPatch
print("Root directory: " .. root_directory)
  • Arguments:
    • mod_id: string (optional)

Returns the directory of a given mod ID if it exists. If mod_id is nil, it will fall back to your mod ID.

Example
local mod_directory = GDPatch.get_mod_directory("example_mod")
-- usually something like /path/to/game/GDPatch/mods/example_mod
print("Mod directory: " .. mod_directory)
  • Arguments:
    • mod_id: string (optional)
    • section: string
    • option: string

Returns the value saved in the config file, or the default value specified in the manifest if set. If mod_id is nil, it will fall back to your mod ID.

Example
local option = GDPatch.get_config_option(nil, "section_id", "option_id")
print(option)
  • Arguments:
    • mod_id: string (optional)
    • section: string
    • option: string
    • value: any

Saves the specified value to the config file, or removes it if value is nil. If mod_id is nil, it will fall back to your mod ID.

Example
local value = "meow"
GDPatch.set_config_option(nil, "section_id", "option_id", value)

A table containing extra context about the target file, provided in the callback of GDPatch.patch_script_as_text/GDPatch.patch_script_as_ast.

  • path: string
    • The path of the file currently being patched.

A table containing extra information that can be passed to GDPatch.patch_script_as_text/GDPatch.patch_script_as_ast.

  • before: string[] (optional)
    • List of mod IDs to attempt to patch before.
  • after: string[] (optional)
    • List of mod IDs to attempt to patch after.

The before/after fields can be used to help resolve conflicts with other mods.

Example
function patch_player_script(ctx, src)
-- ...
end
GDPatch.patch_script_as_text("Scripts/Player.gd", patch_player_script, {
before = { "conflicting_mod_id" },
after = { "another_conflicting_mod_id" }
})

One of the following strings:

  • Nil
  • Bool
  • Int
  • Float
  • String
  • Vector2
  • Vector2i
  • Rect2
  • Rect2i
  • Vector3
  • Vector3i
  • Transform2d
  • Vector4
  • Vector4i
  • Plane
  • Quaternion
  • Aabb
  • Basis
  • Transform3d
  • Projection
  • Color
  • StringName
  • NodePath
  • Rid
  • Object
  • Callable
  • Signal
  • Dictionary
  • Array
  • PackedByteArray
  • PackedInt32Array
  • PackedInt64Array
  • PackedFloat32Array
  • PackedFloat64Array
  • PackedStringArray
  • PackedVector2Array
  • PackedVector3Array
  • PackedColorArray
  • PackedVector4Array

One of the following tables:

  • { type: "None" }
  • { type: "Builtin", value: VariantType }
  • { type: "ClassName", value: string }
  • { type: "Script", value: string }

Lua primitives are automatically encoded as their Godot counterparts (nil, boolean, number, string). Other variants can be created with the gdpatch.variant module:

Example
local variant = require("gdpatch.variant")
local vec = variant.Vector3(0, 0, 0)
  • Constructor: Vector2(x: number, y: number)
  • x: number
  • y: number
  • Constructor: Vector2i(x: number, y: number)
  • x: number
  • y: number
  • Constructor: Rect2(position: Vector2, size: Vector2)
  • position: Vector2
  • size: Vector2
  • Constructor: Rect2i(position: Vector2i, size: Vector2i)
  • position: Vector2i
  • size: Vector2i
  • Constructor: Vector3(x: number, y: number, z: number)
  • x: number
  • y: number
  • z: number
  • Constructor: Vector3i(x: number, y: number, z: number)
  • x: number
  • y: number
  • z: number
  • Constructor: Transform2D(x: Vector2, y: Vector2, origin: Vector2)
  • x: Vector2
  • y: Vector2
  • origin: Vector2
  • Constructor: Vector4(x: number, y: number, z: number, w: number)
  • x: number
  • y: number
  • z: number
  • w: number
  • Constructor: Vector4i(x: number, y: number, z: number, w: number)
  • x: number
  • y: number
  • z: number
  • w: number
  • Constructor: Plane(normal: Vector3, d: number)
  • normal: Vector3
  • d: number
  • Constructor: Quaternion(x: number, y: number, z: number, w: number)
  • x: number
  • y: number
  • z: number
  • w: number
  • Constructor: AABB(position: Vector3, size: Vector3)
  • position: Vector3
  • size: Vector3
  • Constructor: Basis(x: Vector3, y: Vector3, z: Vector3)
  • x: Vector3
  • y: Vector3
  • z: Vector3
  • Constructor: Transform3D(basis: Basis, origin: Vector3)
  • basis: Basis
  • origin: Vector3
  • Constructor: Projection(x: Vector4, y: Vector4, z: Vector4, w: Vector4)
  • x: Vector4
  • y: Vector4
  • z: Vector4
  • w: Vector4
  • Constructor: Color(r: number, g: number, b: number, a: number)
  • r: number
  • g: number
  • b: number
  • a: number
  • Constructor: StringName(value: string)
  • value: string
  • Constructor: NodePath(value: string)
  • value: string
  • Constructor: RID(id: number)
  • id: number
  • Constructor: Object(id: number)
  • Constructor: Object(class_name: string, properties?: { [string]: Variant })
  • id: number (optional)
  • class: string (optional)
  • properties: { [string]: Variant } (optional)
  • Constructor: Callable()
  • Constructor: Signal(object_id: number, name: string)
  • object_id: number
  • name: string
  • Constructor: Dictionary()
  • key_type: ContainerType
  • value_type: ContainerType
  • value: { [Variant]: Variant }
  • Constructor: Array(value?: Variant[])
  • element_type: ContainerType
  • value: Variant[]
  • Constructor: PackedByteArray(value?: number[])
  • value: number[]
  • Constructor: PackedInt32Array(value?: number[])
  • value: number[]
  • Constructor: PackedInt64Array(value?: number[])
  • value: number[]
  • Constructor: PackedFloat32Array(value?: number[])
  • value: number[]
  • Constructor: PackedFloat64Array(value?: number[])
  • value: number[]
  • Constructor: PackedStringArray(value?: string[])
  • value: string[]
  • Constructor: PackedVector2Array(value?: Vector2[])
  • value: Vector2[]
  • Constructor: PackedVector3Array(value?: Vector3[])
  • value: Vector3[]
  • Constructor: PackedColorArray(value?: Color[])
  • value: Color[]
  • Constructor: PackedVector4Array(value?: Vector4[])
  • value: Vector4[]

Some helpful functions provided by GDPatch are contained in the gdpatch.utils module:

Example
local utils = require("gdpatch.utils")
print(utils.escape("..."))
  • Arguments:
    • str: string
    • percent_only: boolean (optional)

Escapes special characters in a string that could be interpreted as a Lua pattern. This is helpful if you’re replacing GDScript in GDPatch.patch_script_as_text, as some GDScript characters are also used for Lua patterns.

If percent_only is true, the return value will only have escaped percentages, matching the behavior of the second argument of string.gsub.

Example
local utils = require("gdpatch.utils")
GDPatch.patch_script_as_text("menu.gdc", function(ctx, src)
src = src:gsub(
utils.escape([[get_tree().root.title = "Game Name"]]),
utils.escape([[get_tree().root.title = "meow :3"]], true)
)
return src
end)