Patching

From CCDirectLink

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.

Mod-assets-folder.png

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.

Lea-face-path.png

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.

Faceless-mod-path-example.png

Faceless-mod-folder-contents-example.png

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.

Ui note reminder.svgThe following code must be ran BEFORE prestart! That being during plugin, preload, or postload.
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}.