# Player mods (RomFS)
[English](#english) | [中文](#chinese)
## English
The Mac app loads **RomFS mods only**. `exefs/` files are not loaded (the runtime uses the rewritten
`main`, `sdk` and `subsdk0`, and IPS or pchtxt files are not picked up automatically), and the app does
not scan folders for mods. Whether a mod is used is decided by one file: `manifest.tsv`.
The launcher points `TOTK_ROMFS_MODS` at
```text
~/Library/Application Support/MacNativeGames/TOTK/mods/
```
where `` is the game version the app was built from: `v0` (1.0.0), `v112` (1.1.2), `v121`
(1.2.1) or `v143` (1.4.3).
### What `manifest.tsv` is
Only the files listed in `manifest.tsv` are served. Each line has four tab-separated fields:
```text
```
At startup the loader checks each listed file's size and SHA-256, and checks the original's SHA-256
against the game image. `-` in the last field adds a file the game's RomFS does not have (used by mods
that add assets). Paths are case-sensitive and keep the RomFS spelling.
A folder without `manifest.tsv` means "no mod". Files copied into the folder but not listed are not
used. In detail:
- A **new path** that is not listed is treated as absent: the game cannot see the file.
- A **replacement whose size or hash changed** without regenerating the manifest stops the start with
`RomFS overlay file missing or wrong size` or `RomFS overlay integrity mismatch`.
- A path that exists in the game but is not listed is read from the game, so the copied file has no
effect.
### Install a mod
1. Quit the game completely.
2. Copy the mod's RomFS paths **directly** into the version folder. If the mod ships a `romfs/` folder,
copy its contents, not the folder itself:
```bash
MODDIR="$HOME/Library/Application Support/MacNativeGames/TOTK/mods/v121"
rsync -a --exclude='.DS_Store' --exclude='manifest.tsv' "/path/to/mod/romfs/" "$MODDIR/"
```
3. Generate the manifest with the app you built. Run this from the repository root:
```bash
python3 games/totk/scripts/make_mod_manifest.py "$MODDIR" \
--app "/path/to/Tears of the Kingdom.app" --version v121
```
The script reads `Contents/Resources/totk/extracted//romfs` from the app, hashes each file
and its original, leaves out files that are identical to the original, and writes `manifest.tsv`.
It prints a line such as `...: 12 files (10 replaced, 2 added)`.
4. Start the game and check the log:
```bash
grep "mods/v121 checked" "$HOME/Library/Logs/MacNativeGames/totk.log" | tail -1
```
It should say `.../mods/v121 checked, N files`; additions also print `M files added to the RomFS`.
### Switching versions
The game reads the installed folder as it is; it does not follow the folder you downloaded or unpacked.
If the new version removes files, set the old version folder aside (or delete it after a backup) before
copying, or the removed files stay active. Generate the manifest again after every change.
### Breath of the Wild
The Breath of the Wild app reads its mods from
```text
~/Library/Application Support/MacNativeGames/BotW/mods/botw160
```
Everything above applies: copy the mod's RomFS contents into that folder and generate the manifest with the app,
naming the folder `botw160` or passing `--version botw160`:
```bash
MODDIR="$HOME/Library/Application Support/MacNativeGames/BotW/mods/botw160"
python3 games/totk/scripts/make_mod_manifest.py "$MODDIR" --app "/path/to/Breath of the Wild.app"
```
Check `grep "mods/botw160 checked" ~/Library/Logs/MacNativeGames/botw.log | tail -1` after the start.
- A mod that makes a resource larger than the original also has to ship the matching resource size table,
`System/Resource/ResourceSizeTable.product.srsizetable`, built for 1.6.0. Without it the game reserves the original
size and stops at start (for example a translation whose `Pack/Bootup_.pack` grew).
- The game's language follows the app's preferred language, which also picks the language folder a mod must use (`USen` for
`en-US`). To give only this game another language:
`defaults write org.macnativegames.botw AppleLanguages -array en-US`.
### Notes
- Do not edit `manifest.tsv` by hand; regenerate it.
- Do not change files while the game runs. The loader records each file's identity at start and stops
if a file changes mid-session.
- There is one overlay directory for player mods: to use several mods, merge their files into one
version folder and generate one manifest. Do not nest mod folders. If two mods change the same path,
choose one file.
- The build replaces a few RomFS files itself: `UI/LayoutArchive/Common.Product..Nin_NX_NVN.blarc.zs` (quick-menu
timing; `` is 100, 110 or 140 depending on the version), `Event/EventFlow/DmF_SY_GameOver.bfevfl.zs`, and with the
2K output `System/HeapSize/Product.Nin_NX_NVN.HeapDef.byml.zs`. A mod that replaces one of them stops the start with
"two overlays replace the same file"; leave that file out of the mod for now (UI translation mods hit this, #73).
- `exefs/` is not loaded; only RomFS content works. (`TOTK_PCHTXT` is a developer experiment for pchtxt
patches, not a general mod loader.)
- This guide covers the Mac app; installing mods into the iPhone/iPad app container is not covered.
## 中文
Mac App 只加载 **RomFS mod**。`exefs/` 里的文件不会被加载(运行时用的是改写后的 `main`、`sdk` 和
`subsdk0`,IPS 和 pchtxt 不会自动生效),App 也不会扫描文件夹找 mod。是否使用某个 mod,完全由
`manifest.tsv` 决定。
启动器把 `TOTK_ROMFS_MODS` 指向
```text
~/Library/Application Support/MacNativeGames/TOTK/mods/
```
`` 是构建这个 App 时使用的游戏版本:`v0`(1.0.0)、`v112`(1.1.2)、`v121`(1.2.1)、
`v143`(1.4.3)。
### manifest.tsv 是什么
只有列在 `manifest.tsv` 里的文件才会被提供给游戏。每一行是四个用制表符分开的字段:
```text
<大小> <原版文件的 SHA-256,或 ->
```
启动时加载器会逐个核对 mod 文件的大小和 SHA-256,并核对原版文件的 SHA-256。最后一个字段是 `-`
表示这是游戏 RomFS 里没有的新文件(用于新增资源)。路径区分大小写,必须和 RomFS 里的写法一致。
没有 `manifest.tsv` 就等于没有 mod。只把文件复制进目录但不写进清单,游戏不会使用:
- 清单里没有的**新路径**会被当成不存在,游戏完全看不到这个文件。
- 覆盖已有文件但大小或哈希变了、又没有重新生成清单,启动会直接报
`RomFS overlay file missing or wrong size` 或 `RomFS overlay integrity mismatch`。
- 原版有这个路径、但清单里没列,游戏会读取原版文件,复制进去的文件看起来“没有效果”。
### 安装步骤
1. 先完全退出游戏。
2. 把 mod 的 RomFS 路径**直接**放进版本目录。如果 mod 带一层 `romfs/` 文件夹,复制的是它里面的
内容,不是 `romfs` 这一层:
```bash
MODDIR="$HOME/Library/Application Support/MacNativeGames/TOTK/mods/v121"
rsync -a --exclude='.DS_Store' --exclude='manifest.tsv' "/path/to/mod/romfs/" "$MODDIR/"
```
3. 用你构建好的 App 生成清单。在仓库根目录执行:
```bash
python3 games/totk/scripts/make_mod_manifest.py "$MODDIR" \
--app "/path/to/Tears of the Kingdom.app" --version v121
```
脚本会读取 App 里的 `Contents/Resources/totk/extracted//romfs`,计算每个文件和原版
文件的哈希,跳过与原始完全相同的文件,然后写入 `manifest.tsv`。成功时会打印类似
`...: 12 files (10 replaced, 2 added)` 的一行。
4. 启动游戏,确认日志:
```bash
grep "mods/v121 checked" "$HOME/Library/Logs/MacNativeGames/totk.log" | tail -1
```
应出现 `.../mods/v121 checked, N files`;有新增文件时还会打印 `M files added to the RomFS`。
### 换版本时
游戏读取的是安装目录本身,不会跟着你下载或解压的那个文件夹走。如果新版本删掉了旧版本的某些
文件,先把旧的版本目录整个移走(或备份后删除)再复制,否则被删掉的文件会继续生效。每次改动后
都要重新生成清单。
### 旷野之息
旷野之息 App 从这里读取 mod:
```text
~/Library/Application Support/MacNativeGames/BotW/mods/botw160
```
上面的说明都适用:把 mod 的 RomFS 内容复制进这个目录,再用 App 生成清单(目录名为 `botw160`,或加 `--version botw160`):
```bash
MODDIR="$HOME/Library/Application Support/MacNativeGames/BotW/mods/botw160"
python3 games/totk/scripts/make_mod_manifest.py "$MODDIR" --app "/path/to/Breath of the Wild.app"
```
启动后用 `grep "mods/botw160 checked" ~/Library/Logs/MacNativeGames/botw.log | tail -1` 检查。
- 如果 mod 让某个资源比原版大,还必须带上为 1.6.0 生成的资源大小表
`System/Resource/ResourceSizeTable.product.srsizetable`。没有它游戏会按原版大小分配,启动时就会停止(例如
`Pack/Bootup_<语言>.pack` 变大的翻译 mod)。
- 游戏语言跟随 App 的首选语言,也由它决定 mod 要放在哪个语言目录(`en-US` 对应 `USen`)。只给这个游戏换语言:
`defaults write org.macnativegames.botw AppleLanguages -array en-US`。
### 注意
- 不要手改 `manifest.tsv`,每次重新生成。
- 游戏运行中不要改 mod 文件;加载器会在启动时记录每个文件的身份,运行中变化会停止游戏。
- 玩家 mod 只有一个覆盖目录:多个 mod 就合并到同一个版本目录,最后生成一份清单;不要把 mod
文件夹嵌套进去。两个 mod 改同一个路径时,只能选一个文件。
- 构建本身也会替换几个 RomFS 文件:`UI/LayoutArchive/Common.Product..Nin_NX_NVN.blarc.zs`(快捷菜单时机,``
按版本是 100、110 或 140)、`Event/EventFlow/DmF_SY_GameOver.bfevfl.zs`,开 2K 输出时还有
`System/HeapSize/Product.Nin_NX_NVN.HeapDef.byml.zs`。模组如果也替换其中一个,启动时会报“two overlays replace the
same file”并停止,目前只能把那个文件从模组里去掉(界面翻译模组会碰到,见 #73)。
- `exefs/` 不会被加载,只有 RomFS 内容生效。(`TOTK_PCHTXT` 是开发者的 pchtxt 实验入口,不是
通用的 mod 加载器。)
- 本文面向 Mac App;iPhone/iPad 的 App 容器内安装未在此说明。