碎碎视界(Event Horizon)修改器 · SSMH 文档

目标包:碎碎视界3.12.26.4916.2.apk 包名:com.suisui.EventHorizon 引擎:Unity IL2CPP(metadata v29,arm64-v8a) 工具:D:\suisui_mod\SSMH(GUI 启动SSMH.bat / CLI ssmh_cli.py) 最后更新:2026-11-05(新增「资源反加」,原「掉落率增加」改名并升级为「资源掉落倍率」)


0. 一句话结论

SSMH = 解包 → 解析 il2cpp 元数据 → 核对 hook 目标 → 注入 Frida gadget + 悬浮窗 dex → 改包名共存 → 回编译签名 的桌面工具。 和目标像素地牢那套(改 smali 逻辑)不同:这是 Unity IL2CPP 包,游戏逻辑全在 libil2cpp.so 里, smali 里只有壳,所以改数值只能在运行期做——用 Frida gadget 在进程内按名字找到 C# 方法并挂钩, Java 侧只负责悬浮窗 UI 和开关存储。


1. 包体结构

碎碎视界3.12.26.4916.2.apk
├─ classes.dex                     3.1 MB   只有 Unity 壳 + AndroidX + GPGS,没有游戏逻辑
├─ lib/arm64-v8a/
│  ├─ libil2cpp.so                85.5 MB   全部游戏逻辑(C# 编译产物)
│  ├─ libunity.so                 21.4 MB   Unity 运行时
│  ├─ libmain.so / lib_burst_generated.so
├─ assets/bin/Data/
│  ├─ Managed/Metadata/global-metadata.dat  12.6 MB  IL2CPP 元数据(v29,15 147 个类型)
│  ├─ data.unity3d                67.9 MB   资源
│  └─ resources.resource          53.7 MB
└─ AndroidManifest.xml            package=com.suisui.EventHorizon,单个 UnityPlayerActivity

要点:


2. 工程结构

D:\suisui_mod\SSMH\
├─ SSMH.py                  tkinter GUI(选包 → 解析 → 生成)
├─ ssmh_cli.py              命令行:python ssmh_cli.py <apk> [--probe] [--no-coexist]
├─ 启动SSMH.bat
├─ core/
│  ├─ paths.py              工具链探测(java/apktool/d8/zipalign/apksigner/7z/keystore/gadget)
│  ├─ meta.py               global-metadata.dat v29 解析 + 8 个 hook 目标自检
│  └─ build.py              解包 → 编译 dex → 打补丁 → 投 gadget → 改包名 → 回编译签名
├─ modsrc/com/ssmh/mod/
│  ├─ Config.java           开关与倍率(静态字段,agent 每 250ms 读一次)
│  ├─ Loader.java           onCreate 里解 agent 脚本 + loadLibrary("gadget");onResume 挂悬浮窗
│  ├─ ModMenu.java          悬浮窗入口
│  └─ FloatingPanel.java    悬浮球 + 面板(纯代码 UI,贴边自动隐藏)
├─ agent/agent.js           运行期引擎:il2cpp 符号解析 / 数值同步 / 掉落倍率 / 资源反加
├─ tools/
│  ├─ libgadget.so          Frida gadget 16.7.19 android-arm64(必须 16.x,17 起无 Java 桥)
│  ├─ android-stub.jar      编译 dex 用的 android.jar
│  └─ modforge.keystore     自签名证书(口令 modforge,别名 modforge)
├─ work/                    解包缓存与中间产物
└─ output/                  成品 APK

外部工具(core/paths.py 里改,支持同名环境变量覆盖):

工具 路径
JDK 17 D:\apkwork\jdk\jdk-17.0.20.1+1
apktool 2.11.1 D:\apkwork\apktool.jar
build-tools 34 D:\apkwork\buildtools\android-14(d8 / zipalign / apksigner)
7-Zip D:\7-Zip\7z.exe

3. 数据模型(为什么这样挂钩)

global-metadata.dat 反出来(Il2CppDumper v6.7.46,配置 Change il2cpp version to 29.1)之后,钱/油/星币的位置是这样:

Session.Content.ResourcesData          // 存档镜像(ISerializableData)
  _money  : ObscuredLong  @0x18
  _fuel   : ObscuredLong  @0x68
  _stars  : ObscuredLong  @0xB8
  _tokens : ObscuredLong  @0x108
  _basicCryptocurrency : BasicCryptocurrency @0x1A8   // ← 玩法真正读写的那份

BasicCryptocurrency
  _money @0x10   _stars @0x18   _tokens @0x20   _fuel @0x28   // 全部是 Cryptocurrency 对象

Cryptocurrency(引用类型,混淆很轻)
  _value @0x20(隐藏值)  _mask @0x28
  GetCurrteValue() = _value ^ _mask
  SetCurrteValue(v) 写回 _value = v ^ _mask,并触发 ValueChanged 信号(UI 刷新走这里)

ObscuredLong(ACTk 七槽轮转混淆,0x50 字节)
  _value1.._value7 @0x00..0x30, _index @0x38, _values[] @0x40, _newindex @0x48
  → 不要手写内存,直接调 GetValue(this) / SetValue(this, v)

GameServices.Player.PlayerResources
  TryConsumeFuel(int)          // 判断油量够不够 + 扣掉,返回 bool
  RemoveResource(ItemId,long)  // 扣任务/普通资源
  AddResource(ItemId,long)     // 加任务/普通资源(掉落、采集、任务奖励的唯一入账口)
  get_BasicCryptocurrency()    // 拿到上面那四个货币对象

GameServices.Player.PlayerSkills
  _experience : Experience(含 ObscuredLong) @0x38   // 玩家等级经验,上限 337 500 000

GameServices.Research.Research
  AddResearchPoints(Faction, long)   // 科技点唯一入口(5 处调用)

Domain.Quests.Loot
  _random : System.Random @0x18      // 战利品判定专用的随机数发生器
GameDatabase.DataModel.ResourceItem
  get_Chance()                       // 星球资源掉率

三条设计决定:

  1. 不手改 ObscuredLong 内存:它是七槽轮转 + _values[] 托管数组,写错槽位等于写坏存档; 统一走 GetValue/SetValue。
  2. 货币不碰 ResourcesData._money:那是序列化镜像,玩法只认 BasicCryptocurrency 里的 Cryptocurrency; 改镜像不生效、改对象才生效。
  3. 倍率用「差额补足」不用「直接乘」:读当前值 → 和 300ms 前的值比 → 增量 ×(倍率−1) 补进去。 好处是不会把商店扣款、修理费这些「减少」也算成倍率,也不会每帧写值把存档写花。
  4. 反加是「补得比扣得多」:同一个差额检测,方向反过来——值变少时把少的补回去, 再额外补 (N−1) 倍;N=1 就是「消耗不减」,N=2 就是「消耗多少反赚多少」。 油量不参与这条(它有自己的 TryConsumeFuel 开关)。

4. 八个功能怎么实现

# 面板文字 挂点 做法
1 油量不减 PlayerResources.TryConsumeFuel(int) onEnter 把参数 amount 改成 0:原逻辑照样返回 true,一滴油都不掉
2 信用点获取倍率 Cryptocurrency(money) 每 300ms 读一次,钱变多就把差额 ×(倍率−1) 补进去
3 星币获取倍率 Cryptocurrency(stars) 同上
4 经验值获取倍率 PlayerSkills._experience@0x38 ObscuredLong.GetValue/SetValue 读改写,补差额,封顶 337 500 000
5 科技点获取倍率 Research.AddResearchPoints(Faction,long) onEnter 直接把 amount × 倍率(奖励入口唯一,不会重复放大)
6 资源使用时不减少 PlayerResources.RemoveResource(ItemId,long) onEnter 把 amount 改成 0(反加的 N=1 特例,保留旧开关)
7 资源掉落倍率 Loot._random + System.Random.Next(int) + ResourceItem.get_Chance() + PlayerResources.AddResource(ItemId,long) 前三条让判定更容易过(Next(max) 返回值除以倍率、星球掉率乘倍率);AddResource 是资源入账唯一口,onEnter 把 amount × 倍率,到手数量直接翻倍
8 资源反加(油量以外的资源) Cryptocurrency(money/stars/tokens) + PlayerResources.RemoveResource 货币:差额检测方向反过来,值变少 M 时补回 M ×(N−1)(N 由「反加返还倍率」控制);任务材料:amount 取反成负数,原逻辑「减去」变成「加上」,净返还 M ×(N−1)。油量不走这条

补充说明:


5. 悬浮窗


6. 注入链(第二轮重写版)

UnityPlayerActivity.onCreate()      ← smali 注入(.locals 之后,不动原有寄存器)
   └ Lcom/ssmh/mod/Loader;->onCreate(Activity)
        ├ Config.init()                       读 SharedPreferences
        ├ 把 assets/ssmh_agent.js 复制到 /data/user/0/<pkg>/files/ssmh_agent.js
        └ System.loadLibrary("gadget")        ← 此时 config 里的脚本路径已经存在
             └ libgadget.so 读 libgadget.config.so,起 QuickJS 跑 agent
UnityPlayerActivity.onResume()      ← smali 注入
   └ Lcom/ssmh/mod/Loader;->onResume(Activity)
        └ ModMenu.attach() → 悬浮球

gadget 配置(打包时写死新包名):

{"interaction":{"type":"script","path":"/data/user/0/<新包名>/files/ssmh_agent.js","on_change":"reload"},
 "log":{"level":"info","console":true}}

6.1 必须用 Frida 16.x 的 gadget

从 Frida 17.0.0 起,Java 桥(frida-java-bridge)不再内置在运行时里, 脚本里 Java.perform() 直接 ReferenceError: Java is not defined(被 try 吞掉就是完全静默)。 本轮第一版用的 17.25.2,症状就是:悬浮球出来了(Java 层正常),引擎一行日志都没有。

现在 tools/libgadget.so 是 16.7.19 android-arm64(25.8 MB)。 要升 17+ 就得用 frida-compile 打包脚本并 import Java from 'frida-java-bridge',本轮没走这条路。

6.2 脚本里不要用 JS 定时器去调 Java

setInterval/setTimeout 的回调跑在 gadget 自己的线程上,在 16.7.19 里: 定时器能正常触发(文件日志能看到),但回调里 Java.perform(...) 会静默卡死, 连 android.util.Log 都写不出来。所以配置/状态改成文件桥:

files/ssmh_cfg.txt     ← Java 写(开关/倍率),agent 每 300ms 读
files/ssmh_status.txt  ← agent 写(引擎状态/当前倍率),面板每秒读来显示

面板左上角那四行「引擎: 已注入 (14 钩子) / 信用点x.. 星币x.. … 反加x2 / 油量.. / hooks=14」 就是 agent 写回来的。

6.3 il2cpp 初始化之前不能碰任何 il2cpp API

脚本在 loadLibrary 那刻就跑,此时 il2cpp_init 还没执行。这时调 il2cpp_domain_get() 会直接 SIGSEGV;更麻烦的是这个异常会把整个脚本运行时搞死(定时器从此不再触发)。 所以 agent 里有硬延迟:Date.now() - t0 < 5000 之前一律不探测。

6.4 镜像名带 .dll

il2cpp_image_get_name() 返回的是 Assembly-CSharp.dll,不是 Assembly-CSharp。 第一版写死不带后缀 → 72 个 assembly 全读到了却永远匹配不上,boot 一直空转。

6.5 数值怎么落地


7. 共存与签名

只改「应用标识」,绝不动类名(这条是像素地牢那边用命换来的经验,这里同样适用):

  1. <manifest package="com.suisui.EventHorizon"> → com.suisui.EventHorizon.ssmh
  2. android:authorities / 私有 <permission> / android:permission|readPermission|writePermission 跟着改名
  3. apktool.yml 的 versionName 追加 -ssmh
  4. android:name(activity/service/receiver/provider)一律不动

再注入自己的 dex 并用独立证书签名 → 系统视为两个应用,数据目录隔离,可以和原版同装。 --no-coexist 时跳过第 1 步,用于覆盖安装原版(会破坏原版数据兼容性,慎用)。


8. 流水线

apktool d                      → smali + res + assets + lib
meta.Metadata(...)             → 8 个 hook 目标自检(类/方法/字段)
javac --release 8 (android-stub.jar) → d8 --min-api 21 → classes.dex
build.patch_activity           → UnityPlayerActivity onCreate/onResume 插入 2 条 invoke-static
build.install_gadget           → libgadget.so + libgadget.config.so + assets/ssmh_agent.js
build.rename_manifest          → 包名/权限/authorities(不动类名)
apktool b                      → base.apk
7z a                           → 注入 classes2.dex(apktool 只产 classes.dex)
zipalign -p 4                  → 4 字节对齐
apksigner sign / verify        → v1+v2+v3

耗时参考(这台机器,i5-12400):首次全流程 30.9 s(解包 + 元数据 + dex + 回编译 + 签名), 复用 work/ 缓存时 12.2 s;成品 165.4 MB(原包 151.3 MB + gadget 37.6 MB)。

实测产物(2026-11-05,新增反加 + 掉落倍率):

output/SSMH-3.12.26.4916.2.apk          161.0 MB
├─ classes.dex       3 125 756 B   原包 smali 回编译,UnityPlayerActivity 两条注入都在
├─ classes2.dex         21 260 B   自建悬浮窗 dex(com.ssmh.mod.*,6 滑杆 + 3 开关)
├─ lib/arm64-v8a/libgadget.so       25 822 568 B  Frida gadget 16.7.19
├─ lib/arm64-v8a/libgadget.config.so       174 B  指向 /data/user/0/com.suisui.EventHorizon.ssmh/files/ssmh_agent.js
├─ assets/ssmh_agent.js                 29 783 B
├─ AndroidManifest.xml  package=com.suisui.EventHorizon.ssmh
zipalign -c -p 4 通过;apksigner verify 通过(CN=ModForge)

9. 验证方法

装包

adb install -r "D:\suisui_mod\SSMH\output\SSMH-3.12.26.4916.2.apk"

看引擎起没起(最关键的一步)

:: 引擎日志(最可靠,任何线程都能写)
adb shell cat /sdcard/Android/data/com.suisui.EventHorizon.ssmh/files/ssmh_agent.log
:: 加载器日志(gadget 有没有起来)
adb logcat -d -s SSMH:V

正常会看到(实测输出,节选):

agent script loaded
主循环已启动 (300ms)
镜像: Assembly-CSharp.dll | mscorlib.dll | ...
类解析: PlayerResources=true PlayerSkills=true Research=true Crypto=true Obscured=true Loot=true Random=true
已 attach 当前线程到 il2cpp
就绪: 钩子 14
状态: money=42662098 star=6140 token=0 fuel=100 exp=121431 cfg={"creditMul":99,"starMul":99,"expMul":99,"techMul":99,"dropMul":20,"resAdd":true,"resAddMul":2,"fuelKeep":true,"resKeep":true}
钩子命中: fuel=1 res=0 resAdd=0 addRes=0 tech=0 loot=0 rand=22204

看不到就按顺序查:

现象 原因 处理
球出来了但日志文件一直只有 1 行 gadget 17.x 没有 Java 桥 换回 16.7.19(tools/libgadget.so)
完全没有日志文件 gadget 没加载 adb logcat -d \| findstr nativeloader,确认 libgadget.so 真的被 dlopen
boot#N 等 il2cpp 初始化: asm=-2 探测太早,il2cpp_init 还没跑 保持 5 秒硬延迟,别改成 0
boot#N ... imgs=72 但一直不就绪 镜像名要带 .dll imageByName() 已兼容两种写法
method 未找到: xxx 版本换了类/方法名 跑 ssmh_cli.py <apk> --probe 看哪一项「注意」
面板显示「引擎: gadget 加载失败 …」 System.loadLibrary("gadget") 失败 看 logcat -s SSMH 里的 Loader 堆栈

看功能生效:进游戏 → 点悬浮球 → 把「信用点获取倍率」拉到 5 → 打一场拿钱, 数字应该是原来的 5 倍(面板状态行会显示当前生效倍率,日志里会出现 补足 money: a -> b)。 注意:花钱仍然会扣——获取倍率只放大「获取」,不补「消费」。

看反加生效(2026-11-05 新增):

  1. 面板里勾上「资源反加(油量以外的资源,消耗转增加)」,把「反加返还倍率」拖到 2;
  2. 看日志里的 cfg 立刻变成 "resAdd":true,"resAddMul":2(300ms 内,说明文件桥是通的);
  3. 回游戏里花一次钱(商店/修理/升级),下一拍日志出现 反加 money: 42662098 -> 42662198 (返还 x2) —— 扣 100 反而净赚 100;
  4. resAddMul=1 时是「等额返还」(净 0,等于消耗不减),想只在特定场景不减就拖到 1。

看掉落倍率生效:「资源掉落倍率」拉高 → 战斗/采集结算时日志里 addRes 计数增长, 同一次入账的数量按倍率放大;rand 计数一直在涨说明 Loot 判定那条路也在拦。

无设备时的自检(只能验证结构,不能验证行为):

python ssmh_cli.py <apk> --probe          :: 只解析,看 8 个目标是否 OK
python -c "import zipfile;print([n for n in zipfile.ZipFile(r'output/xxx-SSMH.apk').namelist() if 'gadget' in n or 'ssmh' in n])"

10. 踩坑记录

10.1 smali 注入不能改寄存器

UnityPlayerActivity.onCreate 原本 .locals 2 且 p1 被复用。注入点选在 .locals 之后、 用 invoke-static {p0} 只吃 p0,不动 v0/p1,避免 VerifyError。

10.2 gadget 必须在脚本落地之后再 load

Loader.onCreate 里先解 assets 到 filesDir,再 loadLibrary("gadget")。 反过来的话 gadget 启动时脚本还不存在,直接静默不加载。

10.3 replace 会吃掉原函数

Interceptor.replace 之后原函数指针就废了,功能需要「可关」的时候必须用 Interceptor.attach + 改参数(args[1] = 0),而不是 replace。

10.4 从 JS 线程调托管代码 = 随机崩

已改成 Handler.postDelayed 到主 Looper(见第 6 节)。setInterval 只负责投递,不碰 il2cpp。

10.5 ObscuredLong 字段间距是 0x50,不是 0x20

ResourcesData 里 _money@0x18、_fuel@0x68 差了 0x50;它是七槽结构 (_value1.._value7 + _index + _values[] + _newindex)。 按 ACTk 老版本 0x20 的布局去猜会写坏存档。

10.6 7z 注入 dex 会顶掉同名 entry(真实炸过)

第一版直接 7z a staged.apk build\dex\classes.dex,7z 按文件名入包, 把 apktool 刚编出来的 3.1 MB classes.dex 覆盖成自己的 17 KB → 游戏壳没了,装上也点不开。

修法(core/build.py: build_apk):先读现有 entry,挑一个没被占用的 classesN.dex 再复制改名注入。 N 必须从 2 开始:classes1.dex 系统根本不加载(第一版就是这么写成 classes1.dex,dex 白放)。 注入后还要回读 zip 确认 entry 真的进去了。

10.7 中文路径会把 aapt2 送走

work\碎碎视界3.12...-dec 这种目录,apktool 调 aapt2 时命令行编码对不上,报 failed to open directory: (2) 和一堆 \ufffd。工作目录一律 safe_stem() 成纯 ASCII。

10.8 复用缓存会二次加后缀

rename_manifest 是就地改文件,第二次跑 manifest_package() 读到的已经是 xxx.ssmh, 再拼一次就成了 xxx.ssmh.ssmh。build() 里先剥掉已知后缀再算新包名。

10.9 注入必须是幂等的

patch_activity 第一条会 raise("没匹配到"),复用缓存时调用已经在里面, 于是被误判成失败。改成:已有这两条 invoke 就记「已有」,只有连方法头都找不到才报错。

10.10 编译缓存会把旧 dex 打进包(第一次「改完没变化」就是这么来的)

build_dex() 早期只看文件存在,modsrc 改了照样复用旧 classes.dex, 现象是加载器日志一行都没有、和改之前完全一样。现在按 mtime 判定,源码新于 dex 就重编。

10.11 Frida 17 没有 Java 桥

见 §6.1。症状:悬浮球正常,引擎静默。判断方法:libgadget.so 是 17.x 就一定中招。

10.12 gadget 脚本里的日志去哪了

console.log 在 Android 上是 /dev/null(不是 logcat), Java.perform + android.util.Log 又只在「loadLibrary 那条线程」上能用。 最终方案:new File('/sdcard/Android/data/<pkg>/files/ssmh_agent.log','a').write(...), adb shell cat 直接看,任何线程都能写。

10.13 File 读文件:readAll() 在 16.7.19 里不是函数

new File(path,'r') 能开,但 f.readAll() 抛 TypeError: not a function。 改成 typeof f.readAll === 'function' ? f.readAll() : f.readBytes(f.size), 再 new Uint8Array(buf) 逐字节转字符串(QuickJS 里 ArrayBuffer.toString() 得到的是 [object ArrayBuffer])。

10.14 定时器线程 + Java = 静默死锁

同一份脚本,脚本顶部(loadLibrary 线程)调 Java.perform 成功; 放到 setInterval 回调里就再也没有下文(try/catch 也来不及写日志)。 所以所有 Java 交互(配置、状态、加载器错误)都被挪到了「Java 写文件 / agent 读文件」。

10.15 第一次读数时 PlayerResources 还没抓到

构造函数 hook 只在会话创建时跑一次,而 agent 要等 5 秒(等 il2cpp 初始化)才挂钩, 大多数启动都会错过。补了两个「UI 每帧都在调」的 getter hook (get_BasicCryptocurrency、get_Experience),谁先到就用谁。


11. 当前验证状态(2026-11-05 00:35,真机 Redmi 23049RAD8C / Android 15)

项目 结果
il2cpp 元数据解析(8 个目标类/方法/字段) ✔ 全 OK(含新增的 get_Basic_Tokens 检查)
悬浮窗 dex 编译 / classes2.dex 注入 ✔ 21 260 B,6 滑杆 + 3 开关
smali 注入(onCreate / onResume) ✔ 成品 classes.dex 反编译核对存在
gadget 加载 ✔ nativeloader: Load ... libgadget.so ... ok
agent 启动 ✔ 文件日志 agent script loaded / 主循环已启动 (300ms)
符号解析 ✔ PlayerResources / PlayerSkills / Research / Cryptocurrency / ObscuredLong / Loot / Random 全 true
钩子安装 ✔ 14 个(日志 就绪: 钩子 14,新增 AddResource)
读数 ✔ money=42662098 star=6140 token=0 fuel=100(和游戏内一致)
倍率补足 ✔ 补足 money / 经验/星币补足在面板与日志双端一致
存档持久化 ✔ 走正常退出存档后重启,数值保持
面板 ↔ 引擎 文件桥 ✔ 勾「资源反加」+ 拖到 x2 后 cfg 立刻变 "resAdd":true,"resAddMul":2
面板文字改名 ✔ 「掉落率增加」→「资源掉落倍率」(上限 20 → 50)
「资源反加」控件 ✔ 复选框 + 返还倍率滑杆,x1 不再显示「关闭」(1 是有效值)
贴边自动隐藏 ✔ 球贴右边缘只露一半,点击可展开
TryConsumeFuel 钩子 ✔ 本次实机计数 fuel=1(进入行星系时被调用,油量 100 未减)
System.Random.Next 钩子 ✔ 每 5s 计数增长(掉落路径的随机数发生器已拦到)
AddResource 钩子 ✔ 已挂上;libil2cpp.so 里 2 处真实 bl(0x2196A6C / 0x2196F40,都归属 private bool Resources(int id)),本轮战斗无资源产出,计数仍 0
RemoveResource 调用点 ℹ️ 全 so 只有 1 处 bl,在 ClearAll() 里 → 玩家扣材料的调用点被内联,反加主路径是货币差额
反加·消费触发 ⚠ 待对局:需要在商店/修理处真实花一次钱看 反加 money: a -> b
实机「跑图掉油」「战斗结算掉落」体感 ⚠ 未做长时间对局验证

12. 换版本怎么办

  1. python ssmh_cli.py <新apk> --probe,看 8 个目标哪个「注意」;
  2. 「注意」的项多半是方法改名/加参数:在 core/meta.py 的 TARGETS 里改候选名, 再到 agent/agent.js 的 S.targets 同步参数个数;
  3. 字段偏移(0x10/0x18/0x28、0x38、0x18)如果变了,用 Il2CppDumper 重新 dump 一次 dump.cs 对照(工具:D:\sssj_work\tools\il2cppdumper);
  4. 出包后先看 logcat -s SSMH 有没有「就绪」,再逐项开功能。

已知待办:


13. 变更记录

13.1 2026-11-05 · 新增「资源反加」,掉落功能改名并升级

项 之前 现在
面板第 7 项 掉落率增加(上限 20) 资源掉落倍率(上限 50,AddResource 入账数量也乘倍率)
面板新增 — 资源反加 复选框 + 反加返还倍率 滑杆(1~9)
Config 字段 dropMul dropMul(语义改为掉落倍率)+ resAdd + resAddMul
agent 钩子 13 个 14 个(新增 PlayerResources.AddResource)
cfg 行 7 行 9 行(多 resAdd / resAddMul)

设计口径:

generated by cloud_sync · 2026-11-14 03:02