diff --git a/app/task/MAA/tools/avd.py b/app/task/MAA/tools/avd.py index f4244ec0d1..5a8d67603f 100644 --- a/app/task/MAA/tools/avd.py +++ b/app/task/MAA/tools/avd.py @@ -50,6 +50,7 @@ from __future__ import annotations import hashlib +import json import shutil from pathlib import Path from typing import Any @@ -82,13 +83,36 @@ def _sha256(path: Path) -> str: return hashlib.sha256(path.read_bytes()).hexdigest() +#: ``modavd-manifest.json`` 里外壳的路径。ModAVD 0.40.1 起整包自带外壳,就在这里。 +PACKAGE_SHIM_PATH = f"{MUMU_SHIM_DIR}/shell/sdk/external_renderer_ipc.dll" + + +def _package_ships_shim(root: str | Path) -> bool: + """根目录的 ``modavd-manifest.json`` 列了外壳:这份文件归 ModAVD 的包管。""" + try: + manifest = json.loads( + (Path(root) / "modavd-manifest.json").read_text(encoding="utf-8-sig") + ) + except (OSError, ValueError): + return False + files = manifest.get("files") if isinstance(manifest, dict) else None + return isinstance(files, dict) and PACKAGE_SHIM_PATH in files + + def ensure_mumu_shim(root: str | Path) -> Path: - """确保 ``<根>\\mumu-shim\\shell\\sdk\\external_renderer_ipc.dll`` 是随本体发的那一份。 + """确保 ``<根>\\mumu-shim\\shell\\sdk\\external_renderer_ipc.dll`` 在,MAA 能当 MuMu 载入。 + + ModAVD 0.40.1 起整包自带这份外壳(列在 ``modavd-manifest.json`` 里),这时用包里的, + 不覆盖:换成本体的那份会和包的清单对不上,ModAVD 用 Mirror 酱增量更新时就核对失败。 + 包里的被删了才补本体这份,下次 ModAVD 更新会换回包里的。 - 随本体更新 DLL 也会变,所以不只看在不在,内容不同就覆盖。正在被 MAA 加载时覆盖会失败, - 那就留着旧的并记警告——旧的也能用,下次运行前再换。 + 更早的包不带外壳,照旧放本体发的那一份:随本体更新 DLL 也会变,所以不只看在不在, + 内容不同就覆盖。正在被 MAA 加载时覆盖会失败,那就留着旧的并记警告——旧的也能用, + 下次运行前再换。 """ target = shim_dll(root) + if target.is_file() and _package_ships_shim(root): + return target if not SHIM_SOURCE.is_file(): raise FileNotFoundError(f"缺少截图组件 {SHIM_SOURCE}") if target.is_file() and _sha256(target) == _sha256(SHIM_SOURCE): diff --git a/changelog.d/fix-avd-shim-package-owned-20261113.fix.md b/changelog.d/fix-avd-shim-package-owned-20261113.fix.md new file mode 100644 index 0000000000..75b80bf26a --- /dev/null +++ b/changelog.d/fix-avd-shim-package-owned-20261113.fix.md @@ -0,0 +1,2 @@ +project: maa +ModAVD 自带 MAA 截图外壳时不再覆盖,免得它的增量更新核对失败 diff --git a/tests/task/test_maa_avd_shim.py b/tests/task/test_maa_avd_shim.py new file mode 100644 index 0000000000..d17123da3b --- /dev/null +++ b/tests/task/test_maa_avd_shim.py @@ -0,0 +1,76 @@ +"""MAA 截图外壳的放置:ModAVD 包自带外壳时不覆盖。""" + +from __future__ import annotations + +import json +from pathlib import Path + +import pytest + +from app.task.MAA.tools import avd + + +@pytest.fixture +def own_shim(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> bytes: + src = tmp_path / "res" / "external_renderer_ipc.dll" + src.parent.mkdir(parents=True) + src.write_bytes(b"auto-mas shim") + monkeypatch.setattr(avd, "SHIM_SOURCE", src) + return b"auto-mas shim" + + +def _root(tmp_path: Path, *, shim: bytes | None, listed: bool | None) -> Path: + root = tmp_path / "root" + root.mkdir() + if shim is not None: + avd.shim_dll(root).parent.mkdir(parents=True) + avd.shim_dll(root).write_bytes(shim) + if listed is not None: + files = {"tools/modavd.exe": {"size": 1, "sha256": "0" * 64}} + if listed: + files[avd.PACKAGE_SHIM_PATH] = {"size": 12, "sha256": "1" * 64} + (root / "modavd-manifest.json").write_text( + json.dumps({"schema": 1, "version": "v0.40.1", "files": files}), + encoding="utf-8", + ) + return root + + +def test_package_shim_is_left_alone(tmp_path: Path, own_shim: bytes) -> None: + root = _root(tmp_path, shim=b"package shim", listed=True) + assert avd.ensure_mumu_shim(root) == avd.shim_dll(root) + assert avd.shim_dll(root).read_bytes() == b"package shim" + + +def test_package_shim_missing_gets_ours(tmp_path: Path, own_shim: bytes) -> None: + root = _root(tmp_path, shim=None, listed=True) + avd.ensure_mumu_shim(root) + assert avd.shim_dll(root).read_bytes() == own_shim + + +@pytest.mark.parametrize("listed", [False, None]) +def test_older_package_gets_ours( + tmp_path: Path, own_shim: bytes, listed: bool | None +) -> None: + """0.40.1 以前的包不带外壳(清单里没有,或者没有清单):照旧换成本体的那份。""" + root = _root(tmp_path, shim=b"old copy", listed=listed) + avd.ensure_mumu_shim(root) + assert avd.shim_dll(root).read_bytes() == own_shim + + +@pytest.mark.parametrize("text", ["{", "[]", '{"files": []}', "{}"]) +def test_unreadable_manifest_counts_as_older( + tmp_path: Path, own_shim: bytes, text: str +) -> None: + root = _root(tmp_path, shim=b"old copy", listed=None) + (root / "modavd-manifest.json").write_text(text, encoding="utf-8") + avd.ensure_mumu_shim(root) + assert avd.shim_dll(root).read_bytes() == own_shim + + +def test_bom_manifest_is_read(tmp_path: Path, own_shim: bytes) -> None: + root = _root(tmp_path, shim=b"package shim", listed=True) + path = root / "modavd-manifest.json" + path.write_bytes(b"\xef\xbb\xbf" + path.read_bytes()) + avd.ensure_mumu_shim(root) + assert avd.shim_dll(root).read_bytes() == b"package shim"