1. 抽屉里那台老 HTC,真正的门槛从来不是“解锁”这一步
每年总有那么几次,会有人抱着一台屏幕已经有点发黄的老机器来找我,说想把它救活。型号多半是 One 系列、Desire 系列或者 Butterfly——这些机器放在今天连当备用机都勉强,但当年确实是一批很有个性的产品。他们的诉求也很统一:解锁 bootloader、刷 recovery、刷 ROM,把原厂那套早就不更新的系统换掉。
我这些年帮人折腾下来,最常见的误区就是大家都把注意力全部放在“解锁”上。觉得只要拿到解锁码、跑完那几行 fastboot 命令,后面就是顺水推舟。实际情况恰恰相反:解锁往往是全流程里最省心的一步,真正让人卡住的是解锁之后那台机器开始不听话——recovery 明明刷进去了,一重启却变回原厂;ROM 包明明刷完了,机器卡在开机动画上十几分钟不动;或者更极端的,刷完之后连 fastboot 都进不去,只能当镇纸用。
这篇内容我想讲清楚的,就是 HTC 这一系列机型从 S-ON 到刷完第三方 ROM 的完整链路。不是那种“复制粘贴六行命令就完事”的速通攻略,而是把每一步背后的分区机制、安全标志位、固件匹配逻辑都摊开来讲。因为 HTC 在安卓阵营里算是把“安全限制”做得最重的一批厂商,它的 S-ON/S-OFF 双状态、CID/MID 双重校验、固件版本绑定,这几层门槛叠在一起,形成一个任何一环没对上就会失败的链条。你只有理解了这层结构,才知道报错码 160 和 170 的区别在哪,才知道为什么有些机型解锁之后依然刷不进 recovery。
适合读这篇的人大概分两类。一类是手上正好有一台 HTC 老机器,想把它刷活,需要一份能照着走的完整流程;另一类是对安卓底层分区、bootloader 安全机制感兴趣,想拿一台结构清晰的机器当样本研究。两类人的目标不同,但需要的底层知识是同一套。我下面会尽量把原理和操作穿插着讲,遇到必须动手的地方给出具体命令和预期输出,遇到容易翻车的地方单独拉出来说。
先说一个总的判断:HTC 刷机的难点不在于“会不会敲命令”,而在于信息核对。你得先知道自己手上这台机器是什么 CID、什么 MID、固件版本到哪一档,这三个信息决定了你能刷什么、不能刷什么。跳过这一步直接开刷,后面所有的坑基本都是这么埋下的。
1.1 S-ON 与 S-OFF:两张不同权限的门禁卡
理解 HTC 刷机,绕不开 S-ON 和 S-OFF 这对概念。S 指的是 Security,本质上是 bootloader 里的一个安全标志位,它决定了哪些分区对你开放写入权限。
用生活化的说法:S-ON 状态下的手机像一栋门禁管理严格的小区,你只有自己那一户的钥匙,公共区域(boot、system、recovery)的门是锁死的;官方解锁相当于给你办了一张访客卡,能进大堂和几部电梯,但配电房、水泵房(radio、hboot、splash 这些底层分区)依然进不去;S-OFF 则是给你配了物业总钥匙,整栋楼随便走。
这三种状态能做的事情差别很大,我整理成表更直观:
| 权限状态 | boot 分区 | system 分区 | recovery 分区 | radio / hboot / splash | 典型用途 |
|---|---|---|---|---|---|
| S-ON 未解锁 | 不可写 | 不可写 | 不可写 | 不可写 | 只能用官方 OTA |
| S-ON 已解锁 | 可写 | 可写 | 可写 | 不可写 | 刷第三方 ROM 的主流状态 |
| S-OFF | 可写 | 可写 | 可写 | 可写 | 刷固件包、改 CID、救砖 |
这里有个关键点很多人没意识到:官方解锁得到的是 S-ON 状态下的有限写入权限,它并没有把 S 标志位改成 OFF。所以你在 bootloader 界面看到的依然是 S-ON,只是多了一行 UNLOCKED。这也解释了为什么解锁之后你能刷 recovery 和 ROM,却刷不了 radio 和 hboot——那些分区不在访客卡的授权范围里。
S-OFF 在 HTC 的历史上是个敏感话题。早期有通过特定 HBOOT 版本漏洞实现的方案,后来主流是通过付费工具或者特定机型的工程模式实现。这块我不展开讲具体手段,因为不同批次、不同 HBOOT 版本的路径差异极大,而且操作本身风险不低——S-OFF 过程中断电或者操作失误,机器变砖的概率明显高于普通刷机。对于绝大多数“我只想刷个第三方 ROM”的需求来说,S-ON 加官方解锁完全够用,没必要为了追求 S-OFF 去冒额外的风险。这是我个人非常明确的一个建议。
1.2 CID 和 MID:决定“这个包能不能进”的两道门
如果说 S-ON/S-OFF 决定的是“能往哪写”,那 CID 和 MID 决定的就是“允许谁写”。
MID(Model ID)是机型标识,比如同一代 One 系列,不同地区版本、不同运营商版本的 MID 可能都不一样。CID(Customer ID)是客户标识,通常和销售区域、运营商绑定。常见的 CID 形式像HTC__001、HTC__621、CWS__001、T-MOB101这种,前面几位基本能看出归属。
这两个值在刷 ROM 时的作用,体现在 ROM 包的 updater-script 里通常会有一段 assert 校验:
assert(getprop("ro.product.device") == "m7" || getprop("ro.build.product") == "m7"); assert(htc_getcid() == "HTC__001" || htc_getcid() == "HTC__621");如果你的机器 CID 不在这份白名单里,recovery 会直接中断刷入,报一个Status 7错误。很多人第一次遇到这个错误会懵,以为是包坏了,其实是 CID 对不上。
查 CID 和 MID 的方法很简单,进 fastboot 模式后跑:
fastboot getvar cid fastboot getvar mid预期输出大概是这样:
cid: HTC__621 mid: PN0710000把这两个值记下来,后面选 ROM 包、判断 RUU 能不能用,全靠它们。
至于 superCID——把 CID 改成11111111以绕过所有 CID 校验——属于 S-OFF 之后才能做的操作,而且改完之后部分官方 OTA 会失效。我一般不建议新手碰这个,除非你确实遇到了“找不到对应 CID 的 RUU”这种死局。
1.3 固件版本:一道看不见但有硬性约束的天花板
第三层限制是固件版本。HTC 的 bootloader 和 radio 打包在一起叫固件(firmware),不同大版本的固件对应的分区布局、boot 镜像格式可能不一样。
举个具体例子:一台机器如果固件停在 5.x 这一档,你直接刷一个要求 7.x 固件的 ROM 包,最常见的结果是刷入过程顺利、重启后卡在第一屏(白底绿字那个 HTC logo 界面)无限循环。因为新 ROM 的 boot.img 里内核期望的分区布局和老固件不匹配,内核起不来。
查固件版本用:
fastboot getvar version-main输出类似6.09.401.5。这个号要和 ROM 包说明里要求的固件版本对得上,差一个大版本基本就不能刷。
所以一个完整的选包逻辑应该是:先看版本号能不能对上,再看 CID 在不在白名单,最后看 MID 是否匹配。三步都过了再动手,能省掉后面大量的排错时间。我见过太多人反过来——先下载一个看起来不错的 ROM,刷不进去再回头查信息,结果查出来是自己的固件版本差了两档,白折腾一晚上。
2. 开工前的三件事:驱动、平台工具、机型信息存档
动手之前的环境准备,是整个过程里最枯燥但最不能省的部分。HTC 这一系列机器对驱动和 USB 环境的要求,比同期很多品牌都要挑剔。我遇到过不止一次“命令没错、包没错,就是 fastboot devices 死活不显示设备”的情况,最后查出来都是驱动或者 USB 口的问题。
2.1 Windows 下的驱动,别指望系统自动装
Windows 10/11 对安卓设备的通用驱动支持其实还行,但 HTC 进 fastboot 模式后的设备标识和正常开机时不一样,系统经常会认成一个未知设备。表现就是adb devices能看到机器,fastboot devices一片空白。
我的做法是手动装驱动,路径是:设备管理器里找到那个带黄色感叹号的设备,右键更新驱动,手动指向解压好的驱动目录。驱动来源有两个选择,一个是 HTC 官方曾经提供的同步工具里自带的驱动包,另一个是通用的安卓 USB 驱动,需要手动编辑 inf 文件把 HTC 的厂商 ID 加进去。
HTC 的 USB 厂商 ID 是0bb4。在 inf 文件里加一段大概长这样:
;HTC %SingleAdbInterface% = USB_Install, USB\VID_0BB4&PID_0FF0 %CompositeAdbInterface% = USB_Install, USB\VID_0BB4&PID_0FF0&MI_01改完之后记得关掉驱动签名强制检查,否则装不上。
这里还有一个特别容易被忽略的细节:别用 USB 3.0 口,也别用前置面板的口,更别接集线器。HTC 老机型在 fastboot 模式下对 USB 3.0 controller 的兼容性很差,插上去大概率识别不稳定,表现为命令能发出去但设备中途掉线。我一般固定用主板后置的 USB 2.0 口,一条质量好点的短线,这个组合的成功率最高。
2.2 平台工具的版本,不是越新越好
安卓的平台工具包(platform-tools)更新很频繁,但新版本对老设备的兼容性反而可能出问题。特别是一些老 HTC 机型,在极新的 fastboot 版本下会出现FAILED (remote: unknown command)这种莫名其妙的错误。
我自己的习惯是备一份相对老一些的版本专门用来刷这类机器,大概在 30 左右的版本号那一档,用下来最稳。新版留着刷新机器用。这个不是玄学,老版本对老协议的兼容处理确实更宽容。
另外提醒一下,平台工具解压出来之后,建议把整个目录加到系统环境变量里,或者干脆把命令都在这个目录下执行。我见过有人把 fastboot.exe 单独复制到桌面用,结果因为找不到同目录下的 AdbWinApi.dll 而报错,排查了半天。
2.3 把机器档案完整存一份下来
这一步是我强烈建议所有人都做的:在动任何东西之前,先把fastboot getvar all的完整输出存成一个文本文件。
fastboot getvar all > my_htc_info.txt输出里会包含 CID、MID、version-main、imei、serialno、分区大小等一大堆信息。这份文件的价值在于,万一后面刷出问题需要救砖,你可以拿它去比对 RUU 包的匹配条件;万一需要找人求助,把这份文件发出去对方一眼就能判断你的机器状态。
getvar all的输出里有些字段涉及设备唯一标识,分享给别人的时候记得把 imei 和 serialno 那两行删掉。
顺带说一句,HTC 的getvar all在某些 HBOOT 版本上会有字段截断或者输出乱序的情况,属于正常现象,不影响使用。
3. 官方解锁的完整走法,以及那几个卡人的返回码
HTC 曾经提供过一条官方的 bootloader 解锁通道,流程是注册账号、提交设备标识、等待邮件、刷入解锁令牌。这套流程在当年算是比较开放的,但也设置了若干限制条件。虽然现在这条通道的实际可用性因机型而异,但流程本身值得完整讲一遍,因为里面涉及的 token 机制、返回码含义,是理解 HTC bootloader 安全模型的最好样本。
3.1 从设备上取出标识 token
第一步是在 fastboot 模式下取设备标识:
fastboot oem get_identifier_token执行后终端会输出一大段以<<<< Identifier Token Start >>>>开头、<<<<< Identifier Token End >>>>>结尾的内容,中间是一堆十六进制字符块。这段内容需要原样完整复制,包括起始和结束标记行,中间不能有多余的空格或者换行错位。我见过有人复制的时候不小心把某一行合并了,提交上去直接报 token 无效。
取完 token 之后,把它粘贴到官方解锁页面的提交框里。如果设备型号在该渠道的支持列表内,几分钟到几小时内会收到一封邮件,附件是一个叫Unlock_code.bin的文件。
3.2 刷入解锁令牌时屏幕会接管操作
拿到Unlock_code.bin之后,把它放到平台工具目录下,机器保持 fastboot 模式,执行:
fastboot flash unlocktoken Unlock_code.bin这条命令的执行结果和普通 fastboot 命令不太一样——它会先输出发送成功的提示,然后手机屏幕上会弹出一个确认界面,问你是否确认解锁。这个界面上需要用音量键移动光标,电源键确认选择。
这个设计当年是为了防止误操作,但也坑了不少人:有些人在电脑前等了半天没反应,以为命令失败了,实际上手机屏幕在等他们按电源键。所以执行完这条命令,眼睛要盯着手机屏幕,不是盯着终端。
选择确认之后,手机会自动重启并清空所有数据。注意,是清空所有数据,包括内置存储里的照片文件。这一步没有撤销余地,动手之前该备份的一定要备份。
3.3 返回码是诊断信息,不是故障编号
解锁过程中最常遇到的几个错误码,含义其实很明确,但官方从来不好好解释,我按实际遇到的情况整理一下:
| 返回码 | 实际含义 | 处理方向 |
|---|---|---|
| Error Code: 160 | MID 不在该渠道的支持列表 | 该机型不支持官方解锁,只能找其他路径 |
| Error Code: 170 | CID 不在支持列表 | 运营商定制机常见,需先改 CID(要 S-OFF) |
| Error Code: 130 | token 校验失败 | 多半是复制粘贴时格式错位,重新取一次 |
| Error Code: 155 | 设备已是解锁状态 | 无需重复操作 |
| Error Code: 159 | 电池电量过低 | 充到 50% 以上再来 |
这里面 170 是最让人无奈的。运营商定制机在出厂时就锁死了 CID,官方渠道不给解锁,而改 CID 又需要先有 S-OFF,形成一个闭环。这类机器的处理路径会复杂很多,不是简单几步能解决的。
还有一个细节:解锁成功之后,bootloader 界面会显示*** UNLOCKED ***,同时 S 状态依然是 S-ON。很多人看到 S-ON 就以为没成功,反复重刷,其实已经到位了。
4. recovery 刷进去了又“消失”:分区覆盖机制与真实原因
这是整个流程里最高频的翻车点,也是我最想重点讲清楚的部分。现象很统一:fastboot flash recovery xxx.img提示成功,紧接着fastboot reboot,等系统起来之后关机再进 recovery——变回原厂那个简陋的蓝色 recovery 了,第三方 recovery 凭空消失。
第一次遇到这个现象的人通常会怀疑是镜像文件有问题,反复换包重刷,折腾半天没有进展。其实这不是刷失败,是刷成功了然后被覆盖了。
4.1 覆盖机制的来龙去脉
安卓系统里有一个机制叫 recovery-from-boot,作用是保证系统更新后 recovery 分区能自动恢复成官方版本。具体实现是 system 分区里放了一份官方 recovery 的备份(通常叫recovery-from-boot.p),在系统启动过程中由某个脚本检测 recovery 分区是否被改动,如果改动过就用备份覆盖回去。
这个机制的初衷是好的——防止恶意软件替换 recovery。但对刷机的人来说就是个障碍。
关键在于触发条件:它是在正常启动进入系统时触发的。所以如果你刷完 recovery 之后立刻重启进入 recovery(而不是重启进入系统),这个覆盖就不会发生。
正确的操作节奏是这样:
fastboot flash recovery twrp.img fastboot boot twrp.img # 部分机型支持直接从内存引导 # 或者刷完后手动断电,用组合键直接进 recoveryHTC 大部分机型不支持fastboot boot从内存引导 recovery,所以实际操作是:刷完之后拔数据线,长按电源键强制关机,然后按住音量下加电源进 bootloader,再用音量键选 RECOVERY 进去。
进去之后要做一件事:立刻在 recovery 里刷入一个能阻止覆盖的东西,或者直接把 system 分区挂载为可写并删掉那个备份文件。TWRP 里通常在高级设置或者文件管理器里能操作,路径是system/recovery-from-boot.p和system/etc/install-recovery.sh这两个,删掉或者改名都行。
4.2 除了覆盖,还有两种“recovery 消失”的假象
第一种假象是根本没写进去。HTC 部分机型的 recovery 分区有额外的写保护,fastboot flash recovery会返回 OKAY,但实际上什么都没写。判断方法是刷完之后不要重启,直接选 RECOVERY 进去看看,如果还是原厂的,那就是没写进去。这种情况多见于 S-ON 状态并且 bootloader 版本较新的机器。
第二种假象是进了错误的 recovery。有些 HTC 机型在 boot 分区里内置了一个简易的恢复模式,当检测到 recovery 分区损坏或者校验失败时会自动跳到这个内置模式。这个界面长得和原厂 recovery 很像,容易让人误判。区分方法是看界面上的版本号和菜单项,内置模式的菜单通常只有三四项,而且没有 adb 支持。
4.3 我总结的一套稳定操作顺序
折腾了这么多台之后,我固定用下面这个顺序,成功率最高:
- 确认机器已解锁(bootloader 显示 UNLOCKED)
- 进 fastboot,刷入 recovery
- 不重启,直接强制关机
- 组合键进 bootloader,选 RECOVERY
- 进入 TWRP 后第一件事:挂载 system,删掉覆盖脚本
- 再做后续的 ROM 刷入操作
第 3 步的“强制关机”看起来粗暴,但它能确保刷入的 recovery 不被系统启动流程干扰。长按电源键 10 到 15 秒,屏幕黑了就松手,这个操作对硬件没有损害。
还有一点提醒:刷 recovery 之前先确认电池电量。HTC 老机器电池老化严重,标称 30% 电量可能实际只剩几分钟。在 recovery 里刷 ROM 的过程中断电,是变砖的高发场景。我一般要求电量显示在 50% 以上才动手。
5. 刷 ROM 的完整链路:固件匹配、boot 分区和首次开机
刷 ROM 这件事,说简单也简单,recovery 里点几下就完事。但它包含了最多的隐性条件,任何一个条件不满足,结果都是同一个——卡第一屏。
5.1 S-ON 状态下 boot 分区要单独刷
这是 HTC 刷机区别于其他品牌的一个关键点,很多人不知道,直接导致刷完开不了机。
在 S-ON 已解锁的状态下,虽然 recovery 有能力写入 boot 分区,但部分机型的 bootloader 会在 recovery 环境下对 boot 分区做额外保护,导致 ROM 包里的 boot.img 写入失败——而且这个失败是静默的,刷机脚本不会报错。结果就是 system 分区更新了,boot 分区还是旧的,内核和系统版本对不上,开机直接卡死。
解决办法是刷完 ROM 包之后,单独把 boot.img 提取出来,用 fastboot 再刷一次:
# 从 ROM zip 里提取 boot.img unzip rom.zip boot.img # 刷入 fastboot flash boot boot.img fastboot erase cache fastboot rebootfastboot erase cache这一步不要省。HTC 的 cache 分区里存着上一次启动的一些状态数据,跨版本刷机之后不清 cache,很容易出现开机动画循环或者应用频繁崩溃。
5.2 双清清的是哪几个分区,顺序有讲究
recovery 里的“双清”这个说法其实不太准确,实际操作涉及的分区不止两个。我一般按下面的顺序清:
- Factory Reset / Wipe Data——清 data 和 cache,这是必须的
- Wipe Cache——单独再清一次 cache,确保干净
- Wipe Dalvik Cache——清虚拟机缓存,跨版本刷机必做
- Format System——格式化 system 分区,可选但建议做
关于最后一项要说一句:format system 之后如果没有成功刷入 ROM,机器是开不了机的,会一直卡在开机界面。所以如果你对自己的 ROM 包没把握,可以先不清 system,等确认包能刷成功之后再考虑彻底格式化。
另外,清 data 会删掉内置存储里的所有东西,包括你自己放进去的 ROM 包。所以刷机包要放在外置 SD 卡上,或者刷之前先确认包在哪个分区。这个错误我见过太多次了,清完 data 发现包没了,只能重新拷。
5.3 首次开机慢到什么程度算正常
刷完 ROM 第一次开机,需要重建 dalvik 缓存、初始化应用数据,耗时明显长于日常开机。一台老 HTC 上,这个过程花 5 到 15 分钟都算正常。
判断标准是看开机动画有没有在动。如果动画在循环播放,说明系统还在初始化,耐心等;如果画面完全静止、卡在某个画面不动超过 20 分钟,那就是有问题了,大概率是 boot.img 不匹配或者固件版本对不上。
遇到卡死的情况,我的排查顺序是:先确认固件版本和 ROM 要求是否一致,再确认 boot.img 有没有单独刷过,最后才怀疑 ROM 包本身有问题。顺序很重要,因为前两个原因占了绝大多数。
还有个小技巧:如果机器支持 adb,卡开机的时候可以尝试adb logcat看内核日志。能连上说明 adb 服务起来了,日志里通常会直接写明是哪个分区挂载失败,比盲目试错高效得多。
6. 变砖抢救:RUU、rebootRUU 和返回码对照
刷机刷到进不去系统、也进不去 recovery,只剩 fastboot 能用的状态,行话叫“半砖”。这种状态其实还有救,只要 fastboot 还认设备,通过 RUU 恢复官方固件的成功率很高。
RUU 是 HTC 官方发布的完整固件包,包含 bootloader、radio、system 所有分区。它的限制条件很明确:CID 和 MID 必须完全匹配,固件版本不能低于当前版本(部分情况可以同版本刷)。
6.1 从 RUU 可执行文件里把 rom.zip 掏出来
官方 RUU 通常是一个 exe 自解压程序,直接运行会走图形界面,但那个界面在老机器上失败率很高。更可靠的做法是把里面的 rom.zip 提取出来手动刷。
提取方法:运行 RUU exe,等它解压到临时目录后(通常在你用户目录的临时文件夹下,会生成一个名字很长的随机目录),把里面的rom.zip复制出来。如果 exe 运行到一半就报错退出,可以在它解压的瞬间去临时目录里找,动作要快。
拿到 rom.zip 之后,确认一下包里的android-info.txt,里面写着这个包要求的 CID 和 MID:
modelid: PN0710000 cidnum: HTC__001 cidnum: HTC__621 mainver: 6.09.401.5对照你自己记下来的机型档案,能对上才能刷。
6.2 为什么同一个包要刷两遍
手动刷 RUU 的流程是这样:
fastboot oem rebootRUU fastboot flash zip rom.zip第一条命令会让手机进入一个黑底银字的 RUU 模式。第二条命令刷入时,第一次执行大概率会在中途返回FAILED (remote: 90 hboot pre-update!)。
这个 90 不是错误,是提示。它的意思是 hboot 部分已经更新完成,需要重新执行一次刷写来继续处理剩下的分区。所以看到 90 不要慌,再跑一遍同样的命令:
fastboot flash zip rom.zip第二次会完整跑完,最后输出OKAY。然后执行:
fastboot reboot如果两次都返回 90,那可能是包和机型不匹配,需要回头检查 CID/MID。
6.3 常见返回码与症状对照
| 返回码 / 症状 | 可能原因 | 处理方式 |
|---|---|---|
| 90 hboot pre-update | 正常的分段更新提示 | 重新执行一次 flash zip |
| 12 signature error | 包签名校验失败,包被修改过 | 换官方原始包 |
| 41 model id check fail | MID 不匹配 | 换对应机型的包 |
| 42/43 CID check fail | CID 不在白名单 | 换对应 CID 的包,或先改 CID |
| 99 unknown fail | 分区写入异常 | 检查 USB 连接,换线换口重试 |
| 刷完卡第一屏 | boot.img 与 system 版本不匹配 | 单独刷对应版本的 boot.img |
| 进不了 recovery 但能进系统 | recovery 被覆盖 | 见第 4 节的覆盖处理 |
最后说一个我自己的经验。老机器的 eMMC 闪存经过这么多年,坏块概率明显上升。如果同一个包反复刷都在同一个进度百分比失败,而且换包换线都一样,那要考虑是不是闪存本身出问题了。这种情况下再怎么折腾软件层面都是徒劳的,唯一的办法是接受它当前的系统版本,或者换一块主板。
还有一种情况值得单独提:有些机器刷完之后能开机、能用,但基带没了,表现为无信号、IMEI 显示未知。这一般是 radio 分区在刷写过程中被意外擦除。S-ON 状态下没法直接刷 radio,只能通过对应版本的 RUU 整包恢复。所以刷 ROM 之前把 RUU 包先下载备好,这个习惯能救不少急。
折腾这类老机器,说到底是个信息核对和耐心的活。命令本身没几条,难的是知道每条命令背后动了哪个分区、为什么必须按这个顺序。我自己这些年刷下来,最深的体会是:动手前花二十分钟把 CID、MID、固件版本、分区布局查清楚,能省掉后面两小时的排错。急着按教程复制粘贴的人,最后往往要花更多时间在“为什么我不一样”这个问题上。至于那些实在救不回来的机器,也别太执着,它们该退休了。