调整文件目录

This commit is contained in:
2026-07-07 14:37:04 +08:00
parent 98a1e9d371
commit 295558836b
53 changed files with 22339 additions and 4242 deletions
+405 -163
View File
@@ -66,17 +66,61 @@ def find_resource(file_name):
return candidates[0]
def resource_dir_candidates(dir_name):
base_dir = get_app_dir()
candidates = []
if getattr(sys, 'frozen', False):
candidates.append(Path(getattr(sys, '_MEIPASS', base_dir)) / dir_name)
candidates.extend([
base_dir / dir_name,
base_dir / 'tools' / dir_name,
base_dir / 'shared' / dir_name,
base_dir.parent / dir_name,
base_dir.parent / 'tools' / dir_name,
base_dir.parent / 'shared' / dir_name,
])
unique = []
for candidate in candidates:
if candidate not in unique:
unique.append(candidate)
return unique
def find_resource_dir(dir_name):
candidates = resource_dir_candidates(dir_name)
for candidate in candidates:
if candidate.exists() and candidate.is_dir():
return candidate
return candidates[0]
def find_tool(file_name, fallback=None):
path = find_resource(file_name)
if path.exists():
return str(path)
return fallback or str(path)
def set_windows_app_id():
if sys.platform != 'win32':
return
try:
import ctypes
app_id = 'YibinKeyi.Q05Lidar.Installer.1.0'
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(app_id)
except Exception:
pass
class ADKAPKGUI:
def __init__(self):
set_windows_app_id()
self.root = tk.Tk()
self.root.title("启源Q05Ultra+激光雷达")
self.root.geometry("900x660")
self.root.resizable(True, True)
self.set_window_icon()
self.root.after(200, self.set_window_icon)
self.default_window_size = (900, 660)
self.right_panel_width = 280
@@ -129,6 +173,7 @@ class ADKAPKGUI:
'auth_yes': '已授权',
'auth_no': '未授权',
'btn_refresh': '🔄 检查',
'btn_install_driver': '🧩 安装驱动',
'hint_factory': '🔧 关闭车辆WI-FI和4G网络,拨号获取的密码进入工程模式',
'hotspot_title': '📶 电脑热点',
'hotspot_start': '🔧 打开热点设置',
@@ -163,11 +208,21 @@ class ADKAPKGUI:
'msg_permission_failed': '获取失败',
'msg_confirm_permission_title': '确认获取权限',
'msg_confirm_permission': '此操作将为当前设备获取权限。\n过程中请不要断电或拔线。\n\n是否继续?',
'msg_driver_missing_title': '驱动环境',
'msg_driver_missing': '驱动缺失,即将自动安装驱动。',
'msg_driver_install_confirm': '即将安装驱动环境,需要管理员权限。',
'msg_driver_install_done': '驱动安装完成。如设备仍无法识别,请重新插拔 USB 线缆。',
'msg_driver_install_failed': '驱动安装失败: {error}',
'status_debug': '🔧 调试模式',
'log_cleared': '日志已清空',
'log_lang_changed': '语言已切换为中文',
'log_debug_on': '🔧 调试模式已开启',
'log_debug_off': '调试模式已关闭',
'log_driver_found': '已检测到驱动环境',
'log_driver_missing': '未检测到驱动环境',
'log_driver_install_start': '正在安装驱动环境...',
'log_driver_install_success': '驱动环境安装完成',
'log_driver_install_failed': '驱动环境安装失败: {error}',
'log_permission_success': '获取成功',
'log_push_success': '语言包刷入完成,重启设备后生效',
'log_push_failed': '语言包刷入失败',
@@ -193,7 +248,7 @@ class ADKAPKGUI:
'progress_flashing': '正在刷入',
'progress_flash_done': '刷入完成',
'progress_extracting_percent': '资源准备中 {percent}%',
'progress_install_module': '安装模块 {module}',
'progress_install_module': '安装资源',
'progress_push_files': '推送文件 {current}/{total}',
'quick_lang_title': '快捷语言设置',
'quick_lang_header': '选择目标语言',
@@ -206,7 +261,7 @@ class ADKAPKGUI:
'reboot_confirm_title': '确认重启',
'reboot_confirm': '确定要重启设备吗?',
'disable_confirm_title': '确认禁用升级',
'disable_confirm': '⚠️ 警告:禁用系统升级后,将无法接收系统更新!\n\n是否确定要禁用系统升级应用?\n\n禁用命令:\nadb shell pm disable-user --user 0 com.incall.apps.softmanager',
'disable_confirm': '⚠️ 警告:禁用系统升级后,将无法接收系统更新!\n\n是否确定要禁用系统升级应用?',
'disable_success_title': '成功',
'disable_success': '系统升级已成功禁用!',
'disable_failed': '禁用失败:{output}',
@@ -227,7 +282,7 @@ class ADKAPKGUI:
'debug_need_vin': '请先刷新设备VIN,或在工程密码输入框填入VIN',
'log_decrypt_success': '解密测试成功: size={size}, sha256={sha}',
'log_package_extract_success': 'Q05_Lidar-package.bin 解压测试成功: APK数量={count}',
'log_package_extract_modules_success': 'Q05_Lidar-package.bin 解压测试成功: 主模块={main}, 附加模块={extra}',
'log_package_extract_modules_success': 'Q05_Lidar-package.bin 解压测试成功: 资源数量={count}',
'hotspot_stopped': '未启动',
'log_device_disconnected': '设备已断开连接',
'log_cache_invalid': '已解压缓存无效: {reason}',
@@ -248,12 +303,11 @@ class ADKAPKGUI:
'log_apps_dir_missing': '警告:未找到 apps 目录',
'log_extracted_resource_invalid': '解压后的资源无效: {reason}。已停止刷入,请检查解压密码或资源包。',
'log_resource_ready': '资源准备完成 (app: {count})',
'log_module_resource_ready': '资源准备完成: 主模块={main}, 附加模块={extra}',
'err_module_prop_missing': '资源包缺少 module.prop',
'err_system_dir_missing': '资源缺少 system 目录',
'err_disable_module_missing': '资源包缺少 disable-wireless-adb-vecentek-magisk.zip',
'err_module_id_missing': 'module.prop 缺少 id 字段',
'err_disable_module_invalid': '附加模块压缩包无效或缺少 module.prop',
'log_module_resource_ready': '资源准备完成',
'err_module_prop_missing': '资源包缺少资源配置',
'err_module_id_missing': '资源配置缺少 ID 字段',
'err_module_zip_invalid': '资源文件无效或缺少配置: {file}',
'err_duplicate_module_id': '资源包存在重复资源 ID',
'log_resource_prepare_failed_detail': '资源准备失败: {error}',
'log_resource_prepare_network': '资源准备失败,请检查网络连接后重试',
'log_package_file_missing': '未找到资源包文件',
@@ -296,7 +350,7 @@ class ADKAPKGUI:
'log_runtime_install_failed': '获取失败',
'log_runtime_install_success': '正在获取权限中',
'log_fastboot_enter_failed': '获取失败',
'log_fastboot_wait': '正在获取权限',
'log_fastboot_wait': '正在获取权限,请不要关闭程序或断开数据连接!',
'log_fastboot_missing': '获取失败',
'log_init_boot_flash_failed': '获取失败',
'log_init_boot_flash_success': '正在获取权限中',
@@ -305,16 +359,16 @@ class ADKAPKGUI:
'log_install_failed_detail': 'install失败: {error}',
'log_prepare_resource': '正在准备资源...',
'log_flash_start': '开始刷入语言包...',
'log_open_magisk': '正在打开 Magisk App,请在弹窗中授予 Shell/root 权限',
'log_root_checking': '正在检测 ADB Shell root 权限...',
'log_root_ok': 'ADB Shell root 权限已授予',
'log_root_failed': '未获得 ADB Shell root 权限,请手动在 Magisk App 授予 Shell 权限',
'msg_root_failed': '请手动在 Magisk App 授予 Shell 权限后重试。',
'log_module_install_start': '开始安装 Magisk 模块: {module}',
'log_module_install_done': 'Magisk 模块安装完成: {module}',
'log_module_install_failed': 'Magisk 模块安装失败: {module}: {error}',
'log_open_magisk': '请在弹窗中点击“允许”授予权限',
'log_root_checking': '正在检测权限...',
'log_root_ok': '权限已授予',
'log_root_failed': '权限获取失败,请手动授予权限',
'msg_root_failed': '请手动授予权限后重试。',
'log_module_install_start': '开始安装资源',
'log_module_install_done': '资源安装完成',
'log_module_install_failed': '资源安装失败: {error}',
'log_push_file_failed': '文件推送失败: {file}: {error}',
'log_root_cmd_failed': 'Root 命令执行失败: {command}: {error}',
'log_root_cmd_failed': '权限操作失败: {error}',
'log_no_lang_pkg': '未找到语言包文件',
'log_install_item_ok': '安装成功: {name}.apk',
'log_install_item_failed': '安装失败: {name}.apk',
@@ -386,6 +440,7 @@ class ADKAPKGUI:
'auth_yes': 'Authorized',
'auth_no': 'Unauthorized',
'btn_refresh': '🔄 Check',
'btn_install_driver': '🧩 Driver',
'hint_factory': '🔧 Turn off the vehicle WiFi & 4G, enter factory mode with dial code',
'hotspot_title': '📶 Hotspot',
'hotspot_start': '🔧 Open Hotspot Settings',
@@ -422,11 +477,21 @@ class ADKAPKGUI:
'msg_permission_failed': 'Permission failed',
'msg_confirm_permission_title': 'Confirm Unlock',
'msg_confirm_permission': 'This will grant permission for the current device.\nDo not disconnect power or USB during the process.\n\nContinue?',
'msg_driver_missing_title': 'Driver Environment',
'msg_driver_missing': 'Driver environment is missing. Driver installation will start automatically.',
'msg_driver_install_confirm': 'Driver environment installation requires administrator permission.',
'msg_driver_install_done': 'Driver installation completed. If the device is still not recognized, reconnect USB cable.',
'msg_driver_install_failed': 'Driver installation failed: {error}',
'status_debug': '🔧 Debug mode',
'log_cleared': 'Log cleared',
'log_lang_changed': 'Language switched to English',
'log_debug_on': '🔧 Debug mode enabled',
'log_debug_off': 'Debug mode disabled',
'log_driver_found': 'Driver environment detected',
'log_driver_missing': 'Driver environment not detected',
'log_driver_install_start': 'Installing driver environment...',
'log_driver_install_success': 'Driver environment installed',
'log_driver_install_failed': 'Driver environment installation failed: {error}',
'log_permission_success': 'Permission granted',
'log_push_success': 'Language package flashed. Reboot to apply changes.',
'log_push_failed': 'Language package flash failed',
@@ -452,7 +517,7 @@ class ADKAPKGUI:
'progress_flashing': 'Flashing',
'progress_flash_done': 'Flash complete',
'progress_extracting_percent': 'Preparing resources {percent}%',
'progress_install_module': 'Installing module {module}',
'progress_install_module': 'Installing resources',
'progress_push_files': 'Pushing files {current}/{total}',
'quick_lang_title': 'Quick Language',
'quick_lang_header': 'Choose target language',
@@ -465,7 +530,7 @@ class ADKAPKGUI:
'reboot_confirm_title': 'Confirm Reboot',
'reboot_confirm': 'Reboot the device now?',
'disable_confirm_title': 'Confirm Disable OTA',
'disable_confirm': '⚠️ Warning: disabling system upgrade prevents system updates.\n\nDisable the system upgrade app?\n\nCommand:\nadb shell pm disable-user --user 0 com.incall.apps.softmanager',
'disable_confirm': '⚠️ Warning: disabling system upgrade prevents system updates.\n\nDisable the system upgrade app?',
'disable_success_title': 'Success',
'disable_success': 'System upgrade app disabled.',
'disable_failed': 'Disable failed: {output}',
@@ -486,7 +551,7 @@ class ADKAPKGUI:
'debug_need_vin': 'Refresh device VIN first, or enter a VIN in the password query field.',
'log_decrypt_success': 'Decrypt test passed: size={size}, sha256={sha}',
'log_package_extract_success': 'Q05_Lidar-package.bin extract test passed: APK count={count}',
'log_package_extract_modules_success': 'Q05_Lidar-package.bin extract test passed: main={main}, extra={extra}',
'log_package_extract_modules_success': 'Q05_Lidar-package.bin extract test passed: resource count={count}',
'hotspot_stopped': 'stopped',
'log_device_disconnected': 'Device disconnected',
'log_cache_invalid': 'Extract cache invalid: {reason}',
@@ -507,12 +572,11 @@ class ADKAPKGUI:
'log_apps_dir_missing': 'apps directory not found',
'log_extracted_resource_invalid': 'Extracted resources are invalid: {reason}. Flashing stopped. Check the password or package.',
'log_resource_ready': 'Resources ready (app: {count})',
'log_module_resource_ready': 'Resources ready: main={main}, extra={extra}',
'err_module_prop_missing': 'module.prop is missing from the package',
'err_system_dir_missing': 'system directory is missing from the package',
'err_disable_module_missing': 'disable-wireless-adb-vecentek-magisk.zip is missing from the package',
'err_module_id_missing': 'module.prop is missing the id field',
'err_disable_module_invalid': 'Extra module archive is invalid or missing module.prop',
'log_module_resource_ready': 'Resources ready',
'err_module_prop_missing': 'Resource config is missing from the package',
'err_module_id_missing': 'Resource config is missing the id field',
'err_module_zip_invalid': 'Resource file is invalid or missing config: {file}',
'err_duplicate_module_id': 'Duplicate resource ID found in package',
'log_resource_prepare_failed_detail': 'Resource preparation failed: {error}',
'log_resource_prepare_network': 'Resource preparation failed. Check the network connection and retry.',
'log_package_file_missing': 'Package file not found',
@@ -555,7 +619,7 @@ class ADKAPKGUI:
'log_runtime_install_failed': 'Permission failed',
'log_runtime_install_success': 'Getting permission',
'log_fastboot_enter_failed': 'Permission failed',
'log_fastboot_wait': 'Getting permission',
'log_fastboot_wait': 'Getting permission. Do not close the program or disconnect the data cable.',
'log_fastboot_missing': 'Permission failed',
'log_init_boot_flash_failed': 'Permission failed',
'log_init_boot_flash_success': 'Getting permission',
@@ -564,16 +628,16 @@ class ADKAPKGUI:
'log_install_failed_detail': 'install failed: {error}',
'log_prepare_resource': 'Preparing resources...',
'log_flash_start': 'Starting language package flash...',
'log_open_magisk': 'Opening Magisk App. Grant Shell/root permission when prompted.',
'log_root_checking': 'Checking ADB Shell root permission...',
'log_root_ok': 'ADB Shell root permission granted',
'log_root_failed': 'ADB Shell root permission was not granted. Grant Shell permission in Magisk manually.',
'msg_root_failed': 'Grant Shell permission in Magisk manually, then retry.',
'log_module_install_start': 'Installing Magisk module: {module}',
'log_module_install_done': 'Magisk module installed: {module}',
'log_module_install_failed': 'Magisk module install failed: {module}: {error}',
'log_open_magisk': 'Tap "允许" to grant permission when prompted.',
'log_root_checking': 'Checking permission...',
'log_root_ok': 'Permission granted',
'log_root_failed': 'Permission grant failed. Grant permission manually.',
'msg_root_failed': 'Grant permission manually, then retry.',
'log_module_install_start': 'Installing resources',
'log_module_install_done': 'Resources installed',
'log_module_install_failed': 'Resource installation failed: {error}',
'log_push_file_failed': 'File push failed: {file}: {error}',
'log_root_cmd_failed': 'Root command failed: {command}: {error}',
'log_root_cmd_failed': 'Permission operation failed: {error}',
'log_no_lang_pkg': 'No language package files found',
'log_install_item_ok': 'Installed: {name}.apk',
'log_install_item_failed': 'Install failed: {name}.apk',
@@ -621,6 +685,8 @@ class ADKAPKGUI:
self.adb = self._find_tool('adb.exe', 'adb')
self.sz = self._find_tool('7za.exe', str(find_resource('7za.exe')))
self.fastboot = self._find_tool('fastboot.exe', 'fastboot')
self.driver_dir = find_resource_dir("usb_driver")
self.driver_inf = self.driver_dir / "android_winusb.inf"
self.package_file = find_resource("Q05_Lidar-package.bin")
self.runtime_file = self._find_data_file("runtime.dat", prefer_embedded=True)
self.resource_file = self._find_data_file("resource.dat", prefer_embedded=True)
@@ -629,8 +695,7 @@ class ADKAPKGUI:
self.resource_key = None
self.apps_dir = None
self.temp_dir = None
self.module_dir = None
self.disable_module_dir = None
self.module_zips = []
self.runtime_cache_dir = None
self.api_url = "https://api.changan.softwindy.cn/api/authorizations/auth-check"
self.boot_challenge_api_url = "https://api.changan.softwindy.cn/api/authorizations/boot-challenge"
@@ -642,6 +707,7 @@ class ADKAPKGUI:
self.device_connected = False
self._refreshing = False # 防止并发刷新
self.debug_mode = False # 调试模式
self.driver_prompted = False
atexit.register(self.cleanup_cache_on_exit)
# 设置样式
@@ -656,6 +722,15 @@ class ADKAPKGUI:
# 启动设备状态监控
self.start_device_monitor()
def set_window_icon(self):
"""Set the Tk window/taskbar icon at runtime; PyInstaller --icon only sets the exe file icon."""
try:
icon_path = find_resource("app.ico")
if icon_path.exists():
self.root.iconbitmap(str(icon_path))
except Exception:
pass
def _find_tool(self, exe_name, fallback):
candidates = resource_candidates(exe_name)
candidates.extend([
@@ -931,15 +1006,27 @@ class ADKAPKGUI:
bg=self.colors['bg_light'])
self.auth_label.pack(side=tk.LEFT, padx=(5, 0))
# 刷新按钮
self.btn_refresh = tk.Button(status_bar_frame, text=self.t('btn_refresh'),
# 操作按钮:检查靠近设备状态,安装驱动在检查右侧
status_actions_frame = tk.Frame(status_bar_frame, bg=self.colors['bg_light'])
status_actions_frame.pack(side=tk.RIGHT, padx=10, pady=5)
self.btn_refresh = tk.Button(status_actions_frame, text=self.t('btn_refresh'),
command=lambda: self.refresh_device_status(force=True),
font=('Microsoft YaHei', 8),
fg=self.colors['accent'],
bg=self.colors['bg_light'],
relief=tk.FLAT,
cursor='hand2')
self.btn_refresh.pack(side=tk.RIGHT, padx=10, pady=5)
self.btn_refresh.pack(side=tk.LEFT, padx=(0, 8))
self.btn_install_driver = tk.Button(status_actions_frame, text=self.t('btn_install_driver'),
command=self.install_fastboot_driver,
font=('Microsoft YaHei', 8),
fg=self.colors['warning'],
bg=self.colors['bg_light'],
relief=tk.FLAT,
cursor='hand2')
self.btn_install_driver.pack(side=tk.LEFT)
# 解压进度条框架
progress_frame = tk.Frame(left_frame, bg=self.colors['bg_dark'])
@@ -1222,6 +1309,7 @@ class ADKAPKGUI:
(getattr(self, 'vin_label_title', None), 'vin_label', None),
(getattr(self, 'auth_label_title', None), 'auth_label', None),
(getattr(self, 'btn_refresh', None), 'btn_refresh', None),
(getattr(self, 'btn_install_driver', None), 'btn_install_driver', None),
(getattr(self, 'hint_label', None), 'hint_factory', None),
(getattr(self, 'pwd_query_label', None), 'pwd_query_label', None),
(getattr(self, 'btn_hotspot', None), 'hotspot_start', None),
@@ -1246,10 +1334,28 @@ class ADKAPKGUI:
self._update_device_status_impl(self.device_connected, self.vin,
getattr(self, '_last_authorized', False))
def _sanitize_user_log_message(self, message):
"""Hide low-level device command details from normal operator logs."""
text = str(message)
replacements = [
(r'adb(?:\.exe)?(?:\s+-d)?\s+shell(?:\s+root)?', '权限获取'),
(r'/debug_ramdisk/su(?:\s+-c)?', '权限获取'),
(r'\bsu\s+-c\b', '权限获取'),
(r'Shell 权限', '权限'),
(r'\bShell permission\b', 'permission'),
(r'\b[Rr]oot command\b', 'permission operation'),
(r'\b[Rr]oot\b', 'permission'),
]
for pattern, replacement in replacements:
text = re.sub(pattern, replacement, text)
return text
def _log_impl(self, message, level="INFO"):
"""日志写入的实际实现(必须在主线程调用)"""
if not self.debug_mode and level in ("INFO", "CMD"):
return
if not self.debug_mode:
message = self._sanitize_user_log_message(message)
timestamp = datetime.now().strftime("%H:%M:%S")
log_entry = f"[{timestamp}] [{level}] {message}\n"
self.log_text.insert(tk.END, log_entry, level)
@@ -1373,11 +1479,11 @@ class ADKAPKGUI:
threading.Thread(target=monitor, daemon=True).start()
# ============================================================
# 核心:adb shell 自动密码输入
# 核心:设备权限命令自动密码输入
# ============================================================
def run_adb_shell(self, shell_command, timeout=15):
"""执行 adb shell 命令,自动静默输入设备密码 adb36987。
"""执行设备权限命令,自动静默输入设备密码 adb36987。
静默执行,不显示 adb 原始输出,仅返回结果。"""
if self.debug_mode:
self.log(f"CMD: adb shell {shell_command}", "CMD")
@@ -1461,79 +1567,70 @@ class ADKAPKGUI:
ok, output = self.run_root_command("id", timeout=20)
if ok and "uid=0" in output:
self.log(self.t('log_root_ok'), "STATUS")
self.run_adb_shell('pm uninstall --user 0 com.topjohnwu.magisk', timeout=30)
return True
self.log(self.t('log_root_failed'), "ERROR")
return False
def install_magisk_module_files(self, module_dir, skip_names=None):
module_dir = Path(module_dir)
skip_names = set(skip_names or [])
mod_id = self.read_module_id(module_dir)
def disable_and_uninstall_preinstalled_packages(self):
self.run_adb_shell('pm disable-user com.incall.apps.softmanager', timeout=30)
package_names = [
"com.carcontrolhome.app",
"com.tinnove.netease.music",
"com.wtcl.electronicdirections",
]
for package_name in package_names:
self.run_adb_shell(f'pm disable-user {package_name}', timeout=30)
self.run_adb_shell(f'pm uninstall --user 0 {package_name}', timeout=30)
def install_magisk_module_zip(self, zip_path):
zip_path = Path(zip_path)
mod_id = self.read_module_id_from_zip(zip_path)
if not mod_id:
return False, self.t('err_module_id_missing')
if not zip_path.exists():
return False, self.tf('err_extract_failed', error=f"{zip_path} not found")
device_stage = f"/data/local/tmp/q05_lidar_modules/{mod_id}"
device_stage_dir = "/data/local/tmp/q05_lidar_modules"
device_zip = f"{device_stage_dir}/{mod_id}.zip"
device_module = f"/data/adb/modules/{mod_id}"
files = [
p for p in module_dir.rglob("*")
if p.is_file()
and not self._is_skipped_module_path(module_dir, p, skip_names)
]
if not files:
return False, self.t('err_no_apks')
self.log(self.tf('log_module_install_start', module=mod_id), "STATUS")
self.log(self.t('log_module_install_start'), "STATUS")
commands = [
f"rm -rf {self.shell_quote(device_stage)} {self.shell_quote(device_module)}",
f"mkdir -p {self.shell_quote(device_stage)} {self.shell_quote(device_module)}",
f"chmod -R 777 {self.shell_quote(device_stage)}",
f"rm -rf {self.shell_quote(device_module)}",
f"mkdir -p {self.shell_quote(device_stage_dir)} {self.shell_quote(device_module)}",
f"chmod 777 {self.shell_quote(device_stage_dir)}",
]
for command in commands:
ok, output = self.run_root_command(command, timeout=60)
if not ok:
return False, self.tf('log_root_cmd_failed', command=command, error=output)
total = len(files)
for idx, local_file in enumerate(files, 1):
rel = local_file.relative_to(module_dir).as_posix()
remote_file = f"{device_stage}/{rel}"
remote_parent = remote_file.rsplit("/", 1)[0]
ok, output = self.run_root_command(f"mkdir -p {self.shell_quote(remote_parent)}", timeout=30)
if not ok:
return False, self.tf('log_root_cmd_failed', command="mkdir", error=output)
ok, output = self.run_root_command(f"chmod 777 {self.shell_quote(remote_parent)}", timeout=30)
if not ok:
return False, self.tf('log_root_cmd_failed', command="chmod", error=output)
ok, output = self.run_adb_command(f'adb -d push "{local_file}" {remote_file}')
if not ok:
return False, self.tf('log_push_file_failed', file=rel, error=output)
self.update_progress(idx, total, self.tf('progress_push_files', current=idx, total=total), is_push=True)
ok, output = self.run_adb_command(f'adb -d push "{zip_path}" {device_zip}')
if not ok:
return False, self.tf('log_push_file_failed', file=zip_path.name, error=output)
install_commands = [
f"cp -af {self.shell_quote(device_stage)}/. {self.shell_quote(device_module)}/",
f"unzip -oq {self.shell_quote(device_zip)} -x 'META-INF/*' -d {self.shell_quote(device_module)}",
f"find {self.shell_quote(device_module)} -type d -exec chmod 755 {{}} \\;",
f"find {self.shell_quote(device_module)} -type f -exec chmod 644 {{}} \\;",
f"find {self.shell_quote(device_module)} -type f -name '*.sh' -exec chmod 755 {{}} \\;",
f"rm -rf {self.shell_quote(device_stage)}",
f"rm -f {self.shell_quote(device_zip)}",
]
for command in install_commands:
ok, output = self.run_root_command(command, timeout=120)
ok, output = self.run_root_command(command, timeout=300)
if not ok:
return False, self.tf('log_root_cmd_failed', command=command, error=output)
self.log(self.tf('log_module_install_done', module=mod_id), "STATUS")
self.log(self.t('log_module_install_done'), "STATUS")
return True, mod_id
def _is_skipped_module_path(self, module_dir, file_path, skip_names):
rel = file_path.relative_to(module_dir)
parts = rel.parts
if parts and (parts[0] == "META-INF" or parts[0].startswith("_")):
return True
return file_path.name in skip_names
def check_package_extracted(self):
"""检查语言包是否已解压"""
has_module = self.module_dir and self.module_dir.exists()
has_module = bool(getattr(self, 'module_zips', None))
if not has_module and self.temp_dir and self.temp_dir.exists():
self.module_zips = self.find_module_zips(self.temp_dir)
has_module = bool(self.module_zips)
if has_module:
ok, reason = self._validate_extracted_modules()
if not ok:
@@ -1543,22 +1640,16 @@ class ADKAPKGUI:
return has_module
def _validate_extracted_modules(self):
if not self.module_dir or not self.module_dir.exists():
if not getattr(self, 'module_zips', None):
return False, self.t('err_module_prop_missing')
if not (self.module_dir / "module.prop").exists():
return False, self.t('err_module_prop_missing')
if not (self.module_dir / "system").exists():
return False, self.t('err_system_dir_missing')
if not (self.module_dir / "disable-wireless-adb-vecentek-magisk.zip").exists():
return False, self.t('err_disable_module_missing')
main_id = self.read_module_id(self.module_dir)
if not main_id:
return False, self.t('err_module_id_missing')
if not self.disable_module_dir or not self.disable_module_dir.exists():
return False, self.t('err_disable_module_invalid')
extra_id = self.read_module_id(self.disable_module_dir)
if not extra_id:
return False, self.t('err_disable_module_invalid')
module_ids = []
for zip_path in self.module_zips:
mod_id = self.read_module_id_from_zip(zip_path)
if not mod_id:
return False, self.tf('err_module_zip_invalid', file=zip_path.name)
module_ids.append(mod_id)
if len(set(module_ids)) != len(module_ids):
return False, self.t('err_duplicate_module_id')
return True, ""
def _clear_extracted_cache(self):
@@ -1566,8 +1657,7 @@ class ADKAPKGUI:
shutil.rmtree(self.temp_dir, ignore_errors=True)
time.sleep(0.5)
self.apps_dir = None
self.module_dir = None
self.disable_module_dir = None
self.module_zips = []
def cleanup_cache_on_exit(self):
self._clear_extracted_cache()
@@ -1650,20 +1740,27 @@ class ADKAPKGUI:
output = self._decode_process_output((result.stderr or b'') + (result.stdout or b''))
return result.returncode == 0, output
def _find_main_module_dir(self, root_dir):
candidates = [root_dir]
candidates.extend([p for p in root_dir.iterdir() if p.is_dir()])
for candidate in candidates:
if (candidate / "module.prop").exists() and (candidate / "system").exists():
return candidate
return None
def find_module_zips(self, root_dir):
root_dir = Path(root_dir)
candidates = []
modules_dir = root_dir / "modules"
search_roots = [modules_dir] if modules_dir.exists() else [root_dir]
for search_root in search_roots:
candidates.extend(sorted(search_root.rglob("*.zip")))
module_zips = []
for zip_path in candidates:
if self.read_module_id_from_zip(zip_path):
module_zips.append(zip_path)
return module_zips
def read_module_id(self, module_dir):
prop_path = Path(module_dir) / "module.prop"
if not prop_path.exists():
return ""
def read_module_id_from_zip(self, zip_path):
try:
for line in prop_path.read_text(encoding='utf-8', errors='replace').splitlines():
with zipfile.ZipFile(zip_path, 'r') as zf:
prop_name = self._find_module_prop_in_zip(zf)
if not prop_name:
return ""
raw = zf.read(prop_name)
for line in raw.decode('utf-8', errors='replace').splitlines():
line = line.strip()
if line.startswith("id="):
return line.split("=", 1)[1].strip()
@@ -1671,6 +1768,25 @@ class ADKAPKGUI:
return ""
return ""
def _find_module_prop_in_zip(self, zf):
names = zf.namelist()
normalized = {}
for name in names:
clean = name.replace("\\", "/").lstrip("./")
normalized[clean] = name
if "module.prop" in normalized:
return normalized["module.prop"]
candidates = []
for clean, original in normalized.items():
parts = [part for part in clean.split("/") if part]
if len(parts) == 2 and parts[-1] == "module.prop":
candidates.append((len(parts), original))
if candidates:
candidates.sort()
return candidates[0][1]
return ""
def extract_package_silent(self):
"""静默解压语言包(带进度)—— Q05-Lidar 使用 Magisk 模块结构"""
if not self.package_file.exists():
@@ -1720,37 +1836,18 @@ class ADKAPKGUI:
return False
self.update_progress(1, 1, self.t('progress_resource_done'))
self.module_dir = self._find_main_module_dir(self.temp_dir)
if not self.module_dir:
self.module_zips = self.find_module_zips(self.temp_dir)
if not self.module_zips:
self.log(self.t('err_module_prop_missing'), "ERROR")
self._clear_extracted_cache()
return False
disable_zip = self.module_dir / "disable-wireless-adb-vecentek-magisk.zip"
self.disable_module_dir = self.temp_dir / "_disable_wireless_adb_vecentek"
if self.disable_module_dir.exists():
shutil.rmtree(self.disable_module_dir, ignore_errors=True)
self.disable_module_dir.mkdir(parents=True, exist_ok=True)
if not disable_zip.exists():
self.log(self.t('err_disable_module_missing'), "ERROR")
self._clear_extracted_cache()
return False
ok, err_msg = self._run_7za_extract_basic(disable_zip, self.disable_module_dir)
if not ok:
self.log(self.tf('err_extract_failed', error=err_msg[:300]), "ERROR")
self._clear_extracted_cache()
return False
ok, reason = self._validate_extracted_modules()
if not ok:
self.log(self.tf('log_extracted_resource_invalid', reason=reason), "ERROR")
self._clear_extracted_cache()
return False
self.log(self.tf(
'log_module_resource_ready',
main=self.read_module_id(self.module_dir),
extra=self.read_module_id(self.disable_module_dir)
), "INFO")
self.log(self.t('log_module_resource_ready'), "INFO")
return True
except Exception as e:
@@ -1779,6 +1876,154 @@ class ADKAPKGUI:
self.log(self.t('log_adb_missing'), "ERROR")
except FileNotFoundError:
self.log(self.t('log_adb_missing'), "ERROR")
self.root.after(800, self.check_fastboot_driver_on_startup)
def _run_command_capture(self, command, timeout=30):
creationflags = subprocess.CREATE_NO_WINDOW if sys.platform == 'win32' else 0
result = subprocess.run(
command,
capture_output=True,
text=True,
timeout=timeout,
creationflags=creationflags
)
output = (result.stdout or "") + (result.stderr or "")
return result.returncode, output.strip()
def ask_ok_cancel_on_ui_thread(self, title, message):
result = {"value": False}
done = threading.Event()
def prompt():
try:
result["value"] = messagebox.askokcancel(title, message, parent=self.root)
finally:
done.set()
self.run_on_ui_thread(prompt)
done.wait()
return result["value"]
def is_fastboot_driver_installed(self):
if sys.platform != 'win32':
return True, ""
try:
code, output = self._run_command_capture(['pnputil', '/enum-drivers'], timeout=40)
if code != 0:
return False, output or "pnputil enum failed"
normalized = output.lower()
installed = (
'android_winusb.inf'.lower() in normalized
or 'android bootloader interface' in normalized
or 'android adb interface' in normalized
or 'fastboot' in normalized
)
return installed, output
except Exception as e:
return False, str(e)
def _install_driver_with_uac(self, inf_path):
quoted_inf = str(inf_path).replace("'", "''")
ps_command = (
"$proc = Start-Process -FilePath pnputil "
"-ArgumentList @('/add-driver', '{0}', '/install') "
"-Verb RunAs -WindowStyle Hidden -PassThru; "
"if ($null -eq $proc) {{ exit 1 }}; "
"$proc.WaitForExit(); "
"Write-Output $proc.ExitCode"
).format(quoted_inf)
creationflags = subprocess.CREATE_NO_WINDOW if sys.platform == 'win32' else 0
result = subprocess.run(
['powershell', '-NoProfile', '-Command', ps_command],
capture_output=True,
text=True,
creationflags=creationflags
)
output = ((result.stdout or "") + (result.stderr or "")).strip()
exit_code = None
for line in reversed(output.splitlines()):
text = line.strip()
if text.isdigit():
exit_code = int(text)
break
if exit_code is None and result.returncode == 0:
exit_code = 0
return exit_code == 0, output or f"powershell exit={result.returncode}"
def install_fastboot_driver(self, prompt=True):
def worker():
try:
if not self.driver_inf.exists():
error = self.tf('msg_driver_install_failed', error=f"{self.driver_inf} not found")
self.log(self.tf('log_driver_install_failed', error=f"{self.driver_inf} not found"), "ERROR")
self.run_on_ui_thread(lambda: messagebox.showerror(self.t('msg_driver_missing_title'), error))
return
if prompt:
confirmed = self.ask_ok_cancel_on_ui_thread(
self.t('msg_driver_missing_title'),
self.t('msg_driver_install_confirm')
)
if not confirmed:
return
self.log(self.t('log_driver_install_start'), "STATUS")
ok, output = self._install_driver_with_uac(self.driver_inf)
if not ok:
self.log(self.tf('log_driver_install_failed', error=output or self.t('unknown_error')), "ERROR")
self.run_on_ui_thread(
lambda: messagebox.showerror(
self.t('msg_driver_missing_title'),
self.tf('msg_driver_install_failed', error=output or self.t('unknown_error')),
parent=self.root
)
)
return
self.log(self.t('log_driver_install_success'), "SUCCESS")
self.run_on_ui_thread(
lambda: messagebox.showinfo(
self.t('msg_driver_missing_title'),
self.t('msg_driver_install_done'),
parent=self.root
)
)
except Exception as e:
self.log(self.tf('log_driver_install_failed', error=str(e)), "ERROR")
self.run_on_ui_thread(
lambda: messagebox.showerror(
self.t('msg_driver_missing_title'),
self.tf('msg_driver_install_failed', error=str(e)),
parent=self.root
)
)
threading.Thread(target=worker, daemon=True).start()
def check_fastboot_driver_on_startup(self):
if self.driver_prompted:
return
self.driver_prompted = True
def worker():
installed, detail = self.is_fastboot_driver_installed()
if installed:
return
if self.debug_mode and detail:
self.log(detail[:500], "CMD")
def notify_and_install():
messagebox.showinfo(
self.t('msg_driver_missing_title'),
self.t('msg_driver_missing'),
parent=self.root
)
self.install_fastboot_driver(prompt=False)
self.run_on_ui_thread(notify_and_install)
threading.Thread(target=worker, daemon=True).start()
def refresh_device_status(self, force=False):
"""刷新设备状态 —— 逸动版使用 ca.car.vin 获取 VIN"""
@@ -2184,6 +2429,10 @@ class ADKAPKGUI:
return True
return False
def fastboot_output_has_okay(self, output):
text = str(output or "").upper()
return "OKAY" in text and "FAILED" not in text
def wait_for_fastboot(self, timeout=180, interval=5):
deadline = time.time() + timeout
while time.time() < deadline:
@@ -2236,9 +2485,9 @@ class ADKAPKGUI:
self.update_progress(4, 7, self.t('progress_reboot_fastboot'), is_push=True)
ok, output = self.run_adb_shell('reboot fastboot')
if not ok:
self.log(self.tf('log_fastboot_enter_failed', output=output), "ERROR")
return
self.log(self.t('log_fastboot_wait'), "INFO")
if self.debug_mode:
self.log(self.tf('log_fastboot_enter_failed', output=output), "CMD")
self.log(self.t('log_fastboot_wait'), "WARNING")
if not self.wait_for_fastboot():
self.log(self.t('log_fastboot_missing'), "ERROR")
return
@@ -2250,7 +2499,7 @@ class ADKAPKGUI:
self.update_progress(6, 7, self.t('progress_flash_init_boot'), is_push=True)
ok, output = self.run_fastboot_command(['flash', 'init_boot', str(temp_img)], timeout=120)
if not ok:
if not ok or not self.fastboot_output_has_okay(output):
self.log(self.tf('log_init_boot_flash_failed', output=output), "ERROR")
return
self.log(self.t('log_init_boot_flash_success'), "INFO")
@@ -2326,7 +2575,7 @@ class ADKAPKGUI:
return
self.show_progress(False, is_push=False)
if not self.module_dir or not self.module_dir.exists():
if not getattr(self, 'module_zips', None):
self.run_on_ui_thread(lambda: messagebox.showerror(self.t('msg_error_title'), self.t('msg_resource_dir_missing')))
return
@@ -2337,25 +2586,18 @@ class ADKAPKGUI:
if not self.open_magisk_and_check_root():
self.run_on_ui_thread(lambda: messagebox.showerror(self.t('msg_auth_failed_title'), self.t('msg_root_failed')))
return
self.disable_and_uninstall_preinstalled_packages()
main_id = self.read_module_id(self.module_dir)
self.update_progress(0, 2, self.tf('progress_install_module', module=main_id), is_push=True)
ok, result = self.install_magisk_module_files(
self.module_dir,
skip_names={"disable-wireless-adb-vecentek-magisk.zip"}
)
if not ok:
self.log(self.tf('log_module_install_failed', module=main_id or "main", error=result), "ERROR")
return
total = len(self.module_zips)
for idx, zip_path in enumerate(self.module_zips, 1):
mod_id = self.read_module_id_from_zip(zip_path)
self.update_progress(idx - 1, total, self.t('progress_install_module'), is_push=True)
ok, result = self.install_magisk_module_zip(zip_path)
if not ok:
self.log(self.tf('log_module_install_failed', error=result), "ERROR")
return
extra_id = self.read_module_id(self.disable_module_dir)
self.update_progress(1, 2, self.tf('progress_install_module', module=extra_id), is_push=True)
ok, result = self.install_magisk_module_files(self.disable_module_dir)
if not ok:
self.log(self.tf('log_module_install_failed', module=extra_id or "extra", error=result), "ERROR")
return
self.update_progress(2, 2, self.t('progress_flash_done'), is_push=True)
self.update_progress(total, total, self.t('progress_flash_done'), is_push=True)
self.log(self.t('log_push_success'), "SUCCESS")
finally:
self.show_progress(False, is_push=True)
@@ -2854,8 +3096,7 @@ class ADKAPKGUI:
if self.extract_package_silent():
self.log(self.tf(
'log_package_extract_modules_success',
main=self.read_module_id(self.module_dir),
extra=self.read_module_id(self.disable_module_dir)
count=len(self.module_zips)
), "SUCCESS")
else:
self.log(self.t('log_package_extract_failed'), "ERROR")
@@ -3147,6 +3388,7 @@ def main():
sys.exit(1)
try:
set_windows_app_id()
app = ADKAPKGUI()
app.run()
except Exception as e: