简介:一套面向安卓ROM开发者与刷机爱好者的第三方ROM编译辅助工具合集,围绕「一键解包/打包」核心能力整合了boot与recovery分解合成、system/ODM/vendor等分区镜像处理、payload.bin与super格式解包、高通机型镜像合并及华为Updata.app/OFP/OZIP格式转换等大量操作,操作项高度集成,尤其适合需要快速制作、定制或移植第三方ROM,又不愿逐条记忆手工命令的中高级玩家使用。7z压缩包约201.29MB,共338个文件,以exe可执行程序与dll动态库为主,另有bat批处理脚本、jar工具、txt说明、sys驱动、inf配置及少量签名密钥类文件,功能模块划分清晰,可直接调用对应脚本完成内核打包、Recovery打包、镜像格式互转等任务。目前已有4018人学习下载。工具覆盖常见品牌的固件解包与签名加密流程,另附开机第一屏logo制作等附加功能,建议在非中文路径下运行以避免脚本报错,是一套上手即可用的ROM制作工具集。
1. ROM解包打包这件事:工具选型和使用边界
ROM解包打包不是新鲜技能,却是做第三方ROM绕不开的基本功。CM(CyanogenMod)或LineageOS系固件、各家官网放出的全量包、酷安上那些名称五花八门的优化包,剥开看都是boot、system、vendor这些img分区按固定规则组成。做第三方ROM、精简系统、预置APK、改开机动画和屏幕密度,本质就是把这个结构拆开,改完再装回去,最后封成别人能直接刷入的zip。
但解包不是双击解压那么简单。卡刷包里那个system.img往往是稀疏镜像,boot.img带内核、ramdisk和一圈header参数,错一步或者动到不该动的权限和SELinux label,刷进去就是不开机,严重时连rec都要重刷。普通教程只讲“解压出来改完再压回去”,不讲工具链选型、分区边界和权限保留,这正是大量翻车的根源。
这篇笔记写给两类人:一是想把官方包改成自己顺手形态的ROM DIY新人,二是已经动过手、但总在sparse镜像、签名校验、分区大小上反复折腾的从业者。前提是你手里有一台能进fastboot或TWRP的设备、一根不松动的数据线,以及刷坏了能救回来的底气。后文按解包、打包、避坑、验证的顺序走,每一步都尽量给到能直接复用的参数。
2. 解包:从线刷包到可编辑的镜像分区
2.1 先分清包类型,再选工具
拿到包别急着解压,先看扩展名和file输出。卡刷包是zip,解压后是几个img和META-INF/update脚本;线刷包是tar或md5+img,要fastboot或Odin刷写;Android 10之后很多全量OTA是payload.bin,直接unzip看不到分区。我就曾经花半天挂载一个sparse镜像,报错报得人发懵,后来才意识到simg2img跑漏了,这就是没先识别格式的代价。
工具选型上,我的默认清单是:unzip和tar干粗活;simg2img把sparse转raw;payload-dumper-go处理payload.bin;unpack_bootimg/mkbootimg管理boot.img;make_ext4fs重新生成system镜像;signapk做卡刷包整体签名。其中unpack_bootimg和mkbootimg要配套使用,解包版本和打包版本最好保持同一套,避免新版本悄悄改过对齐方式。
| 包类型 | 外表特征 | 预处理 | 目标文件 |
|---|---|---|---|
| 卡刷zip | zip,含META-INF | unzip直接解 | boot.img / system.img / vendor.img |
| 线刷包tar | tar/md5,含img | tar解包 | boot.img、modem等分区img |
| payload.bin | 二进制,头两字节为cr | payload-dumper-go | 导出各分区img |
| sparse img | file显示“Android sparse image” | simg2img | 转raw后挂载 |
注意:如果file输出里带“Android sparse image”,而你直接mount -o loop,大概率报wrong fs type。这不是权限问题,是镜像没转raw。这也是后面避坑章里排第一位的经典翻车。
2.2 解包boot.img和system.img的完整步骤
假设你从卡刷包开始。我会在本地建三个目录:out放原包解压产物,boot放boot.img拆出来的东西,system放挂载出来的文件树。
mkdir -p ~/rom/{out,boot,system} unzip ~/download/official_cm.zip -d ~/rom/out file ~/rom/out/system.img # 输出示例: Android sparse image, 51240960 bytes in 1000960 blocks ~/bin/simg2img ~/rom/out/system.img ~/rom/out/system_raw.img cd ~/rom/boot unpack_bootimg --boot_img ~/rom/out/boot.img --out . --format=mkbootimg ls -R ~/rom/boot # 预期看到 kernel、ramdisk目录、dtb,以及header参数文件逻辑说明:unzip只是解开卡刷包外壳,img保持原格式;simg2img把稀疏表展开成raw ext4,展开后文件明显变大,属正常;unpack_bootimg会读取boot header里的base、pagesize、cmdline等并输出在同目录,这些参数在打包时要原样填回。--format=mkbootimg是兼容老包时的常见参数,如果你的boot.img来自很老的平台,建议加上。
挂载system镜像时,我一般先只读挂载看结构,确认后再重新rw挂载。因为root挂载ext4时即使不写文件,atime也可能更新metadata,虽然不影响核心功能,但会让后续对比镜像时多出没必要的diff。
sudo mount -o loop,ro ~/rom/out/system_raw.img ~/rom/system ls -lZ ~/rom/system/build.prop sudo umount ~/rom/system # 确认结构后,以rw重新挂载 sudo mount -o loop ~/rom/out/system_raw.img ~/rom/system参数说明:loop是使用回环设备挂载img文件,ro代表只读,避免atime被更新。如果umount时报target is busy,通常是终端工作目录还停在该挂载点,切到别的目录再umount即可。若你不想用sudo反复挂载,也可以把raw镜像解到目录里直接处理,但那样SELinux label更容易丢,所以我一直保留loop挂载的方式。
2.3 特殊包:payload.bin和线刷包的处理
Android 10之后的官方全量包很多是payload.bin,里面按update_engine的格式存储分区,解包用payload-dumper-go。完整命令如下。
~/bin/payload-dumper-go -o ~/rom/payload_out ~/rom/out/payload.bin # 输出目录里会看到 boot.img、system.img、vendor.img、vbmeta.img 等这个目录下通常还有vbmeta、dtbo这些和启动验证相关的分区。它们一般不改,但等会儿重新打包时不能漏,漏了在新平台上可能被AVB校验拉去fastboot。这一条我们放在第4章的避坑里细说。
老设备的线刷包解开就是tar:
tar -xf ~/rom/smd.tar.md5解出来的img可以直接走boot/unpack流程,没有特别差别。唯一要注意的是部分线刷包里的system.img已经是raw,不需要simg2img,file看清楚再动手。tar包里的分区比卡刷包更全,radio、modem这些都在,但做第三方ROM通常不需要动它们。
2.4 权限、属主和SELinux context为什么不能动
system分区里的每个文件,除了内容,还有三层元数据:属主/组、mode、SELinux label。CM系包的system/build.prop通常是root:root、0644、u:object_r:system_file:s0。你用宿主机root随便cp -r进去,属主可能变成host用户,label直接丢失。现在Android默认强制SELinux,缺少label的可执行文件和库在init阶段就被拒绝,表现为开机动画转几圈后重启。
我的规范做法:解包后建一个改动日志,记录每次chmod/chown/label变更;能用sed、perl原地改的不要新建文件;必须在目录里新增文件时,从旧文件复制属性:
mkdir -p ~/rom/system/app/MyApp cp -p ~/rom/system/app/LegacyApp/LegacyApp.apk ~/rom/system/app/MyApp/MyApp.apk chmod 644 ~/rom/system/app/MyApp/MyApp.apk chown root:root ~/rom/system/app/MyApp/MyApp.apk ls -lZ ~/rom/system/app/MyApp/参数说明:cp -p保留权限、属主、时间戳;ls -lZ用来复核SELinux label。看到unknown或问号说明label已经丢,别试着手动补,回到原始镜像重新解包,再跑一遍你的改动脚本,这样才可控。
一个提高成功率的手段是,把改动写成bash脚本,每次解包后整段重放。比如build.prop的sed、APK目录的创建、二进制替换,都集中在一份脚本里,就不容易因为操作顺序不同产生隐性的不一致。我在给旧机型维护包的时候,脚本里还会顺手把file_contexts的路径打印出来,提醒自己下一步打包要用到。
提示:解包后的第一件事是先记录原包字段,再开始改文件。字段信息是后面打包和排错最大的参照系。
3. 打包:把改动写回镜像并生成可刷入的ZIP
3.1 build.prop调整和预置应用
第三方ROM定制里,改build.prop的需求非常高频:改屏幕密度、机型识别、开发者选项开关,给某些应用伪造设备型号。CM系包常见路径是/system/build.prop,部分厂商包把属性分开放在/vendor/build.prop,两个都要检查。
改之前先备份,这步成本很低但救过我很多次。另一个隐蔽坑是行尾符。Android属性服务对行尾敏感,CRLF会导致一行属性解析异常,表现是设置里少一些选项,某个属性读出来的值是乱码。我用cat -A来检查:
cp -p ~/rom/system/build.prop ~/rom/system/build.prop.bak sed -i 's/ro.sf.lcd_density=480/ro.sf.lcd_density=420/' ~/rom/system/build.prop sed -i '/^ro.debuggable=/d' ~/rom/system/build.prop echo 'ro.debuggable=1' >> ~/rom/system/build.prop cat -A ~/rom/system/build.prop | head -5 # 正常结尾是 $;出现 ^M$ 就是CRLF,需要去\r sed -i 's/\r$//' ~/rom/system/build.prop逻辑说明:第一条sed替换分辨率密度,第二条先删除可能存在的旧ro.debuggable,第三条再追加,避免出现两个同名属性。cat -A看到$才是LF,遇到^M就执行最后的清理。这个操作不会影响其他正常行。
预置应用不只是把APK丢进去。经典布局是system/app/应用名/应用名.apk,权限目录755、文件644、属主root:root。这个三元组错了,包管理服务要么忽略应用,要么开机扫描时刷一堆警告。
mkdir -p ~/rom/system/app/MyApp cp ~/download/MyApp.apk ~/rom/system/app/MyApp/MyApp.apk chmod 755 ~/rom/system/app/MyApp chmod 644 ~/rom/system/app/MyApp/MyApp.apk chown -R root:root ~/rom/system/app/MyApp如果你要预置的是普通应用,签名可以是自己的release签名;如果应用声明了sharedUserId=“android.uid.system”,必须用系统签名重新签,否则放进system一样会被kill。这个属于定制进阶,预置前用apksigner看一圈manifest更稳。
3.2 重新生成boot.img和system.img
打包boot.img本质是复现boot header。unpack_bootimg解包时已经把kernel、ramdisk、dtb和其他字段放在目录里,我习惯把header字段单独抄到记事本,打包时逐项对着填,而不是靠模糊记忆。
cd ~/rom/boot mkbootimg --kernel kernel \ --ramdisk ramdisk \ --base 0x80000000 \ --pagesize 2048 \ --cmdline "androidboot.hardware=qcom ..." \ --output ../out/boot_new.img三个关键参数是base、pagesize和cmdline,都必须与解包所得完全一致。pagesize最容易被忽略,高通平台常见2048或4096,写错后内核可以解压但无法正常进入系统,而且看起来像随机重启。解包结果里有dtb就加--dtb,没有就不加,不要自己硬想一个出来。
system.img打包,我用make_ext4fs。核心是分区长度。从原包分区表拿到system分区合法大小,在这个基础上留1%-3%余量,别按实际文件大小去给。实际文件大小填进去会造出一个缩水的文件系统,部分强验证机型刷完会报分区大小不匹配。
du -sh ~/rom/system # 示例输出 1.1G,但原分区约2GB make_ext4fs -s -l 2147483648 -a system \ -f ~/rom/file_contexts \ ~/rom/out/system_new.img ~/rom/system参数说明:-s生成sparse镜像,刷机脚本写起来更快;-l指定生成镜像的字节大小;-a system表示以/system做挂载锚点,配合-f指定的file_contexts生成正确label。很多第三方教程忽略file_contexts,导致镜像文件对,但label全区缺失,刷进去一样翻车。CM系的file_contexts通常出现在解包zip的根目录或vendor/etc目录下,找不到就先搜。
3.3 updater-script、ZIP结构和签名
重打包ZIP时,把原META-INF/MANIFEST.MF、CERT.RSA、CERT.SF删掉,只保留update-binary和updater-script,否则旧签名和新签名会互相打架。updater-script的顺序是format、mount、package_extract_file、unmount。以高通设备的by-name路径为例:
ui_print("Installing custom system..."); format("ext4", "EMMC", "/dev/block/bootdevice/by-name/system", "0", "/system"); mount("ext4", "EMMC", "/dev/block/bootdevice/by-name/system", "/system"); package_extract_file("system_new.img", "/dev/block/bootdevice/by-name/system"); package_extract_file("boot_new.img", "/dev/block/bootdevice/by-name/boot"); unmount("/system");逻辑说明:先format再mount,保证写入前分区是干净状态;package_extract_file把镜像直接写到块设备,不走逐文件释放,速度快且不易断点。部分rec版本要求不能同时mount和写同一个分区,遇到“Device or resource busy”时,把mount、package_extract_file顺序调成先写后mount也是可行解。
by-name路径不同机型差异很大,小米、三星、老MTK都各有一套写法。我在TWRP终端里会先用readlink确认:
ls -l /dev/block/bootdevice/by-name/system readlink -f /dev/block/bootdevice/by-name/system这条路径是物理分区的符号链接,rec更新或内核变化后仍能保持稳定,比裸的mmcblk0p不写死板。拿到真实路径后再回写updater-script。
组装zip和签名命令:
cd ~/rom/out zip -r0 rom_unsigned.zip META-INF system_new.img boot_new.img java -jar ~/bin/signapk.jar -w \ ~/keys/testkey.x509.pem ~/keys/testkey.pk8 \ rom_unsigned.zip rom_signed.zip逻辑说明:zip -r0里的0是store不压缩,img本身已是压缩后的数据,再压浪费CPU;签名必须带-w,whole-file签名方式才是卡刷包要的,用apksigner签APK那套在rec里会直接失败。发布给陌生人时,我会在签名后再跑一遍java -jar signapk.jar -verify,确认签名完整才拿出去。
4. 避坑:ROM解包打包最常见的5个翻车现场
4.1 解包阶段:挂载失败、权限污染、boot字段对不上
翻车一:直接挂载sparse img报wrong fs type。
现象:mount -o loop提示“wrong fs type, bad magic, superblock”,看文件也确实像ext4,但就是挂不上。
原因:得到的是Android sparse image,不是裸ext4。file命令输出里写着sparse,被大多数人忽略。
解决:先执行simg2img system.img system_raw.img,再mount raw镜像。这个坑我踩过不止一次,后来养成了解包后第一件事就是跑file命令的习惯。还有一个更隐蔽的情况是厂商改过镜像魔数,file输出会显示成data,这时用imjtool去看分区偏移,按偏移用losetup挂载。但那个属于极少数,普通包用simg2img足够。
翻车二:解包后随便改文件,刷入开机应用全部force close。
现象:系统能启动,桌面也有,但Settings和一堆系统应用反复闪退,logcat里大量Permission Denied。
原因:文件和目录的uid/gid/mode被宿主机污染。最常见的动作是用root在system目录里cp -r或touch,把属主改成了host映射的uid,目录权限也从755变成了700。
解决:回到原始包重新解包,把改动脚本化。新加入文件一律chmod 644或755,chown root:root。这一步虽然啰嗦,但能根治问题。后续排查时用find找非root属主的文件:
find ~/rom/system -type f ! -uid 0 | head -20 find ~/rom/system -type d ! -perm -755 | head -20第一行查属主异常的文件,第二行查权限过窄的目录。执行结果正常的话,两条命令的输出应该是空的。
翻车三:boot.img打包后刷入黑屏,手机只能进fastboot。
现象:解包后没改内核,只是重新打包,刷完就黑屏,呼吸灯亮但屏幕没有任何输出。
原因:mkbootimg的base、pagesize、cmdline和原boot header不一致,或ramdisk压缩格式不一样。最常见的是pagesize记成默认2048,但原包是4096。
解决:解包完把unpack_bootimg输出的header参数存好,按原值回填。如果原包ramdisk是gzip,打包前别用lz4重新压缩,保持和原包一致。我一般会在打包前先对比原boot.img和打包后boot_new.img里的字符串:
strings ~/rom/out/boot.img | grep -i "androidboot" | head -5 strings ~/rom/out/boot_new.img | grep -i "androidboot" | head -5两份输出的cmdline必须一致,有一处不同都不进系统。
4.2 刷入阶段:updater脚本报错、签名失败
翻车四:TWRP报Error 7,脚本没执行完。
现象:刷入一到三秒就报“Error 7”或“updater process ended with error”。
原因:updater-script里的getprop断言机型不匹配,或者脚本是从Windows记事本拷贝来的,CRLF行尾导致Edify解析失败。TWRP看日志时,经常能看到“failed to parse”或“expected ; but got”这类提示。
解决:打开updater脚本检查第一行的getprop断言,改成你机型的ro.product.device值;同时执行sed -i 's/\r$//' updater-script。如果日志显示具体某条命令不认识,比如format或package_extract_file拼错,用正确的函数名重写。我可以给一个判断技巧:把报错行的开头和官方LineageOS同机型包里的updater-script对比一下,能省很多排查时间,因为官方脚本的基本框架是经过大量设备验证的。
翻车五:签名后刷入显示signature verification failed。
现象:rec页面明确提示签名校验失败,包根本不会被刷入。
原因:一是zip内残留原厂签名文件,二是用了APK签名工具而不是whole-file签名。很多人拿到signapk直接按APK方式来,生成的签名只在zip的META-INF里有,rec端不认识。
解决:删除META-INF/MANIFEST.MF、CERT.RSA、CERT.SF,重新打包,用signapk -w做整包签名。调试期可以临时关rec签名校验,但向别人发布时别关,签名校验是降低误刷率的第一道门。新平台还有一个连带现象:从payload.bin解出来的vbmeta只读分区没有带进新包,AVB校验会拦到fastboot。处理方式是保留原vbmeta不修改,若要彻底禁用验证,用avbtool清掉vbmeta的flags,而不是把分区从包里删除。
5. 从CM到自定义固件:刷入验证与调优技巧
刷机不是刷进去就完了,我每次发自己的包都会走三步验证。第一步是刷前校验,在电脑上先md5sum签名包,再在TWRP里对照一遍;同时确认包体积小于目标分区剩余空间。第二步是刷完后的logcat过滤。很多问题不是不能开,而是异常刷屏,不抓日志很难定位。
md5sum rom_signed.zip adb logcat -b all -d > flash_log.txt grep -iE "error|fatal|denied|failed" flash_log.txt | head -50grep出来的denied大部分是SELinux拒绝,这时候要区分是label问题还是策略问题:label问题回到重建镜像前加file_contexts,策略问题则提取logcat里的avc信息,到policy里补allow。这一步在CM系和LineageOS系上都适用,因为它们的SELinux策略相对完整,手动包的问题几乎都出在label。
一个我坚持了很久的习惯:每次改动都在干净的原始包上重跑脚本,并把中间镜像、签名产物、分区大小写进一个改动记录文件。某次发布前我图快,在一个已经改过一堆东西的目录上继续改DPI,结果把之前手动调整过的两个配置文件弄混,整个包刷进去WiFi打不开,排查了两个晚上。从那以后,我每做一个版本都强制走一遍“干净环境重跑→校验镜像→签名→双清刷入→logcat过滤”,确认无误才拿出去给身边人测试。这组步骤看着笨,但确实是做第三方ROM最有效的后悔药。希望帮到你。
本文还有配套的精品资源,点击获取