OK-WW专项 由 MAS 自行接管鸣潮游戏更新 - #408
Conversation
审查者指南将《鸣潮》官方启动器版本元数据集成到 OK-WW 自动代理流程中,新增更新/预下载检测、通过 OCR 由启动器驱动的更新编排,以及本地版本轮询,同时继续将实际下载和验证完全交由官方启动器处理。 《鸣潮》官方启动器更新流程时序图sequenceDiagram
participant OKWW as OK-WW AutoProxy
participant API as Official Update API
participant Launcher as Official Launcher
participant FS as Local Version Metadata
OKWW->>API: check_wuthering_waves_update(launcher_path, resource)
API-->>OKWW: default / predownload version metadata
OKWW->>Launcher: _start_wuthering_waves_launcher()
alt Formal update available
OKWW->>Launcher: _click_wuthering_waves_launcher_text(Update)
Launcher-->>FS: Download, install, and verify update
loop Until target version is reached
OKWW->>FS: _read_local_wuthering_waves_version(launcher_path)
FS-->>OKWW: current version
end
OKWW->>OKWW: wait_wuthering_waves_update(launcher_path, target_version)
else Predownload available
OKWW->>Launcher: _click_wuthering_waves_launcher_text(预下载)
OKWW->>Launcher: _click_wuthering_waves_launcher_text(确定下载)
end
OKWW->>OKWW: Start Wuthering Waves client
文件级变更
提示和命令与 Sourcery 交互
自定义使用体验访问你的控制面板:
获取帮助Original review guide in EnglishReviewer's GuideIntegrates Wuthering Waves official launcher version metadata into the OK-WW auto-proxy flow, adding update/predownload detection, launcher-driven update orchestration via OCR, and local version polling, while keeping actual download and verification fully delegated to the official launcher. Sequence diagram for the Wuthering Waves official launcher update flowsequenceDiagram
participant OKWW as OK-WW AutoProxy
participant API as Official Update API
participant Launcher as Official Launcher
participant FS as Local Version Metadata
OKWW->>API: check_wuthering_waves_update(launcher_path, resource)
API-->>OKWW: default / predownload version metadata
OKWW->>Launcher: _start_wuthering_waves_launcher()
alt Formal update available
OKWW->>Launcher: _click_wuthering_waves_launcher_text(Update)
Launcher-->>FS: Download, install, and verify update
loop Until target version is reached
OKWW->>FS: _read_local_wuthering_waves_version(launcher_path)
FS-->>OKWW: current version
end
OKWW->>OKWW: wait_wuthering_waves_update(launcher_path, target_version)
else Predownload available
OKWW->>Launcher: _click_wuthering_waves_launcher_text(预下载)
OKWW->>Launcher: _click_wuthering_waves_launcher_text(确定下载)
end
OKWW->>OKWW: Start Wuthering Waves client
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
嘿——我发现了 2 个问题
面向 AI Agent 的提示
请处理本次代码审查中的评论:
## 单独评论
### 评论 1
<location path="app/services/wuthering_waves.py" line_range="151-152" />
<code_context>
+
+
+def _is_newer_version(candidate: str | None, current: str | None) -> bool:
+ if not candidate or not current:
+ return False
+ return _version_key(candidate) > _version_key(current)
+
+
</code_context>
<issue_to_address>
**问题 (bug_risk):** 当 `launcherDownloadConfig.json` 缺失或不包含可用的本地版本时,`_is_newer_version` 始终返回 `False`,因此正式更新和预下载都会被报告为不可用,并且永远不会启动官方启动器。
**触发条件:** 本地启动器版本记录不存在、为空,或在之前的安装/更新过程中被删除时。
**建议修复:** 将未知的本地版本视为需要启动启动器,或者明确使检查失败,而不是报告没有更新。
```suggestion
if not current:
return True
```
</issue_to_address>
### 评论 2
<location path="app/services/wuthering_waves.py" line_range="234" />
<code_context>
+ if not isinstance(payload, dict):
+ raise ValueError("鸣潮官方更新接口返回格式错误")
+
+ default_info = payload.get("default")
+ predownload_info = payload.get("predownload")
+ if not isinstance(default_info, dict):
+ raise ValueError("鸣潮官方更新接口缺少 default 版本信息")
+
+ release_version = str(default_info.get("version") or "").strip() or None
+ predownload_version = (
+ str(predownload_info.get("version") or "").strip()
+ if isinstance(predownload_info, dict)
</code_context>
<issue_to_address>
**问题 (bug_risk):** 当 API 响应包含 `default` 对象但没有有效的 `version` 时,该响应仍会被接受,导致 `release_version=None`,并且更新检测会被静默禁用,而不是拒绝格式错误的响应。
**触发条件:** 官方更新 API 返回不完整或暂时格式错误的 `default` 条目时。
**建议修复:** 验证 `default_info["version"]` 是非空字符串;当其缺失或无效时,抛出 `ValueError`。
```suggestion
release_version = default_info.get("version")
if not isinstance(release_version, str) or not release_version.strip():
raise ValueError("鸣潮官方更新接口缺少有效的 default version")
release_version = release_version.strip()
```
</issue_to_address>Sourcery 评估
需要人工审查。 需要先处理 2 个发现,并且该变更会根据远程版本接口和 OCR 操作官方启动器,自动下载、解压并覆盖本地游戏安装文件;如果版本判断或按钮识别错误,影响会在代码回滚后仍留在本地安装中。影响范围通常局限于单个游戏安装,可通过官方启动器重新更新或重装修复,但不能仅靠回滚完全撤销。
阻塞性发现:app/services/wuthering_waves.py:152、app/services/wuthering_waves.py:234
帮助我变得更有用!请在每条评论上点击 👍 或 👎,我会利用这些反馈来改进审查结果。
Original comment in English
Hey - I've found 2 issues
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="app/services/wuthering_waves.py" line_range="151-152" />
<code_context>
+
+
+def _is_newer_version(candidate: str | None, current: str | None) -> bool:
+ if not candidate or not current:
+ return False
+ return _version_key(candidate) > _version_key(current)
+
+
</code_context>
<issue_to_address>
**issue (bug_risk):** When `launcherDownloadConfig.json` is missing or contains no usable local version, `_is_newer_version` always returns `False`, so both formal updates and predownloads are reported as unavailable and the official launcher is never started.
**Triggers:** When the local launcher version record is absent, empty, or has been removed during a prior installation/update.
**Suggested fix:** Treat an unknown local version as requiring the launcher to start, or explicitly fail the check instead of reporting no update.
```suggestion
if not current:
return True
```
</issue_to_address>
### Comment 2
<location path="app/services/wuthering_waves.py" line_range="234" />
<code_context>
+ if not isinstance(payload, dict):
+ raise ValueError("鸣潮官方更新接口返回格式错误")
+
+ default_info = payload.get("default")
+ predownload_info = payload.get("predownload")
+ if not isinstance(default_info, dict):
+ raise ValueError("鸣潮官方更新接口缺少 default 版本信息")
+
+ release_version = str(default_info.get("version") or "").strip() or None
+ predownload_version = (
+ str(predownload_info.get("version") or "").strip()
+ if isinstance(predownload_info, dict)
</code_context>
<issue_to_address>
**issue (bug_risk):** An API response with a `default` object but without a valid `version` is accepted, producing `release_version=None` and silently disabling update detection instead of rejecting the malformed response.
**Triggers:** When the official update API returns an incomplete or temporarily malformed `default` entry.
**Suggested fix:** Validate that `default_info["version"]` is a non-empty string and raise `ValueError` when it is missing or invalid.
```suggestion
release_version = default_info.get("version")
if not isinstance(release_version, str) or not release_version.strip():
raise ValueError("鸣潮官方更新接口缺少有效的 default version")
release_version = release_version.strip()
```
</issue_to_address>Sourcery assessment
Needs a human reviewer. 2 findings to address first, and 该变更会根据远程版本接口和 OCR 操作官方启动器,自动下载、解压并覆盖本地游戏安装文件;如果版本判断或按钮识别错误,影响会在代码回滚后仍留在本地安装中。影响范围通常局限于单个游戏安装,可通过官方启动器重新更新或重装修复,但不能仅靠回滚完全撤销。.
Blocking findings: app/services/wuthering_waves.py:152, app/services/wuthering_waves.py:234
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
弃用 OCR 与官方启动器,改由 MAS 拉取官方资源清单完成下载、校验与覆写。 启动器仅用于解析安装目录,不再被拉起。 - 优先使用官方增量补丁包(3.5.3->3.6.0 实测 23GB,整文件同步需 88GB), 按需获取并校验 hpatchz 后应用;单组应用失败自动回退为整文件重下 - 多 CDN 断点续传,逐文件 md5 校验;仅写暂存区,校验通过才移入游戏目录, 版本记录最后写入,中断后可重新规划续做 - 整文件同步超 10GB 时中止并提示手动处理,避免无人值守时静默耗尽流量 - 清单 dest 来自网络,落盘前校验路径穿越;修复 "." 会解析为游戏根目录、 进而被 os.replace 冲掉整个目录的问题 - 修复本地版本记录缺失时被误判为已是最新,从而静默启动旧版客户端的问题
_is_newer_version 的空值分支在当前调用链下已不可达(本地版本缺失时
read_wuthering_waves_local_state 会抛错),但静默返回 False 会把
"查不到版本"误当成"已是最新",是危险方向。去掉后 _version_key("")
为空元组,本地版本未知时任何有效版本都判为需更新,方向安全。
变更摘要
AutoProxyTask._mas_launch_game_before_task,仅在 okww 脚本启用「游戏配置」(Game.Enabled)并由 MAS 拉起鸣潮时触发;其他脚本与专项不受影响。与 dev 基线的差异
dev 上 MAS 完全不参与鸣潮游戏更新:
_mas_launch_game_before_task直接拉起客户端进程,游戏若需更新只能靠 okww 侧日志关键词(游戏更新成功, 游戏即将重启)事后感知并重启任务,MAS 既不知道要更新、也无法判断更新是否完成。本 PR 中间提交曾尝试「OCR 点击官方启动器更新按钮 + 轮询版本号判完成」(85233b8c),因识别失败即无法更新、且完成状态只能推断,已在 328cff6 弃用,改为现方案。该路线未进入 dev。
更新流程
关键设计
os.replace为原子改名。版本记录最后写入,中断后下一轮会重新规划续做,不会误判已完成。environment/hpatchz/,仓库保持零二进制跟踪。上游 sisong/HDiffPatch 为 MIT,与本项目 AGPL 兼容。自查后收紧的两处
以下两处均为本 PR 内部自查发现并在合入前修正,dev 上不存在对应缺陷(相关代码在 dev 上尚未存在),不作为对已发布行为的修复计入:
_is_newer_version在本地版本记录缺失时返回False,会把「读不到版本」当成「已是最新」而静默启动旧客户端。现改为读不到即抛错,且空版本号在比较中判为需更新。dest完全来自网络,除拒绝..与绝对路径外还需拒绝目录自身:Path(".")的parts为空元组,放过则解析结果即游戏根目录,后续os.replace会冲掉整个目录。Range回 200,必须覆盖写而非追加,否则把新内容接在旧字节后静默产出坏文件。协议要点(均经实测)
.krpdiff是 HDiffPatch 目录差分(HDIFF19&zstd&fadler64),标准 MIT 版 hpatchz v5.1.3 可完整解析,非私有格式。fromFolder为逐项下载基址,会覆盖计划级baseUrl。测试
新增 56 个用例(
tests/services/test_wuthering_waves_updater.py38 个、test_wuthering_waves_update.py18 个),覆盖:Range回 200 必须截断而非追加tests/services/+tests/task/共 86 passed;全量 238 passed / 7 failed。7 个失败为 telemetry 与 game_sign 既有问题,涉及文件(app/tools/game_sign_notify.py、tests/tools/test_game_sign_notification.py、tests/test_main.py)本分支均未改动,其中 game_sign 为 dev 上测试与实现的签名不一致。ruff check对本分支改动的 5 个文件全部通过(仓库整体存量告警未处理)。尚未验证
hpatchz -info对真实包解析全部正确(DirHDiff/fadler64/zstd,oldRefSize与清单srcFiles精确吻合),合成目录差分往返亦已验证,但端到端 apply 需要旧版源文件,而 CDN 仅托管当前版本,只能等真实更新窗口。这也是保留「单组失败回退整文件」的原因。C:\Program Files时写盘需管理员权限,目前会抛PermissionError但未做提权引导。Sourcery 摘要
让 MAS 接管鸣潮游戏更新流程,替代官方启动器界面驱动并提升更新可靠性与安全性。
新功能:
错误修复:
改进:
测试:
Original summary in English
Summary by Sourcery
让 MAS 接管鸣潮游戏更新流程,替代官方启动器界面驱动并提升更新可靠性与安全性。
New Features:
Bug Fixes:
Enhancements:
Tests: