Skip to content

Commit f79e979

Browse files
committed
refactor(firmware): simplify AVB disabling by patching only vbmeta.img
- Remove _patch_vendor_boot_fstab() method that modified fstab in vendor_boot - Remove _disable_avb_verify() helper method - Change _patch_vbmeta() to only patch vbmeta.img instead of all vbmeta*.img files - Fix vbmeta.img path to use repack_images subdirectory - Update README.md and README_EN.md to reflect the new AVB disabling approach This change fixes DSU compatibility issues. Previously, modifying fstab in vendor_boot to disable AVB verification would break DSU (Dynamic System Updates) functionality. By directly patching vbmeta.img to disable AVB (setting flags at offset 123 to 0x03), we achieve the same goal without interfering with fstab-based verity checks, allowing DSU to work properly on newer devices. Note: Newer devices only require vbmeta.img modification; patching other vbmeta images (vbmeta_system, vbmeta_vendor, etc.) can cause fastboot loops. Signed-off-by: HyperOS Port Tool
1 parent b481a86 commit f79e979

File tree

3 files changed

+31
-220
lines changed

3 files changed

+31
-220
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
- 🛠️ **流程自动化**: 从底包/移植包 ZIP 到可刷入产物的完整流程。
1515
- 💉 **系统补丁**: 按规则修改固件、系统、框架和 ROM 属性。
1616
- 🧬 **GKI 支持**: 针对 GKI 2.0 (5.10+) 及标准 GKI 设备提供 KernelSU 注入能力。
17-
- 🔓 **Android 16 支持**: 针对 KMI 6.12 提供专用的 `vendor_boot` fstab 修补,跳过标准 VBMETA 以防止 Fastboot 卡死
17+
- 🔓 **AVB 禁用**: 通过直接修改 `vbmeta.img` 禁用 Android 验证启动 (AVB),避免修改 fstab 导致的 DSU 无法使用问题
1818
- 🚀 **Wild Boost(狂暴引擎)**: 将红米机型的狂暴引擎适配并移植到小米机型;要求内核版本一致,当前已验证小米 12S 与小米 13。
1919
- 🧩 **模块化配置**: 通过简单的 JSON 文件开启/关闭功能(AOD、AI 引擎等)。
2020
- 🌏 **EU 本地化**: 为 Global/EU 底包恢复国内特有功能(NFC、小米钱包、小爱同学)。

README_EN.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ A HyperOS ROM porting tool for Xiaomi/Redmi devices. It covers the common workfl
1414
- 🛠️ **Workflow Automation**: From stock/port ZIPs to flashable output in one pipeline.
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.
17-
- 🔓 **Android 16 Ready**: Specialized `vendor_boot` fstab patching for KMI 6.12 to prevent fastboot bootloops.
17+
- 🔓 **AVB Disabling**: Disables Android Verified Boot (AVB) by directly patching `vbmeta.img`, avoiding DSU compatibility issues caused by fstab modifications.
1818
- 🚀 **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.
1919
- 🧩 **Modular Configuration**: Toggle features (AOD, AI Engine, etc.) via simple JSON files.
2020
- 🌏 **EU Localization**: Restore China-exclusive features (NFC, XiaoAi) to Global/EU bases.

src/core/modifiers/firmware_modifier.py

Lines changed: 29 additions & 218 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,7 @@ def __init__(self, context):
2121
self.bin_dir = Path("bin").resolve()
2222

2323
if not self.ctx.tools.magiskboot.exists():
24-
self.logger.error(
25-
f"magiskboot binary not found at {self.ctx.tools.magiskboot}"
26-
)
24+
self.logger.error(f"magiskboot binary not found at {self.ctx.tools.magiskboot}")
2725
return
2826

2927
self.assets_dir = self.bin_dir.parent / "assets"
@@ -39,9 +37,7 @@ def __init__(self, context):
3937

4038
# Make these values configurable via device config
4139
if hasattr(self.ctx, "device_config") and self.ctx.device_config:
42-
self.repo_owner = self.ctx.device_config.get(
43-
"ksu_repo_owner", self.repo_owner
44-
)
40+
self.repo_owner = self.ctx.device_config.get("ksu_repo_owner", self.repo_owner)
4541
self.repo_name = self.ctx.device_config.get("ksu_repo_name", self.repo_name)
4642
self.ksu_config_url_template = self.ctx.device_config.get(
4743
"ksu_gh_api_url_template",
@@ -62,15 +58,8 @@ def run(self):
6258
kmi_version = self._get_kmi_version()
6359
if kmi_version:
6460
self.logger.info(f"Detected KMI Version: {kmi_version}")
65-
if kmi_version == "android16-6.12":
66-
self.logger.info(
67-
"KMI android16-6.12 detected. Skipping vbmeta patching and modifying vendor_boot fstab instead."
68-
)
69-
self._patch_vendor_boot_fstab()
70-
else:
71-
self._patch_vbmeta()
72-
else:
73-
self._patch_vbmeta()
61+
62+
self._patch_vbmeta()
7463

7564
if getattr(self.ctx, "enable_ksu", False):
7665
self._patch_ksu(kmi_version)
@@ -94,195 +83,33 @@ def _get_kmi_version(self) -> Optional[str]:
9483

9584
return self._analyze_kmi(analysis_target)
9685

97-
def _patch_vendor_boot_fstab(self):
98-
"""Modify fstab in vendor_boot to disable AVB for KMI 6.12."""
99-
self.logger.info("Patching vendor_boot fstab...")
100-
vendor_boot = self.ctx.repack_images_dir / "vendor_boot.img"
101-
if not vendor_boot.exists():
102-
self.logger.warning("vendor_boot.img not found, skipping fstab patch.")
103-
return
104-
105-
with tempfile.TemporaryDirectory(prefix="vendor_boot_patch_") as tmp:
106-
tmp_path = Path(tmp)
107-
shutil.copy(vendor_boot, tmp_path / "vendor_boot.img")
108-
109-
try:
110-
self.shell.run(
111-
[str(self.ctx.tools.magiskboot), "unpack", "vendor_boot.img"],
112-
cwd=tmp_path,
113-
)
114-
except Exception as e:
115-
self.logger.error(f"Failed to unpack vendor_boot.img: {e}")
116-
return
117-
118-
ramdisk = tmp_path / "ramdisk.cpio"
119-
if not ramdisk.exists():
120-
self.logger.error("ramdisk.cpio not found in vendor_boot")
121-
return
122-
123-
# Attempt to decompress the ramdisk explicitly, as 'raw' format in v4 images
124-
# might still be compressed in a way magiskboot cpio doesn't auto-detect.
125-
try:
126-
self.shell.run(
127-
[
128-
str(self.ctx.tools.magiskboot),
129-
"decompress",
130-
"ramdisk.cpio",
131-
"ramdisk.cpio.dec",
132-
],
133-
cwd=tmp_path,
134-
)
135-
if (tmp_path / "ramdisk.cpio.dec").exists():
136-
shutil.move(tmp_path / "ramdisk.cpio.dec", ramdisk)
137-
self.logger.info("Successfully decompressed vendor_boot ramdisk.")
138-
except Exception:
139-
# If decompression fails, it might actually be raw, so we continue
140-
pass
141-
142-
# Extract all files to find fstab using system cpio if magiskboot fails
143-
extract_dir = tmp_path / "extracted_ramdisk"
144-
extract_dir.mkdir(parents=True, exist_ok=True)
145-
try:
146-
# Try system cpio first as it is often more robust for extraction
147-
self.shell.run(
148-
f"cpio -id < {ramdisk}",
149-
cwd=extract_dir,
150-
shell=True
151-
)
152-
except Exception as e:
153-
self.logger.warning(f"System cpio failed, trying magiskboot cpio: {e}")
154-
try:
155-
self.shell.run(
156-
[str(self.ctx.tools.magiskboot), "cpio", "ramdisk.cpio", "extract"],
157-
cwd=extract_dir,
158-
)
159-
except Exception as e2:
160-
self.logger.error(f"Both cpio methods failed: {e2}")
161-
return
162-
163-
fstab_files = list(extract_dir.rglob("fstab.*"))
164-
if not fstab_files:
165-
self.logger.info("No fstab files found in vendor_boot ramdisk.")
166-
167-
patched = False
168-
for fstab_path in fstab_files:
169-
# Use relative path for entry name in cpio
170-
fstab_entry_name = str(fstab_path.relative_to(extract_dir))
171-
self.logger.info(f"Checking {fstab_entry_name} in vendor_boot ramdisk...")
172-
173-
with open(fstab_path, "r") as f:
174-
content = f.read()
175-
176-
new_content = self._disable_avb_verify(content)
177-
178-
if new_content != content:
179-
with open(fstab_path, "w") as f:
180-
f.write(new_content)
181-
182-
# Add back to ramdisk
183-
# magiskboot cpio <cpio> 'add MODE ENTRY INFILE'
184-
self.shell.run(
185-
[
186-
str(self.ctx.tools.magiskboot),
187-
"cpio",
188-
"ramdisk.cpio",
189-
f"add 0644 {fstab_entry_name} {fstab_path}",
190-
],
191-
cwd=tmp_path,
192-
)
193-
patched = True
194-
self.logger.info(f"Successfully patched {fstab_entry_name} in vendor_boot.")
195-
196-
# Also patch DTB if it exists
197-
dtb_file = tmp_path / "dtb"
198-
if dtb_file.exists():
199-
self.logger.info("Found DTB in vendor_boot, attempting optional fstab patch...")
200-
# Note: magiskboot dtb patch might fail if it doesn't find fstab nodes,
201-
# we set check=False to avoid error logs in ShellRunner.
202-
try:
203-
res = self.shell.run(
204-
[str(self.ctx.tools.magiskboot), "dtb", "dtb", "patch"],
205-
cwd=tmp_path,
206-
check=False,
207-
capture_output=True
208-
)
209-
if res.returncode == 0:
210-
patched = True
211-
self.logger.info("DTB fstab nodes patched successfully.")
212-
else:
213-
self.logger.info("No patchable fstab nodes found in DTB, skipping.")
214-
except Exception as e:
215-
self.logger.debug(f"Optional DTB patch failed: {e}")
216-
217-
if patched:
218-
self.logger.info("Repacking vendor_boot.img...")
219-
# magiskboot repack will automatically compress ramdisk.cpio
220-
# based on the format detected in the original vendor_boot.img
221-
try:
222-
self.shell.run(
223-
[str(self.ctx.tools.magiskboot), "repack", "vendor_boot.img"],
224-
cwd=tmp_path,
225-
)
226-
new_img = tmp_path / "new-boot.img"
227-
if new_img.exists():
228-
shutil.move(new_img, vendor_boot)
229-
self.logger.info("vendor_boot.img repacked with patched fstab.")
230-
else:
231-
self.logger.error("Failed to repack vendor_boot.img: new-boot.img not found")
232-
except Exception as e:
233-
self.logger.error(f"Failed to repack vendor_boot.img: {e}")
234-
235-
def _disable_avb_verify(self, content: str) -> str:
236-
"""Port of the shell function disable_avb_verify.
237-
238-
Original logic:
239-
sed -i "s/,avb_keys=.*avbpubkey//g" $fstab
240-
sed -i "s/,avb=vbmeta_system//g" $fstab
241-
sed -i "s/,avb=vbmeta_vendor//g" $fstab
242-
sed -i "s/,avb=vbmeta//g" $fstab
243-
sed -i "s/,avb//g" $fstab
244-
"""
245-
# Remove avb_keys pattern
246-
content = re.sub(r",avb_keys=[^, \n]*avbpubkey", "", content)
247-
# Remove specific avb flags
248-
content = re.sub(r",avb=vbmeta_system", "", content)
249-
# Handle some variations that might exist
250-
content = re.sub(r",avb=vbmeta_vendor", "", content)
251-
content = re.sub(r",avb=vbmeta", "", content)
252-
content = re.sub(r",avb", "", content)
253-
254-
return content
255-
25686
def _patch_vbmeta(self):
257-
"""Patch vbmeta images to disable AVB."""
258-
self.logger.info("Patching vbmeta images (Disabling AVB)...")
87+
"""Patch vbmeta.img to disable AVB."""
88+
self.logger.info("Patching vbmeta.img (Disabling AVB)...")
25989

260-
vbmeta_images = list(self.ctx.target_dir.rglob("vbmeta*.img"))
90+
vbmeta_img = self.ctx.target_dir / "repack_images" / "vbmeta.img"
26191

262-
if not vbmeta_images:
263-
self.logger.warning("No vbmeta images found in target directory.")
92+
if not vbmeta_img.exists():
93+
self.logger.warning("vbmeta.img not found in repack_images directory.")
26494
return
26595

26696
AVB_MAGIC = b"AVB0"
26797
FLAGS_OFFSET = 123
26898
FLAGS_TO_SET = b"\x03"
26999

270-
for img_path in vbmeta_images:
271-
try:
272-
with open(img_path, "r+b") as f:
273-
magic = f.read(4)
274-
if magic != AVB_MAGIC:
275-
self.logger.warning(
276-
f"Skipping {img_path.name}: Invalid AVB Magic"
277-
)
278-
continue
279-
280-
f.seek(FLAGS_OFFSET)
281-
f.write(FLAGS_TO_SET)
282-
self.logger.info(f"Successfully patched: {img_path.name}")
100+
try:
101+
with open(vbmeta_img, "r+b") as f:
102+
magic = f.read(4)
103+
if magic != AVB_MAGIC:
104+
self.logger.warning(f"Skipping {vbmeta_img.name}: Invalid AVB Magic")
105+
return
283106

284-
except Exception as e:
285-
self.logger.error(f"Failed to patch {img_path.name}: {e}")
107+
f.seek(FLAGS_OFFSET)
108+
f.write(FLAGS_TO_SET)
109+
self.logger.info(f"Successfully patched: {vbmeta_img.name}")
110+
111+
except Exception as e:
112+
self.logger.error(f"Failed to patch {vbmeta_img.name}: {e}")
286113

287114
def _patch_ksu(self, kmi_version: Optional[str] = None):
288115
"""Patch KernelSU into boot image."""
@@ -298,19 +125,15 @@ def _patch_ksu(self, kmi_version: Optional[str] = None):
298125
patch_target = target_boot
299126

300127
if not patch_target:
301-
self.logger.warning(
302-
"Neither init_boot.img nor boot.img found, skipping KSU patch."
303-
)
128+
self.logger.warning("Neither init_boot.img nor boot.img found, skipping KSU patch.")
304129
return
305130

306131
if not self.ctx.tools.magiskboot.exists():
307132
self.logger.error("magiskboot binary not found!")
308133
return
309134

310135
if not kmi_version:
311-
kmi_version = self._analyze_kmi(
312-
target_boot if target_boot.exists() else patch_target
313-
)
136+
kmi_version = self._analyze_kmi(target_boot if target_boot.exists() else patch_target)
314137

315138
if not kmi_version:
316139
self.logger.error("Failed to determine KMI version.")
@@ -331,9 +154,7 @@ def _analyze_kmi(self, boot_img: Path) -> Optional[str]:
331154
shutil.copy(boot_img, tmp_path / "boot.img")
332155

333156
try:
334-
self.shell.run(
335-
[str(self.ctx.tools.magiskboot), "unpack", "boot.img"], cwd=tmp_path
336-
)
157+
self.shell.run([str(self.ctx.tools.magiskboot), "unpack", "boot.img"], cwd=tmp_path)
337158
except Exception as e:
338159
self.logger.debug(f"Magiskboot unpack failed: {e}")
339160
return None
@@ -378,15 +199,11 @@ def _prepare_ksu_assets(self, kmi_version):
378199
ko_file_expected = self.ctx.device_config.get(
379200
"ksu_module_filename", f"{kmi_version}_kernelsu.ko"
380201
)
381-
init_file_expected = self.ctx.device_config.get(
382-
"ksu_init_filename", "ksuinit"
383-
)
202+
init_file_expected = self.ctx.device_config.get("ksu_init_filename", "ksuinit")
384203
ko_asset_name_pattern = self.ctx.device_config.get(
385204
"ksu_module_asset_pattern", f"{kmi_version}_kernelsu.ko"
386205
)
387-
init_asset_name = self.ctx.device_config.get(
388-
"ksu_init_asset_name", "ksuinit"
389-
)
206+
init_asset_name = self.ctx.device_config.get("ksu_init_asset_name", "ksuinit")
390207
else:
391208
ko_file_expected = f"{kmi_version}_kernelsu.ko"
392209
init_file_expected = "ksuinit"
@@ -451,9 +268,7 @@ def _apply_ksu_patch(self, target_img, kmi_version):
451268
tmp_path = Path(tmp)
452269
shutil.copy(target_img, tmp_path / "boot.img")
453270

454-
self.shell.run(
455-
[str(self.ctx.tools.magiskboot), "unpack", "boot.img"], cwd=tmp_path
456-
)
271+
self.shell.run([str(self.ctx.tools.magiskboot), "unpack", "boot.img"], cwd=tmp_path)
457272

458273
ramdisk = tmp_path / "ramdisk.cpio"
459274
if not ramdisk.exists():
@@ -492,15 +307,11 @@ def _apply_ksu_patch(self, target_img, kmi_version):
492307
cwd=tmp_path,
493308
)
494309

495-
self.shell.run(
496-
[str(self.ctx.tools.magiskboot), "repack", "boot.img"], cwd=tmp_path
497-
)
310+
self.shell.run([str(self.ctx.tools.magiskboot), "repack", "boot.img"], cwd=tmp_path)
498311

499312
new_img = tmp_path / "new-boot.img"
500313
if new_img.exists():
501314
shutil.move(new_img, target_img)
502-
self.logger.info(
503-
f"KernelSU injected successfully into {target_img.name}."
504-
)
315+
self.logger.info(f"KernelSU injected successfully into {target_img.name}.")
505316
else:
506317
self.logger.error(f"Failed to repack {target_img.name}")

0 commit comments

Comments
 (0)