ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

从PGPT分区表自动生成MTK线刷scatter的Python方案

从PGPT分区表自动生成MTK线刷scatter的Python方案 前阵子一个朋友送了我一台MTK平台的安卓手机成色不错但系统被他刷得只剩fastboot。正常情况下去找官方线刷包就能救可这台机器比较冷门网上翻遍都找不到带scatter的现成资源。后来我把他之前随手备份的一个PGPT分区表镜像拿出来用Python花了半小时解析直接生成了一份可用的MTK线刷引导文件scatter刷机一次通过。这事之后我就想与其让大家继续卡在“没有scatter”上不如把这套完整流程写出来。下面这套流程会从PGPT分区表的原理讲起教你如何在手机还能开机、还能root的时候把PGPT备份出来然后用一个纯Python脚本解析分区信息自动生成SP Flash Tool需要的线刷引导文件最后附上我自己踩过的坑以及校验方法。不管你是第一次接触MTK刷机的新手还是被各种半吊子工具坑过多次的老手这套流程应该都能帮上忙。1. 先搞清楚PGPT在MTK手机上到底有多重要1.1 一次误刷分区我把备用机刷成砖之后先说一个我自己的真实案例。去年打算给一台MT6765的老手机刷入第三方REC结果在刷写时用了一个不匹配的scatter把原本在userdata开头的分区写到了system区域。开机后正常系统直接起不来fastboot也进不去只能尝试救砖。当时手上只有一张朋友之前用读分区工具导出的PGPT备份没有官方的scatter。就是在那种局面下我才第一次意识到PGPT备份远比想象中有用它记录了整块存储的分区边界是救砖的“地图”有了它重新生成scatter根本不难。如果你只是正常用手机PGPT一辈子都用不上可一旦走到“需要线刷但找不到配套scatter”这一步它就是你翻盘的关键。所以我现在的习惯是任何MTK手机到手只要已经root先花五分钟做一次PGPT备份放到电脑和网盘各一份并记录校验值。这个习惯的回报率极高不要等砖了才后悔。1.2 GPT分区表的结构LBA0到LBA33都在存什么PGPT在MTK圈子里通常被称为“保护性GPT”或“GPT分区表备份”本质上它就是存储设备最前面那一段用来描述分区布局的原始数据。现代GPT结构大致长这样LBA0保护性MBR512字节防止老式磁盘工具不识别GPT盘。LBA1GPT头512字节里面记录“EFI PART”签名、分区表起始位置、分区项数量和大小。LBA2到LBA33主分区表项标准128个分区项每项128字节一共32个LBA。LBA34及以后真正的用户分区起点。MTK平台通常会把前面这34个LBA看成一块特殊的“pgpt区域”所以你在手机的/dev/block/by-name/下能看到名为 pgpt 的分区。很多教程里说的“备份PGPT”实际操作就是把这个区域的二进制镜像完整抠出来。1.3 MTK的scatter文件为什么必须和PGPT一一对应SP Flash Tool刷机时并不认识分区名它只认scatter文件里写的linear_start_addr线性起始地址和partition_size分区大小。你告诉它某个分区在0x440000它就把数据往0x440000写。如果地址和手机实际的GPT分区布局对不上后果就是数据写到别人的地盘把boot写到system区域、把logo写到recovery区域轻则功能异常重则直接变成砖。官方线刷包里的scatter只适用于原厂分区布局。如果手机被刷过第三方分区方案、扩容过userdata、或者官方本身有版本差异旧scatter就可能与实际布局错位。而基于本机PGPT生成的scatter是从你设备上当前的真实分区表解析出来的天然和这台手机绑定不会再出现“地图和路对不上”的问题。2. 数据从哪来导出手机PGPT的几种可靠办法2.1 条件准备一台能进系统的root手机加adb动手之前先确认两件事手机能正常进入系统并且已经获得root权限。PGPT备份本质上要直接读取物理块设备没有root是做不到的。电脑上装好adb工具手机连接电脑后在开发者选项里打开USB调试。这里我默认你已经会基本adb操作如果还没搞定adb环境可以先花几分钟配一下不影响后面的流程。另外要确认平台是eMMC还是UFS。绝大多数中低端MTK手机用的是eMMC块设备路径是/dev/block/mmcblk0部分新机型用UFS设备名可能是/dev/block/sda或带lun的路径。如果想偷懒优先查/dev/block/by-name/目录那里有直接映射到pgpt分区的软链接路径差异会小很多。2.2 用dd命令从eMMC导出PGPT在root shell里直接用dd命令把物理块设备最前面的34个LBA抠出来命令很简单adb shell su -c dd if/dev/block/mmcblk0 of/sdcard/pgpt.bin bs512 count34 adb pull /sdcard/pgpt.bin ./为什么是count34因为LBA0到LBA33正好是前面说的保护性MBR、GPT头和主分区表项。如果你怕平台差异导致分区表项更多有些平台会写到256项可以保守一点直接count64多读出来的数据不影响解析只会让备份文件大那么一丁点。还有一种更精确的方式直接备份by-name/pgpt分区。adb shell su -c ls -l /dev/block/by-name/ | grep pgpt adb shell su -c dd if/dev/block/by-name/pgpt of/sdcard/pgpt_by_name.bin重定向到/sdcard再pull是因为部分手机的su shell里对/data/local/tmp之外路径的写权限略麻烦放sdcard最稳。备份完成后建议执行一次sha256sum pgpt.bin记录哈希今后想确认文件是否损坏就能直接对比。2.3 已有全量备份时怎么把PGPT单独抠出来如果你没有提前备份但手里恰好有一份之前用SP Flash Tool Readback读出来的全盘镜像或者从其它渠道拿到的整机备份也可以把PGPT部分单独抠出来。标准的PGPT区就是文件最前面的0x4400字节34个LBA×512字节用Python三行就能搞定with open(full_dump.bin, rb) as f: pgpt f.read(0x4400) with open(pgpt_from_full.bin, wb) as f: f.write(pgpt)多读一点也无所谓比如直接读0x8000字节后面多出来的数据解析时会自动忽略。这里有个很容易忽略的坑如果全盘镜像是用bs4096的方式读出来的那LBA大小就不是512而是4096前面的0x4400偏移就会错位。判断方法很简单用十六进制编辑器打开镜像看偏移0x200512字节处是不是“EFI PART”字符串。如果是说明LBA是512如果不是检查偏移0x10004096字节处。找到签名位置后偏移基准自然就清楚了。3. 解析与生成Python脚本完整拆解3.1 先读懂新版MTK scatter文件的字段规范在写脚本之前先认识一下MTK scatter长什么样。现在新平台的scatter文件是类似这样的文本结构- general: platform: MT6785 project: my_device storage: EMMC boot_mode: download ram_pt: 0 - partition_index: SYS0 partition_name: preloader file_name: preloader_my_device.bin is_download: true type: SV5_BL_BIN linear_start_addr: 0x0 partition_size: 0x100000 region: EMMC_BOOT_1 storage: HW_STORAGE_EMMC boundary_check: true is_reserved: false operation_type: UPDATE reserved: 0对GPT解析来说最关键的两个字段是linear_start_addr分区起始字节地址和partition_size分区大小。其它字段更多是给SP Flash Tool做人机交互和校验用的。理解了这一点就能明白为什么只靠PGPT也能生成一份能用的scatter。3.2 脚本整体设计和依赖选择脚本只依赖Python标准库struct负责解包二进制sys负责参数读取不需要安装任何第三方包。整体流程是打开PGPT备份文件定位并解析GPT头得到分区表起始位置、分区项数量和每项大小然后遍历分区项提取分区名、起始LBA、结束LBA最后换算成字节地址并套用scatter模板写出配置。为什么不用别人写好的现成工具一是很多工具只支持固定分区数量遇到特殊平台就翻车二是自己写的脚本可以随时加过滤条件比如想只导出super、userdata等几个大分区改一个参数就行三是整个过程可控出了问题你知道每一行数据是怎么来的。3.3 解析GPT头校验签名、定位分区表GPT头在文件偏移512字节处LBA1。先读16字节判断签名正常的前8字节是ASCII的“EFI PART”。接下来几个关键偏移偏移72主分区表起始LBA8字节小端整数。偏移80分区项数量4字节小端整数。偏移84每个分区项大小4字节小端整数。标准GPT中分区表起始LBA是2分区项128个每项128字节。但MTK不同平台不一定完全按标准来所以脚本里从头部动态读取不写死。3.4 提取分区项名称、起始LBA、大小一个都不能错分区表项从起始LBA对应的字节偏移开始。每个分区项128字节关键字段如下偏移0分区类型GUID16字节。偏移16分区唯一GUID16字节。偏移32起始LBA8字节小端整数。偏移40结束LBA包含8字节小端整数。偏移48分区属性8字节小端整数。偏移56分区名72字节UTF-16LE编码。判断一个分区项是否有效先看类型GUID是不是全零。全零表示空条目直接跳过。分区大小计算时结束LBA是包含的所以(end_lba - start_lba 1) * 512才是字节数。名称必须用utf-16-le解码再用rstrip(\x00)去掉末尾的空字符不然输出里会带一串乱码。3.5 拼装scatter地址计算和模板套用拿到每个分区的起始LBA后字节地址就等于起始LBA乘以512转成十六进制填进linear_start_addr。GPT里的分区名一般是全小写可以直接作为partition_name。type字段可以简单推断preloader、boot、recovery、vbmeta这类引导相关分区用SV5_BL_BIN其它分区用NORMAL。这个推断不是绝对标准但SP Flash Tool对type的容错还算好后续真遇到加载问题再手动调整。还有一个必须提醒的事情从GPT解析出来的分区列表里通常没有preloader。因为preloader不占用用户区LBA它放在eMMC的BOOT1分区。所以如果线刷时需要preloader得单独用dd if/dev/block/mmcblk0boot0导出并手动在scatter里补一个region为EMMC_BOOT_1的条目。3.6 可用的完整代码照抄就能跑把下面这段保存成pgpt2scatter.py然后按注释里的用法执行即可。#!/usr/bin/env python3 # -*- coding: utf-8 -*- import struct import sys GPT_SIGN bEFI PART LBA_SIZE 512 def parse_gpt_header(data): header data[LBA_SIZE:LBA_SIZE * 2] if header[0:8] ! GPT_SIGN: raise ValueError(Invalid GPT signature, not a PGPT backup) part_lba struct.unpack_from(Q, header, 72)[0] part_count struct.unpack_from(I, header, 80)[0] part_size struct.unpack_from(I, header, 84)[0] return part_lba, part_count, part_size def parse_partitions(data, part_lba, part_count, part_size): partitions [] for i in range(part_count): off part_lba * LBA_SIZE i * part_size entry data[off:off part_size] if len(entry) part_size: break type_guid entry[0:16] if not any(type_guid): continue start_lba struct.unpack_from(Q, entry, 32)[0] end_lba struct.unpack_from(Q, entry, 40)[0] raw_name entry[56:128].decode(utf-16-le, errorsreplace) raw_name raw_name.rstrip(\x00).strip() partitions.append({ name: raw_name, start_lba: start_lba, end_lba: end_lba, size: (end_lba - start_lba 1) * LBA_SIZE, addr: start_lba * LBA_SIZE, }) return partitions def infer_part_type(name): name_lower name.lower() if name_lower in (preloader, boot, recovery, vendor_boot, vbmeta, vbmeta_system, vbmeta_vendor): return SV5_BL_BIN return NORMAL def gen_mtk_scatter(partitions, platformMT6785, projectpgpt_backup, storageEMMC): lines [- general:, f platform: {platform}, f project: {project}, f storage: {storage}, boot_mode: download, ram_pt: 0, ] for idx, p in enumerate(partitions): lines.append(f- partition_index: SYS{idx}) lines.append(f partition_name: {p[name]}) lines.append(f file_name: {p[name]}.img) lines.append(f is_download: true) lines.append(f type: {infer_part_type(p[name])}) lines.append(f linear_start_addr: 0x{p[addr]:X}) lines.append(f partition_size: 0x{p[size]:X}) lines.append( region: EMMC_USER) lines.append( storage: HW_STORAGE_EMMC) lines.append( boundary_check: true) lines.append( is_reserved: false) lines.append( operation_type: UPDATE) lines.append( reserved: 0) lines.append() return \n.join(lines) if __name__ __main__: if len(sys.argv) 2: print(用法: python pgpt2scatter.py 你的pgpt备份.bin [平台名] [工程名]) sys.exit(1) pgpt_path sys.argv[1] platform sys.argv[2] if len(sys.argv) 2 else MT6785 project sys.argv[3] if len(sys.argv) 3 else pgpt_backup with open(pgpt_path, rb) as f: data f.read() part_lba, part_count, part_size parse_gpt_header(data) partitions parse_partitions(data, part_lba, part_count, part_size) scatter gen_mtk_scatter(partitions, platformplatform, projectproject) out_path pgpt_path .scatter.txt with open(out_path, w, encodingutf-8) as f: f.write(scatter) print(fOK, 共解析 {len(partitions)} 个分区已输出: {out_path})用法示例python pgpt2scatter.py pgpt.bin MT6765 my_project脚本会在当前目录生成pgpt.bin.scatter.txt。用SP Flash Tool新建下载任务时选中这个txt即可。补充一个Windows下容易遇到的问题如果控制台打印中文乱码先执行chcp 65001把代码页切到UTF-8或者在运行时把系统区域设置里的“Beta版使用Unicode UTF-8提供全球语言支持”打开。另外生成的scatter文件不要拿记事本另存为带BOM的UTF-8SP Flash Tool对BOM比较敏感推荐用VSCode或Notepad保持无BOM的UTF-8。4. 实战验证与踩坑记录生成的scatter到底能不能用4.1 用SP Flash Tool加载前先做一遍反向校验生成scatter之后别急着开刷先做一次反向校验。最简单的办法是用文本编辑器打开生成的scatter逐项检查linear_start_addr是不是递增的partition_size是不是和手机里的大分区实际情况吻合。一般来说第一个用户分区通常是proinfo或bmtpool起始地址不会从0开始而是某个有一定偏移的位置这说明MTK把前面一段留给了PGPT和bootloader。如果想更严谨可以写一个几行的小脚本把scatter里的地址除以512还原成LBA再和PGPT里的起始LBA对比。在手机还能临时进系统的情况下也可以用cat /proc/partitions看每个分区的块数量和生成的partition_size做比对。这一步能过滤掉绝大多数低级错误。4.2 刷机过程中最容易翻车的几个点问题现象根因解决办法SP Flash Tool不加载某个分区type字段不符根据分区名手动改type引导类换SV5_BL_BIN其它用NORMAL刷完其它分区正常但开不了机preloader缺失或region填错从mmcblk0boot0导出preloader手动补条目并把region设为EMMC_BOOT_1地址错位、数据刷到相邻分区计算地址时没乘512或结束LBA处理错误用脚本重新生成并通过4.1的反向校验勾选了system但刷了没反应Android 10动态分区system被合并进super直接刷super分区不要单独勾选system这些坑我基本都踩过一遍尤其是动态分区那个最容易让新手困惑。很多MTK新机的GPT里已经没有独立的system、vendor、product分区条目而是只有一个super。你解析出来的分区表里如果看到super别觉得是解析错了这就是新版Android的常见布局。4.3 不同平台和安卓版本下的差异eMMC、UFS、动态分区MTK这两年平台差异主要集中在存储介质和分区策略两方面。存储方面eMMC的逻辑块大小一般是512字节UFS虽然物理扇区通常更大但逻辑块地址仍按512或4096字节暴露具体以哪个为准看GPT头在镜像里出现在偏移0x200还是0x1000就知道。Android版本方面Android 10开始默认动态分区GPT里的分区数量和名称改了但PGPT的解析逻辑完全不受影响因为它读的是二进制结构不是分区语义。最后说一句偏经验的话不同芯片平台的scatter格式大同小异但SP Flash Tool版本对老格式和新格式的兼容性不一样。MT6765以前的机器有人还在用老版本工具生成的字段太多反而可能报错。遇到这种老机器可以在生成时只保留general和partition_index、partition_name、linear_start_addr、partition_size这几个字段实测老工具也能认。我个人在实际操作中的体会是备份PGPT这件事成本极低但收益极高。MTK手机到手第一件事就建议先root、先备份整块mmcblk0最前面一段然后把备份文件丢到电脑和网盘各存一份顺手算一个SHA256。等到哪天真需要线刷却没有scatter时你会感谢当初花五分钟做的这个备份。这套脚本我自己在MT6765、MT6785、MT6833三个平台上验证过生成的scatter配合SP Flash Tool都能正常识别。如果你在某个机型上发现解析出来的分区信息有异常优先回头检查PGPT备份文件本身是否完整再对照偏移表确认签名和分区项位置通常问题都出在数据源而不是脚本逻辑。
返回列表