前言
对于经常刷机的机友来说,"错误7"可能已是家常便饭。遇到"错误7"时,有的机友会先根据经验尝试一些解决方法(比如喜闻乐见的删机型验证、删基带验证、升级Rec等等),有的机友却可以迅速找出原因并轻松解决,这其中的诀窍其实很简单。
如果你对任何一门编程语言有所了解的话,接下来的内容应该不难理解。
一、updater-script是什么?
刷机党们对于这个文件名一定不陌生,此文件必定出现在卡刷包的META-INF/com/google/android/
目录下,虽说是必定,但对于一个卡刷包来说这个文件并不是必要的。
这里可能就有同学要反驳了:卡刷包在刷入过程中不就是执行这个文件吗?错,真正执行的文件是同目录下的update-binary
文件,一般情况下这是个已经编译好的可执行二进制文件。安装卡刷包时,Rec从卡刷包中提取update-binary文件然后执行它,而updater-script
则是告诉了update-binary应该怎么做。
update-binary
一定是个二进制文件吗?不一定,举个例子,SuperSU/Magisk的卡刷安装包,以及Magisk模块安装包,update-binary其实是一个shell脚本,而updater-script
则只有一行注释。这其实一点也不奇怪,相反在Rom卡刷包之外的卡刷包领域非常常见,因为大多数情况下shell脚本兼容性更好且更加方便灵活。
二、Rom卡刷包的基本组成
对于一个Rom卡刷包(以下简称"Rom包")来说,两部分必不可少:system 和 boot.img。
你可以理解两者属于配套关系,就好比system是锁boot.img是钥匙(不过也有些情况下一个boot.img可以配多个system 比如Treble Rom)。
Rom刷入的过程 主要就是重新刷写system分区和boot分区的过程(对于PT设备,再加上一个vendor分区)。
至于两者在Rom包中的存在形式:
boot.img一直都是单独作为一个文件存在,你可以在卡刷包里很方便地找到它。
至于system:
- Android 5.x之前的版本的Rom包是作为一个system文件夹存在的,刷入过程一般是先格式化&挂载system分区,然后解压Rom包内system文件夹里的所有内容到设备的/system目录,之后设置文件和目录权限 uid gid context等信息,以及符号链接,最后取消挂载system分区。
- Android 5.x之后引入了一种新的格式system.new.dat,这其实是system.img的一种变种,有过线刷经验的机友看到system.img一定会眼前一亮,没错就是同一个东西(这样说可能并不严谨,你还需要区分开simg和img)。它通过索引文件system.transfer.list来刷写定量的块数据到system分区,此操作类似于dd命令。这样做的好处是省去了设置文件和目录信息以及符号链接的过程(因为在这个文件生成时就已经设置好了),甚至不用挂载system分区,也一定程度增加了保密性。至于另一个文件system.patch.dat则常用于增量更新包(比如MIUI),一般情况下是个空文件。有什么方法从system.new.dat中提取文件呢?其实有基于Python编写的解包工具sdat2img(壮哉我大Python),该工具可以将其转换为system.img,然后你就有N种方法从中提取文件了(Win环境下可以使用Imgextractor解包,或用7zip打开,Linux环境下直接挂载即可),但打包就要麻烦一些了。
- Android 8.x之后又引入了一种新的格式system.new.dat.br,文件名多了个
.br
后缀,其实是使用了Google的无损压缩技术Brotli压缩之后的system.new.dat文件。你可以使用Google提供的程序将其转换(解压?)为system.new.dat。
至于其他的部分就要依据实际情况了。
比如某些Rom包会集成Magisk卡刷包或逗比音效卡刷包等等到里面(多出现在各种二改Rom包中),刷入Rom包时会自动刷入它(们)。
官方包一般自带底层文件,刷入官方包的同时会更新底层。
有些Romer还会放一些推广app到Rom包里赚点外快,以及类原生包中常见的backuptool脚本和otasigcheck脚本,有很多,在此不再细说。
然而,以上这些,都是由update-binary
和updater-script
控制刷入的,否则它们只是存在在zip包里而已。
三、Edify脚本语言
updater-script
脚本使用的语言被称为Edify语言,下面简单介绍一下常见的函数:
# 在Rec终端中输出Hello world(注意与Rec的终端功能区分开)
ui_print("Hello world");
# 挂载/dev/block/bootdevice/by-name/system分区到/system目录
mount("ext4", "EMMC", "/dev/block/bootdevice/by-name/system", "/system");
# 取消挂载system分区 注意一般情况下不要取消挂载/data 因为内部存储往往是在/data分区中的 取消挂载会出现一些莫名其妙的问题
unmount("/system");
is_mounted
检查指定分区是否已挂载,若已挂载则返回挂载点,否则返回null,常配合if判断使用。
format
格式化分区,用法与mount类似。
delete
删除文件,定义多个参数可以一次性删除多个,简单粗暴。
delete_recursive
递归删除目录,同上,不多解释。
show_progress
用以控制进度条。
# 此函数之后的语句可能需要执行10秒钟 完成后进度条前进25%
show_progress(0.25, 10);
package_extract_dir
从卡刷包中提取目录中的所有文件到指定位置。
# 提取卡刷包system文件夹中的所有文件到设备/system目录
package_extract_dir("system", "/system");
# 如果读取设备机型字符串不是"kenzo" 就终止脚本运行并产生"错误7" 否则继续执行后面的语句
assert(getprop("ro.product.device") == "kenzo");
# 调用/sbin/busybox来挂载system分区
run_program("/sbin/busybox", "mount", "/system");
# 另一种机型验证的方法
if getprop("ro.product.device") != "kenzo" then
abort("Wrong device!");
endif;
Edify脚本的语法:
- Edify脚本的语法与Shell类似,比如
||
和&&
的用法。
- 注意每行语句结尾都要加上分号。
- 除非必要,否则不建议在其中使用中文字符,建议只使用英文(对于没有中文字体的Rec来说,可能会导致字符显示不全,甚至导致Rec卡死)。
- Edify脚本不支持循环语句,if语句的基本结构为:
# 示例代码中的方括号表示可选
if 条件表达式1 then
语句块1
[elif 条件表达式2 then
语句块2
]
...
[else
语句块n
]
endif;
还有一些其他的函数,比如验证基带版本的函数,是没有统一的,不同的机型不同的Rec不同的update-binary可能有所区别,要根据实际情况判断。
其他的一些,比如ifelse
less_than_int
block_image_update
等等,就不再细说了。
如果这些函数都能够熟悉的话,你就能看懂绝大多数的updater-script脚本了,就好比有了牛刀的庖丁。再往深处,你就可以游刃有余地修改updater-script脚本甚至自己编写一个了。
四、常见的"错误7"出现原因
根据Rec在提示"错误7"红字之前的最后几行输出信息进行分析:
This package is for XXX devices; this is a xxx.
解析:机型验证失败,遇到这种问题,除非你有十足的把握(比如相似机型通刷),否则千万不要删除机型验证强行刷入,不然就准备救砖吧。
This package is for XXX devices; this is a.
解析:看起来和例1是一样的,但仔细看的话,从英语语法上来说这是个病句。请注意后半句this is a.
,比起例1缺少宾语xxx
,也就是Rec识别当前设备的型号的结果是个空字符串,此问题多在一些旧版本TWRP上出现,这种情况将TWRP Recovery更新到3.2.0.0以上的版本即可。
Can't install this package on top of incompatible data. Please try another package or run a factory reset
解析:直译:不能在已有的数据上刷入此包,请换个包或者执行恢复出厂设置(也就是双清)。这很好理解,往往是从其他Rom过来刷这个Rom时没双清所导致的。
assert(xiaomi.verify_modem("MSM8976.LA.1.0.c3-30041-STD.PROD-1.77504.1.83742.1") == "1");
解析:(此语句在不同的机型上可能不尽相同)喜闻乐见的基带验证失败,更新指定版本的底包(firmware)即可,或是删掉基带验证(笑)。
system partition has unexpected non-zero contents after OTA update
或 system partition has unexpected contents after OTA update
解析:此错误已不常见,一般是在刷写system分区之后校验分区哈希失败,这种情况应首先哈希校验刷机包的完整性,然后再排查设备的问题,可以尝试格式化system分区后再试。
E1001: Failed to update system image.
解析:多在刷写system.new.dat时出现,具体原因不明,首先哈希校验刷机包完整性,其次尝试替换/更新Rec。
- 其他的情况
注意以上6种出现"错误7"的情况都是使用assert()
和abort()
函数主动提前终止脚本执行而出现的。那么什么是其他的情况呢?比如执行symlink()
函数出现问题时(比如要创建符号链接的目标不存在或创建时没有写入权限),Rec会提示symlink: some symlinks failed
,这是Rec在执行updater-script时遇到了一个无法处理的问题,此时Rec能够提供的信息就很有限了,就需要看recovery日志查找原因了。
五、"错误x"?
Rec会不会产生除"错误7"之外的错误?答案是可以的:
- "错误1":当update-binary是一个shell脚本时,此脚本的exit命令返回非0值时出现。
- "错误2":当update-binary是一个shell脚本时,此脚本语法错误导致无法执行时出现。
- "错误6":当updater-script有语法错误时出现,当你尝试调用一个不支持的函数(由update-binary决定)时也会出现。
出现"错误2"和"错误6"时,rec一般是什么都不输出的,同样需要看recovery日志找原因。
开发者在开发过程中也会遇到"错误11","错误127"等等各种奇葩的错误,在此就不细说了。
THE END