android-unlock-and-more-box/aubox/library/huawei.py

149 lines
4.5 KiB
Python

"""Parser für Huawei `update.app`-Container.
Format-Beschreibung (öffentlich aus splituapp / Huawei Update Extractor):
Datei beginnt mit 0x5C Bytes Padding/Header, danach folgen Section-Header.
Jeder Section-Header ist 98 Bytes:
uint32 magic = 0xA55AAA55
uint32 header_length = 98
uint32 unknown1
char[8] hardware_id z.B. "WAS-LX1"
uint32 file_sequence
uint32 file_size
char[16] file_date z.B. "2018.04.16"
char[16] file_time z.B. "10:23:45"
char[16] file_type z.B. "BOOT", "SYSTEM", "USERDATA", "CRC"
char[16] blank1
uint16 header_checksum
uint16 block_size
uint16 blank2
Danach `file_size` Bytes Section-Daten, gefolgt von einer CRC-Tabelle
(eine uint16 pro `block_size` Bytes Daten), dann 4-Byte-Alignment auf
die nächste Section-Header-Position.
"""
from __future__ import annotations
import struct
from dataclasses import dataclass, field
from pathlib import Path
UPDATE_MAGIC = 0xA55AAA55
HEADER_FMT = "<III8sII16s16s16s16sHHH"
HEADER_SIZE = 98
@dataclass
class Section:
sequence: int
size: int
date: str
time: str
type: str
@dataclass
class UpdateAppInfo:
hardware_id: str
section_count: int
sections: list[Section] = field(default_factory=list)
has_boot: bool = False
has_system: bool = False
earliest_date: str | None = None
def _cstr(b: bytes) -> str:
return b.split(b"\x00", 1)[0].decode("ascii", errors="replace").strip()
def parse(path: Path, max_sections: int = 500) -> UpdateAppInfo | None:
"""Header der ersten N Sections einlesen, ohne das ganze Image zu lesen."""
if not path.is_file():
return None
if path.stat().st_size < 0x100:
return None
with path.open("rb") as f:
# Magic suchen — sitzt meist bei 0x5C, manche Builds variieren
head = f.read(0x200)
idx = head.find(struct.pack("<I", UPDATE_MAGIC))
if idx < 0:
return None
f.seek(idx)
sections: list[Section] = []
hardware_id = ""
earliest: str | None = None
has_boot = has_system = False
for _ in range(max_sections):
buf = f.read(HEADER_SIZE)
if len(buf) < HEADER_SIZE:
break
(magic, _hlen, _u, hw, seq, size,
date, time_, ftype, _b, _crc, blksz, _b2) = struct.unpack(HEADER_FMT, buf)
if magic != UPDATE_MAGIC:
break
hw_str = _cstr(hw)
type_str = _cstr(ftype)
date_str = _cstr(date)
time_str = _cstr(time_)
if not hardware_id and hw_str:
hardware_id = hw_str
if earliest is None or (date_str and date_str < earliest):
if date_str:
earliest = date_str
if type_str.upper() in ("BOOT", "FASTBOOT", "XLOADER", "BOOTLOADER"):
has_boot = True
if type_str.upper() in ("SYSTEM", "SYSTEM_IMAGE"):
has_system = True
sections.append(Section(
sequence=seq, size=size, date=date_str,
time=time_str, type=type_str,
))
# Daten + CRC + Padding überspringen
if blksz <= 0:
break
crc_size = ((size + blksz - 1) // blksz) * 2
cur_after_data = f.tell() + size + crc_size
pad = (-cur_after_data) % 4
f.seek(size + crc_size + pad, 1)
if not sections:
return None
return UpdateAppInfo(
hardware_id=hardware_id,
section_count=len(sections),
sections=sections,
has_boot=has_boot,
has_system=has_system,
earliest_date=earliest,
)
# ---------------------------------------------------------------------------
# Modell -> SoC-Mapping. Wird mit der Zeit erweitert; was hier nicht steht,
# kommt als 'unknown' raus und der User kann es manuell pflegen.
HUAWEI_MODEL_SOC = {
"WAS-LX1": "kirin-658",
"WAS-LX2": "kirin-658",
"WAS-LX3": "kirin-658",
"PRA-LX1": "kirin-655", # P8 Lite 2017
"VTR-L09": "kirin-960", # P10
"VTR-L29": "kirin-960",
"VKY-L09": "kirin-960", # P10 Plus
"EML-L09": "kirin-970", # P20
"EML-L29": "kirin-970",
"CLT-L29": "kirin-970", # P20 Pro
"ANE-LX1": "kirin-710", # P20 Lite
}
def soc_for(hardware_id: str) -> str | None:
return HUAWEI_MODEL_SOC.get(hardware_id.upper())