149 lines
4.5 KiB
Python
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())
|