Patching
NOTE: This article needs editing!!!
What is patching?
In the scope modding, patching is changing the original game content in any way.
How do I patch game files?
There is more than one way to patch the game. This page will walk you through the various ways to patch.
Asset Replacement
This is the most basic type of patching (anyone can do it).
This is a standard modloader feature that allows you to replace any file the game loads with your own.
Process
Inside your mod folder, create a folder called assets
.
Decide what file you want to replace. For this tutorial, I will be showing you how to replace Lea's portraits.
Find the original relative path to asset of interest.
All you have to do is now mimic the folder structure.
In this example, two folders need to be created.
Inside the assets
folder in your mod, create a media
folder. Inside the media
folder, create a face
folder.
Finally, add the file you want to replace (your own lea.png
).
It should end up looking something like this.
That is it!
You can find more examples in the github:CCDirectLink/CCAssetSwaps repo.
- Used faceless Lea mod for some of the demonstration
JSON Patching (Object Patching)
Note: This requires some know of the JSON format and some programming concepts
As the majority of the game assets are json files, this is really useful to know about.
What this allows you to do is modify any json file without doing a file replacement.
The same ideas apply as assets override, except the file will have .patch
added to the full file name.
You need to have file extensions visible in order to modify it.
Process
Let's say you want to patch a file assets/data/test.json
with this as the contents:
{
"storage" : {
"level": 100,
"xp": 500
}
}
Your mod is located at assets/mods/my-mod/
You would create a file with the filepath assets/mods/my-mod/assets/data/test.json.patch
To replace "storage.level" with 200, test.json.patch
would look like this:
{
"storage" : {
"level": 200
}
}
To add "storage.name":
{
"storage" : {
"name": "Shizuka"
}
}
Object patching is useful for adding new properties and modifying existing ones.
Weakness: - Inability to removal properties - Arrays are hard to patch
Patch Steps
Patch Steps is another way to perform json file Patching. It is more powerful than Object Patching.
The only difference between Object Patching and Patch Steps is the format of the .patch
file.
We will be using the same sample json file used to demonstrate how Object Patching works.
Location: assets/data/test.json
{
"storage" : {
"level": 200
}
}
Below is a list of all the valid patch steps.
ENTER
This changes the context of a patch.
[{
"type": "ENTER",
"index": ["storage"]
}]
or
[{
"type": "ENTER",
"index": "storage"
}]
would change the internal view of:
{
"storage" : {
"level": 200,
"saves": [{
"data": "blob"
}]
}
}
to
{
"level": 200,
"saves": [{
"data": "blob"
}]
}
[{
"type": "ENTER",
"index": ["storage", "saves", 0]
}]
would change the internal view of:
{
"storage" : {
"level": 200,
"saves": [{
"data": "blob"
}]
}
}
to
{
"data": "blob"
}
EXIT
For every ENTER
, there should be at least one EXIT
.
EXIT
undos an ENTER
.
[{
"type": "EXIT"
}]
is equivalent to
[{
"type": "EXIT",
"count": 1
}]
For demonstration purposes, this is the contents of assets/data/test.json
:
{
"storage": {
"user": {
"name": 20
}
}
}
and this is the contents of assets/mods/my-mod/assets/data/test.json.patch
:
[{
"type": "ENTER",
"index": ["storage", "user"]
}, {
"type": "EXIT",
"count": 2
}]
The first step will produce the internal result of:
{
"name": 20
}
The second step will undo the first leading to the initial state.
{
"storage": {
"user": {
"name": 20
}
}
}
SET_KEY
This is the contents of assets/data/test.json
:
{
"storage": {
"user": {
"name": 20
}
}
}
This is the contents of assets/mods/my-mod/assets/data/test.json.patch
:
[{
"type": "SET_KEY",
"index": "storage"
}]
This is the result:
{}
This is the contents of assets/data/test.json
:
{
"storage": {
"user": {
"name": 20
}
}
}
This is the contents of assets/mods/my-mod/assets/data/test.json.patch
:
[{
"type": "SET_KEY",
"index": "storage",
"content": 2
}]
This is the result:
{
"storage": 2
}
INIT_KEY
This is the contents of assets/data/test.json
:
{
"storage": {
"user": {
"name": 20
}
}
}
This is the contents of assets/mods/my-mod/assets/data/test.json.patch
:
[{
"type": "INIT_KEY",
"index": "storage",
"content": {
"a": 2
}
}]
Nothing will change to the data because "storage" already exists.
Changing assets/mods/my-mod/assets/data/test.json.patch
to:
[{
"type": "INIT_KEY",
"index": "storage2",
"content": {
"a": 2
}
}]
would result in:
{
"storage": {
"user": {
"name": 20
}
},
"storage2": {
"a": 2
}
}
REMOVE_ARRAY_ELEMENT
This is the contents of assets/data/test.json
:
{
"a": [1,2,3]
}
This is the contents of assets/mods/my-mod/assets/data/test.json.patch
:
[{
"type": "REMOVE_ARRAY_ELEMENT",
"index": 0
}]
Applying the patch will result in this:
{
"a": [2,3]
}
ADD_ARRAY_ELEMENT
This is the contents of assets/data/test.json
:
{
"a": [1,2,3]
}
This is the contents of assets/mods/my-mod/assets/data/test.json.patch
:
[{
"type": "ENTER",
"index": "a"
},{
"type": "ADD_ARRAY_ELEMENT",
"content": 4
}, {
"type": "ADD_ARRAY_ELEMENT",
"index": 0,
"content": 0
}, {
"type": "EXIT"
}]
After the first step, the internal state will be:
[1,2,3]
After the second step, the number 4 will be pushed to the end of the array:
[1,2,3,4]
After the third step, the number 0 will be added to the start of the array:
[0,1,2,3,4]
The last step will revert the value of focus to the initial object:
{
"a": [0,1,2,3,4]
}
IMPORT
This is the contents of assets/data/test.json
:
{
"a": {
"b": 2
}
}
This is the contents of assets/data/test2.json
:
{
"b": {
"a": 2
},
"c": {
"a": 3
},
"d": 4
}
This is the contents of assets/mods/my-mod/assets/data/test.json.patch
:
[{
"type": "IMPORT",
"src": "data/test2.json"
}]
This is equivalent to:
[{
"type": "IMPORT",
"src": "game:data/test2.json"
}]
The first step would load assets/data/test2.json
then merge it with the current value resulting in this:
{
"a": {
"b": 2
},
"b": {
"a": 2
},
,
"c": {
"a": 3
},
"d": 4
}
If assets/data/test2.json
had patches associated with it, it would be applied before being merged.
Another feature is loading json files inside the mod directory and merging. No patches to the loaded file will be done if mod:
is specified.
This is the contents of assets/data/test.json
:
{
"a": {
"b": 2
}
}
This is the contents of assets/mods/my-mod/assets/data/test.json.patch
:
[{
"type": "IMPORT",
"src": "mod:patches/data/test.json",
"path": ["1", "2", "3"],
"index": "y"
}]
This is the contents of assets/mods/my-mod/patches/data/test.json
:
{
"1": {
"2": {
"3": 4
}
}
}
First it will load assets/mods/my-mod/patches/data/test.json
.
Then it will walk the path specified.
Initial loaded file internal representation:
{
"1": {
"2": {
"3": 4
}
}
}
After entering "1":
{
"2": {
"3": 4
}
}
After entering "2":
{
"3": 4
}
After entering "3":
4
Finally, it will add a key "y" with the value that resulted from the path walk (4).
So the overall result will be:
{
"a": {
"b": 2
},
"y": 4
}
Note: "index" and "path" options are optional.
INCLUDE
The purpose of INCLUDE
is to allow external Object Patching or PatchSteps to be performed.
This is the contents of assets/data/test.json
:
{
"a": {
"b": 2
}
}
This is the contents of assets/mods/my-mod/assets/data/test.json.patch
:
[{
"type": "INCLUDE",
"src": "mod:patches/data/test.json",
}]
This is the contents of assets/mods/my-mod/patches/data/test.json
:
{
"1": {
"2": {
"3": 4
}
}
}
This is like setting the contents of assets/mods/my-mod/assets/data/test.json.patch
to:
{
"1": {
"2": {
"3": 4
}
}
}
However, INCLUDE
is used as an organization method.
This is the contents of assets/data/test.json
:
{
"a": {
"b": 2
}
}
This is the contents of assets/mods/my-mod/assets/data/test.json.patch
:
[{
"type": "INCLUDE",
"src": "mod:patches/data/test.json",
}, {
"type": "SET_KEY",
"index": "b",
"content": [1,2,3]
}]
This is the contents of assets/mods/my-mod/patches/data/test.json
:
[{
"type": "ENTER",
"index": ["a"]
}]
This results in:
{
"a": {
"b": 2
},
"b": [1,2,3]
}
Think of PatchStep files as its own room. What include does is generate a new room to perform some special operation. Rooms do not intefere with each other, they just modify the data.
FOR_IN
This PatchStep is useful for combining repeated set of PatchSteps with only slightly variations in input.
This is the contents of assets/data/test.json
:
{}
This is the contents of assets/mods/my-mod/assets/data/test.json.patch
:
[{
"type": "FOR_IN",
"keyword": "__INDEX__",
"values": [1,2,3,4],
"body": [{
"type": "SET_KEY",
"index": "__INDEX__-id",
"content": "__INDEX__"
}]
}]
This results in:
{
"1-id": "1",
"2-id": "2",
"3-id": "3",
"4-id": "4"
}
This is the contents of assets/data/test.json
:
{}
This is the contents of assets/mods/my-mod/assets/data/test.json.patch
:
[{
"type": "FOR_IN",
"keyword": {
"name": "__NAME__",
"id": "__ID__"
},
"values": [{
"name": "Bob",
"id": 1
},{
"name": "Joe",
"id": 2
}],
"body": [{
"type": "SET_KEY",
"index": "__NAME__",
"content": "__ID__"
}]
}]
This results in:
{
"Bob": "1",
"Joe": "2"
}
COPY
This is the contents of assets/data/test.json
:
{
"a": 2,
"b": [3],
"c": {
"d": 4
}
}
This is the contents of assets/mods/my-mod/assets/data/test.json.patch
:
[{
"type": "FOR_IN",
"keyword": "__VAR__",
"values": ["a", "b", "c"],
"body": [{
"type": "ENTER",
"index": "__VAR__"
},{
"type": "COPY",
"alias": "alias-__VAR__",
}, {
"type": "EXIT"
}]
}]
Nothing will change. All this will do is create an internal key-value storage that looks like this:
{ "alias-a": 2, "alias-b": [3], "alias-c": { "d": 4 } }
MERGE_CONTENT
With the following data:
{
"a": 1,
"b": 2
}
And the following patch step:
{
"type": "MERGE_CONTENT",
"content": {
"a": 3,
"c": 4
}
}
MERGE_CONTENT
would modify the data to be:
{
"a": 3,
"b": 2,
"c": 4
}
For an array, it would work the same way - effectively acting as multiple ADD_ARRAY_ELEMENT
steps being executed at the end of the array.
PASTE
This command relies on COPY.
This is the contents of assets/data/test.json
:
{
"a": 2,
"b": [3],
"c": {
"d": 4
}
}
This is the contents of assets/mods/my-mod/assets/data/test.json.patch
:
[{
"type": "FOR_IN",
"keyword": "__VAR__",
"values": ["a", "b", "c"],
"body": [{
"type": "ENTER",
"index": "__VAR__"
},{
"type": "COPY",
"alias": "alias-__VAR__"
},{
"type": "EXIT"
},{
"type": "SET_KEY",
"index": "__VAR__"
}]
}, {
"type": "SET_KEY",
"index": "saved",
"content": {}
}, {
"type": "ENTER",
"index": "saved"
}, {
"type": "FOR_IN",
"keyword": "__VAR__",
"values": ["a", "b", "c"],
"body": [{
"type": "PASTE",
"alias": "alias-__VAR__",
"index": "__VAR__"
}]
}]
This is the result:
{
"saved": {
"a": 2,
"b": [3],
"c": {
"d": 4
}
}
}
CALL
This step allows you to call a custom step.
First, you define a custom step.
simplifyResources.patchSteps.callable.register("YOUR_ID", (state, args) => {
console.log(args);
});
Then, in your patch file:
[{
"type": "CALL",
"id": "YOUR_ID",
"args": {
"someArg": 1
}
}]
This would log { someArg: 1 }
to the console whenever the step is called.
If you think patch steps are lacking in some way, CALL
allows you to define your own steps that process your args in some way without having to contribute back to the library.
DEBUG
[{
"type": "DEBUG",
"value": true
}, {
"type": "DEBUG",
"value": false
}]
This PatchStep turns Debug Mode on or off.
COMMENT
Debug mode must be on for this PatchStep to work.
[{
"type": "DEBUG",
"value": true
},{
"type": "COMMENT",
"value": "This is a comment"
}, {
"type": "COMMENT",
"value": {
"data": 3
}
}]
This will first display This is a comment
in the DevTools console. It will then display {data: 3}
.