Unity Modding
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:
- Decodes the target bundles with asset4j, using the game’s per-version
ttmapschema (auto-downloaded from the automod releases during setup when the game’smapUriis set but the map is not yet present intools/ttmap). - Patches decoded objects via class-scoped TOML or script patches, and raw files (e.g.,
.dll, non-asset files) via whole-file script patches. - Re-encodes the bundles in-process with asset4j.
- Packages everything into a
<mod-name>.zip/.7zarchive embedding theinstall.pyinstaller.
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.