# AnantaInjector(注入器最小依赖副本) 从 `C:\Game\Ananta_mainleakflow_keitaro_gg\AnantaUnifiedLoader` 里**只挑出需要的部分**拷过来的, 给服务端根目录那个 `一键启动.exe` 用。 ## 带的模块 | 模块 | 通道 | 作用 | |---|---|---| | `modules/bowling_agent.js` | C | 保龄球球瓶 | | `modules/balloon_fix.js` | C | 打气球的气球本体 | **故意不放**的那些(服务端已经接管,注入器再开就是白刷负载): - `gadget_loader`(A 户外 gadget 网格)→ 服务端 `GameRouter.GadgetGrid.cs` - `world_mod`(B 场景物品网格)→ 服务端 `GameRouter.SceneItemGrid.cs` - `train`(D 地铁座位 / E 室内扇区)→ 服务端 `GameRouter.MetroGadget.cs` / indoorSectors - `addbtn`(F 提示按钮)→ 客户端自己会出 F,已降级为手动兜底 - `settingsfix`(设置-图像页错位行)→ 纯手动 RPC,无常驻行为 > ✅ `balloon` 现在是可用的(2026-09-29 修):`GameRouter.Balloon.cs` 已就位,服务端每次 push 会写 > `%LOCALAPPDATA%\AnantaBalloon\balloon_push.json`(实测 21:07 写过 16 条)。 > ⛑ 但 `balloon_fix.js:3417` 的 `PUSH_PATHS` 默认是**作者自己机器**的 `D:\Ananta\gym_tools\_balloon_push.json`, > R146 注释写明「exe 加载器会把这一行整个改写成本机多路径」。原版 exe 做了,**我们之前没做** > ⇒ agent 读不到 ⇒ `pushN` 恒 0 ⇒ 一颗气球都不出。 > 现在由 `loader_common.apply_balloon_paths()` 在 `create_script` 前改写(js 文件本身仍与上游逐字节一致)。 ## 分组开关(2026-10-01) 每个模块用 `"group": "xxx"` 声明组别,`config.json` 顶层 `groups` 整组一起开关, **改完重启加载器**生效(config 只在启动时读一次): ```json "groups": { "minigame": true } ``` - 目前只有一组:`minigame`(= `bowling` + `balloon`)。 - ⛔ `traffic` 组(`modules/vehicle_go.js`)**已于 2026-10-01 晚整组删除**,不要再加回来: 它 hook 的 `AetherVehicleViewManager:CheckShouldNotUseGo`(RVA `0x0B108F00`)实机 8 探针 **全 0 命中** ⇒ 那个类根本没在用。车流现改走服务端 `trafficCruiseDrive` (`SyncAssignVehicleAITask` 68549522 + `VehicleCruiseParameters`,客户端本地 AI 开车)。 - ⛑ 与模块自身的 `enabled`、命令行 `--no a,b` 是**叠加**关系 —— 任一处关掉就不挂。 - ⛑⛑ **别复制第二份目录去"跑两个加载器"**:同一时刻只能有一个 frida 会话挂在 `Ananta.exe` 上 (两个 = 游戏必崩),常驻模式靠 `.loader.lock` 挡着。所谓"拆分"是**同一进程内的模块取舍**。 ## 两道保险(针对「注入器是不是得先进入世界再启动」) ### ① 进入世界闸门 `attachGate` `bowling_agent.js:5547` / `balloon_fix.js` 里装 hook 的 `Il2Cpp.perform(...)` **没有重试**: ```js var blob = bfClass('LX6.Item.SceneItemBlob'); if (!blob) { sendlog('ERR: SceneItemBlob not found'); return; } // ← 永久放弃 ``` 所以「游戏刚起就挂」是有真实风险的。闸门的做法:先看服务端日志里有没有 **只有 world-ready 之后才会打**的行,命中了再 attach —— 等价于复现手动操作时 「进世界 → 再开加载器」这条已经验证过的顺序。 标记(任意一条命中即放行): - `[RESTOCK] raid=` - `[GADGET-GRID] push group` - `[SCENE-ITEM-GRID] push group` ⛑ 只扫**本次开始跟踪之后新增的字节**,不会把上一局的历史内容当成「已进世界」。 ⛑ 三条 marker 对应的服务端功能全关掉时,闸门会走 `timeoutSec`(默认 900s)超时兜底, 按 `fallback=attachAnyway` 照样挂 —— 不会把你卡死。 想关掉闸门(排查时用):`config.json` 里 `attachGate.mode` 改成 `process`。 ### ② armed 守卫 `armedGuard` 闸门只保证「时机对」,不保证脚本里那次 `bfClass(...)` 真的成功。 所以 attach 之后持续轮询 `bfStatus.armed`: - `armed=true` → 正常。`tpl=false` 是正常的(还没拿到模板),**不会**因此重挂 - `armed=false` → 说明脚本里 return 掉了 ⇒ 等 `maxWaitSec`(60s)后**主动重挂** 最多连试 `maxAttempts`(5)次,之后退避 `backoffSec`(120s)再试,不会无限刷。 ## 手动跑 ``` C:\Users\Noiro\.workbuddy\binaries\python\envs\default\Scripts\python.exe unified_loader.py ``` 其它命令同 `AnantaUnifiedLoader`:`diag` / `scan` / `rpc <模块> [参数]` / `--no a,b`。 ⛑ 同一时刻只能有一个 frida 会话挂在 Ananta.exe 上(两个 = 游戏必崩)。 常驻模式用 `.loader.lock` 挡着;手动 `rpc` 之前请先关掉常驻窗口。 ## injector_status.json(界面靠它显示状态) 注入器会把自己的状态写进同目录 `injector_status.json`,**根目录那个「一键启动」界面每秒读一次**, 所以界面上的「已启动 / 等待 / 已注入」是真实状态,不是瞎猜的: | state | 界面显示 | |---|---| | `starting` | 正在启动… | | `waiting_game` | 已启动,等待游戏打开 | | `waiting_world` | 已启动,等你进入游戏世界 | | `attached` | 已连上客户端,正在装载… | | `injected` | 已注入(保龄球瓶 + 气球生效) | | `stopped` | 已退出 | 界面还会用文件里的 `pid` 判断注入器进程是否还活着(**不是**看「有没有 python 进程」, 那样会被机器上别的 python 误导)。 ## 判活 ``` ... unified_loader.py rpc bowling bfStatus ... unified_loader.py rpc balloon bfStatus ``` - bowling:`armed=true` 且 `tpl=true` 才真的生效。`tpl=false` 就站进保龄球馆让 AOI 表加载一次 (模板是「等客户端自己成功查一次表」时顺手收的)。 - balloon:`armed=true` 即可;`pushN` 是服务端推送条目数 —— **必须 >0**, `pushErr` 会写清是哪几个候选路径读不到(修好后应为 `none`、`pushSrc` 指向 LOCALAPPDATA 那份)。