Skip to content

Class Patcher

Yukita Mayako edited this page Jun 16, 2023 · 5 revisions

This submodule of Mod Utils was contributed and documented by David.

Mod Utils contains a class patcher, allowing you to replace parts of the existing game scripts directly, using a simple comment system. If you're planning on using this feature, it's highly recommended to read through this entire page carefully, as not all of the features are obvious.

Setup

A small amount of setup is required in order for the patching to take place, particularly when the mod is exported out to be used by players.

  1. Follow Getting started to set up your mod's ContentInfo script.

  2. Get the file you want to modify by looking inside the game's extracted code, and finding its location. Duplicate this file, and put it in your mod folder. The file can even be renamed; this can help easily differentiate it from the original.


    Here, the file MonsterTape.gd is duplicated, moved to the mod folder, and renamed into montape.gd.

  3. Add the original file to the Modified Files array in your metadata.tres.

    Failure to do so will cause the mod to only work in the editor, as the code needs a copy of the decompiled script to be able to apply the patch.

  4. Apply the patch. This is done with the MODUTILS Dictionary in your ContentInfo script.

    const MODUTILS: Dictionary = {
    	"class_patch": [
    		{
    			"patch": "res://mods/My Mod/montape.gd",
    			"target": "res://data/MonsterTape.gd",
    		},
    	],
    }

    The "patch" is your annotated script which will be applied. The "target" is the original script which will be modified.

  5. Start applying the patch commands inside your patch file, seen in the next section. If you need to add more files that will need to be patched, simply jump back to 2.

Patching Commands

Patching commands are meant to guide the patch along, to tell it exactly what needs to be added, removed, or replaced. Without any patching commands, the patch won't do anything at all. Keep in mind that this list is not exhaustive. If you have any comments, suggestions, or concerns, please feel free to reach out.

Syntax and etc.

  • Commands are written using comments.
  • Please write the commands exactly as written, just the same way a code wouldn't consider true to be the same as True.
  • Do not add additional spaces after the commands.
  • Unless mentioned, the command's ending delimiter is #PATCH: STOP.
  • In case errors may occur in the code, it's possible to use #> at the beginning of lines inside commands. Any #> will be ignored during the patching process.

Example code

This code will be used in all the examples underneath.

var number = 0
var other_number = 2
var lonely_string = "I'm lonely"

func _init():
	number = 5

func multiply():
	return number * other_number

# PATCH: REMOVE LINES

Searches in the target for these specific lines, then removes them.

var number = 0
var other_number = 2
# PATCH: REMOVE LINES
var lonely_string = "I'm lonely"
# PATCH: STOP

func _init():
	number = 5
	
func multiply():
	return number * other_number

Result:

var number = 0
var other_number = 2

func _init():
	number = 5
	
func multiply():
	return number * other_number

# PATCH: REMOVE FUNC

Essentially a REMOVE LINES that covers one, single function. Does not use # PATCH: STOP.

var number = 0
var other_number = 2
var lonely_string = "I'm lonely"

func _init():
	number = 5
	
# PATCH: REMOVE FUNC "multiply"
func multiply():
	return number * other_number

Result:

var number = 0
var other_number = 2
var lonely_string = "I'm lonely"

func _init():
	number = 5

# PATCH: REPLACE LINES

Searches in the target for specific lines, then changes them for other lines, delimited by # PATCH: INTO.

var number = 0
var other_number = 2
var lonely_string = "I'm lonely"

func _init():
	# PATCH: REPLACE LINES
	number = 5
	# PATCH: INTO
	lonely_string = "I'm not lonely anymore"
	# PATCH: STOP
	
func multiply():
	return number * other_number

Result:

var number = 0
var other_number = 2
var lonely_string = "I'm lonely"

func _init():
	lonely_string = "I'm not lonely anymore"

func multiply():
	return number * other_number

# PATCH: REPLACE TEXT

Like a faster way to do a REPLACE LINE. Replaces the first instance of text in the next line. Delimited by #INTO#. Does not use # PATCH: STOP.

var number = 0
var other_number = 2
var lonely_string = "I'm lonely"

func _init():
	number = 5
	
func multiply():
	# PATCH: REPLACE TEXT number * #INTO# other_number *
	return number * other_number

Result:

var number = 0
var other_number = 2
var lonely_string = "I'm lonely"

func _init():
	number = 5
	
func multiply():
	return other_number * other_number

# PATCH: ADD LINES HERE

Adds the mentioned lines directly after the previous instance of text.

var number = 0
var other_number = 2
var lonely_string = "I'm lonely"
# PATCH: ADD LINES HERE
var collection = {
	"item1": 1,
	"item2": 3
}
# PATCH: STOP

func _init():
	number = 5
	
func multiply():
	return number * other_number

Result:

var number = 0
var other_number = 2
var lonely_string = "I'm lonely"
var collection = {
	"item1": 1,
	"item2": 3
}

func _init():
	number = 5
	
func multiply():
	return number  * other_number

# PATCH: ADD FUNC

Adds a function to the target code.

var number = 0
var other_number = 2
var lonely_string = "I'm lonely"

func _init():
	number = 5
	
func multiply():
	return number * other_number

# PATCH: ADD FUNC
func divide():
	return number / other_number
# PATCH: STOP

Result:

var number = 0
var other_number = 2
var lonely_string = "I'm lonely"

func _init():
	number = 5
	
func multiply():
	return number * other_number

func divide():
	return number / other_number

# PATCH: IF MOD

Checks if the mod ID in question is in this game; if it is, whichever # PATCH command is next will run. Otherwise, it will be skipped over. Make sure that the next # PATCH line is directly after. Also works for IF NOT MOD, for the opposite effect.

var number = 0
var other_number = 2
var lonely_string = "I'm lonely"

func _init():
	# PATCH: IF MOD "another_mod_that_is_here"
	# PATCH: REPLACE TEXT 5 #INTO# 9
	number = 5
	# PATCH: IF MOD "another_mod_that_is_not_here"
	# PATCH: ADD LINES AFTER
	number = 5
	# PATCH: STOP
	
func multiply():
	return number * other_number

Result:

var number = 0
var other_number = 2
var lonely_string = "I'm lonely"

func _init():
	number = 9
	
func multiply():
	return number * other_number

Good Practice

  • When adding code to an existing function or adding variables, avoid overly simple variable names, like mod, n, and other commonly used variable names. If two mods attempt to modify the same code, variables will mesh together and cause very unexpected results. The healthiest nomenclature is to declare any and all variables and function with your_mod_id in front; for instance, my_mod_dofunction() .
  • Avoid removing and replacing whenever you can! If you remove a function or variable, you understand and willingly agree that any other mod that will attempt to interact with said variable might break. It's better to let a variable stay obsolete than remove it. Same goes for replacing; do it only if adding lines can't solve the issue.
  • Always remember that it's easy to just edit out the code and slap it on the game; what's hard is making this all mod-friendly. The objective of the class patcher is not just to make class patching easier, but also to make mods more compatible with one another; whenever you can use the class patcher, use it!

Clone this wiki locally