Skip to content

fix(mods): check Mod updates by archive hash like Olympus, verify downloaded archives - #87

Merged
std-microblock merged 1 commit into
std-microblock:masterfrom
std-external:fix/mod-update-hash-detection
Oct 13, 2026
Merged

std-microblock merged 1 commit into
std-microblock:masterfrom
std-external:fix/mod-update-hash-detection

Conversation

@std-external

Copy link
Copy Markdown
Contributor

问题

群里的报告:CeleMod 认为 aonHelper 已经是最新(实际不是),用 Olympus 更新全部后游戏才正常打开;另外 LoennProjectManager 等 Lönn 系插件「更新不了」。两个症状是两回事,都在这里修。

根因 1:更新判定只看 everest.yaml 的版本号

updateNode 之前用的判据是 compareVersion(catalog.version, installed.version) > 0(Manage.tsx 原 1307 行),而版本号两边都可能出错:

数据 值
目录 celeste.weg.fan/api/v2/mod/list 里 aonHelper 的 version 0.19.7
同一个条目的 submissionFile.gameBananaId / xxHash 1827775 / 749a898300d45f58
GameBanana 文件 1827775 的 _sVersion、包内 everest.yaml 0.19.8 / Version: 0.19.7(上游笔误)

也就是说最新文件的 xxHash/文件 ID 已经换了,但 version 还停在 0.19.7,装上这个包的人读到的版本号也是 0.19.7 → CeleMod 判定「已是最新」,而 Olympus 会更新(它能更新 = 说明玩家的包确实不是最新文件,同时 CeleMod 没提示 = 玩家 yaml 里的号 ≥ 0.19.7,两边一夹就是「旧包 + yaml 0.19.7」,正好是这个判据的盲区)。

Olympus/Everest 这一环不看版本号,看归档的 xxHash64(Everest/Celeste.Mod.mm/Mod/Helpers/ModUpdaterHelper.cs:111 ListAvailableUpdates):

string xxHashStringInstalled = BitConverter.ToString(metadata.Hash)...
if (!updateCatalog[metadata.Name].xxHash.Contains(xxHashStringInstalled)) { /* 有更新 */ }

根因 2:镜像把旧包挂在新的文件 ID 上

Lönn Project Manager 最新文件是 GameBanana 1663608(v 0.4.7,103472 字节,包内 Version: 0.4.7)。中国镜像(weg.fan,中文界面的默认源)两个下载接口给出的却是旧包:

来源 大小 包内 everest.yaml xxHash64 目录里的 xxHash
celeste.weg.fan/api/v2/download/gamebanana-files/1663608(= .../files/518525856676099948) 100301 0.4.5 a95e42949b5295aa ✗
gamebanana.com/mmdl/1663608 103472 0.4.7 970da23a3412a94d ✓
celestemodupdater.0x0a.de/banana-mirror/1663608.zip 103472 0.4.7 970da23a3412a94d ✓

weg.fan 那份和 GameBanana 上更早的 1555965(v 0.4.5)字节一致。所以 CeleMod 每次「更新」都装回同一个 v0.4.5,装完版本号还是 0.4.5 < 0.4.7,更新角标永远消不掉(「更新不了」),而 Olympus 用 mmdl/0x0a.de 拿到的是真包。

改动

判定改用归档哈希(与 Olympus 一致)

  • 新增 src-tauri/src/archive_hash.rs:xxHash64(和 Everest.GetChecksum 同算法,测试里带空串/abc/xxhash 的规范向量),结果按 (路径, 大小, 修改时间) 缓存,hash_file:40 / cached_hash:59 / ensure_hash:67。
  • everest.rs:28 目录条目带出 xx_hash;backend.rs:5278 get_mod_latest_info 一并返回;backend.rs:5342 新增 get_mod_archive_hashes 返回已装归档的哈希(只读一次,之后只 stat)。
  • 新增 src/celemod-ui/src/modUpdates.ts:41 collectModUpdates:版本号变大 或 已装归档不在最新文件的哈希列表里 → 提示更新;解包安装(文件夹)和目录没给哈希时退回原来的版本号比较。管理页 Manage.tsx:1326 用它算更新列表。
  • 版本号没变的更新在角标上显示「更新到 最新版」,避免出现「0.19.7 → 0.19.7」。

更新包按目录哈希校验,不匹配就换源

Manage.tsx:1662 起把更新流程改成「下载 → 校验 → 不行就换下一个源」:本机配置的镜像优先,然后 0x0ade 镜像、GameBanana 直链(0x0ade 那份与 GameBanana 字节一致)。装完的归档哈希不在目录给出的列表里就算这次更新失败,不再默默留着旧包(Everest 的 AutoModUpdater 也是逐个镜像 + VerifyChecksum 这么做的)。目录没提供哈希(例如自定义 URL 条目)时保持原行为。

验证

  • cd src-tauri && cargo test --lib:110 passed(新增 3 个:xxHash 规范向量、缓存复用/失效、Mods 目录哈希扫描)。
  • cd src/celemod-ui && pnpm test:mod-updates、pnpm typecheck、pnpm build 通过(pnpm lint 里那 5 条 TS6133 是仓库里既有的)。
  • 用线上目录跑新的判定(collectModUpdates 入参就是上面表格里的真实数据):
catalog rows: [["LoennProjectManager","0.4.7","1663608",...,["970da23a3412a94d"]],
               ["aonHelper","0.19.7","1827775",...,["749a898300d45f58"]]]
updates: [["aonHelper","0.19.7","0.19.7"],          // 版本号相同,旧代码看不到
          ["LoennProjectManager","0.4.5","0.4.7"]]
weg.fan LoennProjectManager matches catalog: false
0x0ade LoennProjectManager matches catalog: true
GameBanana aonHelper (0.19.8 file) matches catalog: true

上游/镜像侧

  • aonHelper 包内 everest.yaml 的 Version: 0.19.7 是作者的笔误(GameBanana 文件本身叫 v0.19.8),目录和 Everest 的 everest_update.yaml 都跟着写 0.19.7;本次改动让 CeleMod 不再被它挡住。
  • LoennProjectManager 是镜像数据问题(weg.fan 把 1663608 存成了旧的 v0.4.5 包),CeleMod 现在会识别并改用其他源;镜像那边该修,但不在本仓库。

CeleMod decided whether an installed Mod was up to date by comparing the
version in its `everest.yaml` with the version the catalog lists.  Upstream
archives do not always keep that number in sync: the aonHelper 0.19.8 file
still declares `Version: 0.19.7` (a typo, GameBanana calls the file 0.19.8),
so players whose installed archive declares 0.19.7 never see the update,
while Olympus offers it.  Olympus and Everest ignore the version for this
check: they compare the xxHash64 of the installed archive with the hashes of
the newest file in the update database.

Do the same:

* `archive_hash` hashes installed archives with xxHash64 (matching
  `Everest.GetChecksum`) and caches the result per file, so the Mod list keeps
  loading without reading every archive.
* `get_mod_latest_info` now returns the catalog `xxHash` entries and
  `get_mod_archive_hashes` returns the installed archive hashes.
* `collectModUpdates` reports an update when the version moved, or when the
  installed archive is not one of the newest file's hashes.  Unpacked Mods and
  entries without a hash keep the version comparison.

The update itself is also verified against the catalog hash now, and the next
source (0x0ade mirror, then GameBanana) is tried when the archive does not
match.  The Chinese mirror serves an old LoennProjectManager archive under the
newest file id, so every update installed the same v0.4.5 build again and the
update badge never went away.
@std-microblock
std-microblock merged commit 435e5cc into std-microblock:master Oct 13, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants