Moto Edge 60 Pro OTA 升级保 Root 实录(刷回原版 init_boot + Android 16 重新 Root)

前言:

本文承接上一篇:Moto Edge 60 Pro Root & 隐藏方案(KSU-NEXT + Play Integrity FULL PASS)

上一篇主要记录了 Moto Edge 60 Pro 在 Android 15 上如何通过 KSU-NEXT / LKM 获取 root,并通过 Play Integrity Strong。这次官方推送 Android 16 后,问题就从“怎么 root”变成了“怎么升级,而且尽量不丢数据,再把 root 平稳接回去”。

这次我没有直接拿完整包开刷,而是先把完整包结构、flashfile.xml、A/B OTA 的行为、分区刷写路径和 Windows 驱动问题重新捋了一遍。过程中也顺手让 Codex 帮我把步骤和判断拆清楚,最后走通了一条相对稳妥的路线。中间踩到的坑都挺典型,单独记一篇,后面自己翻也方便。

这篇主要记录:

  • 为什么完整包不能直接原样线刷
  • 为什么刷回旧版原版 init_boot 之后,官方 OTA 又重新通过了
  • OTA 完成后,如何从新版完整包重新提取、修补并刷回 init_boot
  • 过程中几个很容易浪费时间的坑

1. 我的环境与目标

我的环境如下:

  • 机型:Moto Edge 60 Pro(RETCN)
  • 旧系统版本:V2VVC35.58-70-3
  • 新系统版本:W1VVC36H.7-73-6
  • root 方式:KernelSU NEXT 修补 init_boot.img,模式为 LKM
  • 原有模块环境:Zygisk Next + LSPosed + Shamiko + PIF + TrickyStore

这次升级的目标很简单:

  • 尽量保留现有数据
  • 优先走官方 OTA,而不是直接整包清刷
  • OTA 完成后重新接回 root
  • 后续继续沿用上一篇里的完整性隐藏方案

2. 为什么不能直接原样刷完整包

我下载的完整包是:

1
XT2507-5_CYBERT_RETCN_16_W1VVC36H.7-73-6_subsidy-DEFAULT_regulatory-DEFAULT_CFC.xml.zip

实际上,这次折腾并不是从“我要手动刷完整包”开始的,而是先在系统里直接点了 OTA 更新。结果更新器给出的提示很明确:

1
版本不兼容:正在下载的更新包与设备上的当前软件版本不兼容

也正是这个报错,才让我开始认真怀疑:当前 root 状态下的系统,已经不再满足官方 OTA 所需的源版本校验条件。

接下来思路自然分成了两条:

  • 一条是研究能不能绕过 OTA,直接用完整包保数据升级
  • 另一条是想办法把当前系统尽量恢复到 OTA 能接受的状态,再走官方更新

前者在把 flashfile.xml 看完之后,基本就可以排除了;后者后来证明才是正路。

所以后面才会去研究完整包,而不是上来就直接开刷。

但把包里的 flashfile.xml 仔细看完之后,这条路基本也就可以放弃了。

原因很直接:这不是普通 OTA 包,而是完整 fastboot 固件包,官方 XML 里明确包含这些擦除项:

  • erase nvdata
  • erase userdata
  • erase metadata
  • erase debug_token

也就是说,如果按 XML 原样整包刷:

  • root 一定会被覆盖
  • 用户数据会被清空
  • 整个过程也不再属于“保数据升级”

这里其实要先统一一个认知:

Moto 的完整 CFC.xml.zip 固件包,不等于系统里的 OTA 更新包。

如果目标是保留数据,并继续沿用现有 root 路线,就不能把“完整包线刷”和“OTA 升级”看成同一件事。


3. 关键判断:先刷回旧版原版 init_boot,再试官方 OTA

我这台机器的 root 路线,就是上一篇里那套 KSU-NEXT + LKM

  • 从官方固件提取 init_boot.img
  • 在手机端修补 init_boot.img
  • 刷回当前活动槽位

所以从技术上讲,我主动改动过的 boot 链关键分区,核心就是 init_boot

而 A/B OTA 的工作方式,可以简单理解成:

  • 以当前活动槽位作为“源状态”
  • 对当前系统做校验
  • 再把新系统写到未使用槽位

那么这里就有一个比较自然的推论:

如果 OTA 过不了,很大概率是当前槽位里的 patched init_boot 让校验失败了。

所以这次我没有一上来乱刷,而是先做一个低风险验证:

  • 从旧版官方包提取当前版本的原版 init_boot.img
  • 刷回当前活动槽位
  • 再重新尝试官方 OTA

模块要不要先删?

这里我的结论是:

  • 不需要卸载 LSPosedTaichi 或其它模块
  • 但建议先全部停用,尤其是会作用于系统、设置、Play 服务、更新器的模块

原因也不复杂。这一步的目的不是“永久清环境”,而是尽量干净地验证:

到底是不是 patched init_boot 导致 OTA 过不去。


4. 提取并刷回旧版原版 init_boot

旧版完整包是:

1
XT2507-5_CYBERT_RETCN_15_V2VVC35.58-70-3_subsidy-DEFAULT_regulatory-DEFAULT_CFC.xml.zip

先确认当前系统确实还是旧版:

1
2
3
adb shell getprop ro.build.display.id
adb shell getprop ro.build.fingerprint
adb shell getprop ro.boot.slot_suffix

然后先把当前 patched 的 init_boot 备份出来,这样如果 OTA 还是不通,可以快速恢复旧 root:

1
2
adb shell su -c "dd if=/dev/block/by-name/init_boot_a of=/sdcard/init_boot_current_a.img bs=4M"
adb pull /sdcard/init_boot_current_a.img .

如果当前槽位不是 a,就把上面命令里的 a 改掉。

接着从旧官方包里提取原版 init_boot.img

1
2
3
4
5
6
7
8
$zip = 'C:\adb-scrcpy\motoedge60pRETCN\RETCN_15_V2VVC35.58-70-3\XT2507-5_CYBERT_RETCN_15_V2VVC35.58-70-3_subsidy-DEFAULT_regulatory-DEFAULT_CFC.xml.zip'
$out = 'C:\adb-scrcpy\motoedge60pRETCN\RETCN_15_V2VVC35.58-70-3\init_boot_stock_V2VVC35.58-70-3.img'
Add-Type -AssemblyName System.IO.Compression.FileSystem
$z = [System.IO.Compression.ZipFile]::OpenRead($zip)
$entry = $z.GetEntry('init_boot.img')
[System.IO.Compression.ZipFileExtensions]::ExtractToFile($entry, $out, $true)
$z.Dispose()
Get-FileHash -LiteralPath $out -Algorithm SHA256

这里的第一个坑:不要进 fastbootd

一开始我习惯性用了:

1
adb reboot fastboot

结果刷 init_boot_a 的时候直接报错,类似:

1
FAILED (remote: 'No such file or directory')

后来排查才确认,这一步不能进 fastbootd,而要进 bootloader 模式。正确命令应该是:

1
adb reboot bootloader

然后再确认槽位:

1
2
fastboot getvar current-slot
fastboot getvar is-userspace

这里要注意:

  • current-slot 决定你该刷 init_boot_a 还是 init_boot_b
  • is-userspace 应该返回 no
  • 如果是 yes,说明你还在 fastbootd

确认无误后,刷回原版 init_boot

1
fastboot flash init_boot_a init_boot_stock_V2VVC35.58-70-3.img

如果当前槽位是 b,就改刷 init_boot_b

刷回后重启,root 会临时消失,这是预期现象。


5. 刷回原版 init_boot 后,官方 OTA 重新通过

这一步是整条路线里最关键的验证点。

刷回原版 init_boot 之后,我再次点击系统里的 OTA 更新,结果很明确:

  • 验证阶段通过
  • 正式进入 apply 更新

这说明前面的判断基本是对的:

当前活动槽位里的 patched init_boot,确实是阻碍官方 OTA 的主要因素之一。

也正因为这里走的是官方正常 OTA 流程,所以数据安全性比完整包线刷高得多。只要是标准 A/B OTA,就不应该去碰 erase userdata 这种路径。

我的建议是:

  • OTA 一旦进入 apply,就不要手动中断
  • 不要强制重启
  • 保证电量充足
  • 等它完整结束,并成功启动一次新系统

6. OTA 完成后,Windows 下 fastboot 驱动又出了问题

OTA 完成后,我原本准备按计划重新 root,结果又遇到一个很别扭的问题:

  • 系统开机状态下,adb 正常
  • 进入 bootloaderfastbootd 后,执行 fastboot 一直 waiting for any device

后来发现这不是手机坏了,也不是 init_boot 有问题,而是 Windows 对 bootloader / fastboot 模式的驱动绑定丢了

打开设备管理器后,可以看到一个带黄色感叹号的 Android 设备。

解决方法是:

  1. 保持手机停在 bootloader 界面
  2. 打开设备管理器
  3. 找到带黄色感叹号的 Android
  4. 右键 → 更新驱动程序
  5. 选择“浏览我的电脑以查找驱动程序”
  6. 再选“让我从计算机上的可用驱动程序列表中选取”
  7. 手动绑定到:Android Bootloader Interface

这一点也值得单独记一下:

ADB 能用,不代表 fastboot 驱动就一定正常。

两者在 Windows 里对应的是不同的 USB 设备模式。


7. 从新版完整包提取 init_boot,再重新 root

OTA 成功进入新系统一次之后,后面的动作就回到了上一篇最熟悉的套路,只不过镜像必须换成新版。

先确认当前已经是新系统:

1
2
3
adb shell getprop ro.build.display.id
adb shell getprop ro.build.fingerprint
adb shell getprop ro.boot.slot_suffix

然后从新版完整包提取 init_boot.img

1
2
3
4
5
6
7
8
$zip = 'C:\adb-scrcpy\motoedge60pRETCN\RETCN_16_W1VVC36H.7-73-6\XT2507-5_CYBERT_RETCN_16_W1VVC36H.7-73-6_subsidy-DEFAULT_regulatory-DEFAULT_CFC.xml.zip'
$out = 'C:\adb-scrcpy\motoedge60pRETCN\RETCN_16_W1VVC36H.7-73-6\init_boot_stock_W1VVC36H.7-73-6.img'
Add-Type -AssemblyName System.IO.Compression.FileSystem
$z = [System.IO.Compression.ZipFile]::OpenRead($zip)
$entry = $z.GetEntry('init_boot.img')
[System.IO.Compression.ZipFileExtensions]::ExtractToFile($entry, $out, $true)
$z.Dispose()
Get-FileHash -LiteralPath $out -Algorithm SHA256

把新版 init_boot 推到手机:

1
adb push init_boot_stock_W1VVC36H.7-73-6.img /sdcard/Download/

然后在手机上打开 KernelSU NEXT

  • 选择修补镜像
  • 选中新推送的新版 init_boot
  • 模式继续选 LKM

修补完成后,把 patched 镜像再拉回电脑:

1
adb pull /sdcard/Download/<修补后的文件名>.img

接着再次进入 bootloader

1
2
3
adb reboot bootloader
fastboot getvar current-slot
fastboot getvar is-userspace

确认:

  • is-userspace = no
  • 再根据 current-slot 判断刷 a 还是 b

我这次 OTA 完成后的实际目标槽位是 b,所以最终刷的是:

1
fastboot flash init_boot_b kernelsu_next_patched_xxxxx.img

我在刷新版 patched init_boot 的时候,又遇到了一次报错:

1
writing 'init_boot_b'... FAILED (status read failed (Too many links))

第一眼看确实有点唬人,因为这个错误完全不像普通 fastboot 报错。

但后来把分区名、槽位、驱动、模式都排查了一遍之后,结论反而很朴素:

不是镜像问题,也不是分区问题,而是数据线问题。

换了一根线之后,立刻正常刷入。

所以这里顺手记一个经验:

  • 如果 sending 成功,但 writing 阶段出现这种奇怪的 I/O 报错
  • 而且分区名、驱动、模式都已经确认没问题

那就优先排查:

  • 数据线
  • USB 口
  • 转接器 / Hub

不要第一时间怀疑镜像本身。


9. 刷入成功后,模块恢复建议

新版 root 恢复之后,不建议一上来把全部模块一次性打开。

我更推荐的顺序是:

  1. 先确认 KernelSU NEXT 本体已经正常工作,模式仍为 LKM
  2. 再启用 Zygisk Next
  3. 再启用 LSPosed
  4. 再启用 Shamiko
  5. 再检查 PIF
  6. 再检查 TrickyStore
  7. 最后再恢复其它零碎模块

至于 Play Integrity 的配置,这篇就不重复展开了,完全可以直接沿用上一篇里的方案:

  • PIF
  • TrickyStore
  • TrickyStore Addon
  • momo
  • SPIC
  • TBCheck

换句话说,这篇主要解决的是:

如何从“已经 root 的旧系统”安全升级到“新的已 root 系统”。

而完整性隐藏链路本身,仍然以前一篇为主。


10. 这次升级过程里最值得记住的几个结论

1. 完整包不等于 OTA 包

CFC.xml.zip 是完整 fastboot 固件包,不是普通 OTA 包。原样线刷通常会清数据,不能和系统里的 OTA 更新等同看待。

2. patched init_boot 确实可能阻断官方 OTA

如果你的 root 路线和我一样,是 KSU-NEXT + LKM 修补 init_boot,那 OTA 过不去时,优先尝试刷回当前版本的原版 init_boot,是一个非常高价值的验证步骤。

3. 刷 boot 链分区时,要进 bootloader,不要进 fastbootd

adb reboot fastboot 不一定是你想要的模式。对于 init_boot 这类分区,优先使用:

1
adb reboot bootloader

4. Windows 下 ADB 正常,不代表 fastboot 驱动正常

开机能 adb,但 bootloaderfastboot waiting for any device,大概率是驱动绑定问题。设备管理器里出现黄色感叹号时,手动绑定 Android Bootloader Interface 往往就能解决。

不要被报错文字吓到,排查顺序仍然应该从最基础的 USB 链路开始。


11. 总结

如果把这次过程压缩成一句话,那就是:

对已经用 KSU-NEXT root 的 Moto Edge 60 Pro 来说,最稳妥的升级路线,不是直接拿完整包原样线刷,而是先刷回旧版原版 init_boot 让官方 OTA 通过,升级完成后再从新版完整包重新提取并修补 init_boot,把 root 接回去。

这条路线的好处也很明确:

  • 数据更安全
  • root 路线不需要推倒重来
  • Play Integrity 那套后续配置仍可承接上一篇
  • 整个排错过程也更清楚

至此,这台 Moto Edge 60 Pro 的 root 方案基本就算完整了:

  • 上一篇解决“如何在旧系统上获取 root 并通过完整性”
  • 这一篇解决“如何在不粗暴清数据的前提下,跨版本升级并重新接回 root”

如果后面再遇到小版本 OTA,我大概率还是会优先沿用这条思路,而不是直接整包线刷。


Moto Edge 60 Pro OTA 升级保 Root 实录(刷回原版 init_boot + Android 16 重新 Root)
http://example.com/2026/06/16/20260617-motoedge60pro-ota-root-update/
作者
Caleb
发布于
2026年6月16日
许可协议