Patching
Patching is a powerful way to mod Sapiens. Patching allows you to edit the vanilla scripts before they are loaded into the environment. This means that patches are applied before the regular mods are loaded. Hammerstone helps you with this by introducing patch mods.
How it works
At the start of scripts, you'll often find a function called "mjrequire" followed by the path of a module. This function first loads the vanilla lua file and then checks if the enabled mods provide a mod file of the same path. Hammerstone intercepts this steps and loads the vanilla lua file as a text file. It then checks for patch mods matching the same path and does some text editing before returning it to the modManager for it to apply regular mods to.
Everything is done in memory. This means that the original files on the disk are never edited and there is no danger of permanently damaging the game's files.
When to use patch mods
Patch mods should only be used when regular mods simply don't cut it. A good example would be to override a local function. Hammerstone already provides a way for you to make that function global. You can then create a regular mod to override it as usual.
When NOT to use patch mods
Patch mods should not be used to add new global functions or variables (unless part of a bigger patch). This can already be done via regular mods.
The reason is that the more patches are applied to one file, the harder it is for other modders to patch it themselves. Their own operations might fail because a patch with a lower patchOrder has removed or renamed what they wanted to patch.
Please be careful and mindful when using patch mods! (Otherwise, have lots of fun and enjoy the power 😃 )
Creating patch mods
To create a patch mod, create a "patches" folder at the root of your mod folder. Then, create a lua file matching the same path as the "script" file you want to patch as you would with regular script mods. For example, if you want to patch the game file "server/planManager.lua", your patch would be at "modFolder/patches/server/planManager.lua"
Inside this file, return the following information:
local patch = {
version = "0.4.2.5", --for future use. The version of the game the patch was made for
patchOrder = 1, -- same as "loadOrder" in regular mods. Indicates the priority of the patch vs other patches
debugCopyBefore = false, -- if true, Hammerstone will save a "before" copy of the file at the same location as your patch mod file. This could be useful in case an other patch mod modified the file before you did
debugCopyAfter = true, -- if true, Hammerstone will save an "after" copy of the file so you can see your edits
debugOnly = false, --if true, Hammerstone will patch the file but won't load it into the environment. Use this in combinaison with "debugCopyAfter" while you're working on your patch
appliesTo = {}, -- for 'Universal patches' only. Specifies which files this patch is for
operations = {} -- see "Operations"
}
return patch
Universal patches
If you want to patch something in many files, you can make a 'Universal patch'. These kinds of patch MUST be placed at the root of the patches
folder and contain a table named appliesTo
.
Each entry in this table represents the path of the module(s) to patch as a pattern. For example, if you want to patch everything under "mainThread/ui", use mainThread/ui/.+
.
Chunk Files
If you have a lot of code to insert or replace, typing one long multiline string just isn't manageable. For this purpose, you can create separate lua files containing all that neat code and place it in "modFolder/chunks". The name of the file without the extension will become the name of the chunk.
Operations
Operations are the "edits" you want to make to the original lua file. Hammerstone will run each operation sequentially. If an operation fails, unless it is allowed to fail, the whole patch will be abandonned and the original file will remain as is. An operation can either be a table or a function.
NOTE: Operations are an indexed array iterated through "ipairs". If you set the indices yourself (as I like to do to better be able to debug if the logs say "operation 'x' failed"), make sure they are sequential!
Function operations
This function only receives one parameter: the content of the file as it is currently patched. This function must return two things: the new patched file string and a success indicator. Ex:
operations = {
[1] = function(fileContent)
fileContent, count = fileContent:gsub("variableName", "newVariableName")
return fileContent, count ~= 0
end
}
Table operations
Table operations are a clean and short way to write edits. By default, all table operations will contain these fields:
type
The type of operation (see Operation Types below)skipOnFail
(optional) If true, Hammerstone will move onto the next operation instead of failing the whole patch if the operation failscondition
(optional) A function which receives the fileContent and the context as a parameter and returns a boolean indicating if this operation should be run or not
The rest of the fields will depend on the operation type
.
Ex:
operations = {
--replaces the whole local function "doStuff" with the content of the chunk file at "chunks/newDoStuff.lua"
[1] = { type = "replace", startAt="local function doStuff(", repl={chunk="newDoStuff"}, endAt="\r\nend" }
}
Context
The context contains useful information about the file currently being patched:
path
Path of the module (ex: "common/skills")moduleName
Name of the module (ex: "skills")
Operation Types
Hammerstone provides the following operation types:
replace
Equivalent of callingstring.gsub
pattern
(string+) The pattern to search the file withrepl
(string+) The replacement string
replaceAt
Replaces the file content (inclusive) between "startAt" and "endAt"startAt
(nodes) Where to start looking for the text to replaceendAt
(nodes) (optional) Where to stop looking for the text to replacerepl
(string+) The replacement string
replaceBetween
Replaces the file content (exclusive) between "startAt" and "endAt"startAt
(nodes) Where to start looking for the text to replaceendAt
(nodes) Where to stop looking for the text to replacerepl
(string+) The replacement string
removeAt
Removes text from the file content (inclusive) between "startAt" and "endAt"startAt
(nodes) Where to start looking for the text to removeendAt
(nodes) (optional) Where to stop looking for the text to remove
insertAfter
Inserts text after "after"after
(nodes) (optional) Where to insert the text. If nil, the string will be added at the end of the filestring
(string+) The text to insert
insertBefore
Inserts text before "before"before
(nodes) (optional) Where to insert the text. If nil, the string will be added at the top of the filestring
(string+) The text to insert
localVariableToModule
Transform a local variable into part of the module so it can be recalled with moduleName.variableNamemoduleName
(string+) The name of the modulevariableName
(string+) The name of the variable
localFunctionToGlobal
Transform a local function into a global onemoduleName
(string+) The name of the modulevariableName
(string+) The name of the variable
Note: When endAt
is optional, the end of the edit is the end of the file.
Inclusive vs Exclusive replacements
Inclusive means that the last string at startAt
and the last string at endAt
will be included in the replacement. Exclusive means the opposite.
string+
In order to be as powerful as can be, Hammerstone provides many ways to fill in operation parameters. A string+ can be the following:
string
Plain old regular string (orpattern
in the case ofreplace
)function
Receives the fileContent and name of the parameter and returns the value of the parametersub-table
Must contain the name and value of the argument (no use for now)chunk table
Used to fill the parameter with the content of a chunkchunk
(string) The name of the chunk. Equivalent to the name of the chunk file without the ".lua" extensionindent
(number) (Optional) Number of times to indent the content of the chunk. 1 indent equals to 4 spaces
Keywords
All string+ can use keywords
. They are placeholders for values pertaining to the current file being patched. They correspond to the values of the context
.
You use them by putting the name of the context value surrounded by a pound sign (ex: "#PATH#").
During the operation, they keywords will be replaced by the value in the context
.
nodes
In order to better search for the right place in the file to start or stop an edit, Hammerstone provide "nodes". They can be the following:
string
Hammerstone will search for that string in plain texttext table
Contains the following:text
(string) The text to search forplain
(boolean) If true, Hammerstone will search with a plain text. If false, it will search with a patternreps
(number) (optional) How many times the text should be looked for. If nil, the default value is 1. If the value is -1, the text will be searched for until it can't be found anymore.
function
Receives the fileContent and the index to start its search at as parameter. Must return the first and last index of its search resultnodes table
Contains a list of nodes. Each node within that list must be either a string, a text table or a function as previously defined.
Nodes are used to pinpoint the location of an edit with better accuracy. Consider the following code:
local function foo()
local result = getResult()
if result then
doSomething()
end
end
local function bar()
local result = getResult()
if result then
doSomethingElse()
end
end
We would like to replace getResult()
with getMyNewResult()
BUT only when it is being called by bar
. If we search for the string local result =
, it'll return the location of the first result, which is under foo
.
This is not what we want. We first want to search for local function bar()
and THEN search for local result =
. To tell Hammerstone this, we setup the startAt nodes as such:
startAt = {
"local function bar()",
"local result = "
}
The endAt
node always start their search after the results of startAt
. This means that it's not necessary to repeat the nodes from startAt
.
Hammerstone's patches
Hammerstone already patches some files for its own needs in order to better serve you.
Some of these changes are minor, some are massive. By default, a copy of the patched files can be found in Hammerstone's mod folder under "patched" if you ever need to consult them. They are created when Hammerstone patches a module. This means that, for example, if you want to see if a certain UI module has been patched, you must first open up that UI in game so that its file and patch are loaded.
This file will contain ALL patches applied to the original file. If you only want to see the patches done by Hammerstone unload all other mods.