diff --git a/crates/ltbox-core/src/model.rs b/crates/ltbox-core/src/model.rs index 317da0ce..1e80d16f 100644 --- a/crates/ltbox-core/src/model.rs +++ b/crates/ltbox-core/src/model.rs @@ -6,6 +6,12 @@ pub const TB320FC_MODEL: &str = "TB320FC"; /// Model token reported by LAVIE Tab 9QHD1 firmware. pub const LAVIE_TAB_9QHD1_MODEL: &str = "LAVIETab9QHD1"; +/// Model token reported by Lenovo Y700 Gen 5 firmware. +pub const TB323FU_MODEL: &str = "TB323FU"; + +/// Model token reported by Lenovo Y700 5G firmware. +pub const TB324ZC_MODEL: &str = "TB324ZC"; + /// Model token reported by Xiaoxin Pro 13 PRC firmware. pub const TB376FC_MODEL: &str = "TB376FC"; @@ -121,5 +127,13 @@ mod tests { "qti/LAVIETab9QHD1X/build", TB320FC_MODEL )); + assert!(!fingerprint_model_match( + "qti/TB323FU/TB323FU:15/build", + TB324ZC_MODEL + )); + assert!(!fingerprint_model_match( + "qti/TB324ZC/TB324ZC:16/build", + TB323FU_MODEL + )); } } diff --git a/crates/ltbox-gui/assets/devices/tb324zc.png b/crates/ltbox-gui/assets/devices/tb324zc.png new file mode 100644 index 00000000..9025159e Binary files /dev/null and b/crates/ltbox-gui/assets/devices/tb324zc.png differ diff --git a/crates/ltbox-gui/lang/en.json b/crates/ltbox-gui/lang/en.json index d64ca314..2a7d3d83 100644 --- a/crates/ltbox-gui/lang/en.json +++ b/crates/ltbox-gui/lang/en.json @@ -206,6 +206,7 @@ "err_edl_probe_work_dir_failed": "EDL probe work directory creation failed: {error}", "err_edl_session_open_failed": "EDL session open failed: {error}", "err_edl_transition_failed": "Could not reboot the device to EDL mode.", + "err_efisp_loader_manifest_required": "{model} requires a multi-image loader manifest. Pick `qsahara_device_programmer.xml` (or its encrypted `.x`) directly, or place it next to the chosen .melf file ({path}).", "err_fastboot_command_failed": "{command}: {error}", "err_fastboot_open_failed": "Bootloader device open failed: {error}", "err_fastboot_recovery_unsupported": "Bootloader mode cannot reboot to recovery directly; switch to ADB first.", @@ -228,8 +229,8 @@ "err_flash_parts_erase_failed": "Failed to erase partition {label}: {error}", "err_flash_parts_part_failed": "Failed to flash partition {label}: {error}", "err_flash_phys_write_failed": "Failed to write LUN {lun}: {error}", + "err_flash_prc_only": "Flash: {model} is PRC-only - cross-region or non-CN country settings are not allowed; aborting", "err_flash_set_bootable_lun_failed": "Failed to set bootable LUN: {error}", - "err_flash_tb322fc_prc_only": "Flash: TB322FC is PRC-only - cross-region or non-CN country settings are not allowed; aborting", "err_flash_vbmeta_key_unknown": "Flash: firmware vbmeta_system is signed with an unknown key (neither AOSP test-key nor LenovoRSA-key) - aborting before any write", "err_flash_xiaoxin_arb_downgrade": "The device rollback index is higher than this firmware's, so this firmware cannot be installed.", "err_flash_xiaoxin_arb_floor_unreadable": "Could not determine both device rollback floors from boot and vbmeta_system; installation aborted.", @@ -307,7 +308,6 @@ "err_task_failed": "Task failed.", "err_task_failed_with_error": "Task failed: {error}", "err_task_panicked": "Task panicked.", - "err_tb323fu_loader_manifest_required": "TB323FU requires a multi-image loader manifest. Pick `qsahara_device_programmer.xml` (or its encrypted `.x`) directly, or place it next to the chosen .melf file ({path}).", "err_unroot_backup_manifest_invalid": "Invalid root backup manifest {manifest}: {error}", "err_unroot_flash_failed": "Flash {label} failed: {error}", "err_unroot_image_missing": "{image} not found in selected folder", @@ -581,6 +581,7 @@ "live_flash_efisp_flash": "Flashing efisp (LUN 4)…", "live_flash_efisp_flash_failed": "efisp flash failed ({error}) — continuing to system reset", "live_flash_efisp_flashed": "efisp flashed", + "live_flash_efisp_force_auto": "Efisp/GBL target detected — rollback protection bypass forced to Auto.", "live_flash_fastboot_timeout": "adb reboot bootloader returned success, but Fastboot did not appear within {seconds}s — continuing without bootloader vars", "live_flash_files_count": ".x files: {x_count}, rawprogram XML: {xml_count}", "live_flash_firmware_folder": "Firmware folder: {path}", @@ -589,11 +590,10 @@ "live_flash_no_rawprogram_pack": "No rawprogram pack (.x / .xml) in this folder - it doesn't look like a valid firmware image folder.", "live_flash_phase3_xml_counts": "{raw} rawprogram + {patch} patch XML", "live_flash_region_convert": "Region convert: {value}", + "live_flash_region_efisp": "Region handled by efisp GBL; vendor_boot/vbmeta AVB conversion skipped.", "live_flash_rollback_bypass": "Bypass rollback protection: {value}", "live_flash_set_bootable": "Set bootable LUN to {lun} (xbl_a)", "live_flash_skip_adb": "Device in Bootloader/EDL — skipping ADB step", - "live_flash_tb323fu_force_auto": "TB323FU detected — rollback protection bypass forced to Auto.", - "live_flash_tb323fu_region_efisp": "TB323FU: region handled by efisp GBL; vendor_boot/vbmeta AVB conversion skipped.", "live_flash_vbmeta_key_unknown": "Firmware vbmeta_system signing key is unknown (neither AOSP test-key nor LenovoRSA-key) - aborting", "live_flash_vbmeta_status": "vbmeta.img: {status}", "live_flash_vendor_boot_status": "vendor_boot.img: {status}", @@ -674,10 +674,10 @@ "live_xml_decrypt_done": "Decrypted {count} .x file(s) → .xml", "live_xml_keep_excludes": "Excluding userdata/metadata", "loader_default_ext_unsupported": "The file set as the default EDL loader has an extension that isn't supported on the connected device.", + "loader_desc_efisp_gbl": "qsahara_device_programmer.xml", "loader_desc_standard": "xbl_s_devprg_ns.melf", - "loader_desc_tb323fu": "qsahara_device_programmer.xml", - "loader_desc_unknown": "xbl_s_devprg_ns.melf\nor qsahara_device_programmer.xml (Y700 Gen 5 only)", - "loader_model_mismatch_tooltip": "The connected device needs a different EDL loader (TB323FU: .xml/.x manifest; every other model: .melf).", + "loader_desc_unknown": "xbl_s_devprg_ns.melf\nor qsahara_device_programmer.xml (Snapdragon 8 Elite Gen 5 only)", + "loader_model_mismatch_tooltip": "The connected device needs a different EDL loader (TB323FU/TB324ZC: .xml/.x manifest; every other model: .melf).", "log_apatch_extract_kernel": "Extracting kernel: {input} → {output}", "log_apatch_extracted_kpimg": "extracted assets/kpimg → {path} ({bytes} bytes)", "log_apatch_kallsyms_all_missing": "CONFIG_KALLSYMS_ALL=y missing — non-fatal, but some KPMs may need it", @@ -789,7 +789,7 @@ "log_reboot_target_from": "→ {target} (from {source})", "log_reboot_target_from_edl": "→ {target} (from EDL, loader={loader})", "log_release_latest_asset": "latest: {tag} — asset {name}", - "log_root_efisp_check": "TB323FU detected — verifying efisp (GBL) provisioning…", + "log_root_efisp_check": "Efisp/GBL route detected — verifying efisp provisioning…", "log_root_efisp_ok": "efisp is provisioned — continuing (AVB/vbmeta handled by GBL)", "log_root_installing_manager_apk": "Installing manager APK: {path}", "log_root_loader": "Loader: {path}", @@ -999,6 +999,7 @@ "rollback_format_raw": "Raw (hex)", "rollback_format_unix": "Unix timestamp", "rollback_manual_desc": "Set the boot and vbmeta_system rollback indexes. Values must be earlier than now.", + "rollback_manual_efisp_disabled": "Efisp/GBL targets cannot use Manual rollback; it was disabled.", "rollback_manual_error_clock": "System clock unavailable.", "rollback_manual_error_date_shape": "Use YYYY-MM-DD.", "rollback_manual_error_date_value": "Enter a real calendar date.", @@ -1009,7 +1010,6 @@ "rollback_manual_error_prefix": "Start with 0x.", "rollback_manual_original": "Image index: {index}", "rollback_manual_placeholder": "Rollback index", - "rollback_manual_tb323fu_disabled": "TB323FU cannot use Manual rollback; it was disabled.", "rollback_off": "Off", "rollback_on": "On", "rollback_open_tip": "View the stored rollback indices", diff --git a/crates/ltbox-gui/lang/ja.json b/crates/ltbox-gui/lang/ja.json index 4986ef9c..cc52e100 100644 --- a/crates/ltbox-gui/lang/ja.json +++ b/crates/ltbox-gui/lang/ja.json @@ -206,6 +206,7 @@ "err_edl_probe_work_dir_failed": "EDL プローブ作業フォルダーの作成に失敗しました: {error}", "err_edl_session_open_failed": "EDL セッションを開けませんでした: {error}", "err_edl_transition_failed": "デバイスを EDL モードで再起動できませんでした。", + "err_efisp_loader_manifest_required": "{model} にはマルチイメージローダーマニフェストが必要です。`qsahara_device_programmer.xml` (または暗号化された `.x`) を直接選択するか、選択した .melf ファイルの隣に配置してください ({path})。", "err_fastboot_command_failed": "{command}: {error}", "err_fastboot_open_failed": "ブートローダーデバイスを開けませんでした: {error}", "err_fastboot_recovery_unsupported": "ブートローダーモードではリカバリへ直接再起動できません。先に ADB に切り替えてください。", @@ -228,8 +229,8 @@ "err_flash_parts_erase_failed": "パーティション {label} の消去に失敗しました: {error}", "err_flash_parts_part_failed": "パーティション {label} のフラッシュに失敗しました: {error}", "err_flash_phys_write_failed": "LUN {lun} の書き込みに失敗しました: {error}", + "err_flash_prc_only": "フラッシュ: {model} は中国専用 — 他リージョンへの変換や CN 以外の国設定は不可、中止します", "err_flash_set_bootable_lun_failed": "起動可能 LUN の設定に失敗しました: {error}", - "err_flash_tb322fc_prc_only": "フラッシュ: TB322FC は中国専用 — 他リージョンへの変換や CN 以外の国設定は不可、中止します", "err_flash_vbmeta_key_unknown": "フラッシュ: ファームウェアの vbmeta_system が不明な鍵で署名されています(AOSP テスト鍵でも LenovoRSA 鍵でもない)— 書き込み前に中止します", "err_flash_xiaoxin_arb_downgrade": "デバイスのロールバックインデックスがこのファームウェアより高いため、インストールできません。", "err_flash_xiaoxin_arb_floor_unreadable": "boot と vbmeta_system から両方のデバイスロールバック下限を取得できなかったため、インストールを中止しました。", @@ -307,7 +308,6 @@ "err_task_failed": "タスクに失敗しました。", "err_task_failed_with_error": "タスクに失敗しました: {error}", "err_task_panicked": "タスクがパニックで停止しました。", - "err_tb323fu_loader_manifest_required": "TB323FU にはマルチイメージローダーマニフェストが必要です。`qsahara_device_programmer.xml` (または暗号化された `.x`) を直接選択するか、選択した .melf ファイルの隣に配置してください ({path})。", "err_unroot_backup_manifest_invalid": "root バックアップマニフェスト {manifest} が無効です: {error}", "err_unroot_flash_failed": "{label} のフラッシュに失敗しました: {error}", "err_unroot_image_missing": "選択したフォルダーに {image} が見つかりません", @@ -581,6 +581,7 @@ "live_flash_efisp_flash": "efisp (LUN 4) をフラッシュ中...", "live_flash_efisp_flash_failed": "efisp のフラッシュに失敗しました ({error}) — システムリセットを続行します", "live_flash_efisp_flashed": "efisp がフラッシュされました", + "live_flash_efisp_force_auto": "efisp/GBL 対象が検出されました — ロールバック保護のバイパスを自動に強制します。", "live_flash_fastboot_timeout": "adb reboot bootloader は成功しましたが、{seconds} 秒以内に Fastboot が現れませんでした — ブートローダー変数なしで続行します", "live_flash_files_count": ".x ファイル: {x_count}個, rawprogram XML: {xml_count}個", "live_flash_firmware_folder": "ファームウェアフォルダ: {path}", @@ -589,11 +590,10 @@ "live_flash_no_rawprogram_pack": "このフォルダーに rawprogram(.x / .xml)がありません — 正しいファームウェアイメージのフォルダーではないようです。", "live_flash_phase3_xml_counts": "{raw} rawprogram + {patch} patch XML", "live_flash_region_convert": "リージョン変換: {value}", + "live_flash_region_efisp": "リージョンは efisp GBL によって処理されます。vendor_boot/vbmeta の AVB 変換をスキップします。", "live_flash_rollback_bypass": "ロールバック保護のバイパス: {value}", "live_flash_set_bootable": "起動可能なLUNを {lun} (xbl_a) に設定します", "live_flash_skip_adb": "デバイスはブートローダー/EDLモードです — ADBステップをスキップします", - "live_flash_tb323fu_force_auto": "TB323FU が検出されました — ロールバック保護のバイパスを自動に強制します。", - "live_flash_tb323fu_region_efisp": "TB323FU: リージョンは efisp GBL によって処理されます。vendor_boot/vbmeta の AVB 変換をスキップします。", "live_flash_vbmeta_key_unknown": "ファームウェア vbmeta_system の署名鍵が不明(AOSP テスト鍵/LenovoRSA 鍵ではない)— 中止します", "live_flash_vbmeta_status": "vbmeta.img: {status}", "live_flash_vendor_boot_status": "vendor_boot.img: {status}", @@ -674,10 +674,10 @@ "live_xml_decrypt_done": "{count} 個の .x ファイルを復号しました → .xml", "live_xml_keep_excludes": "userdata / metadata を除外しています", "loader_default_ext_unsupported": "既定の EDL ローダーに指定されたファイルの拡張子は、接続中の端末ではサポートされていません。", + "loader_desc_efisp_gbl": "qsahara_device_programmer.xml", "loader_desc_standard": "xbl_s_devprg_ns.melf", - "loader_desc_tb323fu": "qsahara_device_programmer.xml", - "loader_desc_unknown": "xbl_s_devprg_ns.melf\nまたは qsahara_device_programmer.xml (Y700 Gen 5 のみ)", - "loader_model_mismatch_tooltip": "接続中の端末には別の EDL ローダーが必要です(TB323FU: .xml/.x マニフェスト、その他のモデル: .melf)。", + "loader_desc_unknown": "xbl_s_devprg_ns.melf\nまたは qsahara_device_programmer.xml (Snapdragon 8 Elite Gen 5 のみ)", + "loader_model_mismatch_tooltip": "接続中の端末には別の EDL ローダーが必要です(TB323FU/TB324ZC: .xml/.x マニフェスト、その他のモデル: .melf)。", "log_apatch_extract_kernel": "カーネルを抽出中: {input} → {output}", "log_apatch_extracted_kpimg": "assets/kpimg を抽出しました → {path} ({bytes} バイト)", "log_apatch_kallsyms_all_missing": "CONFIG_KALLSYMS_ALL=y がありません — 致命的ではありませんが、一部の KPM で必要になる場合があります", @@ -789,7 +789,7 @@ "log_reboot_target_from": "→ {target} ({source} から)", "log_reboot_target_from_edl": "→ {target} (EDLから, ローダー={loader})", "log_release_latest_asset": "最新: {tag} — アセット {name}", - "log_root_efisp_check": "TB323FU が検出されました — efisp (GBL) プロビジョニングを検証中...", + "log_root_efisp_check": "efisp/GBL ルートが検出されました — efisp のプロビジョニングを検証中...", "log_root_efisp_ok": "efisp がプロビジョニングされています — 続行します (AVB/vbmetaはGBLによって処理されます)", "log_root_installing_manager_apk": "マネージャー APK をインストール中: {path}", "log_root_loader": "ローダー: {path}", @@ -999,6 +999,7 @@ "rollback_format_raw": "元の値 (16進数)", "rollback_format_unix": "Unix タイムスタンプ", "rollback_manual_desc": "boot および vbmeta_system のロールバックインデックスを設定します。値は現在時刻より前である必要があります。", + "rollback_manual_efisp_disabled": "efisp/GBL 対象では手動ロールバックを使用できません。無効になりました。", "rollback_manual_error_clock": "システムクロックを利用できません。", "rollback_manual_error_date_shape": "YYYY-MM-DD 形式で入力してください。", "rollback_manual_error_date_value": "実在する日付を入力してください。", @@ -1009,7 +1010,6 @@ "rollback_manual_error_prefix": "0x で始めてください。", "rollback_manual_original": "イメージインデックス: {index}", "rollback_manual_placeholder": "ロールバックインデックス", - "rollback_manual_tb323fu_disabled": "TB323FU は手動ロールバックを使用できません。無効になりました。", "rollback_off": "オフ", "rollback_on": "オン", "rollback_open_tip": "保存されたロールバックインデックスを表示", diff --git a/crates/ltbox-gui/lang/ko.json b/crates/ltbox-gui/lang/ko.json index 79c00112..5017180c 100644 --- a/crates/ltbox-gui/lang/ko.json +++ b/crates/ltbox-gui/lang/ko.json @@ -206,6 +206,7 @@ "err_edl_probe_work_dir_failed": "EDL 검사 작업 폴더 생성 실패: {error}", "err_edl_session_open_failed": "EDL 세션 열기 실패: {error}", "err_edl_transition_failed": "기기를 EDL 모드로 재부팅하지 못했습니다.", + "err_efisp_loader_manifest_required": "{model}에는 다중 이미지 로더 매니페스트가 필요합니다. `qsahara_device_programmer.xml`(또는 암호화된 `.x`)을 직접 선택하거나, 선택한 .melf 파일 옆에 두세요({path}).", "err_fastboot_command_failed": "{command}: {error}", "err_fastboot_open_failed": "부트로더 기기 열기 실패: {error}", "err_fastboot_recovery_unsupported": "부트로더 모드에서는 복구 모드로 직접 재부팅할 수 없습니다. 먼저 ADB로 전환하세요.", @@ -228,8 +229,8 @@ "err_flash_parts_erase_failed": "{label} 파티션을 지우지 못했습니다: {error}", "err_flash_parts_part_failed": "{label} 파티션 플래싱에 실패했습니다: {error}", "err_flash_phys_write_failed": "LUN {lun} 쓰기에 실패했습니다: {error}", + "err_flash_prc_only": "플래싱: {model} 모델은 중국 전용 — 타 지역 변환이나 비-CN 국가 설정 불가, 중단", "err_flash_set_bootable_lun_failed": "부팅 LUN 설정 실패: {error}", - "err_flash_tb322fc_prc_only": "플래싱: TB322FC는 중국 전용 — 타 지역 변환이나 비-CN 국가 설정 불가, 중단", "err_flash_vbmeta_key_unknown": "플래싱: 펌웨어 vbmeta_system 서명 키를 알 수 없음(AOSP 테스트 키도 LenovoRSA 키도 아님) — 쓰기 전 중단", "err_flash_xiaoxin_arb_downgrade": "기기의 롤백 인덱스가 이 펌웨어보다 높아 설치할 수 없습니다.", "err_flash_xiaoxin_arb_floor_unreadable": "boot 및 vbmeta_system에서 기기의 두 롤백 하한값을 확인하지 못해 설치를 중단했습니다.", @@ -307,7 +308,6 @@ "err_task_failed": "작업에 실패했습니다.", "err_task_failed_with_error": "작업 실패: {error}", "err_task_panicked": "작업이 패닉으로 중단되었습니다.", - "err_tb323fu_loader_manifest_required": "TB323FU는 다중 이미지 로더 매니페스트가 필요합니다. `qsahara_device_programmer.xml`(또는 암호화된 `.x`)을 직접 선택하거나, 선택한 .melf 파일 옆에 두세요({path}).", "err_unroot_backup_manifest_invalid": "루트 백업 매니페스트 {manifest}가 올바르지 않습니다: {error}", "err_unroot_flash_failed": "{label} 플래시 실패: {error}", "err_unroot_image_missing": "선택한 폴더에서 {image}를 찾을 수 없습니다.", @@ -581,6 +581,7 @@ "live_flash_efisp_flash": "efisp 플래싱 중 (LUN 4)…", "live_flash_efisp_flash_failed": "efisp 플래싱 실패 ({error}) — 시스템 재부팅은 계속 진행", "live_flash_efisp_flashed": "efisp 플래싱 완료", + "live_flash_efisp_force_auto": "efisp/GBL 대상 감지됨 — 롤백 보호 우회를 자동으로 강제 전환합니다.", "live_flash_fastboot_timeout": "adb reboot bootloader는 성공했지만 {seconds}초 안에 Fastboot가 나타나지 않음 — 부트로더 변수 없이 계속 진행", "live_flash_files_count": ".x 파일: {x_count}개, rawprogram XML: {xml_count}개", "live_flash_firmware_folder": "펌웨어 폴더: {path}", @@ -589,11 +590,10 @@ "live_flash_no_rawprogram_pack": "이 폴더에 rawprogram(.x/.xml)이 없습니다 — 올바른 펌웨어 이미지 폴더가 아닌 것 같습니다.", "live_flash_phase3_xml_counts": "rawprogram {raw}개 + patch XML {patch}개", "live_flash_region_convert": "지역 변환: {value}", + "live_flash_region_efisp": "지역은 efisp GBL로 처리 — vendor_boot/vbmeta AVB 변환 건너뜀.", "live_flash_rollback_bypass": "롤백 보호 우회: {value}", "live_flash_set_bootable": "부팅 가능 LUN을 {lun}로 설정 (xbl_a)", "live_flash_skip_adb": "기기가 부트로더/EDL 모드 — ADB 단계 건너뜀", - "live_flash_tb323fu_force_auto": "TB323FU 감지됨 — 롤백 보호 우회를 자동으로 강제 전환합니다.", - "live_flash_tb323fu_region_efisp": "TB323FU: 지역은 efisp GBL로 처리 — vendor_boot/vbmeta AVB 변환 건너뜀.", "live_flash_vbmeta_key_unknown": "펌웨어 vbmeta_system 서명 키 알 수 없음(AOSP 테스트 키/LenovoRSA 키 아님) — 중단", "live_flash_vbmeta_status": "vbmeta.img: {status}", "live_flash_vendor_boot_status": "vendor_boot.img: {status}", @@ -674,10 +674,10 @@ "live_xml_decrypt_done": ".x 파일 {count}개 복호화 완료 → .xml", "live_xml_keep_excludes": "userdata/metadata 제외", "loader_default_ext_unsupported": "기본 EDL 로더로 지정된 파일이 연결된 기기에서 지원되지 않는 확장자입니다.", + "loader_desc_efisp_gbl": "qsahara_device_programmer.xml", "loader_desc_standard": "xbl_s_devprg_ns.melf", - "loader_desc_tb323fu": "qsahara_device_programmer.xml", - "loader_desc_unknown": "xbl_s_devprg_ns.melf\n또는 qsahara_device_programmer.xml (Y700 5세대 전용)", - "loader_model_mismatch_tooltip": "연결된 기기에는 다른 EDL 로더가 필요합니다 (TB323FU: .xml/.x 매니페스트, 그 외 모델: .melf).", + "loader_desc_unknown": "xbl_s_devprg_ns.melf\n또는 qsahara_device_programmer.xml (Snapdragon 8 Elite Gen 5 전용)", + "loader_model_mismatch_tooltip": "연결된 기기에는 다른 EDL 로더가 필요합니다 (TB323FU/TB324ZC: .xml/.x 매니페스트, 그 외 모델: .melf).", "log_apatch_extract_kernel": "커널 추출 중: {input} → {output}", "log_apatch_extracted_kpimg": "assets/kpimg 추출 완료 → {path} ({bytes} 바이트)", "log_apatch_kallsyms_all_missing": "CONFIG_KALLSYMS_ALL=y 없음 — 치명적이지 않지만 일부 KPM에 필요할 수 있음", @@ -789,7 +789,7 @@ "log_reboot_target_from": "{source} → {target}", "log_reboot_target_from_edl": "EDL → {target} (로더={loader})", "log_release_latest_asset": "최신 릴리스: {tag} — 에셋 {name}", - "log_root_efisp_check": "TB323FU 감지됨 — efisp(GBL) 설치 여부 확인 중…", + "log_root_efisp_check": "efisp/GBL 경로 감지됨 — efisp 설치 여부 확인 중…", "log_root_efisp_ok": "efisp 설치 확인 — 계속 진행 (AVB/vbmeta는 GBL이 처리)", "log_root_installing_manager_apk": "매니저 APK 설치 중: {path}", "log_root_loader": "로더: {path}", @@ -999,6 +999,7 @@ "rollback_format_raw": "원본값 (16진수)", "rollback_format_unix": "유닉스 타임스탬프", "rollback_manual_desc": "boot 및 vbmeta_system 롤백 인덱스를 설정합니다. 값은 현재 시간보다 이전이어야 합니다.", + "rollback_manual_efisp_disabled": "efisp/GBL 대상은 수동 롤백을 사용할 수 없어 비활성화했습니다.", "rollback_manual_error_clock": "시스템 시계를 사용할 수 없습니다.", "rollback_manual_error_date_shape": "YYYY-MM-DD 형식으로 입력하세요.", "rollback_manual_error_date_value": "실제 존재하는 날짜를 입력하세요.", @@ -1009,7 +1010,6 @@ "rollback_manual_error_prefix": "0x로 시작하세요.", "rollback_manual_original": "이미지 인덱스: {index}", "rollback_manual_placeholder": "롤백 인덱스", - "rollback_manual_tb323fu_disabled": "TB323FU는 수동 롤백을 사용할 수 없어 비활성화했습니다.", "rollback_off": "끄기", "rollback_on": "켜기", "rollback_open_tip": "저장된 롤백 인덱스 보기", diff --git a/crates/ltbox-gui/lang/ru.json b/crates/ltbox-gui/lang/ru.json index efd528b0..7965201f 100644 --- a/crates/ltbox-gui/lang/ru.json +++ b/crates/ltbox-gui/lang/ru.json @@ -206,6 +206,7 @@ "err_edl_probe_work_dir_failed": "Не удалось создать рабочую папку EDL-проверки: {error}", "err_edl_session_open_failed": "Не удалось открыть EDL-сессию: {error}", "err_edl_transition_failed": "Не удалось перезагрузить устройство в режим EDL.", + "err_efisp_loader_manifest_required": "{model} требует манифест загрузчика с несколькими образами. Выберите `qsahara_device_programmer.xml` (или его зашифрованный `.x`) напрямую либо поместите его рядом с выбранным .melf-файлом ({path}).", "err_fastboot_command_failed": "{command}: {error}", "err_fastboot_open_failed": "Не удалось открыть устройство в режиме загрузчика: {error}", "err_fastboot_recovery_unsupported": "Из режима загрузчика нельзя напрямую перезагрузиться в recovery; сначала переключитесь на ADB.", @@ -228,8 +229,8 @@ "err_flash_parts_erase_failed": "Не удалось стереть раздел {label}: {error}", "err_flash_parts_part_failed": "Не удалось прошить раздел {label}: {error}", "err_flash_phys_write_failed": "Не удалось записать LUN {lun}: {error}", + "err_flash_prc_only": "Прошивка: {model} только для Китая — конвертация в другой регион или не-CN страна недопустимы; прерывание", "err_flash_set_bootable_lun_failed": "Не удалось установить загрузочный LUN: {error}", - "err_flash_tb322fc_prc_only": "Прошивка: TB322FC только для Китая — конвертация в другой регион или не-CN страна недопустимы; прерывание", "err_flash_vbmeta_key_unknown": "Прошивка: vbmeta_system прошивки подписана неизвестным ключом (ни тестовым ключом AOSP, ни ключом LenovoRSA) — прерывание до записи", "err_flash_xiaoxin_arb_downgrade": "Индекс отката устройства выше, чем у этой прошивки, поэтому установить её нельзя.", "err_flash_xiaoxin_arb_floor_unreadable": "Не удалось определить оба пороговых индекса отката устройства из boot и vbmeta_system; установка прервана.", @@ -307,7 +308,6 @@ "err_task_failed": "Задача не удалась.", "err_task_failed_with_error": "Задача не удалась: {error}", "err_task_panicked": "Задача остановлена из-за panic.", - "err_tb323fu_loader_manifest_required": "TB323FU требует манифест загрузчика с несколькими образами. Выберите `qsahara_device_programmer.xml` (или его зашифрованный `.x`) напрямую либо поместите его рядом с выбранным .melf-файлом ({path}).", "err_unroot_backup_manifest_invalid": "Некорректный манифест резервной копии root {manifest}: {error}", "err_unroot_flash_failed": "Прошивка {label} не удалась: {error}", "err_unroot_image_missing": "В выбранной папке не найден {image}", @@ -581,6 +581,7 @@ "live_flash_efisp_flash": "Прошивка efisp (LUN 4)…", "live_flash_efisp_flash_failed": "Не удалось прошить efisp ({error}) — продолжаем сброс в систему", "live_flash_efisp_flashed": "efisp прошит", + "live_flash_efisp_force_auto": "Обнаружена цель efisp/GBL — обход защиты от отката принудительно переведён в авто.", "live_flash_fastboot_timeout": "adb reboot bootloader завершился успешно, но Fastboot не появился за {seconds} с — продолжаем без переменных загрузчика", "live_flash_files_count": ".x файлов: {x_count}, rawprogram XML: {xml_count}", "live_flash_firmware_folder": "Папка прошивки: {path}", @@ -589,11 +590,10 @@ "live_flash_no_rawprogram_pack": "В этой папке нет rawprogram (.x / .xml) — похоже, это не папка с образом прошивки.", "live_flash_phase3_xml_counts": "{raw} rawprogram + {patch} patch XML", "live_flash_region_convert": "Смена региона: {value}", + "live_flash_region_efisp": "Регион обрабатывается efisp GBL; преобразование vendor_boot/vbmeta AVB пропущено.", "live_flash_rollback_bypass": "Обход защиты от отката: {value}", "live_flash_set_bootable": "Загрузочный LUN установлен в {lun} (xbl_a)", "live_flash_skip_adb": "Устройство в Загрузчике/EDL — пропуск шага ADB", - "live_flash_tb323fu_force_auto": "Обнаружен TB323FU — обход защиты от отката принудительно переведён в авто.", - "live_flash_tb323fu_region_efisp": "TB323FU: регион обрабатывается efisp GBL; преобразование vendor_boot/vbmeta AVB пропущено.", "live_flash_vbmeta_key_unknown": "Ключ подписи vbmeta_system прошивки неизвестен (не тестовый ключ AOSP / не ключ LenovoRSA) — прерывание", "live_flash_vbmeta_status": "vbmeta.img: {status}", "live_flash_vendor_boot_status": "vendor_boot.img: {status}", @@ -674,10 +674,10 @@ "live_xml_decrypt_done": "Расшифровано {count} файлов .x → .xml", "live_xml_keep_excludes": "Исключая userdata/metadata", "loader_default_ext_unsupported": "Расширение файла, заданного загрузчиком EDL по умолчанию, не поддерживается на подключённом устройстве.", + "loader_desc_efisp_gbl": "qsahara_device_programmer.xml", "loader_desc_standard": "xbl_s_devprg_ns.melf", - "loader_desc_tb323fu": "qsahara_device_programmer.xml", - "loader_desc_unknown": "xbl_s_devprg_ns.melf\nили qsahara_device_programmer.xml (только Y700 5-го поколения)", - "loader_model_mismatch_tooltip": "Подключённому устройству нужен другой загрузчик EDL (TB323FU: манифест .xml/.x; остальные модели: .melf).", + "loader_desc_unknown": "xbl_s_devprg_ns.melf\nили qsahara_device_programmer.xml (только Snapdragon 8 Elite Gen 5)", + "loader_model_mismatch_tooltip": "Подключённому устройству нужен другой загрузчик EDL (TB323FU/TB324ZC: манифест .xml/.x; остальные модели: .melf).", "log_apatch_extract_kernel": "Извлечение ядра: {input} → {output}", "log_apatch_extracted_kpimg": "извлечён assets/kpimg → {path} ({bytes} байт)", "log_apatch_kallsyms_all_missing": "CONFIG_KALLSYMS_ALL=y отсутствует — не критично, но может требоваться некоторым KPM", @@ -789,7 +789,7 @@ "log_reboot_target_from": "{source} → {target}", "log_reboot_target_from_edl": "EDL → {target} (загрузчик={loader})", "log_release_latest_asset": "последний релиз: {tag} — ассет {name}", - "log_root_efisp_check": "Обнаружен TB323FU — проверка наличия efisp (GBL)…", + "log_root_efisp_check": "Обнаружен маршрут efisp/GBL — проверка наличия efisp…", "log_root_efisp_ok": "efisp присутствует — продолжаем (AVB/vbmeta обрабатывает GBL)", "log_root_installing_manager_apk": "Установка APK менеджера: {path}", "log_root_loader": "Загрузчик: {path}", @@ -999,6 +999,7 @@ "rollback_format_raw": "Исходное значение (hex)", "rollback_format_unix": "Метка времени Unix", "rollback_manual_desc": "Задайте индексы отката boot и vbmeta_system. Значения должны быть раньше текущего времени.", + "rollback_manual_efisp_disabled": "Цели efisp/GBL не могут использовать ручной откат; он отключен.", "rollback_manual_error_clock": "Системные часы недоступны.", "rollback_manual_error_date_shape": "Используйте формат YYYY-MM-DD.", "rollback_manual_error_date_value": "Введите существующую дату.", @@ -1009,7 +1010,6 @@ "rollback_manual_error_prefix": "Начните с 0x.", "rollback_manual_original": "Индекс образа: {index}", "rollback_manual_placeholder": "Индекс отката", - "rollback_manual_tb323fu_disabled": "TB323FU не может использовать ручной откат; он отключен.", "rollback_off": "Выкл", "rollback_on": "Вкл", "rollback_open_tip": "Показать сохранённые индексы отката", diff --git a/crates/ltbox-gui/lang/zh.json b/crates/ltbox-gui/lang/zh.json index a37abfdb..f80c6343 100644 --- a/crates/ltbox-gui/lang/zh.json +++ b/crates/ltbox-gui/lang/zh.json @@ -206,6 +206,7 @@ "err_edl_probe_work_dir_failed": "创建 EDL 探测工作目录失败:{error}", "err_edl_session_open_failed": "打开 EDL 会话失败:{error}", "err_edl_transition_failed": "无法将设备重启到 EDL 模式。", + "err_efisp_loader_manifest_required": "{model} 需要多镜像加载器清单。请直接选择 `qsahara_device_programmer.xml`(或其加密 `.x`),或将它放在所选 .melf 文件旁边({path})。", "err_fastboot_command_failed": "{command}: {error}", "err_fastboot_open_failed": "打开引导加载程序设备失败:{error}", "err_fastboot_recovery_unsupported": "引导加载程序模式不能直接重启到恢复模式;请先切换到 ADB。", @@ -228,8 +229,8 @@ "err_flash_parts_erase_failed": "擦除分区 {label} 失败:{error}", "err_flash_parts_part_failed": "刷写分区 {label} 失败:{error}", "err_flash_phys_write_failed": "写入 LUN {lun} 失败:{error}", + "err_flash_prc_only": "刷写:{model} 仅限国行 — 不支持跨区域或非 CN 国家设置,已中止", "err_flash_set_bootable_lun_failed": "设置可启动 LUN 失败:{error}", - "err_flash_tb322fc_prc_only": "刷写:TB322FC 仅限国行 — 不支持跨区域或非 CN 国家设置,已中止", "err_flash_vbmeta_key_unknown": "刷写:固件 vbmeta_system 的签名密钥未知(既非 AOSP 测试密钥也非 LenovoRSA 密钥)— 写入前中止", "err_flash_xiaoxin_arb_downgrade": "设备回滚索引高于此固件,因此无法安装此固件。", "err_flash_xiaoxin_arb_floor_unreadable": "无法从 boot 和 vbmeta_system 确定设备的两个回滚下限,已中止安装。", @@ -307,7 +308,6 @@ "err_task_failed": "任务失败。", "err_task_failed_with_error": "任务失败:{error}", "err_task_panicked": "任务因 panic 停止。", - "err_tb323fu_loader_manifest_required": "TB323FU 需要多镜像加载器清单。请直接选择 `qsahara_device_programmer.xml`(或其加密 `.x`),或将它放在所选 .melf 文件旁边({path})。", "err_unroot_backup_manifest_invalid": "Root 备份清单 {manifest} 无效:{error}", "err_unroot_flash_failed": "刷写 {label} 失败:{error}", "err_unroot_image_missing": "所选文件夹中未找到 {image}", @@ -581,6 +581,7 @@ "live_flash_efisp_flash": "正在刷入 efisp(LUN 4)…", "live_flash_efisp_flash_failed": "efisp 刷入失败({error})— 仍将继续重启到系统", "live_flash_efisp_flashed": "efisp 刷入完成", + "live_flash_efisp_force_auto": "检测到 efisp/GBL 目标 — 回滚保护绕过强制为自动。", "live_flash_fastboot_timeout": "adb reboot bootloader 返回成功,但在 {seconds} 秒内未出现 Fastboot — 将在无引导加载程序变量的情况下继续", "live_flash_files_count": ".x 文件: {x_count}, rawprogram XML: {xml_count}", "live_flash_firmware_folder": "固件目录: {path}", @@ -589,11 +590,10 @@ "live_flash_no_rawprogram_pack": "此文件夹中没有 rawprogram(.x / .xml)— 看起来不是有效的固件镜像文件夹。", "live_flash_phase3_xml_counts": "{raw} 个 rawprogram + {patch} 个 patch XML", "live_flash_region_convert": "区域转换:{value}", + "live_flash_region_efisp": "区域由 efisp GBL 处理;跳过 vendor_boot/vbmeta 的 AVB 转换。", "live_flash_rollback_bypass": "绕过回滚保护:{value}", "live_flash_set_bootable": "将可启动 LUN 设置为 {lun} (xbl_a)", "live_flash_skip_adb": "设备处于引导加载程序/EDL — 跳过 ADB 步骤", - "live_flash_tb323fu_force_auto": "检测到 TB323FU — 回滚保护绕过强制为自动。", - "live_flash_tb323fu_region_efisp": "TB323FU:区域由 efisp GBL 处理;跳过 vendor_boot/vbmeta 的 AVB 转换。", "live_flash_vbmeta_key_unknown": "固件 vbmeta_system 签名密钥未知(非 AOSP 测试密钥/LenovoRSA 密钥)— 中止", "live_flash_vbmeta_status": "vbmeta.img: {status}", "live_flash_vendor_boot_status": "vendor_boot.img: {status}", @@ -674,10 +674,10 @@ "live_xml_decrypt_done": "已解密 {count} 个 .x 文件 → .xml", "live_xml_keep_excludes": "排除 userdata/metadata", "loader_default_ext_unsupported": "设置为默认 EDL 加载器的文件的扩展名在连接的设备上不受支持。", + "loader_desc_efisp_gbl": "qsahara_device_programmer.xml", "loader_desc_standard": "xbl_s_devprg_ns.melf", - "loader_desc_tb323fu": "qsahara_device_programmer.xml", - "loader_desc_unknown": "xbl_s_devprg_ns.melf\n或 qsahara_device_programmer.xml(仅 Y700五代)", - "loader_model_mismatch_tooltip": "连接的设备需要不同的 EDL 加载器(TB323FU:.xml/.x 清单;其他机型:.melf)。", + "loader_desc_unknown": "xbl_s_devprg_ns.melf\n或 qsahara_device_programmer.xml(仅 Snapdragon 8 Elite Gen 5)", + "loader_model_mismatch_tooltip": "连接的设备需要不同的 EDL 加载器(TB323FU/TB324ZC:.xml/.x 清单;其他机型:.melf)。", "log_apatch_extract_kernel": "正在提取内核: {input} → {output}", "log_apatch_extracted_kpimg": "已提取 assets/kpimg → {path}({bytes} 字节)", "log_apatch_kallsyms_all_missing": "缺少 CONFIG_KALLSYMS_ALL=y — 非致命,但某些 KPM 可能需要", @@ -789,7 +789,7 @@ "log_reboot_target_from": "{source} → {target}", "log_reboot_target_from_edl": "EDL → {target}(加载器={loader})", "log_release_latest_asset": "最新发行:{tag} — 资源 {name}", - "log_root_efisp_check": "检测到 TB323FU — 正在检查 efisp(GBL)写入状态…", + "log_root_efisp_check": "检测到 efisp/GBL 路径 — 正在检查 efisp 写入状态…", "log_root_efisp_ok": "efisp 已写入 — 继续(AVB/vbmeta 由 GBL 处理)", "log_root_installing_manager_apk": "安装管理器 APK 中:{path}", "log_root_loader": "加载器:{path}", @@ -999,6 +999,7 @@ "rollback_format_raw": "原始值(十六进制)", "rollback_format_unix": "Unix 时间戳", "rollback_manual_desc": "设置 boot 和 vbmeta_system 回滚索引。值必须早于当前时间。", + "rollback_manual_efisp_disabled": "efisp/GBL 目标无法使用手动回滚,已将其禁用。", "rollback_manual_error_clock": "系统时钟不可用。", "rollback_manual_error_date_shape": "请使用 YYYY-MM-DD 格式。", "rollback_manual_error_date_value": "请输入真实存在的日期。", @@ -1009,7 +1010,6 @@ "rollback_manual_error_prefix": "请以 0x 开头。", "rollback_manual_original": "镜像索引:{index}", "rollback_manual_placeholder": "回滚索引", - "rollback_manual_tb323fu_disabled": "TB323FU 无法使用手动回滚,已将其禁用。", "rollback_off": "关闭", "rollback_on": "开启", "rollback_open_tip": "查看已保存的回滚索引", diff --git a/crates/ltbox-gui/src/loader.rs b/crates/ltbox-gui/src/loader.rs index de16dbfe..e4750c03 100644 --- a/crates/ltbox-gui/src/loader.rs +++ b/crates/ltbox-gui/src/loader.rs @@ -159,14 +159,15 @@ pub(crate) fn is_melf_loader(path: &std::path::Path) -> bool { } /// True when `path`'s extension is the EDL loader form the given model needs: -/// TB323FU → `.xml` / `.x` (Sahara manifest); every other model → `.melf`. +/// Efisp/GBL-route models → `.xml` / `.x` (Sahara manifest); every other model +/// → `.melf`. /// Inspects only the file's own extension, not the images a manifest references. -pub(crate) fn loader_ext_fits_model(is_tb323fu: bool, path: &std::path::Path) -> bool { +pub(crate) fn loader_ext_fits_model(uses_efisp_gbl_route: bool, path: &std::path::Path) -> bool { let ext = path .extension() .and_then(|e| e.to_str()) .map(|e| e.to_ascii_lowercase()); - if is_tb323fu { + if uses_efisp_gbl_route { matches!(ext.as_deref(), Some("xml") | Some("x")) } else { ext.as_deref() == Some("melf") @@ -235,7 +236,7 @@ mod tests { #[test] fn loader_ext_fits_model_by_device() { - // TB323FU needs the .xml / .x manifest. + // Efisp/GBL-route models need the .xml / .x manifest. assert!(loader_ext_fits_model( true, Path::new("x/qsahara_device_programmer.xml") diff --git a/crates/ltbox-gui/src/main.rs b/crates/ltbox-gui/src/main.rs index fc2303b6..b6b93164 100644 --- a/crates/ltbox-gui/src/main.rs +++ b/crates/ltbox-gui/src/main.rs @@ -2780,14 +2780,14 @@ impl App { /// Kick off region auto-detection on entering the Flash wizard (called /// right after `flash.reset()`): - /// * PRC-only model (TB322FC) → preselect PRC and jump to the target step. + /// * PRC-only model → preselect PRC and jump to the target step. /// * usable polled serial → probe PTSTPD (region step shows an indicator). /// * no usable serial → open the manual-serial prompt. /// /// On a fetch failure / inconclusive SaleArea the handler falls back to the /// manual PRC/ROW cards, so this never blocks the wizard. fn begin_flash_region_auto(&mut self) -> Task { - if self.is_tb322fc() { + if self.is_prc_only() { // PRC-only SKU: no lookup needed; skip straight to the target step. self.flash.device_region = Some(DeviceRegion::Prc); self.flash.step = 1; @@ -3011,14 +3011,14 @@ impl App { let path = std::path::Path::new(selected_path); if path.is_file() { self.remember_recent(pickers::PickerKind::File, selected_path); - // TB323FU model gate: if the user picked a `.melf` but the - // device is a TB323FU, upgrade to the + // Efisp/GBL-route model gate: if the user picked a `.melf`, upgrade + // it to the // `qsahara_device_programmer.xml` manifest sitting in the // same folder. If the manifest is missing the .melf // alone is wrong and would fail mid-Sahara — abort up // front. Performed during resolve so the wizard's Confirm // step shows the correct path. - if self.is_tb323fu() + if self.uses_efisp_gbl_route() && is_melf_loader(path) && let Some(parent) = path.parent() { @@ -3026,7 +3026,8 @@ impl App { return Ok(manifest.to_string_lossy().to_string()); } return Err(tr_args!( - "err_tb323fu_loader_manifest_required", + "err_efisp_loader_manifest_required", + model = self.device_model.as_str(), path = path.display() )); } @@ -3173,16 +3174,16 @@ impl App { self.device_class() == DeviceClass::TB320FC } - /// Whether the polled device is a TB323FU. Drives the multi-image - /// EDL loader path: TB323FU doesn't accept a single + /// Whether the polled device uses the efisp/GBL exploit route. These models + /// also use the multi-image EDL loader path and don't accept a single /// `xbl_s_devprg_ns.melf`; it needs the full /// `qsahara_device_programmer.xml` manifest + the per-id ELF / /// MBN payloads it references. The loader resolver upgrades a /// stray `.melf` selection to the manifest when one exists in /// the same folder; if not, it aborts up front rather than /// failing mid-Sahara. - fn is_tb323fu(&self) -> bool { - self.device_class() == DeviceClass::TB323FU + fn uses_efisp_gbl_route(&self) -> bool { + self.device_class().uses_efisp_gbl_route() } fn is_xiaoxin_pro13(&self) -> bool { @@ -3190,12 +3191,12 @@ impl App { } /// True when `path`'s extension is the EDL loader form the connected device - /// needs: TB323FU (Y700 Gen 5) loads the multi-image Sahara manifest + /// needs: efisp/GBL-route models load the multi-image Sahara manifest /// (`.xml` / `.x`); every other model loads a `.melf` single-blob programmer. /// Inspects only the picked file's own extension, never the `.mbn` / `.elf` /// images a manifest references internally. fn loader_fits_model(&self, path: &std::path::Path) -> bool { - loader_ext_fits_model(self.is_tb323fu(), path) + loader_ext_fits_model(self.uses_efisp_gbl_route(), path) } /// True when the Settings default EDL loader is unset, or its extension fits @@ -3209,12 +3210,12 @@ impl App { } /// Loader-picker description, resolved live against the connected - /// device. TB323FU (Y700 Gen 5) needs the `qsahara_device_programmer.xml` + /// device. Efisp/GBL-route models need the `qsahara_device_programmer.xml` /// manifest, not the `.melf`; with no recognised model the picker hints /// both. Every loader picker routes its subtitle through this. fn loader_picker_desc(&self) -> String { - if self.is_tb323fu() { - self.t("loader_desc_tb323fu").to_string() + if self.uses_efisp_gbl_route() { + self.t("loader_desc_efisp_gbl").to_string() } else if self.device_model.is_empty() { self.t("loader_desc_unknown").to_string() } else { @@ -3222,12 +3223,12 @@ impl App { } } - /// Whether the polled device is a TB322FC. PRC-only SKU — the Flash + /// Whether the polled device is a PRC-only SKU. The Flash /// wizard hides ROW + OtherRegion as disabled cards so the user /// cannot pick a region or cross-region flash target that the /// hardware doesn't ship with. - fn is_tb322fc(&self) -> bool { - self.device_class() == DeviceClass::TB322FC + fn is_prc_only(&self) -> bool { + self.device_class().is_prc_only() } /// True when the dashboard poll has placed the device in a mode @@ -4273,6 +4274,44 @@ mod tests { assert_eq!(app.country_popup_selected_code(), Some("CN")); } + #[test] + fn tb324zc_capabilities_drive_loader_and_region_entry() { + let mut app = App { + device_model: "TB324ZC".into(), + ..App::default() + }; + assert!(app.uses_efisp_gbl_route()); + assert!(app.is_prc_only()); + assert!(app.loader_fits_model(std::path::Path::new("qsahara_device_programmer.xml"))); + assert!(app.loader_fits_model(std::path::Path::new("qsahara_device_programmer.x"))); + assert!(!app.loader_fits_model(std::path::Path::new("prog.melf"))); + + let _ = app.begin_flash_region_auto(); + assert_eq!(app.flash.device_region, Some(DeviceRegion::Prc)); + assert_eq!(app.flash.step, 1); + } + + #[test] + fn tb324zc_flash_country_dispatch_rejects_non_cn_but_advanced_stays_unrestricted() { + let mut app = App { + device_model: "TB324ZC".into(), + country_popup_open: true, + wf_config: WorkflowConfig { + country_action: CountryAction::Set("CN".into()), + ..WorkflowConfig::default() + }, + ..App::default() + }; + let _ = app.update(Message::SelectCountry("KR".into())); + assert_eq!(app.wf_config.country_action.target(), Some("CN")); + assert!(app.country_popup_open); + + app.adv_needs_country = true; + let _ = app.update(Message::SelectCountry("KR".into())); + assert_eq!(app.adv_wizard.country.as_deref(), Some("KR")); + assert!(!app.country_popup_open); + } + #[test] fn flash_keep_data_preserves_confirm_country_override() { let mut app = App { @@ -4587,6 +4626,7 @@ mod tests { assert_eq!(arb_from_model(""), ""); assert_eq!(arb_from_model(" "), ""); assert_eq!(arb_from_model("TB322FC"), "arb_no"); + assert_eq!(arb_from_model("TB324ZC"), "arb_yes"); assert_eq!(arb_from_model("TB520FU"), "arb_yes"); } diff --git a/crates/ltbox-gui/src/model/device.rs b/crates/ltbox-gui/src/model/device.rs index 4bffeb26..37dce4ca 100644 --- a/crates/ltbox-gui/src/model/device.rs +++ b/crates/ltbox-gui/src/model/device.rs @@ -2,7 +2,10 @@ //! and the live connection state, split out of `main.rs`. use crate::theme::Palette; -use ltbox_core::model::{LAVIE_TAB_9QHD1_MODEL, TB320FC_MODEL, is_tb320fc_model}; +use ltbox_core::model::{ + LAVIE_TAB_9QHD1_MODEL, TB320FC_MODEL, TB323FU_MODEL, TB324ZC_MODEL, fingerprint_model_match, + is_tb320fc_model, +}; /// Classifies the device model into a known SKU so wizard gates ask /// "what device class are we on?" once instead of comparing the raw @@ -25,6 +28,9 @@ pub(crate) enum DeviceClass { /// `qsahara_device_programmer.xml` Sahara manifest rather than a /// single `.melf` loader. TB323FU, + /// TB324ZC — Lenovo Y700 5G. Uses the efisp/GBL exploit route and + /// multi-image Sahara manifest, and is restricted to PRC firmware. + TB324ZC, /// Any other supported model. No special-case gates apply. Generic, } @@ -35,12 +41,31 @@ impl DeviceClass { Self::TB320FC } else if model.eq_ignore_ascii_case("TB322FC") { Self::TB322FC - } else if model.eq_ignore_ascii_case("TB323FU") { + } else if model.eq_ignore_ascii_case(TB323FU_MODEL) { Self::TB323FU + } else if model.eq_ignore_ascii_case(TB324ZC_MODEL) { + Self::TB324ZC } else { Self::Generic } } + + /// Whether this class uses the efisp/GBL exploit route. + pub(crate) const fn uses_efisp_gbl_route(self) -> bool { + matches!(self, Self::TB323FU | Self::TB324ZC) + } + + /// Whether this class is limited to PRC firmware and country settings. + pub(crate) const fn is_prc_only(self) -> bool { + matches!(self, Self::TB322FC | Self::TB324ZC) + } +} + +/// Whether a firmware fingerprint belongs to an efisp/GBL-route model. +pub(crate) fn fingerprint_uses_efisp_gbl_route(fingerprint: &str) -> bool { + [TB323FU_MODEL, TB324ZC_MODEL] + .iter() + .any(|model| fingerprint_model_match(fingerprint, model)) } /// Lenovo tablets that expose two USB-C ports. Only the port on the long @@ -53,7 +78,7 @@ pub(crate) const DUAL_USBC_MODELS: [&str; 5] = [ LAVIE_TAB_9QHD1_MODEL, "TB321FU", "TB322FC", - "TB323FU", + TB323FU_MODEL, ]; /// Whether `model` is one of the [`DUAL_USBC_MODELS`] (case-insensitive). @@ -84,10 +109,28 @@ mod tests { DeviceClass::TB320FC ); assert!(is_dual_usbc_model(LAVIE_TAB_9QHD1_MODEL)); - assert_eq!(DeviceClass::from_model("TB323FU"), DeviceClass::TB323FU); + assert_eq!(DeviceClass::from_model(TB323FU_MODEL), DeviceClass::TB323FU); assert!(!is_dual_usbc_model("TB330FU")); } + #[test] + fn tb324zc_classifies_on_both_capability_axes_without_dual_usbc() { + let class = DeviceClass::from_model(TB324ZC_MODEL); + assert_eq!(class, DeviceClass::TB324ZC); + assert!(class.uses_efisp_gbl_route()); + assert!(class.is_prc_only()); + assert!(!is_dual_usbc_model(TB324ZC_MODEL)); + assert!(fingerprint_uses_efisp_gbl_route( + "qti/TB324ZC/TB324ZC:16/build:user/release-keys" + )); + } + + #[test] + fn tb324zc_remains_rollback_protected() { + assert!(is_rollback_protected_model(TB324ZC_MODEL)); + assert!(!is_rollback_protected_model("TB322FC")); + } + #[test] fn xiaoxin_pro13_models_are_rollback_protected() { assert!(is_rollback_protected_model( diff --git a/crates/ltbox-gui/src/update/flash.rs b/crates/ltbox-gui/src/update/flash.rs index 776fed0f..3b3f17a3 100644 --- a/crates/ltbox-gui/src/update/flash.rs +++ b/crates/ltbox-gui/src/update/flash.rs @@ -7,11 +7,11 @@ impl App { pub(crate) fn update_flash(&mut self, msg: FlashMsg) -> Task { match msg { FlashMsg::FlashRegion(r) => { - // TB322FC is a PRC-only SKU. The region card UI grays + // PRC-only SKUs gray // out ROW, but a stale message from a pre-poll click // could still land here. Drop it so the wizard never // accepts a region the hardware doesn't ship with. - if self.is_tb322fc() && r == DeviceRegion::Row { + if self.is_prc_only() && r == DeviceRegion::Row { return Task::none(); } self.flash.device_region = Some(r); @@ -86,10 +86,10 @@ impl App { Task::none() } FlashMsg::FlashTarget(t) => { - // TB322FC: cross-region (OtherRegion) flashes are blocked + // PRC-only SKUs block cross-region (OtherRegion) flashes // because the only valid region is PRC. Drop the message // even if a stale dispatch slips past the disabled card. - if self.is_tb322fc() && t == FlashTarget::OtherRegion { + if self.is_prc_only() && t == FlashTarget::OtherRegion { return Task::none(); } self.flash.target = Some(t); @@ -288,18 +288,18 @@ impl App { Task::none() } FlashMsg::FlashConfirmSetRegion(r) => { - // TB322FC is PRC-only; the editor grays out ROW, but drop a + // PRC-only models gray out ROW; drop a // stale dispatch defensively like the region card handler does. - if !(self.is_tb322fc() && r == DeviceRegion::Row) { + if !(self.is_prc_only() && r == DeviceRegion::Row) { self.wf_config.device_region = Some(r); } self.confirm_edit_field = None; Task::none() } FlashMsg::FlashConfirmSetTarget(t) => { - // Target ↔ region edit both map onto `modify_region`. TB322FC + // Target ↔ region edit both map onto `modify_region`. PRC-only models // can't cross regions, so block OtherRegion defensively. - if !(self.is_tb322fc() && t == FlashTarget::OtherRegion) { + if !(self.is_prc_only() && t == FlashTarget::OtherRegion) { self.wf_config.modify_region = t == FlashTarget::OtherRegion; } self.confirm_edit_field = None; @@ -324,9 +324,9 @@ impl App { } FlashMsg::FlashConfirmSetRegionEdit(on) => { // Region edit drives the same `modify_region` cross-region flag - // as the Target row, so apply the TB322FC PRC-only guard here + // as the Target row, so apply the PRC-only guard here // too — otherwise this path bypasses the disabled Target option. - if !(self.is_tb322fc() && on) { + if !(self.is_prc_only() && on) { self.wf_config.modify_region = on; } self.confirm_edit_field = None; @@ -449,4 +449,33 @@ mod tests { ); } } + + #[test] + fn tb324zc_rejects_stale_row_and_cross_region_dispatches() { + let mut app = App { + device_model: "TB324ZC".into(), + flash: FlashWizard { + device_region: Some(DeviceRegion::Prc), + target: Some(FlashTarget::SameRegion), + ..FlashWizard::default() + }, + wf_config: WorkflowConfig { + device_region: Some(DeviceRegion::Prc), + modify_region: false, + ..WorkflowConfig::default() + }, + ..App::default() + }; + + let _ = app.update_flash(FlashMsg::FlashRegion(DeviceRegion::Row)); + let _ = app.update_flash(FlashMsg::FlashTarget(FlashTarget::OtherRegion)); + let _ = app.update_flash(FlashMsg::FlashConfirmSetRegion(DeviceRegion::Row)); + let _ = app.update_flash(FlashMsg::FlashConfirmSetTarget(FlashTarget::OtherRegion)); + let _ = app.update_flash(FlashMsg::FlashConfirmSetRegionEdit(true)); + + assert_eq!(app.flash.device_region, Some(DeviceRegion::Prc)); + assert_eq!(app.flash.target, Some(FlashTarget::SameRegion)); + assert_eq!(app.wf_config.device_region, Some(DeviceRegion::Prc)); + assert!(!app.wf_config.modify_region); + } } diff --git a/crates/ltbox-gui/src/update/konabess.rs b/crates/ltbox-gui/src/update/konabess.rs index 453c7cfa..81ecd39a 100644 --- a/crates/ltbox-gui/src/update/konabess.rs +++ b/crates/ltbox-gui/src/update/konabess.rs @@ -119,7 +119,7 @@ impl App { let phases = self .begin_phased_op(View::KonaBess, OperationPhaseKind::KonaBess); let conn = self.connection; - let is_tb323fu = self.is_tb323fu(); + let uses_efisp_gbl_route = self.uses_efisp_gbl_route(); let device_model = self.device_model.clone(); let ll = self.live_labels(); let loader = std::path::PathBuf::from(loader); @@ -130,7 +130,7 @@ impl App { konabess_inspection_worker( conn, loader, - is_tb323fu, + uses_efisp_gbl_route, device_model, ll, phases, diff --git a/crates/ltbox-gui/src/update/mod.rs b/crates/ltbox-gui/src/update/mod.rs index da9fe219..159820fa 100644 --- a/crates/ltbox-gui/src/update/mod.rs +++ b/crates/ltbox-gui/src/update/mod.rs @@ -159,11 +159,11 @@ impl App { Message::Flash(m) => return self.update_flash(m), // Country code popup Message::SelectCountry(code) => { - // TB322FC ships PRC-only — the Flash wizard only accepts `CN`. + // PRC-only models make the Flash wizard accept only `CN`. // The popup grays out other entries, but a stale dispatch could // still land here. Drop it — EXCEPT for the Advanced "Change // Country Code" op, which allows any country on any model. - if self.is_tb322fc() && !self.adv_needs_country && !code.eq_ignore_ascii_case("CN") + if self.is_prc_only() && !self.adv_needs_country && !code.eq_ignore_ascii_case("CN") { return Task::none(); } diff --git a/crates/ltbox-gui/src/update/root.rs b/crates/ltbox-gui/src/update/root.rs index 8d04bd54..cb2b1556 100644 --- a/crates/ltbox-gui/src/update/root.rs +++ b/crates/ltbox-gui/src/update/root.rs @@ -62,9 +62,9 @@ impl App { return Task::none(); } // TODO(root): LTBox currently only swaps the boot.img Image - // for GKI, which corrupts boot on TB323FU. Keep it disabled + // for GKI, which corrupts boot on efisp/GBL-route models. Keep it disabled // until vbmeta handling is added. - if self.is_tb323fu() && m == RootMode::Gki { + if self.uses_efisp_gbl_route() && m == RootMode::Gki { return Task::none(); } self.root.mode = Some(m); @@ -382,14 +382,17 @@ impl App { return Task::none(); } // TODO(root): LTBox currently only swaps the boot.img Image - // for GKI, which corrupts boot on TB323FU. The mode card is + // for GKI, which corrupts boot on efisp/GBL-route models. The mode card is // disabled, but stale selections can survive from before the // model was identified; refuse them until vbmeta handling is // added. - if self.is_tb323fu() && self.root.is_gki() { + if self.uses_efisp_gbl_route() && self.root.is_gki() { self.root.mode = None; self.root.step = 1; // Mode step - self.error_msg = Some(tr_args!("model_unsupported", model = "TB323FU")); + self.error_msg = Some(tr_args!( + "model_unsupported", + model = self.device_model.as_str() + )); return Task::none(); } if self @@ -702,6 +705,26 @@ mod tests { assert!(app.op_steps.is_empty()); } + #[test] + fn tb324zc_blocks_gki_dispatch_and_start_with_its_own_model_name() { + let mut app = App { + device_model: "TB324ZC".into(), + ..App::default() + }; + let _ = app.update_root(RootMsg::RootMode(RootMode::Gki)); + assert_eq!(app.root.mode, None); + + app.root.mode = Some(RootMode::Gki); + let _ = app.update_root(RootMsg::RootExecStart); + assert_eq!(app.root.mode, None); + assert_eq!(app.root.step, 1); + assert!( + app.error_msg + .as_deref() + .is_some_and(|msg| msg.contains("TB324ZC")) + ); + } + #[test] fn root_route_label_separates_the_two_magisk_routes() { let label = |provider| { diff --git a/crates/ltbox-gui/src/update/sys.rs b/crates/ltbox-gui/src/update/sys.rs index 40583aa8..9af9b162 100644 --- a/crates/ltbox-gui/src/update/sys.rs +++ b/crates/ltbox-gui/src/update/sys.rs @@ -8,10 +8,12 @@ impl App { pub(crate) fn update_sys(&mut self, msg: SysMsg) -> Task { match msg { SysMsg::SysAction(a) => { - // TB323FU can't run Boot Recovery (vendor_boot/vbmeta LUN + // Efisp/GBL-route models can't run Boot Recovery (vendor_boot/vbmeta LUN // mismatch). The card is disabled, but drop a stale dispatch // here too so the action can never latch. - if a == SysUpdateAction::Rescue && (self.is_tb323fu() || self.is_xiaoxin_pro13()) { + if a == SysUpdateAction::Rescue + && (self.uses_efisp_gbl_route() || self.is_xiaoxin_pro13()) + { return Task::none(); } // Switching action resets Rescue-specific state so a stale @@ -107,15 +109,15 @@ impl App { let Some(action) = self.sysupdate.action else { return Task::none(); }; - // Final guard: never start Boot Recovery on TB323FU even if a + // Final guard: never start Boot Recovery on an efisp/GBL-route model even if a // stale Rescue selection slipped past the disabled card. if action == SysUpdateAction::Rescue - && (self.is_tb323fu() || self.is_xiaoxin_pro13()) + && (self.uses_efisp_gbl_route() || self.is_xiaoxin_pro13()) { self.error_msg = Some(tr_args!( "model_unsupported", - model = if self.is_tb323fu() { - "TB323FU" + model = if self.uses_efisp_gbl_route() { + self.device_model.as_str() } else { "TB376FC / TB390FU" } @@ -179,3 +181,26 @@ impl App { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn tb324zc_blocks_boot_recovery_dispatch_and_start_with_its_own_model_name() { + let mut app = App { + device_model: "TB324ZC".into(), + ..App::default() + }; + let _ = app.update_sys(SysMsg::SysAction(SysUpdateAction::Rescue)); + assert_eq!(app.sysupdate.action, None); + + app.sysupdate.action = Some(SysUpdateAction::Rescue); + let _ = app.update_sys(SysMsg::SysExecStart); + assert!( + app.error_msg + .as_deref() + .is_some_and(|msg| msg.contains("TB324ZC")) + ); + } +} diff --git a/crates/ltbox-gui/src/view/flash.rs b/crates/ltbox-gui/src/view/flash.rs index 4100208c..3f3abb91 100644 --- a/crates/ltbox-gui/src/view/flash.rs +++ b/crates/ltbox-gui/src/view/flash.rs @@ -73,16 +73,16 @@ impl App { let d = self.density(); let side = self.wizard_square_side(); let prc_icon = lucide_primary(icon::region_prc(), self.wizard_square_icon()); - // TB322FC is a PRC-only SKU. Render ROW as a disabled card with + // PRC-only SKUs render ROW as a disabled card with // a grayed icon so the constraint is visible — silent skip // would confuse users who expect both options. - let tb322fc = self.is_tb322fc(); - let unsupported_tb322fc = tr_args!("model_unsupported", model = "TB322FC"); - let row_card: Element<'_, Message> = if tb322fc { + let prc_only = self.is_prc_only(); + let unsupported_model = tr_args!("model_unsupported", model = self.device_model.as_str()); + let row_card: Element<'_, Message> = if prc_only { icon_option_card_sub_square_disabled_sized( lucide_disabled(icon::region_row(), self.wizard_square_icon()), self.t("region_row"), - &unsupported_tb322fc, + &unsupported_model, side, ) } else { @@ -143,11 +143,11 @@ impl App { let d = self.density(); let side = self.wizard_square_side(); let device = lucide_primary(icon::tile_device(), self.wizard_square_icon()); - // TB322FC ships only in PRC, so cross-region (OtherRegion) is + // PRC-only SKUs cannot use a cross-region (OtherRegion) target, so it is // never a valid target. Disable the card with a grayed icon to // keep the constraint visible on the picker. - let tb322fc = self.is_tb322fc(); - let unsupported_tb322fc = tr_args!("model_unsupported", model = "TB322FC"); + let prc_only = self.is_prc_only(); + let unsupported_model = tr_args!("model_unsupported", model = self.device_model.as_str()); // Region-aware target descriptions spell out the hardware market and // the ROM being installed so users don't conflate the two (the most // common point of confusion in this wizard). device_region is chosen @@ -157,11 +157,11 @@ impl App { Some(DeviceRegion::Row) => ("flashtarget_same_desc_row", "flashtarget_other_desc_row"), None => ("flashtarget_same_desc", "flashtarget_other_desc"), }; - let other_card: Element<'_, Message> = if tb322fc { + let other_card: Element<'_, Message> = if prc_only { icon_option_card_sub_square_disabled_sized( lucide_disabled(icon::tile_globe(), self.wizard_square_icon()), self.t(FlashTarget::OtherRegion.label_key()), - &unsupported_tb322fc, + &unsupported_model, side, ) } else { diff --git a/crates/ltbox-gui/src/view/popups.rs b/crates/ltbox-gui/src/view/popups.rs index 2966ec17..3f254e9b 100644 --- a/crates/ltbox-gui/src/view/popups.rs +++ b/crates/ltbox-gui/src/view/popups.rs @@ -826,16 +826,16 @@ impl App { ); list = list.push(widget::rule::horizontal(1)); } - // TB322FC PRC-only: only CN is selectable in the Flash wizard. Non-CN + // PRC-only models: only CN is selectable in the Flash wizard. Non-CN // rows render as disabled buttons so the constraint stays visible. The // Advanced "Change Country Code" op has no such restriction (any country, // any model), so the gate is lifted there. "Do not change" stays usable. - let tb322fc = self.is_tb322fc() && !self.adv_needs_country; + let prc_only = self.is_prc_only() && !self.adv_needs_country; for entry in COUNTRY_CODES { let code = entry.code.to_string(); let selected = selected_code == Some(entry.code); let label = format!("{} — {}", entry.code, entry.name); - let disabled = tb322fc && !entry.code.eq_ignore_ascii_case("CN"); + let disabled = prc_only && !entry.code.eq_ignore_ascii_case("CN"); let mut btn = button(text(label).size(13)) .padding([6, 14]) .width(Length::Fill) @@ -950,7 +950,7 @@ impl App { pub(crate) fn flash_confirm_edit_popup(&self, field: ConfirmField) -> Element<'_, Message> { // (label, selected, on_press, disabled) let cfg = &self.wf_config; - let tb322 = self.is_tb322fc(); + let prc_only = self.is_prc_only(); let opts: Vec<(String, bool, Message, bool)> = match field { ConfirmField::Region => [DeviceRegion::Prc, DeviceRegion::Row] .into_iter() @@ -959,7 +959,7 @@ impl App { self.t(r.label_key()).to_string(), cfg.device_region == Some(r), Message::Flash(FlashMsg::FlashConfirmSetRegion(r)), - tb322 && r == DeviceRegion::Row, + prc_only && r == DeviceRegion::Row, ) }) .collect(), @@ -970,7 +970,7 @@ impl App { self.t(t.label_key()).to_string(), cfg.modify_region == (t == FlashTarget::OtherRegion), Message::Flash(FlashMsg::FlashConfirmSetTarget(t)), - tb322 && t == FlashTarget::OtherRegion, + prc_only && t == FlashTarget::OtherRegion, ) }) .collect(), @@ -1002,9 +1002,9 @@ impl App { .to_string(), cfg.modify_region == on, Message::Flash(FlashMsg::FlashConfirmSetRegionEdit(on)), - // PRC-only TB322FC can't cross regions — disable "On" + // PRC-only models can't cross regions — disable "On" // to match the Target editor's OtherRegion gate. - tb322 && on, + prc_only && on, ) }) .collect(), diff --git a/crates/ltbox-gui/src/view/root.rs b/crates/ltbox-gui/src/view/root.rs index b957c023..b873360b 100644 --- a/crates/ltbox-gui/src/view/root.rs +++ b/crates/ltbox-gui/src/view/root.rs @@ -728,8 +728,8 @@ impl App { pub(crate) fn root_mode_step(&self) -> Element<'_, Message> { let d = self.density(); let side = self.wizard_square_side(); - let tb323fu = self.is_tb323fu(); - let unsupported_tb323fu = tr_args!("model_unsupported", model = "TB323FU"); + let uses_efisp_gbl_route = self.uses_efisp_gbl_route(); + let unsupported_model = tr_args!("model_unsupported", model = self.device_model.as_str()); let lkm_card = icon_option_card_sub_square_sized( RootMode::Lkm.icon(self.wizard_square_icon()), self.t(RootMode::Lkm.label_key()), @@ -740,13 +740,13 @@ impl App { ); let lkm_card = recommended_overlay(d, lkm_card, self.t("root_recommended_tip").to_string()); // TODO(root): LTBox currently only swaps the boot.img Image for - // GKI, which corrupts boot on TB323FU. Keep GKI disabled until + // GKI, which corrupts boot on efisp/GBL-route models. Keep GKI disabled until // vbmeta handling is added. - let gki_card: Element<'_, Message> = if tb323fu { + let gki_card: Element<'_, Message> = if uses_efisp_gbl_route { icon_option_card_sub_square_disabled_sized( RootMode::Gki.icon_disabled(self.wizard_square_icon()), self.t(RootMode::Gki.label_key()), - &unsupported_tb323fu, + &unsupported_model, side, ) } else { diff --git a/crates/ltbox-gui/src/view/sysupdate.rs b/crates/ltbox-gui/src/view/sysupdate.rs index cd1ea887..7ca9c6a9 100644 --- a/crates/ltbox-gui/src/view/sysupdate.rs +++ b/crates/ltbox-gui/src/view/sysupdate.rs @@ -99,11 +99,12 @@ impl App { let side = self.wizard_square_side(); let off_icon = lucide_primary(icon::tile_update_off(), self.wizard_square_icon()); let on_icon = lucide_primary(icon::tile_update_on(), self.wizard_square_icon()); - // TB323FU's vendor_boot/vbmeta sit on a different UFS LUN than the + // Efisp/GBL-route models' vendor_boot/vbmeta sit on a different UFS LUN than the // Boot Recovery worker targets, so the flow can't run on it — disable // the card (alongside the non-Qualcomm platform gate). - let rescue_disabled = - self.platform_supported == Some(false) || self.is_tb323fu() || self.is_xiaoxin_pro13(); + let rescue_disabled = self.platform_supported == Some(false) + || self.uses_efisp_gbl_route() + || self.is_xiaoxin_pro13(); // Gray the icon when disabled, matching the other wizards' disabled // option cards (region ROW / OtherRegion). let rescue_icon = if rescue_disabled { @@ -134,8 +135,8 @@ impl App { // Disabled rescue card — no on_press, grayed out; still mirrors // the sub-row layout of the other tiles with the Qualcomm-required // hint so the label sits at the same height. - let rescue_req = if self.is_tb323fu() { - tr_args!("model_unsupported", model = "TB323FU") + let rescue_req = if self.uses_efisp_gbl_route() { + tr_args!("model_unsupported", model = self.device_model.as_str()) } else if self.is_xiaoxin_pro13() { tr_args!("model_unsupported", model = "TB376FC / TB390FU") } else { diff --git a/crates/ltbox-gui/src/widgets.rs b/crates/ltbox-gui/src/widgets.rs index 21cd6c15..917c2b34 100644 --- a/crates/ltbox-gui/src/widgets.rs +++ b/crates/ltbox-gui/src/widgets.rs @@ -4,6 +4,7 @@ use crate::*; use iced::widget::{Space, button, canvas, container, row, text}; use iced::{Element, Length, Point, Radians, Rectangle, Renderer, Theme, mouse, window}; +use ltbox_core::model::TB324ZC_MODEL; const MATERIAL_PROGRESS_PERIOD: std::time::Duration = std::time::Duration::from_millis(1_400); const MATERIAL_PROGRESS_FRAME: std::time::Duration = std::time::Duration::from_millis(33); @@ -1032,6 +1033,12 @@ static TB323FU_HANDLE: std::sync::LazyLock = include_bytes!("../assets/devices/tb323fu.png").as_slice(), ) }); +static TB324ZC_HANDLE: std::sync::LazyLock = + std::sync::LazyLock::new(|| { + iced::widget::image::Handle::from_bytes( + include_bytes!("../assets/devices/tb324zc.png").as_slice(), + ) + }); static TB376FC_HANDLE: std::sync::LazyLock = std::sync::LazyLock::new(|| { iced::widget::image::Handle::from_bytes( @@ -1070,6 +1077,7 @@ pub(crate) fn device_portrait(model: &str) -> DevicePortrait { "TB321FU" => DevicePortrait::Png(TB321FU_HANDLE.clone()), "TB322FC" => DevicePortrait::Png(TB322FC_HANDLE.clone()), "TB323FU" => DevicePortrait::Png(TB323FU_HANDLE.clone()), + TB324ZC_MODEL => DevicePortrait::Png(TB324ZC_HANDLE.clone()), "TB376FC" | "TB390FU" => DevicePortrait::Png(TB376FC_HANDLE.clone()), "TB520FU" => DevicePortrait::Png(TB520FU_HANDLE.clone()), "TB710FU" => DevicePortrait::Png(TB710FU_HANDLE.clone()), @@ -1428,6 +1436,16 @@ mod tests { }; use iced::widget::button; + #[test] + fn tb324zc_has_its_own_portrait_handle() { + match (device_portrait("TB323FU"), device_portrait("TB324ZC")) { + (DevicePortrait::Png(tb323fu), DevicePortrait::Png(tb324zc)) => { + assert!(tb323fu != tb324zc); + } + _ => panic!("both models must dispatch to a PNG portrait"), + } + } + #[test] fn xiaoxin_pro13_models_use_the_shared_png_portrait() { assert!(matches!(device_portrait("TB376FC"), DevicePortrait::Png(_))); diff --git a/crates/ltbox-gui/src/workers/flash/full.rs b/crates/ltbox-gui/src/workers/flash/full.rs index bd6e66e8..a0ced771 100644 --- a/crates/ltbox-gui/src/workers/flash/full.rs +++ b/crates/ltbox-gui/src/workers/flash/full.rs @@ -1,5 +1,34 @@ use super::*; -use crate::{ManualRollbackIndices, PhaseReporter}; +use crate::{ + CountryAction, DeviceClass, ManualRollbackIndices, PhaseReporter, + fingerprint_uses_efisp_gbl_route, +}; + +fn prc_only_flash_violation( + model: &str, + modify_region: bool, + country_action: &CountryAction, +) -> bool { + if !DeviceClass::from_model(model).is_prc_only() { + return false; + } + let non_cn_country = country_action + .target() + .map(|code| !code.eq_ignore_ascii_case("CN")) + .unwrap_or(false); + modify_region || non_cn_country +} + +fn target_uses_efisp_gbl_route(firmware_fingerprint: Option<&str>) -> bool { + firmware_fingerprint + .map(fingerprint_uses_efisp_gbl_route) + .unwrap_or(false) +} + +fn efisp_gbl_skip_region(firmware_fingerprint: Option<&str>, device_model: &str) -> bool { + target_uses_efisp_gbl_route(firmware_fingerprint) + || DeviceClass::from_model(device_model).uses_efisp_gbl_route() +} #[allow(clippy::too_many_arguments)] pub(crate) fn flash_worker( @@ -334,21 +363,15 @@ pub(crate) fn flash_worker( // Phase 4/9 — Prepare the flash and safety plan. live!(log, "[Flash] {}", phases.marker(4)); - // TB323FU keeps region vendor_boot/vbmeta AVB conversion off (it + // Efisp/GBL-route models keep region vendor_boot/vbmeta AVB conversion off (they // provisions a GBL on efisp instead) but DOES take ARB // overlays. Region detect uses fp first, then model. - let tb323fu_skip_region = firmware_fingerprint - .as_deref() - .map(|fp| fingerprint_token_match(fp, "TB323FU")) - .unwrap_or(false) - || fingerprint_token_match(&device_model, "TB323FU"); + let skip_region_for_efisp_gbl = + efisp_gbl_skip_region(firmware_fingerprint.as_deref(), &device_model); // GBL/ARB work follows the TARGET firmware identity // (vendor_boot fp), never the connected device. - let target_is_tb323fu = firmware_fingerprint - .as_deref() - .map(|fp| fingerprint_token_match(fp, "TB323FU")) - .unwrap_or(false); + let target_efisp_gbl = target_uses_efisp_gbl_route(firmware_fingerprint.as_deref()); let xiaoxin_skip_region = xiaoxin_pro13_token(&device_model).is_some() || firmware_fingerprint .as_deref() @@ -363,24 +386,24 @@ pub(crate) fn flash_worker( // user's selected rollback-bypass + region modes are preserved exactly as // on an ADB/bootloader start. - // TB323FU must never run a blind ON: ON bumps even + // Efisp/GBL-route models must never run a blind ON: ON bumps even // matching indices and would force the testkey/_arb // chain when no downgrade is in play. Demote to AUTO so // the EDL-dumped device index decides per partition. - if target_is_tb323fu && rb_mode != ltbox_patch::rollback::RollbackMode::Off { + if target_efisp_gbl && rb_mode != ltbox_patch::rollback::RollbackMode::Off { if rb_mode == ltbox_patch::rollback::RollbackMode::On { rb_mode = ltbox_patch::rollback::RollbackMode::Auto; ltbox_core::live!( log, "[ARB] {}", - ltbox_core::i18n::tr("live_flash_tb323fu_force_auto") + ltbox_core::i18n::tr("live_flash_efisp_force_auto") ); } else if rb_mode == ltbox_patch::rollback::RollbackMode::Manual { rb_mode = ltbox_patch::rollback::RollbackMode::Off; ltbox_core::live!( log, "[ARB] {}", - ltbox_core::i18n::tr("rollback_manual_tb323fu_disabled") + ltbox_core::i18n::tr("rollback_manual_efisp_disabled") ); } } @@ -392,11 +415,11 @@ pub(crate) fn flash_worker( ltbox_core::i18n::tr("live_flash_xiaoxin_force_auto") ); } - if tb323fu_skip_region { + if skip_region_for_efisp_gbl { ltbox_core::live!( log, "[Flash] {}", - ltbox_core::i18n::tr("live_flash_tb323fu_region_efisp") + ltbox_core::i18n::tr("live_flash_region_efisp") ); } if xiaoxin_skip_region { @@ -451,7 +474,7 @@ pub(crate) fn flash_worker( // ARB dump decides `_arb` (testkey-root) vs stock — see // the post-rawprogram-staging block below. let mut efisp_efi: Option = None; - let mut tb323fu_arb_need = false; + let mut efisp_gbl_arb_need = false; // Count .x and .xml files // Count flashable `.x` (rawprogram) files. The @@ -502,7 +525,7 @@ pub(crate) fn flash_worker( // via testkeys in KEY_MAP). Classify the firmware via vbmeta_system's pubkey // (the unified key source): an `Unknown` key aborts; a fixed-key ("key2") // firmware aborts on cross-region for now (same-region key2 is handled after - // EDL opens; cross-region key2 re-sign is a separate change). TB323FU has its + // EDL opens; cross-region key2 re-sign is a separate change). Efisp/GBL models have their // own region path (efisp GBL) and is exempt here. let fw_key_class = match ltbox_patch::avb::extract_image_avb_info(&vbmeta_system) { Ok(info) => ltbox_patch::key_map::classify_pubkey(info.public_key_sha1.as_deref()), @@ -524,7 +547,7 @@ pub(crate) fn flash_worker( // (testkey device) or an abort (fixed device). let mut region_pair: Option = None; if cfg.modify_region - && !tb323fu_skip_region + && !skip_region_for_efisp_gbl && !xiaoxin_skip_region && fw_key_class != ltbox_patch::key_map::KeyClass::Fixed { @@ -753,6 +776,13 @@ pub(crate) fn flash_worker( match read_edl_start_device(&mut session, firmware_fingerprint.as_deref(), &mut log) { Ok(probe) => { device_model = probe.model_token; + if prc_only_flash_violation(&device_model, cfg.modify_region, &cfg.country_action) { + let _ = session.reset_to_edl(&mut log); + return Err(tr_args!( + "err_flash_prc_only", + model = device_model.as_str() + )); + } if !xiaoxin_pro13_flash && ltbox_core::model::is_xiaoxin_pro13_model(&device_model) { xiaoxin_pro13_flash = true; @@ -767,29 +797,16 @@ pub(crate) fn flash_worker( } match probe.rollback_floors { None => { - // TB322FC is PRC-only. The pre-EDL UI gates that block - // cross-region / non-CN country never fired (the model - // was unknown until now), so enforce the constraint - // here, before any region or country write. - let non_cn_country = cfg - .country_action - .target() - .map(|c| !c.eq_ignore_ascii_case("CN")) - .unwrap_or(false); + // TB322FC has no rollback protection. PRC-only + // restrictions were enforced explicitly above, without + // coupling them to this missing-floor sentinel. if rb_mode != ltbox_patch::rollback::RollbackMode::Manual { - if cfg.modify_region || non_cn_country { - let _ = session.reset_to_edl(&mut log); - return Err(ltbox_core::i18n::tr("err_flash_tb322fc_prc_only")); - } rb_mode = ltbox_patch::rollback::RollbackMode::Off; ltbox_core::live!( log, "[ARB] {}", ltbox_core::i18n::tr("live_arb_device_index_none") ); - } else if cfg.modify_region || non_cn_country { - let _ = session.reset_to_edl(&mut log); - return Err(ltbox_core::i18n::tr("err_flash_tb322fc_prc_only")); } } Some(floors) => { @@ -881,13 +898,13 @@ pub(crate) fn flash_worker( // AVB root-of-trust gate (device side). The firmware vbmeta was already // classified (`fw_key_class`): `Unknown` aborted before region conversion; - // `Testkey` firmware and a `Fixed` firmware on TB323FU take their existing + // `Testkey` firmware and a `Fixed` firmware on the efisp/GBL route take their existing // paths. Here a `Fixed` ("key2") firmware on any other model classifies the // device's active-slot vbmeta and either proceeds as-is (fixed device, same // region, no downgrade), re-signs to the testkey + preserves the device // bootloader (testkey device — including a cross-region convert-then-resign), // or aborts (fixed device cross-region/downgrade, or unknown device key). - if fw_key_class == ltbox_patch::key_map::KeyClass::Fixed && !target_is_tb323fu { + if fw_key_class == ltbox_patch::key_map::KeyClass::Fixed && !target_efisp_gbl { let kc_dir = ltbox_core::app_paths::work_dir_for("flash_keyclass"); let _ = std::fs::remove_dir_all(&kc_dir); std::fs::create_dir_all(&kc_dir) @@ -1163,8 +1180,8 @@ pub(crate) fn flash_worker( ) ); } - } else if rb_mode != ltbox_patch::rollback::RollbackMode::Off && target_is_tb323fu { - // TB323FU stages the testkey chain whenever the + } else if rb_mode != ltbox_patch::rollback::RollbackMode::Off && target_efisp_gbl { + // Efisp/GBL-route models stage the testkey chain whenever the // install is a downgrade, independent of region / // wipe: the matching `_arb` GBL is flashed to efisp // below in the exact same `need` cases, so the chain @@ -1186,7 +1203,7 @@ pub(crate) fn flash_worker( None, &mut log, )?; - tb323fu_arb_need = need; + efisp_gbl_arb_need = need; arb_patched = overlays; } else if rb_mode != ltbox_patch::rollback::RollbackMode::Off { let arb_work_dir = ltbox_core::app_paths::work_dir_for("flash_arb"); @@ -1410,12 +1427,12 @@ pub(crate) fn flash_worker( // gated on data wipe — flashing efisp no longer forces // a data reset, so it provisions in data-keep mode too. // Both flash below. - if target_is_tb323fu && (tb323fu_arb_need || cfg.modify_region) { - // TB323FU's AVB fingerprint carries no region token; read the region + if target_efisp_gbl && (efisp_gbl_arb_need || cfg.modify_region) { + // The AVB fingerprint carries no region token; read the region // from the firmware vendor_boot's `product_region` DTB marker instead. let is_prc = ltbox_patch::region::detect_product_region(&vendor_boot) == Some(ltbox_patch::region::RegionTarget::Prc); - let suffix = efisp_asset_suffix(is_prc, tb323fu_arb_need); + let suffix = efisp_asset_suffix(is_prc, efisp_gbl_arb_need); let expected_name = efisp_expected_asset(suffix).ok_or_else(|| { tr_args!( "err_flash_efisp_asset_unknown", @@ -1545,7 +1562,7 @@ pub(crate) fn flash_worker( // variant on a region-provisioning wipe (best-effort). // With no EFI fetched, a same-region wipe strips efisp; // every other mode leaves it untouched. - if target_is_tb323fu { + if target_efisp_gbl { let efisp_lun = ltbox_core::partition_lun::lun_for_partition("efisp").unwrap_or(4); match &efisp_efi { Some(efi) => { @@ -1564,7 +1581,7 @@ pub(crate) fn flash_worker( // with this `_arb` GBL. Abort loudly // (device stays in EDL for retry) rather // than resetting into a rollback brick. - if tb323fu_arb_need { + if efisp_gbl_arb_need { return Err(tr_args!( "err_flash_efisp_arb_failed", error = e.to_string() @@ -1582,7 +1599,7 @@ pub(crate) fn flash_worker( // A downgrade always fetches the `_arb` GBL, // so reaching here with `need` set is an // internal inconsistency — fail safe. - if tb323fu_arb_need { + if efisp_gbl_arb_need { return Err(ltbox_core::i18n::tr("err_flash_efisp_arb_missing")); } // Same-region wipe with no downgrade strips @@ -1762,30 +1779,30 @@ fn xiaoxin_rollback_decision( } /// Pinned gbl_root_baldur release used for TB323FU efisp GBL images. -const EFISP_GBL_RELEASE_TAG: &str = "5.3.120-mod5"; +const EFISP_GBL_RELEASE_TAG: &str = "5.3.120-mod6"; /// Exact asset names accepted for the pinned efisp release. const EFISP_EXPECTED_ASSETS: &[(&str, &str)] = &[ ( "generic_superfastboot_prc.efi", - "22471c543e13a433cb05c5c54bcfaf107f2643a6cfd7e46d8d73764b349e8cf2", + "375d3f4ec5ca9f02745077fba6ef8b5b7e7c5ad77c17b7bdb28b794da4fb72f2", ), ( "generic_superfastboot_prc_arb.efi", - "fc55e6a4912f20c1bf0c664bb985e10d0c4e0944f92fab5e4f855b22e2aefcdf", + "d0496c898667bd63fe30809c3ab68a4bcd009dae8f961048583c37ee4a004086", ), ( "generic_superfastboot_row.efi", - "90b16cacc4f2f6aded2c5bf0eed7d20b6a294115f6cf09dab555b4a4496e2628", + "024244ea7d2ac42947ced7688eada4082bf642e1cf205cf64a0b82a7f6291155", ), ( "generic_superfastboot_row_arb.efi", - "99b62f4aeca619df4f480f6bffa3f0fea3ef2516a8fe48a092613c2aa68d10e2", + "defa391eed6164801cb5e8bec0a5175a164aa0ed44f776f9637e2c6c108efc33", ), ]; /// Map a region/ARB suffix to the exact pinned asset name for the -/// `5.3.120-mod5` gbl_root_baldur release. Unknown suffixes refuse. +/// `5.3.120-mod6` gbl_root_baldur release. Unknown suffixes refuse. fn efisp_expected_asset(suffix: &str) -> Option<&'static str> { match suffix { "_prc.efi" => Some("generic_superfastboot_prc.efi"), @@ -1847,6 +1864,51 @@ fn verify_efisp_asset(path: &std::path::Path, asset_name: &str) -> Result<(), St Ok(()) } +#[cfg(test)] +mod tb324zc_route_tests { + use super::{efisp_gbl_skip_region, prc_only_flash_violation, target_uses_efisp_gbl_route}; + use crate::CountryAction; + + const TB324ZC_FP: &str = "qti/TB324ZC/TB324ZC:16/build:user/release-keys"; + + #[test] + fn target_and_live_efisp_predicates_keep_their_inputs_separate() { + assert!(target_uses_efisp_gbl_route(Some(TB324ZC_FP))); + assert!(!target_uses_efisp_gbl_route(None)); + assert!(efisp_gbl_skip_region(None, "TB324ZC")); + assert!(!efisp_gbl_skip_region(None, "TB520FU")); + } + + #[test] + fn edl_start_prc_recheck_is_independent_of_rollback_floors() { + assert!(prc_only_flash_violation( + "TB324ZC", + true, + &CountryAction::Set("CN".into()) + )); + assert!(prc_only_flash_violation( + "TB324ZC", + false, + &CountryAction::Set("KR".into()) + )); + assert!(!prc_only_flash_violation( + "TB324ZC", + false, + &CountryAction::Set("CN".into()) + )); + assert!(prc_only_flash_violation( + "TB322FC", + true, + &CountryAction::Skip + )); + assert!(!prc_only_flash_violation( + "TB520FU", + true, + &CountryAction::Set("KR".into()) + )); + } +} + #[cfg(test)] mod tests { use super::{ @@ -1902,7 +1964,7 @@ mod tests { #[test] fn expected_hashes_cover_all_accepted_assets() { - assert_eq!(EFISP_GBL_RELEASE_TAG, "5.3.120-mod5"); + assert_eq!(EFISP_GBL_RELEASE_TAG, "5.3.120-mod6"); assert_eq!(EFISP_EXPECTED_ASSETS.len(), 4); for (name, hash) in EFISP_EXPECTED_ASSETS { assert_eq!(hash.len(), 64, "{name} hash length"); diff --git a/crates/ltbox-gui/src/workers/flash/mod.rs b/crates/ltbox-gui/src/workers/flash/mod.rs index d2c52553..18c9bc20 100644 --- a/crates/ltbox-gui/src/workers/flash/mod.rs +++ b/crates/ltbox-gui/src/workers/flash/mod.rs @@ -9,6 +9,7 @@ use crate::{ fingerprint_token_match, is_rollback_protected_model, open_edl_session, read_device_rollback_index_via_edl, transition_to_edl, }; +use ltbox_core::model::{TB323FU_MODEL, TB324ZC_MODEL}; use ltbox_core::{live, tr_args}; fn require_firmware_loader( @@ -78,8 +79,16 @@ fn fixed_firmware_device_policy( /// model from an EDL-dumped vendor_boot when fastboot/ADB never ran. /// LAVIE Tab 9QHD1 is represented by TB320FC because the shared matcher treats /// its reported token as equivalent and the recovered value is internal only. -const SUPPORTED_MODELS: [&str; 8] = [ - "TB320FC", "TB321FU", "TB322FC", "TB323FU", "TB376FC", "TB390FU", "TB520FU", "TB710FU", +const SUPPORTED_MODELS: [&str; 9] = [ + "TB320FC", + "TB321FU", + "TB322FC", + TB323FU_MODEL, + TB324ZC_MODEL, + ltbox_core::model::TB376FC_MODEL, + ltbox_core::model::TB390FU_MODEL, + "TB520FU", + "TB710FU", ]; fn xiaoxin_pro13_token(text: &str) -> Option<&'static str> { @@ -675,16 +684,18 @@ fn decompress_zst_file( result } -/// Country-code partitions to dump/patch/flash for a model. The TB320FC hardware -/// path and TB323FU keep the code ONLY in `oemowninfo` (LUN 0); every other model -/// keeps it in `devinfo` + `persist`. The model is matched against the +/// Country-code partitions to dump/patch/flash for a model. TB320FC and TB323FU +/// keep the code ONLY in `oemowninfo` (LUN 0); TB324ZC, TB376FC, and TB390FU +/// use `proinfo` + `persist`; every other model uses `devinfo` + `persist`. +/// The model is matched against the /// vendor_boot AVB fingerprint (works on an EDL-start flash) or the /// probe-reported model name. fn country_partitions_for( device_model: &str, firmware_fingerprint: Option<&str>, ) -> &'static [&'static str] { - let xiaoxin_pro13_sku = [ + let proinfo_sku = [ + TB324ZC_MODEL, ltbox_core::model::TB376FC_MODEL, ltbox_core::model::TB390FU_MODEL, ] @@ -695,10 +706,10 @@ fn country_partitions_for( .unwrap_or(false) || fingerprint_token_match(device_model, m) }); - if xiaoxin_pro13_sku { + if proinfo_sku { return &["proinfo", "persist"]; } - let oemowninfo_sku = ["TB320FC", "TB323FU"].iter().any(|m| { + let oemowninfo_sku = ["TB320FC", TB323FU_MODEL].iter().any(|m| { firmware_fingerprint .map(|fp| fingerprint_token_match(fp, m)) .unwrap_or(false) @@ -716,8 +727,10 @@ fn is_field_only_country_partition(label: &str) -> bool { } /// Rewrite the device's country code in the model's country partitions over an -/// open EDL session: the TB320FC hardware path and TB323FU use `oemowninfo`; -/// every other model uses `devinfo` + `persist`. Best-effort per partition +/// open EDL session: TB320FC and TB323FU use `oemowninfo`; TB324ZC, TB376FC, +/// and TB390FU use `proinfo` + `persist`; every other model uses +/// `devinfo` + `persist`. +/// Best-effort per partition /// (logs + continues on failure). Shared by `flash_worker`'s post-flash country /// phase and the standalone `change_country_worker`. #[allow(clippy::too_many_arguments)] @@ -1035,11 +1048,17 @@ pub(crate) use simple::simple_flash_worker; #[cfg(test)] mod tests { use super::{ - FixedFirmwareDevicePolicy, ZSTD_FREE_SPACE_RESERVE_BYTES, fixed_firmware_device_policy, - require_firmware_loader, should_reboot_fastboot_to_system_after_pre_edl_abort, - stream_zstd_decoder, zstd_output_limit, + FixedFirmwareDevicePolicy, SUPPORTED_MODELS, ZSTD_FREE_SPACE_RESERVE_BYTES, + fixed_firmware_device_policy, require_firmware_loader, + should_reboot_fastboot_to_system_after_pre_edl_abort, stream_zstd_decoder, + zstd_output_limit, }; + #[test] + fn edl_fingerprint_recovery_recognizes_tb324zc() { + assert!(SUPPORTED_MODELS.contains(&"TB324ZC")); + } + #[test] fn country_partitions_select_by_model() { use super::country_partitions_for; @@ -1050,6 +1069,10 @@ mod tests { &["oemowninfo"][..] ); assert_eq!(country_partitions_for("TB323FU", None), &["oemowninfo"][..]); + assert_eq!( + country_partitions_for("TB324ZC", None), + &["proinfo", "persist"][..] + ); assert_eq!( country_partitions_for("TB376FC", None), &["proinfo", "persist"][..] @@ -1068,6 +1091,10 @@ mod tests { country_partitions_for("", Some("Lenovo/TB323FU/TB323FU:14/build")), &["oemowninfo"][..] ); + assert_eq!( + country_partitions_for("", Some("Lenovo/TB324ZC/TB324ZC:16/build")), + &["proinfo", "persist"][..] + ); assert_eq!( country_partitions_for("", Some("qti/LAVIETab9QHD1/LAVIETab9QHD1:15/build_NEC")), &["oemowninfo"][..] diff --git a/crates/ltbox-gui/src/workers/konabess.rs b/crates/ltbox-gui/src/workers/konabess.rs index 1ecd922c..8e156c69 100644 --- a/crates/ltbox-gui/src/workers/konabess.rs +++ b/crates/ltbox-gui/src/workers/konabess.rs @@ -24,12 +24,12 @@ struct InspectionPaths { #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum ExploitGateKind { SignedVbmeta, - Tb323fuEfisp, + EfispGbl, } -const fn exploit_gate_kind(is_tb323fu: bool) -> ExploitGateKind { - if is_tb323fu { - ExploitGateKind::Tb323fuEfisp +const fn exploit_gate_kind(uses_efisp_gbl_route: bool) -> ExploitGateKind { + if uses_efisp_gbl_route { + ExploitGateKind::EfispGbl } else { ExploitGateKind::SignedVbmeta } @@ -64,7 +64,7 @@ trait KonaBessInspectionBackend { struct DeviceBackend<'a> { conn: ConnectionStatus, loader: &'a Path, - is_tb323fu: bool, + uses_efisp_gbl_route: bool, device_model: &'a str, session: Option, writes_started: bool, @@ -141,8 +141,8 @@ impl KonaBessInspectionBackend for DeviceBackend<'_> { if ltbox_core::model::is_xiaoxin_pro13_model(self.device_model) || detected_xiaoxin { return Err(tr_args!("model_unsupported", model = "TB376FC / TB390FU")); } - match exploit_gate_kind(self.is_tb323fu) { - ExploitGateKind::Tb323fuEfisp => { + match exploit_gate_kind(self.uses_efisp_gbl_route) { + ExploitGateKind::EfispGbl => { let efi_dir = work_dir.join("efisp_gbl"); let staged = prepare_tb323fu_efisp( self.session()?, @@ -236,7 +236,7 @@ fn execute_inspection( backend.dump_partition(&vendor_boot_partition, &vendor_boot, log)?; backend.dump_partition(&vbmeta_partition, &vbmeta, log)?; - // Gate only after both source images exist. TB323FU takes the shared efisp + // Gate only after both source images exist. Efisp/GBL-route models take the shared efisp // path; every other model resolves vbmeta through KEY_MAP and permits an // absent key as unsigned. backend.run_exploit_gate(&slot_suffix, &vendor_boot, &vbmeta, &paths.work_dir, log)?; @@ -265,7 +265,7 @@ fn execute_inspection( pub(crate) fn konabess_inspection_worker( conn: ConnectionStatus, loader: PathBuf, - is_tb323fu: bool, + uses_efisp_gbl_route: bool, device_model: String, ll: LiveLabels, phases: PhaseReporter, @@ -289,7 +289,7 @@ pub(crate) fn konabess_inspection_worker( let mut backend = DeviceBackend { conn, loader: &loader, - is_tb323fu, + uses_efisp_gbl_route, device_model: &device_model, session: None, writes_started: false, @@ -816,8 +816,8 @@ mod tests { } #[test] - fn tb323fu_empty_efisp_requires_provision_and_bypasses_avb_gate() { - assert_eq!(exploit_gate_kind(true), ExploitGateKind::Tb323fuEfisp); + fn efisp_gbl_route_requires_provision_and_bypasses_avb_gate() { + assert_eq!(exploit_gate_kind(true), ExploitGateKind::EfispGbl); assert_eq!(exploit_gate_kind(false), ExploitGateKind::SignedVbmeta); assert!(crate::efisp_is_empty(&[0; 32])); assert!(!crate::efisp_is_empty(&[0, 0, 1, 0])); diff --git a/crates/ltbox-gui/src/workers/root.rs b/crates/ltbox-gui/src/workers/root.rs index 8d39602b..ecf17d9a 100644 --- a/crates/ltbox-gui/src/workers/root.rs +++ b/crates/ltbox-gui/src/workers/root.rs @@ -4,9 +4,9 @@ use crate::{ ConnectionStatus, Family, LiveLabels, PhaseReporter, Provider, RootMode, VerChoice, - fingerprint_token_match, install_root_manager_apk, open_edl_session, prepare_tb323fu_efisp, - provision_tb323fu_efisp, stage_manager_apk_for_manual_install, transition_to_edl, - wait_and_install_root_manager_apk, + fingerprint_token_match, fingerprint_uses_efisp_gbl_route, install_root_manager_apk, + open_edl_session, prepare_tb323fu_efisp, provision_tb323fu_efisp, + stage_manager_apk_for_manual_install, transition_to_edl, wait_and_install_root_manager_apk, }; use ltbox_core::{i18n::tr, live, tr_args}; @@ -335,11 +335,11 @@ pub(crate) fn root_worker( let backup_dir = ltbox_core::app_paths::backup_dir_for(&format!("backup_{base_name}")); // Set inside the dump block from the dumped root image's // fingerprint; carried to Phase 5 to skip AVB + vbmeta. - let is_tb323fu; + let uses_efisp_gbl_route; // Whether the stock vbmeta ended up in the backup folder, so the // manifest can tell Unroot which partitions to restore. let vbmeta_backed_up; - // TB323FU only: when the dumped efisp is empty (stock, + // Efisp/GBL route: when the dumped efisp is empty (stock, // GBL-unprovisioned) we download the region GBL here and // flash it alongside the patched root target image at Phase 6. let mut root_efisp_efi: Option = None; @@ -406,12 +406,12 @@ pub(crate) fn root_worker( fingerprint = root_image_fingerprint )); } - is_tb323fu = fingerprint_token_match(&root_image_fingerprint, "TB323FU"); + uses_efisp_gbl_route = fingerprint_uses_efisp_gbl_route(&root_image_fingerprint); - // vbmeta is read only when the run rebuilds it: TB323FU takes + // vbmeta is read only when the run rebuilds it: efisp/GBL models take // the GBL route, and every other non-TB320FC model chains the // boot target, which leaves vbmeta byte-identical either way. - vbmeta_backed_up = rebuild_vbmeta && !is_tb323fu; + vbmeta_backed_up = rebuild_vbmeta && !uses_efisp_gbl_route; if vbmeta_backed_up { session .dump_partition( @@ -430,10 +430,10 @@ pub(crate) fn root_worker( })?; } - // TB323FU root needs provisioned efisp; once present, skip AVB + // Efisp/GBL root needs provisioned efisp; once present, skip AVB // footer and vbmeta writes. Keep the verified fingerprint so // an empty efisp can fetch the matching region GBL. - if is_tb323fu { + if uses_efisp_gbl_route { let efi_dir = ltbox_core::app_paths::work_dir_for("root_efisp"); root_efisp_efi = prepare_tb323fu_efisp( &mut session, @@ -525,7 +525,7 @@ pub(crate) fn root_worker( // keeps the two phases in lockstep automatically // if a future field gets added to the struct. let cfg = manager_cfg.clone(); - let artifacts = build_patched_artifacts(&cfg, is_tb323fu, &mut log) + let artifacts = build_patched_artifacts(&cfg, uses_efisp_gbl_route, &mut log) .map_err(|e| tr_args!("err_root_patch_failed", error = e))?; if manager_apk.is_none() { manager_apk = artifacts.manager_apk.clone(); @@ -705,10 +705,14 @@ mod tests { fn dumped_image_fingerprint_must_match_detected_model() { let tb320fc = "qti/TB320FC/TB320FC:15/build:user/release-keys"; let tb323fu = "qti/TB323FU/TB323FU:15/build:user/release-keys"; + let tb324zc = "qti/TB324ZC/TB324ZC:16/build:user/release-keys"; assert!(fingerprint_matches_detected_model(tb320fc, "TB320FC")); assert!(fingerprint_matches_detected_model(tb320fc, "LAVIETab9QHD1")); assert!(!fingerprint_matches_detected_model(tb323fu, "TB320FC")); + assert!(fingerprint_matches_detected_model(tb324zc, "TB324ZC")); + assert!(fingerprint_uses_efisp_gbl_route(tb324zc)); + assert!(!fingerprint_uses_efisp_gbl_route(tb320fc)); } #[test]