## 概述
代码 review 发现的一批正确性问题批量修正,都是 `src/MaaCore` 下单行 / 局部 logic 级别的
bug,彼此独立,按严重度从高到低逐条 commit。没有功能改动、没有 API 变化、没有 breaking behavior。
每个 commit 单独对应一条 bug,便于逐条 review 或按需
revert。下表也按严重度从高到低排序,附上「原问题」与「修法理由」。
### 发现来源
使用 Claude Code 新版 `/ultrareview` 多 agent review 扫了 `src/MaaCore` 源码(~58k
行),人工逐条核对后提 PR。另有 2 条(AvatarCacheManager 并发保护 / CopilotConfig
默认值语义)涉及设计决策或 breaking change,已单独提 issue 讨论,不在本 PR 内。
---
## Fix 列表(按严重度排序)
### 1. `Controller::back_to_home` 空指针解引用崩溃 — HIGH
**位置**:`src/MaaCore/Controller/Controller.cpp:137`
**原问题**:
```cpp
bool asst::Controller::back_to_home()
{
m_controller->back_to_home(); // 无 null 检查
return true;
}
```
未连接设备时 `m_controller == nullptr`,此处直接 segfault。同文件 `sync_params` /
`start_game` / `stop_game` / `click` 等所有公开方法都有 null 保护(前者用 `if
(!m_controller) return`,后者用 `CHECK_EXIST(m_controller, false)` 宏),**只有
`back_to_home` 漏了**。
**触发路径**:任务运行中 ADB 断连使 `m_controller` 被 reset 成 null → 调度走到"回主页"分支 → 崩溃。
**修法**:函数开头加 `CHECK_EXIST(m_controller,
false);`,与同文件其他方法保持一致。最小改动,零行为变化(未连接时 `back_to_home` 返回 `false` 而不是崩溃)。
---
### 2. `MumuExtras::screencap` reload 之后没有真正重试 — MEDIUM-HIGH
**位置**:`src/MaaCore/Controller/MumuExtras.cpp:54-83`
**原问题**:
```cpp
int ret = capture_display_func_(...); // 第一次 capture
if (ret) {
// Try reloading once before giving up.
if (!reload()) { ... return std::nullopt; }
if (ret) { // ← 检查的是同一个 ret,不是重试之后的
LogError << "Failed to capture display, but reload before retrying capture was successful." ...;
return std::nullopt;
}
}
```
注释承诺"失败时 reload 后重试 capture",但第二个 `if (ret)` 检查的还是第一次调用写入的
`ret`,`capture_display_func_` **从未被重新调用过**。reload 成功或失败都必然返回
`std::nullopt`,注释里的"reload before retrying capture was
successful"日志其实**只是字面,retry 压根没发生**。
**后果**:MuMu 模拟器短暂 capture 失败(内存抖动 / 显示切换)时,原本 reload
应能恢复,但代码从不真正重试,上层看到的永远是「截图失败」。
**修法**:在两个 `if (ret)` 之间加上**真正的**重试:重新调用 `capture_display_func_` 并更新
`ret`。reload 可能改变设备状态,所以 `display_id` 也重新 `get_display_id()`
取一次。失败信息相应改为"Failed to capture display even after reload",语义准确。
---
### 3. `Controller` 中 `d_size` 被 `const static` 固化,分辨率变化后缩放错误 — MEDIUM
**位置**:`src/MaaCore/Controller/Controller.cpp:146` 和 `:438`(两处)
**原问题**:
```cpp
const static cv::Size d_size(m_scale_size.first, m_scale_size.second);
```
C++ 函数内 `static` 局部变量的初始化表达式**仅在第一次进入该函数时执行一次**。首次调用时 `m_scale_size`
的值被固化到 `d_size`,之后用户切换模拟器、改分辨率预设、连接新设备等导致 `m_scale_size` 变化时,**`d_size`
不会更新**。
**后果**:
- `cv::resize(..., d_size, ...)` 把图缩到错误的目标尺寸 → OCR / 模板匹配的坐标/尺寸都错
- 截图失败兜底路径 `m_cache_image = cv::Mat(d_size, CV_8UC3)` 也分配错误尺寸的空图
- 用户看到各种"识别不到 / 坐标偏移"但不崩溃,**只能通过重启 MaaCore 恢复**
**修法**:去掉 `static`,每次调用重新构造。`cv::Size` 就是两个
`int`,栈上构造基本无开销,不存在性能回归。两处都修。
---
### 4. `PlayToolsController::swipe` 起点钳制没有应用到 `toucher_down` —
MEDIUM-LOW
**位置**:`src/MaaCore/Controller/PlayToolsController.cpp:230`
**原问题**:
```cpp
int x1 = p1.x, y1 = p1.y; // 拷贝到局部变量
// ...
if (x1 < 0 || x1 >= width || y1 < 0 || y1 >= height) {
x1 = std::clamp(x1, 0, width - 1); // 只钳制了局部 x1/y1
y1 = std::clamp(y1, 0, height - 1);
}
// ...
toucher_down(p1); // ← 仍用原始的 const Point& p1
```
注释"起点不能在屏幕外,但是终点可以"已经说明了钳制意图,但钳制结果只存在局部 `x1/y1`,`toucher_down(p1)`
使用的还是未钳制的原始 `p1`。后续 `progressive_move` 用 `x1/y1` 所以中段 swipe 在屏幕内,**但首个
touch-down 事件发到屏幕外坐标**,iOS PlayTools 侧多半会被丢弃或忽略 → 整个手势无效。
**修法**:`toucher_down(p1)` → `toucher_down({ x1, y1 })`,用已钳制的坐标构造临时
`Point`。与同文件下方 `toucher_move({ x, y })` 的风格一致。
---
### 5. `Assistant::m_task_id` 非原子 static 导致跨实例数据竞争 — MEDIUM-LOW
**位置**:`src/MaaCore/Assistant.h:214`(声明)+
`src/MaaCore/Assistant.cpp:295-296`(使用)
**原问题**:
```cpp
// Assistant.h
inline static TaskId m_task_id = 0; // 进程级 static,非 atomic
mutable std::mutex m_mutex; // 实例级 mutex
inline static std::atomic<AsyncCallId> m_call_id = 0; // 旁证:同文件另一个 static 是 atomic
// Assistant.cpp:295
std::unique_lock<std::mutex> lock(m_mutex); // 锁的是当前实例的 mutex
int task_id = ++m_task_id; // 改的是进程共享的 static
```
`m_task_id` 是 static(**所有 Assistant 实例共享一个内存地址**),但 `m_mutex`
是每个实例各自的锁。两个不同实例并发 `++m_task_id` 时,各自 lock 各自的 mutex,彼此不互斥 ——
**数据竞争**,`++` 对非原子 `int` 是 RMW 三步,可能让两个实例拿到同一个 `task_id`。
单实例客户端(MaaWpfGui、MaaMacGui)触发不了;多实例 + 并发下任务的客户端(命令行批处理 / 自动化测试)会偶现任务 id
冲突,后续 `AsstSetTaskParams` / `AsstStop` 可能误中另一个实例的任务。
旁证:同文件 line 227 的另一个同类 static `m_call_id` 已经声明成
`std::atomic<AsyncCallId>`,一致性也对不上,显然是作者漏改。
**修法**:`TaskId m_task_id` → `std::atomic<TaskId>
m_task_id`。`++m_task_id` 靠 `std::atomic::operator++` 原子化,语法不变。`m_mutex`
原先是为紧跟的 `m_tasks_list.emplace_back` 服务的,继续保留。
---
### 6. `OcrPack::load` 复制粘贴导致 `rec_label` 变化检测用错字段 — LOW-MEDIUM
**位置**:`src/MaaCore/Config/Miscellaneous/OcrPack.cpp:61`
**原问题**:
```cpp
// 前两块都正确:当前保存路径 != 新路径 时才更新
if (exists(det_model_file) && m_det_model_path != det_model_file) { ... }
if (exists(rec_model_file) && m_rec_model_path != rec_model_file) { ... }
// 第三块:应该检查 m_rec_label_path,却写成了 m_rec_model_path
if (exists(rec_label_file) && m_rec_model_path != rec_label_file) {
m_rec_label_path = rec_label_file;
m_rec = nullptr;
}
```
模型路径以 `.onnx` 结尾,标签路径以 `.txt` 结尾,`m_rec_model_path != rec_label_file`
**恒为真**(只要标签文件存在)。结果每次调用 `load()` 都会:
- 无效地重新给 `m_rec_label_path` 赋同样的值
- 把 `m_rec` 置空 → 触发下一次 OCR 时 lazy 重载
不会导致正确性崩塌(`check_and_load` 路径会 lazy 重建
`m_rec`),但"路径没变则保留缓存"的设计目的完全失败,且多次 `load()` 会产生不必要的模型重载开销。
**修法**:`m_rec_model_path != rec_label_file` → `m_rec_label_path !=
rec_label_file`,与上面 `det` / `rec_model` 两块的模式对齐。
---
### 7. `RoguelikeStageEncounterConfig` `fallback_choices` 校验写错变量 — LOW
**位置**:`src/MaaCore/Config/Roguelike/RoguelikeStageEncounterConfig.cpp:77`
**原问题**:
```cpp
int option_num = pair_arr[0].as_integer();
if (option_num < 0) { ... return false; } // 校验 option_num,OK
int choice = pair_arr[1].as_integer();
if (option_num < 0) { // ← 重复检查 option_num
Log.error(
std::format(
"RoguelikeEncounterConfig | callback choice for event {} with {} option(s) is less than zero",
...));
return false;
}
```
错误信息本身已经说明是在校验 `choice`("callback choice ... is less than
zero"),但实际比较的是上面已经保证过非负的 `option_num`,这个 `if` 永远进不去。
**后果**:坏 JSON 中 `[3, -5]` 这类负数 `choice` 能被静默接受,后续
`static_cast<size_t>(-5)` 产生巨大无符号数,作为索引使用时 UB。正常配置不触发,但这条防御校验已名存实亡。
**修法**:第二个 `if` 判断变量改为 `choice`。错误消息中 `option_num` 作为上下文保留(用于打印 option
数量,语义正确)。
---
### 8. `AsstDestroy` 对值传入的形参赋 `nullptr` 是死代码 — TRIVIAL
**位置**:`src/MaaCore/AsstCaller.cpp:98`
**原问题**:
```cpp
void AsstDestroy(AsstHandle handle)
{
if (handle == nullptr) return;
delete handle;
handle = nullptr; // ← 改的是局部拷贝,对调用方的变量无影响
}
```
`AsstHandle` 按值传入,`handle = nullptr` 只修改本函数作用域的局部拷贝。`delete handle`
已正确销毁对象,这行既没有替调用方置空(要做到需接受 `AsstHandle*`),也不会影响 delete 行为,纯死代码。
**修法**:删掉这行,保持 `delete` 行为不变,避免给读者错误印象。
---
### 9. `AvatarCacheManager.h` 开头两行重复的 `#pragma once` — TRIVIAL
**位置**:`src/MaaCore/Config/Miscellaneous/AvatarCacheManager.h:1-3`
**原问题**:
```cpp
#pragma once
#pragma once // ← 重复
#include "Config/AbstractResource.h"
```
pragma once 幂等,没有功能影响,但看起来是编辑时的残留。清理即可。
---
## Test plan
- [x] `git diff dev-v2..HEAD --stat`:8 文件 / +18/-11,只有目标修改,无无关改动
- [x] 每条 fix 都有对应的单独 commit,便于逐条 review 或按需 revert
- [x] **本地 macOS arm64 build 通过**:`cmake --preset macos-arm64 && cmake
--build build --config Release` → 203/203 targets 编译链接成功,0 error,修改的 8
个文件 0 warning(全局唯一 1 条 warning 是 `MaaUtils/Logger.cpp` 的 `#pragma
message` 版本信息打印,与本 PR 无关)
- [ ] 单元测试:macOS preset 默认没有编译 test target(`unit_test/MaaCore/` 下仅 1 个
`AlgorithmTest.cpp`,与本 PR 改动的函数无交集),本地 `ctest` 返回 "No tests were
found"。依赖上游 CI 完整矩阵(Linux / Windows / macOS + 各自测试)验证。
## Notes for reviewers
- 每条 fix 的 commit message 都附了原理说明,结合 PR 正文对应条目阅读
- **没有功能性增加**,也**不动任何 public API / ABI**
- 不涉及 resource / JSON 数据文件
- 合并之后建议开一个跟进 issue 讨论 ultrareview 的另外两条 finding(AvatarCacheManager
并发保护、CopilotConfig 默认值语义)—— 这两条需要设计决策,不适合塞进本 PR
---
_PR 发现来源:Claude Code `/ultrareview` 多 agent 扫描后人工复核。_
## Summary by Sourcery
在不改变公共 API 或行为的前提下,修复了控制器、OCR、肉鸽配置以及工具组件中的多处正确性问题。
Bug 修复:
- 在 `Controller::back_to_home` 中增加空指针检查,以防在底层控制器不可用时发生崩溃。
- 确保 `MumuExtras::screencap` 在成功重新加载后确实会重试屏幕捕获,并且能准确记录失败日志。
- 在 Controller 中为缩放后的图像尺寸使用非静态的 `cv::Size`,以便在无需重启的情况下正确响应分辨率变化。
- 对传递给 `PlayTools toucher_down` 的滑动起始点进行限制,确保初始触控事件始终在屏幕范围内。
- 将 Assistant 的进程级任务 ID 计数器改为原子类型,避免在多实例场景下产生数据竞争。
- 修正 `OcrPack` 标签路径变更检测逻辑,使 OCR 模型只在标签文件实际变更时才会重新加载。
- 修复 `RoguelikeStageEncounterConfig` 的回退选项校验逻辑,按预期拒绝负数选项。
- 移除 `AsstDestroy` 中无效的句柄重置代码,该代码试图将值传递参数置空但实际上无效。
- 移除 `AvatarCacheManager.h` 中多余的 `#pragma once`,以清理头文件。
<details>
<summary>Original summary in English</summary>
## Summary by Sourcery
Fix multiple correctness issues across controller, OCR, roguelike
config, and utility components without changing public APIs or
behaviors.
Bug Fixes:
- Add a null check in Controller::back_to_home to prevent crashes when
the underlying controller is unavailable.
- Ensure MumuExtras::screencap actually retries display capture after a
successful reload and logs failures accurately.
- Use a non-static cv::Size for scaled image dimensions in Controller so
resolution changes are respected without restart.
- Clamp the swipe starting point passed to PlayTools toucher_down to
keep initial touch events within screen bounds.
- Make Assistant's process-wide task ID counter atomic to avoid data
races across instances.
- Correct OcrPack label path change detection so OCR models are only
reloaded when the label file actually changes.
- Fix RoguelikeStageEncounterConfig fallback choice validation to reject
negative choices as intended.
- Remove ineffective handle reset code in AsstDestroy that tried to null
a by-value parameter.
- Remove a redundant #pragma once in AvatarCacheManager.h to clean up
the header.
</details>
MAA
简体中文 | 繁體中文 | English | 日本語 | 한국어
MAA 的意思是 MAA Assistant Arknights
一款明日方舟游戏小助手
基于图像识别技术,一键完成全部日常任务!
绝赞更新中 ✿✿ヽ(°▽°)ノ✿
下载与安装
请阅读 文档 后前往 官网 或 Releases 下载,并参考 新手上路 进行安装。
亮点功能
- 理智作战,掉落识别及上传 企鹅物流,一图流
- 智能基建换班,自动计算干员效率,单设施内最优解;同时也支持 自定义排班
- 自动公招,可选使用加急许可,一次全部刷完!公招数据自动上传 企鹅物流,一图流
- 支持手动识别公招界面,方便对高星公招做出选择
(你的这个高资回费出的是推王呢还是推王呢) - 支持识别干员列表,统计已有和未有干员及潜能,并在公招识别显示
- 支持识别养成材料,并导出至 企鹅物流刷图规划、明日方舟工具箱、ARK-NIGHTS 干员培养表
- 访问好友、收取信用及购物、领取日常奖励等,一键全日常自动长草
- 肉鸽全自动刷源石锭和等级,自动烧水和凹直升,智能识别干员及练度
- 选择作业 JSON 文件,自动抄作业, 视频演示
- 支持 C, Python, Java, Rust, Golang, Java HTTP, Rust HTTP 等多种接口,方便集成调用,自定义你的 MAA!
使用说明
功能介绍
请参阅 用户手册。
外服支持
目前国际服(美服)、日服、韩服、繁中服的绝大部分功能均已支持。但由于外服用户较少及项目人手不足,很多功能并没有进行全面的测试,所以请自行体验。
若您遇到了 Bug,或对某个功能有强需求,欢迎在 Issues 和 讨论区 催更;或加入我们一起建设 MAA!请参阅 外服适配教程
CLI 支持
MAA 支持命令行界面(CLI)操作,支持 Linux,macOS 和 Windows,可用于自动化脚本或在无图形界面的服务器上使用。请参阅 CLI 使用指南
加入我们
主要关联项目
- 全新框架:MaaFramework
- 作业站 前端:zoot-plus-frontend
- 作业站 后端:ZootPlusBackend
- 官网:前端
- 深度学习:MaaAI
多语言 (i18n)
MAA 以中文(简体)为第一语言,翻译词条均以中文(简体)为准。
参与开发
请参阅 开发指南。
API
- C 接口:集成示例
- Python 接口:集成示例
- Golang 接口:集成示例
- Dart 接口
- Java 接口:集成示例
- Java HTTP 接口
- Rust 接口:HTTP 接口
- TypeScript 接口
- Woolang 接口:集成示例
- 集成文档
- 回调消息协议
- 任务流程协议
- 自动抄作业协议
外服适配
请参阅 外服适配教程,对于国服已支持的功能,绝大部分的外服适配工作仅需要截图 + 简单的 JSON 修改即可。
Issue bot
请参阅 Issue Bot 使用方法
致谢
开源库
- 图像识别库:opencv
文字识别库:chineseocr_lite- 文字识别库:PaddleOCR
- 深度学习部署库:FastDeploy
- 机器学习加速器:onnxruntime
关卡掉落识别:企鹅物流识别- 地图格子识别:Arknights-Tile-Pos
- C++ JSON 库:meojson
- C++ 运算符解析器:calculator
C++ base64 编解码:cpp-base64- C++ 解压压缩库:zlib
- C++ Gzip 封装:gzip-hpp
- 安卓触控事件器:Minitouch
- 安卓触控事件器:MaaTouch
- WPF MVVM 框架:Stylet
- WPF 控件库:HandyControl -> HandyControls
- C# 日志:Serilog
- C# JSON 库:Newtonsoft.Json & System.Text.Json
下载器:aria2
数据源
公开招募数据:明日方舟工具箱干员及基建数据:PRTS Wiki- 关卡数据:企鹅物流数据统计
- 游戏数据及资源:明日方舟客户端素材
- 游戏数据:《明日方舟》Yostar游戏数据
贡献/参与者
感谢所有参与到开发/测试中的朋友们,是大家的帮助让 MAA 越来越好! (*´▽`)ノノ
声明
- 本软件使用 GNU Affero General Public License v3.0 only 开源,并附带额外 用户协议。
- 本软件 logo 并非使用 AGPL 3.0 协议开源,耗毛、vie 两位画师及软件全体开发者保留所有权利。不得以 AGPL 3.0 协议已授权为由在未经授权的情况下使用本软件 logo,不得在未经授权的情况下将本软件 logo 用于任何商业用途。
- 本软件开源、免费,仅供学习交流使用。若您遇到商家使用本软件进行代练并收费,可能是设备与时间等费用,产生的问题及后果与本软件无关。
DirectML 支持说明
本软件支持 GPU 加速功能,其在 Windows 平台上依赖于 Microsoft 提供的独立组件 DirectML。DirectML 并非本项目的开源部分,也不受 AGPL 3.0 的约束。为方便用户,我们随安装包附带了一个未经修改的 DirectML.dll 文件。如果您无需 GPU 加速功能,可安全删除该 DLL 文件,软件的核心功能仍可正常运行。
广告
用户交流 QQ 群:MAA 使用 & 粥游交流 QQ 群
Discord 服务器: 邀请链接
用户交流 TG 群:Telegram 群
自动战斗 JSON 作业分享:prts.plus
Bilibili 直播间:MrEO 直播间 直播敲代码 & MAA-Official 直播间 游戏/杂谈
技术群(舟无关、禁水):内卷地狱!(QQ 群)
开发者群:QQ 群
如果觉得软件对你有帮助,帮忙点个 Star 吧!~(网页最上方右上角的小星星),这就是对我们最大的支持了!



