From ee3dc6ea6d04ae7a7e0aa505541585bc4c0c555f Mon Sep 17 00:00:00 2001 From: Jason's Miku <452467769@qq.com> Date: Sun, 27 Sep 2026 01:18:04 +0800 Subject: [PATCH 1/2] feat: support building for Android/Termux Add a workflow that cross-compiles maa-cli for Android and packages it together with the official MaaCore component, so maa-cli can run inside Termux on the device itself. What's included: - .github/workflows/build-android.yml: builds aarch64/x86_64-linux-android with the Android NDK, downloads the official MAAComponent-android-*.tar.gz for MaaCore, converts the OCR models to ncnn (Android uses OcrPackNcnn; the shared resource only ships onnx), and assembles a runnable package. - android/maa-android-connect.sh: helper that connects MaaCore to the local adb (mDNS / root-set tcp port / common ports), since Termux cannot use the Android native control path. - Fix TLS on Android: rustls-platform-verifier needs an Android Context via JNI to initialize, which a plain Termux process has no access to; every HTTPS request panicked with "Expect rustls-platform-verifier to be initialized". Android now uses the bundled Mozilla roots, other platforms keep the platform verifier (so self-signed certs in corporate networks still work). Verified on a rooted Android 12 x86_64 emulator: after deployment, `maa fight 1-7 --times 10` ran to completion and reported drops. The packages set RPATH ($ORIGIN for libs, $ORIGIN/../lib for bin/maa) so no LD_LIBRARY_PATH is needed -- setting it leaked into the adb child process and made it load the NDK libc++, which broke the connection. --- .github/workflows/build-android.yml | 566 ++++++++++++++++++++++++++++ android/maa-android-connect.sh | 197 ++++++++++ crates/maa-cli/Cargo.toml | 8 + crates/maa-cli/src/state.rs | 15 +- 4 files changed, 781 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/build-android.yml create mode 100644 android/maa-android-connect.sh diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml new file mode 100644 index 00000000..380f7f39 --- /dev/null +++ b/.github/workflows/build-android.yml @@ -0,0 +1,566 @@ +name: Build Android + +# 交叉编译 maa-cli 到 Android(Termux 本机运行),并与官方 MaaCore 组件包打包在一起。 +# +# === 为什么单独一个 workflow === +# 上游 release.yml 是发版流程(会打 tag、改 version 分支、发布 Release),把 Android +# 塞进去会让 fork 每次同步 upstream 时冲突。这里做成独立 workflow,只在 fork 上运行。 +# +# === 产物结构 === +# maa_cli-v-aarch64-linux-android.tar.gz +# maa_cli-v-x86_64-linux-android.tar.gz +# 每个包内(与 maa-cli 在其他平台的包一致,但多带 MaaCore): +# maa ← maa-cli 可执行文件 +# lib/libMaaCore.so 等 ← 来自官方 MAAComponent-android-*.tar.gz +# +# === 关键事实(来自实测与源码确认)=== +# 1. MaaCore 不在本仓库。而上游 MaaAssistantArknights 已发布 Android 组件包 +# MAAComponent-v-android-{arm64,x64}.tar.gz,且其 ci.yml 有 android job +# (提交 c6f930a60d "feat: support native android (#16179)")。直接复用它, +# 无需在本仓库重新编译 MaaCore(省掉约 40 分钟构建)。 +# 2. git2 → openssl 在 Android 上编不过:MaaDeps 不含 openssl,而 vendored +# 编译需要 Perl(CI 上没装会失败)。故用 --no-default-features --features +# core_installer(保留 MaaCore 安装/更新能力,去掉 git2 资源后端)。 +# 3. maa-cli 用 consts::DLL_SUFFIX 推断库文件名,Android 上自动得到 libMaaCore.so, +# 无需改 Rust 代码。 +# 4. 打包格式沿用上游 xtask 的命名规则(maa_cli-v{version}-{target}.tar.gz), +# 其中 aarch64/x86_64-linux-android 含 "-linux-",会被识别为 TarGz。 + +on: + workflow_dispatch: + inputs: + maa_version: + description: MAACore/MAAComponent 版本(如 v6.18.0),留空则取最新正式版 + required: false + default: "" + maa_cli_version: + description: 覆盖 maa-cli 版本号(写入二进制与包名),留空则用 Cargo.toml 的版本 + required: false + default: "" + schedule: + # 每周一 04:00 UTC 构建一次,避免产物过期 + - cron: "0 4 * * 1" + +defaults: + run: + shell: bash + +permissions: + contents: read + +jobs: + # 取得要用的版本号(MAAComponent 版本 + maa-cli 版本) + meta: + name: Meta + runs-on: ubuntu-latest + outputs: + maa_version: ${{ steps.resolve.outputs.maa_version }} + cli_version: ${{ steps.resolve.outputs.cli_version }} + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Resolve versions + id: resolve + env: + INPUT_MAA: ${{ inputs.maa_version }} + INPUT_CLI: ${{ inputs.maa_cli_version }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + + # MAAComponent 版本:优先用输入,否则取上游最新正式版 + maa_version="$INPUT_MAA" + if [ -z "$maa_version" ]; then + maa_version="$(gh api \ + repos/MaaAssistantArknights/MaaAssistantArknights/releases/latest \ + --jq .tag_name)" + echo "未指定 MAA 版本,取最新正式版:$maa_version" + fi + case "$maa_version" in + v*) ;; + *) maa_version="v$maa_version" ;; + esac + + # 确认该版本确实有 Android 组件包,避免把不存在的版本带进后续步骤 + available="$(gh api \ + "repos/MaaAssistantArknights/MaaAssistantArknights/releases/tags/$maa_version" \ + --jq '.assets[].name' | grep -c 'android' || true)" + if [ "$available" -eq 0 ]; then + echo "::error::$maa_version 没有 Android 组件包(MAAComponent-*-android-*.tar.gz)" + echo "该版本可能早于 native android 支持(见 MAA PR #16179),请换一个版本。" + exit 1 + fi + echo "确认 $maa_version 有 $available 个 android 资产" + + # maa-cli 版本:优先用输入,否则读 Cargo.toml + cli_version="$INPUT_CLI" + if [ -z "$cli_version" ]; then + cli_version="$(grep -m1 '^version' crates/maa-cli/Cargo.toml \ + | sed -E 's/.*"(.*)".*/\1/')" + fi + cli_version="${cli_version#v}" + + echo "maa_version=$maa_version" >> "$GITHUB_OUTPUT" + echo "cli_version=$cli_version" >> "$GITHUB_OUTPUT" + echo "MAAComponent 版本: $maa_version / maa-cli 版本: $cli_version" + + # 静态检查:脚本与 workflow 自身(快速失败,避免白跑构建) + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Install shellcheck + run: | + shellcheck --version || { sudo apt-get update -qq && sudo apt-get install -y shellcheck; } + + - name: Shellcheck + run: | + shellcheck --version + shellcheck -s bash android/maa-android-connect.sh + + - name: Actionlint + run: | + bash <(curl -sSf https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash) + ./actionlint -color + + # 交叉编译 maa-cli 到 Android + build-cli: + name: Build maa-cli (${{ matrix.abi }}) + needs: [meta, lint] + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - abi: arm64 + target: aarch64-linux-android + clang: aarch64-linux-android24-clang + - abi: x64 + target: x86_64-linux-android + clang: x86_64-linux-android24-clang + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Setup Rust target + run: rustup target add ${{ matrix.target }} + + - name: Setup Android NDK + id: setup-ndk + uses: nttld/setup-ndk@v1 + with: + ndk-version: r29 + add-to-path: false + + - name: Configure cross compiler + env: + NDK: ${{ steps.setup-ndk.outputs.ndk-path }} + run: | + set -euo pipefail + tool="$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin" + sysroot="$NDK/toolchains/llvm/prebuilt/linux-x86_64/sysroot" + upper="$(echo "${{ matrix.target }}" | tr 'a-z-' 'A-Z_')" + under="$(echo "${{ matrix.target }}" | tr '-' '_')" + + # 用 clang 本体做 linker,显式给出 --target/--sysroot + { + echo "CARGO_TARGET_${upper}_LINKER=$tool/clang" + echo "CARGO_TARGET_${upper}_RUSTFLAGS=-Clink-arg=--target=${{ matrix.target }}24 -Clink-arg=--sysroot=$sysroot" + echo "CC_${under}=$tool/${{ matrix.clang }}" + echo "AR_${under}=$tool/llvm-ar" + echo "RANLIB_${under}=$tool/llvm-ranlib" + } >> "$GITHUB_ENV" + + - name: Setup Cache + uses: Swatinem/rust-cache@v2 + with: + key: android-${{ matrix.abi }} + + - name: Build + env: + MAA_VERSION: ${{ needs.meta.outputs.cli_version }} + run: | + set -euo pipefail + # 去掉 git2:其 openssl 依赖在 Android 上无法编译(见文件头说明) + cargo build --release --locked \ + --target ${{ matrix.target }} \ + --package maa-cli \ + --no-default-features --features core_installer + + binary="target/${{ matrix.target }}/release/maa" + test -f "$binary" + + # 冒烟测试:交叉编译产物无法在本机执行,故只做静态校验 + file "$binary" + file "$binary" | grep -qE "$( [ '${{ matrix.abi }}' = 'arm64' ] && echo aarch64 || echo 'x86-64' )" + + # 校验版本号已正确嵌入:maa-cli 通过 build.rs 读取 MAA_VERSION 环境变量 + # 写入二进制(crates/maa-cli/build.rs),若未设置则回退到 CARGO_PKG_VERSION。 + # 这里确保 CI 传的版本真的进了产物,避免发布出无版本号/占位版本的包。 + # + # 注意用 grep -a 而非 strings:版本串与相邻字符串紧挨着(无 null 分隔), + # strings 不会单独提取它,实测会误报。 + if ! grep -aqF "$MAA_VERSION" "$binary"; then + echo "::error::二进制中未找到版本号 $MAA_VERSION,MAA_VERSION 可能未生效" + exit 1 + fi + echo "版本号 $MAA_VERSION 已嵌入产物" + + # 打成 tar(保持上游命名规则:maa_cli-v{version}-{triplet}.tar) + tar -cf "${{ matrix.target }}.tar" -C "$(dirname "$binary")" maa + + - name: Upload Artifact + uses: actions/upload-artifact@v7 + with: + name: maa_cli-${{ matrix.target }} + path: ${{ matrix.target }}.tar + retention-days: 1 + if-no-files-found: error + + # 下载官方 MaaCore 组件包,与 maa-cli 组装成完整运行时 + package: + name: Package (${{ matrix.abi }}) + needs: [meta, build-cli] + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - abi: arm64 + target: aarch64-linux-android + - abi: x64 + target: x86_64-linux-android + steps: + # 需要仓库里的 android/maa-android-connect.sh(随包分发的辅助脚本) + - name: Checkout + uses: actions/checkout@v7 + with: + sparse-checkout: android + sparse-checkout-cone-mode: false + + - name: Download maa-cli artifact + uses: actions/download-artifact@v8 + with: + name: maa_cli-${{ matrix.target }} + path: cli + + - name: Download MaaCore component + env: + MAA_VERSION: ${{ needs.meta.outputs.maa_version }} + ABI: ${{ matrix.abi }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + # 用 gh(GitHub 官方 CLI,runner 自带)而非第三方 action, + # 避免其参数名变更或行为差异。 + mkdir -p core + gh release download "$MAA_VERSION" \ + --repo MaaAssistantArknights/MaaAssistantArknights \ + --pattern "MAAComponent-${MAA_VERSION}-android-${ABI}.tar.gz" \ + --dir core \ + --clobber + ls -lh core/ + tar xzf "core/MAAComponent-${MAA_VERSION}-android-${ABI}.tar.gz" -C core + # 官方包解压后含 ./resource ./lib*.so ./AsstCaller.h 等 + test -d core/resource + test -f core/libMaaCore.so + + # Android 的 OCR 走 OcrPackNcnn(ncnn 后端),而官方 resource(含 + # MAAComponent 包)只带 onnx 模型 —— ncnn 权重是 Android 专用、约 50MB, + # 官方不在共享 resource 里分发,必须在构建期从 onnx 转换而来。 + # 这里直接复用 MAA-Meow 的转换脚本:它已在生产环境验证过转换参数 + # (rec: pnnx inputshape=[1,3,48,320];det: [1,3,640,640] 且必须 fp32)。 + - name: Checkout MAA-Meow conversion script + uses: actions/checkout@v7 + with: + repository: Aliothmoon/MAA-Meow + ref: v0.22.0 + sparse-checkout: scripts + sparse-checkout-cone-mode: false + path: meow + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install pnnx + run: | + set -euo pipefail + pip install -r meow/scripts/requirements.txt + pnnx --version || true + + - name: Convert OCR models to ncnn + run: | + set -euo pipefail + python meow/scripts/convert_ocr_ncnn.py --resource core/resource + + echo "=== 转换产物 ===" + find core/resource -name '*.ncnn.param' | sort + echo "=== 自检 ===" + # 必须同时具备 det 与 rec 两套 ncnn 权重,否则 Android 上 OCR 会加载失败 + test -n "$(find core/resource -name 'det.ncnn.param' | head -1)" + test -n "$(find core/resource -name 'rec.ncnn.param' | head -1)" + echo "det / rec 权重均已生成" + + # patchelf 用于给产物设置 RPATH(见 Assemble 步骤里的说明) + - name: Install patchelf + run: | + sudo apt-get update -qq + sudo apt-get install -y -qq patchelf + patchelf --version + + - name: Assemble package + env: + VERSION: ${{ needs.meta.outputs.cli_version }} + MAA_VERSION: ${{ needs.meta.outputs.maa_version }} + run: | + set -euo pipefail + + # ── 目录结构 ───────────────────────────────────────────── + # 依 maa-cli 的查找逻辑(crates/maa-dirs/src/lib.rs): + # find_library: 先看 /libMaaCore.so,再看 /../lib/ + # find_resource: 先看 /resource,再看 /../share/maa/resource + # 故采用 bin/ + lib/ + share/maa/resource/ 布局,与其他平台的包一致。 + mkdir -p stage/bin stage/lib stage/share/maa + + # maa-cli 可执行文件 + tar -xf "cli/${{ matrix.target }}.tar" -C stage/bin + test -f stage/bin/maa + chmod +x stage/bin/maa + + # 连接辅助脚本(Termux 里一键连本机 adb) + cp android/maa-android-connect.sh stage/ + chmod +x stage/maa-android-connect.sh + bash -n stage/maa-android-connect.sh + + # ── MaaCore 组件 ───────────────────────────────────────── + echo "=== 官方组件包内容 ===" + find core -type f -name '*.so*' | sort + + # 复制 MaaCore 及其依赖库 + # 排除清单(Termux 用不上,见 README 说明): + # libMaaAndroidNativeControlUnit.so —— Android 原生控制单元,靠 + # MediaProjection + AccessibilityService + JNI 回调工作,需要 + # Android App 运行时;Termux 是纯 Linux 进程,无此环境, + # 只能改用 adb。该库是给 MAA-Meow 那类 APK 项目用的。 + # libfastdeploy_ppocr.so —— 桌面版 OCR 后端;Android 走 OcrPackNcnn + # (用 ncnn 权重),已实测 MaaCore 的 NEEDED 与本库字符串表中都 + # 不含 fastdeploy,属死重量(约 17MB)。 + for so in $(find core -type f -name '*.so*' \ + ! -name 'libMaaAndroidNativeControlUnit.so' \ + ! -name 'libfastdeploy_ppocr.so'); do + cp "$so" stage/lib/ + done + + # 资源(官方包内已含完整 resource,无需另行下载) + if [ -d core/resource ]; then + cp -r core/resource stage/share/maa/ + echo "resource 条目数: $(find stage/share/maa/resource -type f | wc -l)" + else + echo "::error::官方组件包内未找到 resource 目录" + exit 1 + fi + + # ── 自检 ──────────────────────────────────────────────── + test -f stage/lib/libMaaCore.so + test -f stage/share/maa/resource/version.json + + # ── 设置 RPATH ────────────────────────────────────────── + # 实测发现(Android 12 + Termux):不设 RPATH 时,MaaCore 找不到同目录的 + # 依赖库(libMaaUtils.so 等),只能靠 LD_LIBRARY_PATH 补; + # 而 LD_LIBRARY_PATH 会被子进程继承 —— MaaCore 调用 adb 时, + # adb 也带上了本目录,于是优先加载了本目录的 libc++_shared.so + # (NDK 版),与 Termux 环境不兼容,报: + # CANNOT LINK EXECUTABLE "adb": cannot locate symbol + # "_ZNSt6__ndk113__hash_memoryEPKvm" + # 表现为 adb 启动失败 → MaaCore 报 connect failed。 + # + # 给库设 $ORIGIN 后一切自解析,用户无需设置 LD_LIBRARY_PATH, + # 也就不可能污染 adb 等子进程。 + for so in stage/lib/*.so; do + patchelf --set-rpath '$ORIGIN' "$so" + done + # bin/maa 需要找到 ../lib 下的 MaaCore + patchelf --set-rpath '$ORIGIN/../lib' stage/bin/maa + + # 验证 RPATH 确实写入了(patchelf 静默成功不代表生效) + echo "=== RPATH 校验 ===" + patchelf --print-rpath stage/lib/libMaaCore.so + patchelf --print-rpath stage/bin/maa + case "$(patchelf --print-rpath stage/lib/libMaaCore.so)" in + *'$ORIGIN'*) echo "lib 的 RPATH 正确" ;; + *) echo "::error::libMaaCore.so 的 RPATH 未设置成功"; exit 1 ;; + esac + case "$(patchelf --print-rpath stage/bin/maa)" in + *'$ORIGIN/../lib'*) echo "maa 的 RPATH 正确" ;; + *) echo "::error::bin/maa 的 RPATH 未设置成功"; exit 1 ;; + esac + + cat > stage/README.md </dev/null || { + mkdir -p ~/.local/share/maa/lib && cp lib/*.so ~/.local/share/maa/lib/ + } + cp -r share/maa/resource ~/.local/share/maa/ + + # 3. 验证 + maa version + \`\`\` + + > 不需要设置 \`LD_LIBRARY_PATH\`:包内所有库都已设好 + > \`RPATH=$ORIGIN\`(\`bin/maa\` 为 \`$ORIGIN/../lib\`), + > maa-cli 能自行找到同目录的 MaaCore 及其依赖。 + > + > 请**不要**用 \`LD_LIBRARY_PATH\` 指到本包的 lib 目录: + > 该变量会传给子进程,导致 MaaCore 调用的 \`adb\` 误加载本包的 + > \`libc++_shared.so\`(NDK 版),报 + > \`CANNOT LINK EXECUTABLE "adb": cannot locate symbol\`, + > 最终表现为连接失败。 + + 若 \`~/.local/bin\` 不在 PATH: + + \`\`\`bash + echo 'export PATH=\$HOME/.local/bin:\$PATH' >> ~/.bashrc && . ~/.bashrc + \`\`\` + + ## 连接游戏(必须走 adb) + + ⚠️ Termux 里**无法使用** Android 原生控制,只能通过 adb。 + 原因:原生控制要求 MaaCore 通过 \`libbridge.so\` 提供截图与输入注入, + 而官方那份 \`libbridge.so\` 是 Android App 的 JNI 桥,依赖 + MediaProjection(虚拟显示)+ AccessibilityService,需要 Android App + 运行时;Termux 是纯 Linux 进程,没有这些能力。 + (这也是包内不含 \`libMaaAndroidNativeControlUnit.so\` 的原因—— + 那是给 MAA-Meow 这类 APK 项目用的。) + + ### 用附带脚本自动连接(推荐) + + \`\`\`bash + ./maa-android-connect.sh + \`\`\` + + 脚本按成功率依次尝试三种方式: + + 1. **mDNS 发现**(无需 root,要求此前配对过无线调试) + 2. **root 设固定端口**(有 su 时最省事): + \`setprop service.adb.tcp.port 5555\` + 重启 adbd,然后连 127.0.0.1:5555 + 3. **扫描常见端口** + + 全部失败时会打印手动步骤。也可指定单一方式: + + \`\`\`bash + ./maa-android-connect.sh --root # 只试 root 方式 + ./maa-android-connect.sh --mdns # 只试 mDNS + ./maa-android-connect.sh --verbose # 看详细过程 + \`\`\` + + ### 手动连接 + + \`\`\`bash + # 无 root:用系统「无线调试」(需配对码) + adb pair 127.0.0.1:<配对端口> + adb connect 127.0.0.1:<调试端口> + + # 有 root:直接设固定端口,免配对 + su -c 'setprop service.adb.tcp.port 5555; setprop ctl.restart adbd' + adb connect 127.0.0.1:5555 + \`\`\` + + 连上后即可使用: + + \`\`\`bash + maa startup # 开始唤醒 + maa fight 1-7 --times 5 # 刷理智 + \`\`\` + + > MAA 只支持 16:9 分辨率。识别异常时先 \`wm size\` 查看, + > 必要时 \`wm size 1080x1920\` 调整(部分设备需 root)。 + + ## 已知限制 + + - **必须用 adb**:如上,无法用原生控制,稳定性与性能受 adb 回环影响。 + - **无 git2 资源后端**:maa-cli 以 \`--no-default-features --features + core_installer\` 构建(git2 的 openssl 依赖无法在 Android 交叉编译)。 + 影响:\`maa update\` 走 HTTP 而非 git 拉取,功能可用。 + - 若只用官方 MAA-Meow 就够了,可优先考虑它(原生控制、免 adb、带 GUI)。 + EOF + + # 打成上游风格的 tar.gz + archive="maa_cli-v${VERSION}-${{ matrix.target }}.tar.gz" + tar czf "$archive" -C stage . + echo "=== 产物 ===" + ls -lh "$archive" + tar tzf "$archive" | head -20 + + # 生成校验和 + sha256sum "$archive" > "$archive.sha256" + + - name: Upload package + uses: actions/upload-artifact@v7 + with: + name: android-package-${{ matrix.abi }} + path: | + maa_cli-v*-${{ matrix.target }}.tar.gz + maa_cli-v*-${{ matrix.target }}.tar.gz.sha256 + if-no-files-found: error + + # 汇总:所有包齐了才提示可发布(发布需手动,避免误触发) + summary: + name: Summary + needs: [meta, package] + runs-on: ubuntu-latest + steps: + - name: Download all packages + uses: actions/download-artifact@v8 + with: + pattern: android-package-* + merge-multiple: true + + - name: Show artifacts + run: | + echo "## Android 构建产物(maa-cli v${{ needs.meta.outputs.cli_version }})" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "MaaCore 版本:${{ needs.meta.outputs.maa_version }}" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "| 文件 | 大小 |" >> "$GITHUB_STEP_SUMMARY" + echo "|------|------|" >> "$GITHUB_STEP_SUMMARY" + for f in *.tar.gz; do + printf '| `%s` | %s |\n' "$f" "$(du -h "$f" | cut -f1)" >> "$GITHUB_STEP_SUMMARY" + done + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "从本次 run 的 Artifacts 下载。" >> "$GITHUB_STEP_SUMMARY" + ls -lh diff --git a/android/maa-android-connect.sh b/android/maa-android-connect.sh new file mode 100644 index 00000000..750ba625 --- /dev/null +++ b/android/maa-android-connect.sh @@ -0,0 +1,197 @@ +#!/usr/bin/env bash +# maa-android-connect —— 在 Termux 里把 MAA 自动连到本机 adb +# +# 为什么需要它:在 Android 本机跑 MAA,MaaCore 只能通过 adb 连游戏(原生控制需要 +# Android App 运行时,Termux 拿不到)。而手动连 adb 很繁琐: +# 要开无线调试、要 adb pair 输配对码、端口每次还可能变。 +# 本脚本按「成功率从高到低」依次尝试,免去手动操作。 +# +# 用法: +# ./maa-android-connect.sh # 自动尝试所有方式 +# ./maa-android-connect.sh --root # 只试 root 方式(设固定端口) +# ./maa-android-connect.sh --mdns # 只试 mDNS 发现(已配对过的设备) +# ./maa-android-connect.sh --verbose # 打印更多过程 +# +# 退出码:0 = 已连上;1 = 全部失败(会打印手动步骤) + +set -uo pipefail + +# ── 常见端口(部分设备/模拟器会监听这些)──────────────────────── +COMMON_PORTS="5555 5556 5557 7555 16384 16385" + +ONLY="" +VERBOSE=0 +for arg in "$@"; do + case "$arg" in + --root) ONLY="root" ;; + --mdns) ONLY="mdns" ;; + --verbose) VERBOSE=1 ;; + -h|--help) sed -n '2,18p' "$0"; exit 0 ;; + *) echo "未知参数:$arg(用 --help 查看用法)" >&2; exit 2 ;; + esac +done + +C_RESET=$'\033[0m'; C_CYAN=$'\033[36m'; C_GREEN=$'\033[32m' +C_YELLOW=$'\033[33m'; C_RED=$'\033[31m' +log() { printf '%s[*]%s %s\n' "$C_CYAN" "$C_RESET" "$*"; } +ok() { printf '%s[+]%s %s\n' "$C_GREEN" "$C_RESET" "$*"; } +warn() { printf '%s[!]%s %s\n' "$C_YELLOW" "$C_RESET" "$*"; } +err() { printf '%s[x]%s %s\n' "$C_RED" "$C_RESET" "$*"; } +trace() { [ "$VERBOSE" -eq 1 ] && printf ' %s\n' "$*"; return 0; } + +# ── 前置检查 ──────────────────────────────────────────────────── +if ! command -v adb >/dev/null 2>&1; then + err "未找到 adb。请先安装:pkg install -y android-tools" + exit 1 +fi + +# 已有可用设备就直接收工 +connected() { + adb devices 2>/dev/null | awk 'NR>1 && $2=="device" { n++ } END { exit !(n>0) }' +} +show_devices() { + adb devices 2>/dev/null | sed 's/^/ /' +} + +if connected; then + ok "已有可用的 adb 设备,无需重连" + show_devices + exit 0 +fi +log "当前没有可用设备,开始尝试自动连接" + +# ── 方式 1:mDNS 发现(无需 root,但要求之前配对过)────────────── +try_mdns() { + log "方式 1:mDNS 发现无线调试服务" + command -v adb >/dev/null || return 1 + + # 启动本地 adb server(mdns 发现需要它) + adb start-server >/dev/null 2>&1 + + local found + found="$(adb mdns services 2>/dev/null \ + | awk '$2 ~ /_adb-tls-connect/ { print $3 }' | head -5)" + trace "发现的服务:${found:-(无)}" + + if [ -z "$found" ]; then + warn "未发现无线调试服务(可能未开启无线调试,或此前未配对过)" + return 1 + fi + + local addr + while read -r addr; do + [ -n "$addr" ] || continue + log "尝试连接 $addr" + adb connect "$addr" >/dev/null 2>&1 + sleep 1 + if connected; then + ok "已通过 mDNS 连接到 $addr" + show_devices + return 0 + fi + done <<< "$found" + + warn "mDNS 发现的地址均未能连接(多半是尚未配对,需先 adb pair)" + return 1 +} + +# ── 方式 2:root 设固定端口(最可靠,但需要 su)────────────────── +have_root() { + command -v su >/dev/null 2>&1 || return 1 + su -c id 2>/dev/null | grep -q 'uid=0' +} + +try_root() { + log "方式 2:使用 root 设固定 adb 端口" + + if ! have_root; then + warn "没有可用的 su(root),跳过" + return 1 + fi + + # 注意:重启 adbd 会短暂断开现有 adb 连接,故放在此处(前面已确认无设备) + log "设置 service.adb.tcp.port=5555 并重启 adbd" + su -c 'setprop service.adb.tcp.port 5555' 2>/dev/null + if ! su -c 'setprop ctl.restart adbd' 2>/dev/null; then + # 部分设备不支持 ctl.restart,退回 stop/start + trace "ctl.restart 不可用,改用 stop/start" + su -c 'stop adbd' 2>/dev/null + sleep 1 + su -c 'start adbd' 2>/dev/null + fi + + # 等 adbd 起来 + for _ in 1 2 3 4 5 6 7 8 9 10; do + sleep 1 + adb connect 127.0.0.1:5555 >/dev/null 2>&1 + if connected; then + ok "已通过 root 连接到 127.0.0.1:5555" + show_devices + return 0 + fi + done + + warn "root 方式未成功(设备可能限制 setprop adb 端口)" + return 1 +} + +# ── 方式 3:扫常见端口 ────────────────────────────────────────── +try_ports() { + log "方式 3:扫描常见 adb 端口" + local port + for port in $COMMON_PORTS; do + trace "尝试 127.0.0.1:$port" + adb connect "127.0.0.1:$port" >/dev/null 2>&1 + sleep 1 + if connected; then + ok "已连接到 127.0.0.1:$port" + show_devices + return 0 + fi + done + warn "常见端口均不可用" + return 1 +} + +# ── 依次尝试 ──────────────────────────────────────────────────── +case "$ONLY" in + root) try_root ;; + mdns) try_mdns ;; + *) try_mdns || try_root || try_ports ;; +esac + +if connected; then + exit 0 +fi + +# ── 全部失败:给出手动步骤 ────────────────────────────────────── +echo +err "自动连接失败,请手动操作(任选一种):" + +cat <<'EOF' + +【A. 有 root:设固定端口(最省事)】 + su -c 'setprop service.adb.tcp.port 5555; setprop ctl.restart adbd' + adb connect 127.0.0.1:5555 + +【B. 无 root:用系统「无线调试」】 + 1. 打开 设置 → 开发者选项 → 无线调试 + 2. 点「使用配对码配对设备」,记下 配对码 与 端口号 + 3. 在 Termux 里执行(端口换成配对界面显示的): + adb pair 127.0.0.1:<配对端口> + (提示输入配对码时填入) + 4. 再连接(端口看「无线调试」主界面显示的 IP 与端口): + adb connect 127.0.0.1:<调试端口> + 5. adb devices 确认设备为 device 状态 + +【C. 若以上都失败】 + - 确认已安装 android-tools:pkg install -y android-tools + - 确认「开发者选项」已开启 + - 部分设备需要先通过 USB 连一次电脑执行 adb tcpip 5555 + +连上之后即可使用 maa-cli,例如: + maa startup + maa fight 1-7 --times 5 +EOF + +exit 1 diff --git a/crates/maa-cli/Cargo.toml b/crates/maa-cli/Cargo.toml index 7ec6d5e1..4a91f078 100644 --- a/crates/maa-cli/Cargo.toml +++ b/crates/maa-cli/Cargo.toml @@ -40,8 +40,16 @@ sha2 = { workspace = true, optional = true } signal-hook = { workspace = true } tempfile = { workspace = true } toml = { workspace = true } +[target.'cfg(not(target_os = "android"))'.dependencies] ureq = { workspace = true, features = ["json", "platform-verifier"] } +# Android 上 rustls-platform-verifier 需要 Android Context(经 JNI)才能初始化, +# 而 Termux / 纯命令行环境拿不到它,会在发起 HTTPS 请求时 panic +# ("Expect rustls-platform-verifier to be initialized")。 +# 故改用内置的 Mozilla 根证书,不依赖系统证书库。 +[target.'cfg(target_os = "android")'.dependencies] +ureq = { workspace = true, features = ["json", "rustls-webpki-roots"] } + [target.'cfg(windows)'.dependencies] winreg = { workspace = true } diff --git a/crates/maa-cli/src/state.rs b/crates/maa-cli/src/state.rs index 37bf55c1..f8b9ce93 100644 --- a/crates/maa-cli/src/state.rs +++ b/crates/maa-cli/src/state.rs @@ -87,12 +87,17 @@ impl Middleware for GitHubProxyMiddleware { } pub static AGENT: LazyLock = LazyLock::new(|| { + // Android 上 rustls-platform-verifier 需要 Android Context(经 JNI)才能初始化, + // 而 Termux / 纯命令行环境拿不到它,会在发起 HTTPS 请求时 panic + // ("Expect rustls-platform-verifier to be initialized")。 + // 故改用内置的 Mozilla 根证书;其他平台仍用系统证书库,以支持企业内网的自签证书。 + #[cfg(target_os = "android")] + let root_certs = RootCerts::WebPki; + #[cfg(not(target_os = "android"))] + let root_certs = RootCerts::PlatformVerifier; + let mut config = Agent::config_builder() - .tls_config( - TlsConfig::builder() - .root_certs(RootCerts::PlatformVerifier) - .build(), - ) + .tls_config(TlsConfig::builder().root_certs(root_certs).build()) .user_agent(format!("maa-cli/{CLI_VERSION_STR}")); if GITHUB_PROXY.is_some() { From 2001756cbcaaac3bfdffe6e2b5e9b88ab56d2042 Mon Sep 17 00:00:00 2001 From: Jason's Miku <452467769@qq.com> Date: Sun, 27 Sep 2026 02:28:34 +0800 Subject: [PATCH 2/2] fix(deps): bump rustls to 0.23.45 (RUSTSEC-2026-0285) `cargo deny check advisories` started failing with RUSTSEC-2026-0285: rustls accepted TLS 1.3 handshake messages sent at the wrong encryption level when they followed a key-changing message in the same record. The advisory requires >= 0.23.45. Note that a plain `cargo update -p rustls` is not enough: it only picks 0.23.43, which is still vulnerable. An explicit precise version is needed: cargo update -p rustls --precise 0.23.45 This also bumps rustls-webpki 0.103.13 -> 0.103.15 as a side effect. --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 80ac9673..ed120666 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1499,9 +1499,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.42" +version = "0.23.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c54fcab019b409d04215d3a17cb438fd7fbf192ee61461f20f4fe18704bc138" +checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634" dependencies = [ "log", "once_cell", @@ -1562,9 +1562,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.13" +version = "0.103.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" dependencies = [ "ring", "rustls-pki-types",