Skip to content

Commit 2e83a87

Browse files
committed
merge: integrate custom AVB chain and packaging resume improvements
2 parents cd8118e + 1ee8df4 commit 2e83a87

File tree

10 files changed

+1682
-4
lines changed

10 files changed

+1682
-4
lines changed

README.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
- 💉 **系统补丁**: 按规则修改固件、系统、框架和 ROM 属性。
1616
- 🧬 **GKI 支持**: 针对 GKI 2.0 (5.10+) 及标准 GKI 设备提供 KernelSU 注入能力。
1717
- 🔓 **AVB 禁用**: 通过直接修改 `vbmeta.img` 禁用 Android 验证启动 (AVB),避免修改 fstab 导致的 DSU 无法使用问题。
18+
- 🔐 **自定义 AVB 验证链**: 可基于 stock `vbmeta/vbmeta_system` 自动重建 AVB 拓扑,为目标镜像补齐 footer、重建 `vbmeta*.img` 并执行链路校验;同时对非动态分区应用物理分区上限保护,避免 OTA 写入超分区。
1819
- 🚀 **Wild Boost(狂暴引擎)**: 将红米机型的狂暴引擎适配并移植到小米机型;要求内核版本一致,当前已验证小米 12S 与小米 13。
1920
- 🧩 **模块化配置**: 通过简单的 JSON 文件开启/关闭功能(AOD、AI 引擎等)。
2021
- 🌏 **EU 本地化**: 为 Global/EU 底包恢复国内特有功能(NFC、小米钱包、小爱同学)。
@@ -160,6 +161,8 @@ sudo python3 main.py --stock <底包路径> --port <移植包路径> --pack-type
160161
| `--rollback-to-snapshot` | 从指定快照恢复目标工作目录并退出 | `null` |
161162
| `--enable-diff-report` | 生成产物差异报告(前后文件/属性/APK变化) | `false` |
162163
| `--diff-report` | 差异报告 JSON 输出路径 | `build/diff-report.json` |
164+
| `--custom-avb-chain` | 启用“自定义 AVB 验证链”(按 stock AVB 拓扑重建 footer/vbmeta 并校验) | `false` |
165+
| `--resume-from-packer` | 从已保存的 repack 检查点恢复,直接进入打包阶段 | `false` |
163166

164167
---
165168

@@ -177,6 +180,7 @@ sudo python3 main.py --stock <底包路径> --port <移植包路径> --pack-type
177180
- `features.json` - 功能开关和属性
178181
- `replacements.json` - 资源替换规则
179182
- `partition_info.json` - 分区布局信息(动态分区列表、固件分区列表、super 大小)
183+
- 启用 `--custom-avb-chain` 时会自动补全 AVB 固化字段(`physical_partition_sizes``avb_*_partitions``avb_strict_partitions`
180184

181185
**示例生成的 partition_info.json**:
182186
```json
@@ -187,7 +191,17 @@ sudo python3 main.py --stock <底包路径> --port <移植包路径> --pack-type
187191
"odm", "product", "system", "system_dlkm",
188192
"system_ext", "vendor", "vendor_dlkm", "mi_ext"
189193
],
190-
"firmware_partitions": ["abl", "aop", "boot", ...]
194+
"firmware_partitions": ["abl", "aop", "boot", ...],
195+
"physical_partition_sizes": {
196+
"boot": 100663296,
197+
"recovery": 104857600
198+
},
199+
"avb_hash_partitions": ["boot", "dtbo", "..."],
200+
"avb_hashtree_partitions": ["system", "vendor", "..."],
201+
"avb_chain_partitions": [
202+
{"name": "boot", "rollback_index_location": 3}
203+
],
204+
"avb_strict_partitions": ["boot", "recovery", "dtbo", "..."]
191205
}
192206
```
193207

README_EN.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ A HyperOS ROM porting tool for Xiaomi/Redmi devices. It covers the common workfl
1515
- 💉 **System Patching**: Rule-based modifications for firmware, system, framework, and ROM properties.
1616
- 🧬 **GKI Support**: KernelSU injection support for GKI 2.0 (5.10+) and standard GKI devices.
1717
- 🔓 **AVB Disabling**: Disables Android Verified Boot (AVB) by directly patching `vbmeta.img`, avoiding DSU compatibility issues caused by fstab modifications.
18+
- 🔐 **Custom AVB Verification Chain**: Rebuilds AVB topology from stock `vbmeta/vbmeta_system`, re-signs required image footers, regenerates `vbmeta*.img`, and verifies the chain; also enforces physical partition caps for non-dynamic partitions to prevent OTA ENOSPC at flash time.
1819
- 🚀 **Wild Boost (Rage Engine)**: Ports the Redmi-specific rage engine to Xiaomi targets; requires matching kernel versions. Currently validated on Xiaomi 12S and Xiaomi 13.
1920
- 🧩 **Modular Configuration**: Toggle features (AOD, AI Engine, etc.) via simple JSON files.
2021
- 🌏 **EU Localization**: Restore China-exclusive features (NFC, XiaoAi) to Global/EU bases.
@@ -162,6 +163,8 @@ sudo python3 main.py --stock <path_to_stock_zip> --port <path_to_port_zip> --pac
162163
| `--rollback-to-snapshot` | Restore target workspace from a named snapshot and exit | `null` |
163164
| `--enable-diff-report` | Generate artifact diff report (files/props/APK changes) | `false` |
164165
| `--diff-report` | Output path for artifact diff report JSON | `build/diff-report.json` |
166+
| `--custom-avb-chain` | Enable custom AVB chain rebuild (stock-profiled footer/vbmeta rebuild + verification) | `false` |
167+
| `--resume-from-packer` | Resume from a saved repack checkpoint and jump directly to packaging | `false` |
165168

166169
---
167170

@@ -179,6 +182,7 @@ The tool supports automatic device configuration extraction from the Stock ROM.
179182
- `features.json` - Feature toggles and properties
180183
- `replacements.json` - Resource replacement rules
181184
- `partition_info.json` - Partition layout (dynamic partitions, firmware partitions, super size)
185+
- With `--custom-avb-chain`, AVB-persistent fields are auto-synced (`physical_partition_sizes`, `avb_*_partitions`, `avb_strict_partitions`)
182186

183187
**Example generated partition_info.json**:
184188
```json
@@ -189,7 +193,17 @@ The tool supports automatic device configuration extraction from the Stock ROM.
189193
"odm", "product", "system", "system_dlkm",
190194
"system_ext", "vendor", "vendor_dlkm", "mi_ext"
191195
],
192-
"firmware_partitions": ["abl", "aop", "boot", ...]
196+
"firmware_partitions": ["abl", "aop", "boot", ...],
197+
"physical_partition_sizes": {
198+
"boot": 100663296,
199+
"recovery": 104857600
200+
},
201+
"avb_hash_partitions": ["boot", "dtbo", "..."],
202+
"avb_hashtree_partitions": ["system", "vendor", "..."],
203+
"avb_chain_partitions": [
204+
{"name": "boot", "rollback_index_location": 3}
205+
],
206+
"avb_strict_partitions": ["boot", "recovery", "dtbo", "..."]
193207
}
194208
```
195209

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
{
2+
"device_code": "pudding",
3+
"super_size": 13411287040,
4+
"dynamic_partitions": [
5+
"odm",
6+
"product",
7+
"system",
8+
"system_dlkm",
9+
"system_ext",
10+
"vendor",
11+
"vendor_dlkm",
12+
"mi_ext"
13+
],
14+
"firmware_partitions": [
15+
"abl",
16+
"aop",
17+
"aop_config",
18+
"bluetooth",
19+
"boot",
20+
"countrycode",
21+
"cpucp",
22+
"cpucp_dtb",
23+
"dcp",
24+
"devcfg",
25+
"dsp",
26+
"dtbo",
27+
"featenabler",
28+
"hyp",
29+
"hyp_ac_config",
30+
"idmanager",
31+
"imagefv",
32+
"init_boot",
33+
"keymaster",
34+
"modem",
35+
"modemfirmware",
36+
"multiimgqti",
37+
"pdp",
38+
"pdp_cdb",
39+
"pvmfw",
40+
"qtvm_dtbo",
41+
"qupfw",
42+
"recovery",
43+
"secretkeeper",
44+
"shrm",
45+
"soccp",
46+
"soccp_dcd",
47+
"soccp_debug",
48+
"spuservice",
49+
"tme_config",
50+
"tme_fw",
51+
"tme_seq_patch",
52+
"tz",
53+
"tz_ac_config",
54+
"tz_qti_config",
55+
"uefi",
56+
"uefisecapp",
57+
"vbmeta",
58+
"vbmeta_system",
59+
"vendor_boot",
60+
"vm-bootsys",
61+
"xbl",
62+
"xbl_ac_config",
63+
"xbl_config",
64+
"xbl_ramdump"
65+
],
66+
"physical_partition_sizes": {
67+
"boot": 100663296,
68+
"countrycode": 1048576,
69+
"dtbo": 23068672,
70+
"init_boot": 8388608,
71+
"pvmfw": 1048576,
72+
"recovery": 104857600,
73+
"vendor_boot": 100663296
74+
},
75+
"avb_hash_partitions": [
76+
"countrycode",
77+
"dtbo",
78+
"init_boot",
79+
"pvmfw",
80+
"vendor_boot"
81+
],
82+
"avb_hashtree_partitions": [
83+
"mi_ext",
84+
"odm",
85+
"product",
86+
"system",
87+
"system_dlkm",
88+
"system_ext",
89+
"vendor",
90+
"vendor_dlkm"
91+
],
92+
"avb_chain_partitions": [
93+
{
94+
"name": "boot",
95+
"rollback_index_location": 3
96+
},
97+
{
98+
"name": "recovery",
99+
"rollback_index_location": 1
100+
},
101+
{
102+
"name": "vbmeta_system",
103+
"rollback_index_location": 2
104+
}
105+
],
106+
"avb_strict_partitions": [
107+
"boot",
108+
"countrycode",
109+
"dtbo",
110+
"init_boot",
111+
"pvmfw",
112+
"recovery",
113+
"vendor_boot"
114+
]
115+
}

src/app/cli.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,16 @@ def build_parser() -> argparse.ArgumentParser:
4141
default=None,
4242
help="Filesystem type for repacking. Default: from config or 'erofs'",
4343
)
44+
parser.add_argument(
45+
"--custom-avb-chain",
46+
action="store_true",
47+
help="Enable custom AVB chain generation (re-sign images, rebuild vbmeta, verify chain)",
48+
)
49+
parser.add_argument(
50+
"--resume-from-packer",
51+
action="store_true",
52+
help="Resume from existing target workspace and run repacking only",
53+
)
4454
parser.add_argument("--eu-bundle", help="Path/URL to EU Localization Bundle zip")
4555
parser.add_argument(
4656
"--preflight-only",

src/app/workflow.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import json
66
import logging
77
from pathlib import Path
8+
from types import SimpleNamespace
89

910
from src.app.bootstrap import clean_work_dir, initialize_cache_manager
1011
from src.app.diff_report import collect_artifact_state, generate_diff_report, save_diff_report
@@ -20,6 +21,7 @@
2021
from src.utils.otatools_manager import OtaToolsManager
2122

2223
DEFAULT_PHASES = ["system", "apk", "framework", "firmware"]
24+
REPACK_CHECKPOINT_NAME = "repack-context.json"
2325

2426

2527
def resolve_work_paths(work_dir: str | Path) -> tuple[Path, Path, Path, Path]:
@@ -76,6 +78,17 @@ def determine_pack_settings(args, ctx: PortingContext, logger: logging.Logger) -
7678
f"(from {'CLI' if args.ksu else 'config'})"
7779
)
7880

81+
pack_cfg = ctx.device_config.get("pack", {})
82+
config_custom_avb_chain = False
83+
if isinstance(pack_cfg, dict):
84+
config_custom_avb_chain = bool(pack_cfg.get("custom_avb_chain", False))
85+
ctx.enable_custom_avb_chain = bool(args.custom_avb_chain or config_custom_avb_chain)
86+
logger.info(
87+
"Custom AVB chain: %s (from %s)",
88+
"enabled" if ctx.enable_custom_avb_chain else "disabled",
89+
"CLI" if args.custom_avb_chain else "config",
90+
)
91+
7992
pack_type = args.pack_type or ctx.device_config.get("pack", {}).get("type", "payload")
8093
fs_type = args.fs_type or ctx.device_config.get("pack", {}).get("fs_type", "erofs")
8194
logger.info(f"Pack Type: {pack_type} (from {'CLI' if args.pack_type else 'config'})")
@@ -133,6 +146,77 @@ def run_repacking(
133146
packer.pack_ota_payload()
134147

135148

149+
def _checkpoint_path(work_dir: Path) -> Path:
150+
return work_dir / REPACK_CHECKPOINT_NAME
151+
152+
153+
def save_repack_checkpoint(ctx: PortingContext, work_dir: Path) -> Path:
154+
def as_str(value, default: str = "") -> str:
155+
return value if isinstance(value, str) else default
156+
157+
def as_bool(value, default: bool = False) -> bool:
158+
return bool(value) if isinstance(value, (bool, int, str)) else default
159+
160+
raw_device_config = getattr(ctx, "device_config", {})
161+
device_config = raw_device_config if isinstance(raw_device_config, dict) else {}
162+
payload = {
163+
"stock_rom_code": as_str(getattr(ctx, "stock_rom_code", ""), "unknown"),
164+
"target_rom_version": as_str(getattr(ctx, "target_rom_version", ""), ""),
165+
"security_patch": as_str(getattr(ctx, "security_patch", ""), "Unknown"),
166+
"is_ab_device": as_bool(getattr(ctx, "is_ab_device", False), False),
167+
"base_android_version": as_str(getattr(ctx, "base_android_version", ""), "0"),
168+
"port_android_version": as_str(getattr(ctx, "port_android_version", ""), "0"),
169+
"is_port_eu_rom": as_bool(getattr(ctx, "is_port_eu_rom", False), False),
170+
"is_port_global_rom": as_bool(getattr(ctx, "is_port_global_rom", False), False),
171+
"port_global_region": as_str(getattr(ctx, "port_global_region", ""), ""),
172+
"device_config": device_config,
173+
}
174+
path = _checkpoint_path(work_dir)
175+
path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
176+
return path
177+
178+
179+
def load_repack_checkpoint(work_dir: Path, target_work_dir: Path, logger: logging.Logger):
180+
path = _checkpoint_path(work_dir)
181+
if not path.exists():
182+
raise FileNotFoundError(f"Repack checkpoint not found: {path}")
183+
184+
data = json.loads(path.read_text(encoding="utf-8"))
185+
186+
def get_target_prop_file(part_name: str):
187+
part_dir: Path = target_work_dir / part_name
188+
candidates = [
189+
part_dir / "build.prop",
190+
part_dir / "system" / "build.prop",
191+
part_dir / "etc" / "build.prop",
192+
]
193+
for candidate in candidates:
194+
if candidate.exists():
195+
return candidate
196+
return None
197+
198+
ctx = SimpleNamespace(
199+
stock_rom_code=data.get("stock_rom_code", "unknown"),
200+
target_rom_version=data.get("target_rom_version", ""),
201+
security_patch=data.get("security_patch", "Unknown"),
202+
is_ab_device=bool(data.get("is_ab_device", False)),
203+
base_android_version=data.get("base_android_version", "0"),
204+
port_android_version=data.get("port_android_version", "0"),
205+
is_port_eu_rom=bool(data.get("is_port_eu_rom", False)),
206+
is_port_global_rom=bool(data.get("is_port_global_rom", False)),
207+
port_global_region=data.get("port_global_region", ""),
208+
target_dir=target_work_dir,
209+
target_config_dir=target_work_dir / "config",
210+
repack_images_dir=target_work_dir / "repack_images",
211+
device_config=data.get("device_config", {}),
212+
enable_ksu=False,
213+
enable_custom_avb_chain=False,
214+
get_target_prop_file=get_target_prop_file,
215+
)
216+
logger.info("Loaded repack checkpoint from %s", path)
217+
return ctx
218+
219+
136220
def log_diff_report_summary(diff_report: dict[str, object], logger: logging.Logger) -> None:
137221
"""Log a compact summary for generated artifact diff reports."""
138222
summary = diff_report.get("summary", {})
@@ -279,6 +363,19 @@ def execute_porting(args, logger: logging.Logger) -> int:
279363
resolve_remote_inputs(args, is_official_modify, logger)
280364

281365
work_dir, stock_work_dir, port_work_dir, target_work_dir = resolve_work_paths(args.work_dir)
366+
367+
if getattr(args, "resume_from_packer", False):
368+
logger.info("Resume mode: packer-only repacking from existing target workspace.")
369+
try:
370+
ctx = load_repack_checkpoint(work_dir, target_work_dir, logger)
371+
except (FileNotFoundError, json.JSONDecodeError) as exc:
372+
logger.error(str(exc))
373+
return 2
374+
pack_type, fs_type = determine_pack_settings(args, ctx, logger) # type: ignore[arg-type]
375+
run_repacking(ctx, ["repack"], pack_type, fs_type, target_work_dir, logger)
376+
logger.info("Repack-only resume completed successfully.")
377+
return 0
378+
282379
snapshot_manager = (
283380
StageSnapshotManager(args.snapshot_dir or (work_dir / "snapshots"), logger)
284381
if args.enable_snapshots or args.rollback_to_snapshot
@@ -379,6 +476,8 @@ def execute_porting(args, logger: logging.Logger) -> int:
379476
cache_manager.cache_partitions = True
380477

381478
pack_type, fs_type = determine_pack_settings(args, ctx, logger)
479+
checkpoint_path = save_repack_checkpoint(ctx, work_dir)
480+
logger.info("Saved repack checkpoint to: %s", checkpoint_path)
382481

383482
work_dir.mkdir(parents=True, exist_ok=True)
384483
stock.export_props(work_dir / "stock_debug.prop")

src/core/context.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ def __init__(
4747
self.syncer: ROMSyncEngine = ROMSyncEngine(self, logging.getLogger("SyncEngine"))
4848
self.shell: ShellRunner = ShellRunner()
4949
self.enable_ksu: bool = False
50+
self.enable_custom_avb_chain: bool = False
5051
self.cache_manager: PortRomCacheManager | None = None
5152
self.device_config: dict[str, Any] = {}
5253
self.eu_bundle: str | None = None

0 commit comments

Comments
 (0)