This commit is contained in:
firestar5683
2026-04-09 21:52:47 -05:00
parent 6f22a65224
commit 00abf5bc2c
4 changed files with 168 additions and 21 deletions

View File

@@ -21,7 +21,7 @@ fi
export QCOM_PRIORITY=12
if [ -z "$AGNOS_VERSION" ]; then
export AGNOS_VERSION="12.8.7"
export AGNOS_VERSION="12.8.16"
fi
export STAGING_ROOT="/data/safe_staging"

View File

@@ -67,9 +67,9 @@
},
{
"name": "system",
"url": "https://www.dropbox.com/scl/fi/1l949sdyse7mgiz2n09t7/system.img.xz?rlkey=63kws9ktx9pr9c7su2vgtf9xc&st=sm8qfkp3&dl=1",
"hash": "d26f8e0f105a93ea0b386f259e1597d1cb10720cb6406491c570749e01d550c1",
"hash_raw": "d26f8e0f105a93ea0b386f259e1597d1cb10720cb6406491c570749e01d550c1",
"url": "https://www.dropbox.com/scl/fi/uglf3j1ne3pqtlx8j4mzp/system2.img.xz?rlkey=t1dp8rmyv6kuiyagy4p02ah6q&st=iai09tjz&dl=1",
"hash": "06641f9fb602e6b046d1a619cc43dbaa724ff5c3700a32f1ef6677e40146359e",
"hash_raw": "06641f9fb602e6b046d1a619cc43dbaa724ff5c3700a32f1ef6677e40146359e",
"size": 5368709120,
"sparse": false,
"full_check": false,

View File

@@ -1,14 +1,43 @@
#!/usr/bin/env python3
from pathlib import Path
from openpilot.system.hardware import HARDWARE
import openpilot.system.ui.tici_updater as tici_updater
import openpilot.system.ui.mici_updater as mici_updater
def _framebuffer_size() -> tuple[int, int] | None:
fb_path = Path("/sys/class/graphics/fb0/virtual_size")
if not fb_path.is_file():
return None
try:
raw = fb_path.read_text().strip().replace(" ", "")
width_s, height_s = raw.split(",", 1)
return int(width_s), int(height_s)
except Exception:
return None
def _ui_device_type() -> str:
reported_type = HARDWARE.get_device_type()
fb_size = _framebuffer_size()
if fb_size is not None and max(fb_size) < 1000:
return "mici"
return reported_type
def main():
if HARDWARE.get_device_type() in ("tici", "tizi"):
tici_updater.main()
device_type = _ui_device_type()
# The updater stack imports application sizing during module import, so patch the
# hardware probe before importing either UI implementation.
HARDWARE.get_device_type = lambda: device_type
if device_type in ("tici", "tizi"):
import openpilot.system.ui.tici_updater as updater_impl
else:
mici_updater.main()
import openpilot.system.ui.mici_updater as updater_impl
updater_impl.main()
if __name__ == "__main__":

View File

@@ -16,6 +16,7 @@ from pathlib import Path
RESET_PATH_IN_IMAGE = "/usr/comma/reset"
SETUP_PATH_IN_IMAGE = "/usr/comma/setup"
UPDATER_PATH_IN_IMAGE = "/usr/comma/updater"
RESET_ENTRY_IN_ZIPAPP = "openpilot/system/ui/reset.py"
MICI_RESET_ENTRY_IN_ZIPAPP = "openpilot/system/ui/mici_reset.py"
TICI_RESET_ENTRY_IN_ZIPAPP = "openpilot/system/ui/tici_reset.py"
@@ -24,12 +25,14 @@ WIFI_MANAGER_ENTRY_IN_SETUP_ZIPAPP = "openpilot/system/ui/lib/wifi_manager.py"
SETUP_ENTRY_IN_SETUP_ZIPAPP = "openpilot/system/ui/setup.py"
TICI_SETUP_ENTRY_IN_SETUP_ZIPAPP = "openpilot/system/ui/tici_setup.py"
MICI_SETUP_ENTRY_IN_SETUP_ZIPAPP = "openpilot/system/ui/mici_setup.py"
UPDATER_ENTRY_IN_ZIPAPP = "openpilot/system/ui/updater.py"
VERSION_PATH_IN_IMAGE = "/VERSION"
PATCH_MARKER = "STARPILOT_C4_RESET_LAYOUT_V1"
MICI_RESET_PATCH_MARKER = "STARPILOT_C4_MICI_RESET_LAYOUT_V1"
TICI_RESET_PATCH_MARKER = "STARPILOT_C4_TICI_RESET_LAYOUT_V1"
APP_PATCH_MARKER = "STARPILOT_C4_RESET_APP_DIMENSIONS_V1"
SETUP_WIFI_PATCH_MARKER = "JEEPNY_AVAILABLE = True"
SETUP_BRANDING_PATCH_MARKER = "STARPILOT_SETUP_BRANDING_V1"
SETUP_SSH_RESTORE_PATCH_MARKER = "STARPILOT_SETUP_SSH_RESTORE_V1"
ANDROID_SPARSE_MAGIC = 0xED26FF3A
CHUNK_TYPE_RAW = 0xCAC1
@@ -287,6 +290,34 @@ def patch_setup_wifi_manager() -> bytes:
return data
def patch_setup_branding_script(original: bytes, entry_name: str) -> bytes:
text = original.decode("utf-8")
if SETUP_BRANDING_PATCH_MARKER in text:
return text.encode("utf-8")
text = text.replace(
'OPENPILOT_URL = "https://openpilot.comma.ai"',
'NETWORK_CHECK_URL = "https://openpilot.comma.ai"\n'
'DEFAULT_INSTALLER_URL = "https://installer.comma.ai/firestar5683/StarPilot"\n'
f'# {SETUP_BRANDING_PATCH_MARKER}',
)
text = text.replace("urllib.request.Request(OPENPILOT_URL, method=\"HEAD\")",
"urllib.request.Request(NETWORK_CHECK_URL, method=\"HEAD\")")
text = text.replace("urllib.request.urlopen(OPENPILOT_URL, timeout=2)",
"urllib.request.urlopen(NETWORK_CHECK_URL, timeout=2)")
text = text.replace("self.download(OPENPILOT_URL)", "self.download(DEFAULT_INSTALLER_URL)")
if entry_name == MICI_SETUP_ENTRY_IN_SETUP_ZIPAPP:
text = text.replace('LargerSlider("slide to use\\nopenpilot"', 'LargerSlider("slide to use\\nstarpilot"')
elif entry_name == TICI_SETUP_ENTRY_IN_SETUP_ZIPAPP:
text = text.replace('ButtonRadio("openpilot"', 'ButtonRadio("StarPilot"')
if SETUP_BRANDING_PATCH_MARKER not in text:
raise RuntimeError(f"Failed to patch setup branding for {entry_name}")
return text.encode("utf-8")
def patch_setup_module(relative_path: str) -> bytes:
"""
Replace setup zipapp module with repo version so setup behavior stays in sync.
@@ -345,21 +376,32 @@ def _restore_ssh_after_reset():
def get_setup_replacements() -> dict[str, bytes]:
"""
Build the list of setup zipapp entries that must be synced from repo.
Keep the reference setup bundle intact and patch only the networking backend.
This intentionally includes key mici UI modules used by the C4 setup flow.
Some reference AGNOS images ship older setup zipapps that do not include all
of these entries; patching code adds missing entries when needed.
The reference AGNOS setup bundle already contains the correct small-screen
selector and matching mici UI modules. Replacing those modules with repo-head
versions caused bootstrap incompatibilities. The only setup-side changes we
still need are the jeepney fallback plus the StarPilot branding/url strings.
"""
return {
APPLICATION_ENTRY_IN_ZIPAPP: patch_setup_module("system/ui/lib/application.py"),
WIFI_MANAGER_ENTRY_IN_SETUP_ZIPAPP: patch_setup_wifi_manager(),
SETUP_ENTRY_IN_SETUP_ZIPAPP: patch_setup_module("system/ui/setup.py"),
TICI_SETUP_ENTRY_IN_SETUP_ZIPAPP: patch_setup_script_with_ssh_restore("system/ui/tici_setup.py"),
MICI_SETUP_ENTRY_IN_SETUP_ZIPAPP: patch_setup_script_with_ssh_restore("system/ui/mici_setup.py"),
}
def patch_updater_module() -> bytes:
"""
Replace only the bundled updater selector with the repo version.
The selector itself carries the small-screen fallback logic; the rest of the
reference updater zipapp stays unchanged.
"""
repo_root = Path(__file__).resolve().parents[2]
src = repo_root / "system/ui/updater.py"
if not src.is_file():
raise RuntimeError(f"Unable to find repo updater source: {src}")
return src.read_bytes()
def patch_reset_script() -> bytes:
"""
Use repo reset.py so AGNOS reset stays in sync with upstream selector logic.
@@ -424,7 +466,7 @@ def patch_tici_reset_script() -> bytes:
def parse_args() -> argparse.Namespace:
p = argparse.ArgumentParser(description="Patch AGNOS system image /usr/comma/reset for C4 tappity reset UI")
p = argparse.ArgumentParser(description="Patch AGNOS system image with minimal C4 reset/setup/updater fixes")
p.add_argument("--manifest", default="system/hardware/tici/agnos.json", help="Path to AGNOS manifest JSON")
p.add_argument("--work-dir", default=".cache/agnos_reset_patch", help="Working directory")
p.add_argument("--source-url", default=None, help="Override source raw system image URL")
@@ -725,6 +767,43 @@ def patch_reset_zipapp(original: bytes) -> bytes:
return shebang + dst_io.getvalue()
def patch_updater_zipapp(original: bytes) -> bytes:
shebang, zip_payload = split_shebang(original)
replacement = patch_updater_module()
src_io = BytesIO(zip_payload)
dst_io = BytesIO()
changed = False
found_updater = False
with zipfile.ZipFile(src_io, "r") as src, zipfile.ZipFile(dst_io, "w", compression=zipfile.ZIP_DEFLATED) as dst:
for info in src.infolist():
payload = src.read(info.filename)
if info.filename == UPDATER_ENTRY_IN_ZIPAPP:
found_updater = True
if payload != replacement:
payload = replacement
changed = True
new_info = zipfile.ZipInfo(info.filename, info.date_time)
new_info.compress_type = zipfile.ZIP_DEFLATED
new_info.external_attr = info.external_attr
new_info.create_system = info.create_system
dst.writestr(new_info, payload)
if not found_updater:
new_info = zipfile.ZipInfo(UPDATER_ENTRY_IN_ZIPAPP)
new_info.compress_type = zipfile.ZIP_DEFLATED
new_info.external_attr = 0o100644 << 16
new_info.create_system = 3
dst.writestr(new_info, replacement)
changed = True
if not changed:
return original
return shebang + dst_io.getvalue()
def patch_setup_zipapp(original: bytes) -> bytes:
shebang, zip_payload = split_shebang(original)
@@ -742,6 +821,11 @@ def patch_setup_zipapp(original: bytes) -> bytes:
if info.filename in replacements and payload != replacements[info.filename]:
payload = replacements[info.filename]
changed = True
elif info.filename in (MICI_SETUP_ENTRY_IN_SETUP_ZIPAPP, TICI_SETUP_ENTRY_IN_SETUP_ZIPAPP):
patched_payload = patch_setup_branding_script(payload, info.filename)
if patched_payload != payload:
payload = patched_payload
changed = True
new_info = zipfile.ZipInfo(info.filename, info.date_time)
new_info.compress_type = zipfile.ZIP_DEFLATED
@@ -749,7 +833,6 @@ def patch_setup_zipapp(original: bytes) -> bytes:
new_info.create_system = info.create_system
dst.writestr(new_info, payload)
# Inject missing setup modules required by current StarPilot setup flow.
default_external_attr = 0o100644 << 16
for entry, payload in replacements.items():
if entry in seen_entries:
@@ -792,11 +875,27 @@ def setup_zipapp_has_expected_content(data: bytes) -> bool:
for entry, payload in replacements.items():
if z.read(entry) != payload:
return False
for entry in (MICI_SETUP_ENTRY_IN_SETUP_ZIPAPP, TICI_SETUP_ENTRY_IN_SETUP_ZIPAPP):
setup_script = z.read(entry)
if SETUP_BRANDING_PATCH_MARKER.encode() not in setup_script:
return False
if b"installer.comma.ai/firestar5683/StarPilot" not in setup_script:
return False
except KeyError:
return False
return True
def updater_zipapp_has_expected_content(data: bytes) -> bool:
_shebang, zip_payload = split_shebang(data)
with zipfile.ZipFile(BytesIO(zip_payload), "r") as z:
try:
updater_script = z.read(UPDATER_ENTRY_IN_ZIPAPP)
except KeyError:
return False
return updater_script == patch_updater_module()
def parse_inode(debugfs_output: str) -> int:
m = re.search(r"Inode:\s+(\d+)", debugfs_output)
if not m:
@@ -1000,7 +1099,9 @@ def main() -> int:
original_setup = work_dir / "comma_setup.orig"
patched_setup = work_dir / "comma_setup.patched"
verify_setup = work_dir / "comma_setup.verify"
original_updater = work_dir / "comma_updater.orig"
patched_updater = work_dir / "comma_updater.patched"
verify_updater = work_dir / "comma_updater.verify"
print("Extracting /usr/comma/reset from image", flush=True)
run_debugfs(debugfs, patched_img, f"dump -p {RESET_PATH_IN_IMAGE} {original_reset}", write=False)
@@ -1024,7 +1125,7 @@ def main() -> int:
setup_original_data = original_setup.read_bytes()
setup_patched_data = patch_setup_zipapp(setup_original_data)
if setup_patched_data == setup_original_data:
print("Setup zipapp already contains jeepney fallback patch; continuing", flush=True)
print("Setup zipapp already contains the expected compat patches; continuing", flush=True)
patched_setup.write_bytes(setup_patched_data)
print("Writing patched /usr/comma/setup back into image", flush=True)
@@ -1035,6 +1136,23 @@ def main() -> int:
if not setup_zipapp_has_expected_content(verify_setup_data):
raise RuntimeError("Setup zipapp verification failed after writing setup file into image")
print("Extracting /usr/comma/updater from image", flush=True)
run_debugfs(debugfs, patched_img, f"dump -p {UPDATER_PATH_IN_IMAGE} {original_updater}", write=False)
updater_original_data = original_updater.read_bytes()
updater_patched_data = patch_updater_zipapp(updater_original_data)
if updater_patched_data == updater_original_data:
print("Updater zipapp already contains the expected selector patch; continuing", flush=True)
patched_updater.write_bytes(updater_patched_data)
print("Writing patched /usr/comma/updater back into image", flush=True)
write_regular_file_to_image(debugfs, patched_img, UPDATER_PATH_IN_IMAGE, patched_updater, "0100775", 0, 0)
run_debugfs(debugfs, patched_img, f"dump -p {UPDATER_PATH_IN_IMAGE} {verify_updater}", write=False)
verify_updater_data = verify_updater.read_bytes()
if not updater_zipapp_has_expected_content(verify_updater_data):
raise RuntimeError("Updater zipapp verification failed after writing updater file into image")
if args.set_version:
version_file = work_dir / "VERSION.patched"
version_file.write_text(args.set_version.strip() + "\n", encoding="utf-8")