Unity Modding

Patching Unity games with class-scoped TOML/script patches and raw file patches

Unity Modding

Besides Unreal Engine games, automod patches Unity games using asset4j (the JVM port of AssetsTools.NET). Unity mods are scoped to a game’s data directory (e.g., WarmSnow_Data) configured in .config.json, and the patch files live in patches\<game-id>\<mod-name>\ just like UE mods.

A Unity game is selected with its unity flag in .config.json. automod then:

  1. Decodes the target bundles with asset4j, using the game’s per-version ttmap schema (auto-downloaded from the automod releases during setup when the game’s mapUri is set but the map is not yet present in tools/ttmap).
  2. Patches decoded objects via class-scoped TOML or script patches, and raw files (e.g., .dll, non-asset files) via whole-file script patches.
  3. Re-encodes the bundles in-process with asset4j.
  4. Packages everything into a <mod-name>.zip/.7z archive embedding the install.py installer.

Enabling a mod is just removing the leading dot from its folder name (.easy-mode -> easy-mode); dot-prefixed folders are skipped by .batch.

Class-scoped TOML patches

A patch file named <ClassName>@<bundle>.toml (e.g., MonsterSetting@resources.assets.toml) targets the objects of a script class (MonsterSetting) inside a bundle (resources.assets). Every .@ section is a JSONPath applied to each matching object’s decoded data, and $ refers to the object’s own root:

# Warm Snow "easy mode" — MonsterSetting@resources.assets.toml
['.@: $.list.Array[*]']
MonsterHP = '=> v.orig[Double] * 0.5'
MonsterAtk = '=> v.orig[Double] * 0.5'
MonsterDef = '=> v.orig[Double] * 0.5'
SoulDrop = '=> v.orig[Double] * 10'

Class-scoped script patches

A patch file named <ClassName>@<bundle>.kt (or .js, .ts, .py, .sc, .lua) runs the script body as the transform over v.objects – a list of {id, data} where data is each matching object’s decoded JSON. The script edits the objects in place and returns them; automod re-encodes via the targeted round-trip. v.className exposes the class name.

// MonsterSetting@resources.assets.kt — halve every monster's stats
import com.fasterxml.jackson.databind.node.*
import com.fasterxml.jackson.databind.ObjectMapper

for (obj in v.objects) {
  val data = obj["data"] as ObjectNode
  for (m in data["list"]["Array"]) {
    m as ObjectNode
    m.replace("MonsterHP", DoubleNode.valueOf(m["MonsterHP"].asDouble() * 0.5))
    m.replace("MonsterAtk", DoubleNode.valueOf(m["MonsterAtk"].asDouble() * 0.5))
  }
}
v.objects

Whole-file raw patches

A patch file named after the target file (with $ in place of /) patches a whole file. v.orig is the original bytes, v.current the bytes after any earlier patches; the script returns the patched bytes.

# Warm Snow player cooldowns -> 2s — Managed$Assembly-CSharp.dll.py
import struct
data = bytearray(v["current"])
new = struct.pack("<f", 2.0)
for base, expected in [
    (0x127E73, 14.0),   # base drawCoolDown
    (0x126E74, 28.0),   # GourdLiquor
]:
    assert data[base] == 0x22 and struct.unpack("<f", bytes(data[base+1:base+5]))[0] == expected
    data[base + 1:base + 5] = new
data

For a bundle/asset file (e.g. config), the script can instead work on the JSON representation: v.toJson() decodes the current bytes with asset4j, the script patches the tree, and v.fromJson(tree) re-encodes. v.resource(name) reads any file from the patch directory.

// Bladed Fury "easier mode" — config.kt
val tree = v.toJson() as com.fasterxml.jackson.databind.node.ObjectNode
// ... JSONPath-select and scale MonsterData / BehaviorNode ...
v.fromJson(tree)

ttmap schemas

A ttmap records the Unity type trees (and script classes) a game needs for decoding external-tree assets. Each supported game pins its ttmap (e.g., warmsnow_3.1.0.1.ttmap); when the game’s mapUri is configured but the map file is not yet present in tools/ttmap (or tools/usmap for Unreal Engine games), automod downloads it from the automod releases during setup. To regenerate schemas (e.g., after a game update), use the .ttmapgen command:

./automod .ttmapgen dll "game/WarmSnow_Data/Managed" warmsnow.ttmap 2020.3.22f1 3.1.0.1
./automod .ttmapgen il2cpp GameAssembly.dll global-metadata.dat silksong.ttmap 2022.3.10f1 1.0.0

ttmapgen.jar is auto-installed (version-matched to automod’s asset4j) on first use; the dll/il2cpp modes additionally require a .NET 10 runtime to build the C# harness.

Installing Unity mods

Each generated archive embeds install.py and a gameDataDir.txt hint. Uncompress the archive, then:

python3 install.py        # install; originals are backed up first
python3 install.py -r     # restore the originals (undo)

The installer needs Python 3 on the machine installing the mod (not to run automod itself); if Python is unavailable, uncompress the archive and copy the files into the game’s data directory manually. Backups live in <game-dir>/../<mod-name>-backup/. The installer is cross-platform (Windows, Linux, macOS) and sets the executable bit on patched .dll files on non-Windows platforms.