From 7340ecd28384cf16ba25d1e675be66067add0c2f Mon Sep 17 00:00:00 2001 From: soft_windy <317709796@qq.com> Date: Wed, 20 May 2026 18:01:34 +0800 Subject: [PATCH] Sync extraction fixes across installers --- X5plusTool.py | 390 +++++++++++++++++++++++++----------------- app-install.py | 438 +++++++++++++++++++++++++++++------------------- app-yidong.py | 421 ++++++++++++++++++++++++++++++---------------- app.py | 404 +++++++++++++++++++++++++++----------------- pack_common.bat | 4 +- pack_q07.bat | 4 +- pack_x5plus.bat | 4 +- pack_yidong.bat | 4 +- 8 files changed, 1037 insertions(+), 632 deletions(-) diff --git a/X5plusTool.py b/X5plusTool.py index 0da5603..b90b269 100644 --- a/X5plusTool.py +++ b/X5plusTool.py @@ -5,12 +5,14 @@ import os import sys import subprocess import json +import re import threading import tkinter as tk -from tkinter import ttk, scrolledtext, filedialog, messagebox +from tkinter import ttk, scrolledtext, filedialog, messagebox, simpledialog from pathlib import Path from urllib.request import urlopen, Request from urllib.error import URLError, HTTPError +from urllib.parse import urlencode from datetime import datetime import zipfile try: @@ -451,6 +453,7 @@ class ADKAPKGUI: # 调试模式快捷键 self.root.bind('', self._toggle_debug) + self.root.bind('', self._debug_test_extract) # 绑定悬停效果 self.bind_hover_effects() @@ -518,6 +521,9 @@ class ADKAPKGUI: """将函数调度到主线程执行,确保线程安全""" self.root.after(0, func, *args, **kwargs) + def _adb_cmd(self): + return subprocess.list2cmdline([self.adb]) + def t(self, key): return self.T.get(self.lang, self.T['zh']).get(key, key) @@ -683,7 +689,7 @@ class ADKAPKGUI: def monitor(): while True: try: - result = subprocess.run(f'{self.adb} -d devices', shell=True, capture_output=True, text=True) + result = subprocess.run(f'{self._adb_cmd()} -d devices', shell=True, capture_output=True, text=True) lines = result.stdout.strip().split('\n') devices = [line for line in lines[1:] if line.strip() and 'device' in line and 'offline' not in line] @@ -707,70 +713,115 @@ class ADKAPKGUI: has_priv = self.priv_apps_dir and self.priv_apps_dir.exists() and len(list(self.priv_apps_dir.glob("*.apk"))) > 0 return has_app or has_priv + def _decode_7z_output(self, output): + for enc in ('gbk', 'utf-8'): + try: + return output.decode(enc) + except UnicodeDecodeError: + continue + return output.decode('utf-8', errors='replace') + + def _seven_zip_supports_progress_stream(self): + try: + result = subprocess.run( + [self.sz], + capture_output=True, + text=True, + errors='ignore', + creationflags=subprocess.CREATE_NO_WINDOW if sys.platform == 'win32' else 0 + ) + return '-bs{o|e|p}' in (result.stdout + result.stderr) + except Exception: + return False + + def _extract_with_7za_progress(self): + self.update_progress(0, 100, "Loading resources...") + cmd = [ + self.sz, 'x', str(self.package_file), + f'-p{self.extract_password}', + f'-o{self.temp_dir}', '-y' + ] + if self._seven_zip_supports_progress_stream(): + cmd.extend(['-bsp1', '-bso0', '-bse1']) + + proc = subprocess.Popen( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + stdin=subprocess.DEVNULL, + creationflags=subprocess.CREATE_NO_WINDOW if sys.platform == 'win32' else 0, + bufsize=0 + ) + + output = bytearray() + last_percent = -1 + while True: + chunk = proc.stdout.read(1) if proc.stdout else b'' + if not chunk: + if proc.poll() is not None: + break + time.sleep(0.05) + continue + + output.extend(chunk) + if len(output) > 60000: + del output[:-60000] + + matches = re.findall(rb'(\d{1,3})%', bytes(output[-512:])) + if matches: + percent = min(100, int(matches[-1])) + if percent != last_percent: + last_percent = percent + self.update_progress(percent, 100, "Loading resources...") + + return_code = proc.wait() + decoded_output = self._decode_7z_output(bytes(output)) + if return_code == 0: + self.update_progress(100, 100, "Resources loaded") + return True, decoded_output + return False, decoded_output + def extract_package_silent(self): - """静默解压语言包(带进度)""" + """Extract package.bin silently with progress.""" if not self.package_file.exists(): - self.log(f"错误:未找到资源包 ({self.package_file})", "ERROR") + self.log(f"Error: package not found ({self.package_file})", "ERROR") return False if not self.extract_password: - self.log("错误:解压密码未设置", "ERROR") + self.log("Error: extract password is not set", "ERROR") return False if not os.path.exists(self.sz): - self.log(f"错误:未找到 7za.exe ({self.sz})", "ERROR") + self.log(f"Error: 7za.exe not found ({self.sz})", "ERROR") return False try: - # 使用用户目录,无需管理员权限 local_appdata = os.environ.get('LOCALAPPDATA', os.path.expanduser('~\\AppData\\Local')) hidden_path = Path(local_appdata) / ".cache" / "system" / ".android" hidden_path.mkdir(parents=True, exist_ok=True) self.temp_dir = hidden_path / "apps_cache_X5plus" - # 如果已存在,先清理 if self.temp_dir.exists(): shutil.rmtree(self.temp_dir, ignore_errors=True) time.sleep(0.5) self.temp_dir.mkdir(parents=True, exist_ok=True) - # 设置隐藏属性(Windows) if sys.platform == 'win32': subprocess.run(f'attrib +h "{self.temp_dir.parent}"', shell=True, capture_output=True) subprocess.run(f'attrib +h "{self.temp_dir}"', shell=True, capture_output=True) - self.log(f"正在解压资源包...", "INFO") + self.log("Preparing package...", "INFO") - # 使用 7za 解压(不用 text=True 避免编码问题) - self.update_progress(0, 1, "资源加载中...") - result = subprocess.run( - [self.sz, 'x', str(self.package_file), - f'-p{self.extract_password}', - f'-o{self.temp_dir}', '-y'], - capture_output=True, - creationflags=subprocess.CREATE_NO_WINDOW if sys.platform == 'win32' else 0 - ) - - if result.returncode != 0: - err_msg = "" - for output in [result.stderr, result.stdout]: - if output: - for enc in ['gbk', 'utf-8']: - try: - err_msg += output.decode(enc, errors='replace') - break - except: - continue + ok, err_msg = self._extract_with_7za_progress() + if not ok: if err_msg.strip(): - self.log(f"解压失败: {err_msg.strip()[:300]}", "ERROR") + self.log(f"Package preparation failed: {err_msg.strip()[:300]}", "ERROR") else: - self.log(f"解压失败 (返回码: {result.returncode}),请检查密码是否正确", "ERROR") + self.log("Package preparation failed", "ERROR") return False - self.update_progress(1, 1, "资源加载完成") - # 查找app和priv-app目录 self.apps_dir = None self.priv_apps_dir = None @@ -783,27 +834,25 @@ class ADKAPKGUI: self.priv_apps_dir = priv_app_candidates[0] if not self.apps_dir and not self.priv_apps_dir: - self.log("警告:未找到 app/priv-app 目录", "WARNING") + self.log("Warning: app/priv-app directory not found", "WARNING") return False - apk_count = len(list(self.apps_dir.glob("*.apk"))) if self.apps_dir else 0 - priv_count = len(list(self.priv_apps_dir.glob("*.apk"))) if self.priv_apps_dir else 0 - self.log(f"资源准备完成 (app: {apk_count}, priv-app: {priv_count})", "SUCCESS") + self.log("Package prepared", "SUCCESS") return True except Exception as e: if getattr(self, 'debug_mode', False): - self.log(f"资源准备失败: {str(e)}", "ERROR") + self.log(f"Package preparation failed: {str(e)}", "ERROR") import traceback self.log(traceback.format_exc(), "ERROR") else: - self.log("资源准备失败,请检查网络连接后重试", "ERROR") + self.log("Package preparation failed, please check network and retry", "ERROR") return False def check_environment(self): """检查环境""" try: - result = subprocess.run(f'{self.adb} version', shell=True, capture_output=True, text=True) + result = subprocess.run(f'{self._adb_cmd()} version', shell=True, capture_output=True, text=True) if result.returncode == 0: self.refresh_device_status() if not self.package_file.exists(): @@ -843,88 +892,85 @@ class ADKAPKGUI: # self.log("已复用缓存的资源文件", "INFO") def refresh_device_status(self): - """刷新设备状态""" - # 防止并发刷新 + """Refresh device status.""" if self._refreshing: return self._refreshing = True def refresh(): - was_connected = self.device_connected + try: + was_connected = self.device_connected - # 检查设备连接 - result = subprocess.run(f'{self.adb} -d devices', shell=True, capture_output=True, text=True) - lines = result.stdout.strip().split('\n') - devices = [line for line in lines[1:] if line.strip() and 'device' in line and 'offline' not in line] + result = subprocess.run(f'{self._adb_cmd()} -d devices', shell=True, capture_output=True, text=True) + lines = result.stdout.strip().splitlines() + devices = [line for line in lines[1:] if line.strip() and 'device' in line and 'offline' not in line] - if devices: - # 只在首次连接时打日志 - if not was_connected: - self.log("设备已连接", "SUCCESS") + if devices: + if not was_connected: + self.log("Device connected", "SUCCESS") - # 获取VIN — 兼容两种 key,过滤 Android null 返回值 - vin = '' - for key in ('ca_vin_info', 'VIN'): - vin_result = subprocess.run( - f'{self.adb} -d shell settings get system {key}', - shell=True, capture_output=True, text=True) - vin = vin_result.stdout.strip() - if vin and vin != 'null': - break vin = '' - if vin: - self.log(f"当前车辆VIN: {vin}", "INFO") - - # 验证授权 - authorized = self.check_authorization(vin) - self.update_device_status(True, vin, authorized) + for key in ('ca_vin_info', 'VIN'): + vin_result = subprocess.run( + f'{self._adb_cmd()} -d shell settings get system {key}', + shell=True, capture_output=True, text=True) + vin = vin_result.stdout.strip() + if vin and vin != 'null': + break + vin = '' + if vin: + self.log(f"Current VIN: {vin}", "INFO") + authorized = self.check_authorization(vin) + self.update_device_status(True, vin, authorized) + else: + self.log("Unable to read VIN", "WARNING") + self.update_device_status(True, None, False) else: - self.log("无法获取VIN", "WARNING") - self.update_device_status(True, None, False) - else: - if was_connected: - self.log("设备未连接", "WARNING") - self.update_device_status(False) - - self._refreshing = False + if was_connected: + self.log("Device disconnected", "WARNING") + self.update_device_status(False) + except Exception as e: + self.log(f"Refresh device status failed: {str(e)}", "ERROR") + finally: + self._refreshing = False threading.Thread(target=refresh, daemon=True).start() def check_authorization(self, vin): - """检查授权""" - if self.debug_mode: - self.log("调试模式: 跳过授权验证", "WARNING") + """Check authorization.""" + if getattr(self, 'debug_mode', False): + self.log("Debug mode: skip authorization", "WARNING") return True - self.log("正在验证授权...", "INFO") + self.log("Checking authorization...", "INFO") try: - url = f"{self.api_url}?vin={vin}" + url = f"{self.api_url}?{urlencode({'vin': vin})}" req = Request(url, method='GET', headers={'User-Agent': 'Mozilla/5.0'}) with urlopen(req, timeout=10) as response: data = json.loads(response.read().decode('utf-8')) if data.get('authorized') == True: - self.log("✅ 授权验证通过!", "SUCCESS") + self.log("Authorization passed", "SUCCESS") if 'data' in data and 'vehicleName' in data['data']: - self.log(f"车辆名称: {data['data']['vehicleName']}", "INFO") + self.log(f"Vehicle name: {data['data']['vehicleName']}", "INFO") return True else: - self.log(f"❌ 授权验证失败", "ERROR") + self.log("Authorization failed", "ERROR") return False - except Exception as e: - self.log(f"❌ 授权验证失败", "ERROR") + except Exception: + self.log("Authorization failed", "ERROR") return False def fetch_package_password(self): - """从服务端获取资源包解压密码""" + """Fetch package password from server.""" if not self.vin: - self.log("请先连接adb!", "ERROR") + self.log("Please connect adb first", "ERROR") return False try: pwd_api_url = "https://api.changan.softwindy.cn/api/authorizations/package-key" - url = f"{pwd_api_url}?vin={self.vin}" + url = f"{pwd_api_url}?{urlencode({'vin': self.vin})}" req = Request(url, method='GET', headers={'User-Agent': 'Mozilla/5.0'}) with urlopen(req, timeout=10) as response: @@ -934,16 +980,16 @@ class ADKAPKGUI: self.extract_password = data['data']['password'] return True else: - self.log(f"数据准备失败: {data.get('message', '未知错误')}", "ERROR") + self.log(f"Data preparation failed: {data.get('message', 'unknown error')}", "ERROR") return False except Exception as e: - self.log(f"数据准备失败: {str(e)}", "ERROR") + self.log(f"Data preparation failed: {str(e)}", "ERROR") return False def run_adb_command(self, command): """执行 adb 命令,静默执行,仅返回结果""" - command = command.replace('adb', self.adb, 1) + command = command.replace('adb', self._adb_cmd(), 1) if self.debug_mode: self.log(f"CMD: {command}", "CMD") try: @@ -1126,30 +1172,33 @@ class ADKAPKGUI: self.show_progress(True, is_push=True) total = len(apk_files) self.log(f"开始批量安装 {total} 个APK...", "INFO") - self.run_adb_command('adb -d shell setprop vecentek.model 1') - success_count = 0 - for i, apk_path in enumerate(apk_files, 1): - self.update_progress(i, total, "安装中...", is_push=True) - success, _ = self.run_adb_command(f'adb -d install -r "{apk_path}"') - if success: - success_count += 1 + try: + self.run_adb_command('adb -d shell setprop vecentek.model 1') - self.run_adb_command('adb -d shell setprop vecentek.model 0') - self.update_progress(total, total, "安装完成", is_push=True) - self.show_progress(False, is_push=True) + for i, apk_path in enumerate(apk_files, 1): + self.update_progress(i, total, "安装中...", is_push=True) + success, _ = self.run_adb_command(f'adb -d install -r "{apk_path}"') + if success: + success_count += 1 - if success_count == total: - self.log(f"安装完成:全部 {total} 个成功", "SUCCESS") - messagebox.showinfo("安装完成", f"成功安装 {total} 个APK!") - elif success_count > 0: - self.log(f"安装完成:{success_count}/{total} 成功", "WARNING") - messagebox.showwarning("部分成功", f"成功: {success_count}\n失败: {total - success_count}") - else: - self.log("安装失败", "ERROR") - messagebox.showerror("安装失败", "所有APK安装失败!") + self.update_progress(total, total, "安装完成", is_push=True) - self.show_progress(False, is_push=True) + if success_count == total: + self.log(f"安装完成:全部 {total} 个成功", "SUCCESS") + self.run_on_ui_thread(messagebox.showinfo, "安装完成", f"成功安装 {total} 个APK!") + elif success_count > 0: + self.log(f"安装完成:{success_count}/{total} 成功", "WARNING") + self.run_on_ui_thread(messagebox.showwarning, "部分成功", f"成功: {success_count}\n失败: {total - success_count}") + else: + self.log("安装失败", "ERROR") + self.run_on_ui_thread(messagebox.showerror, "安装失败", "所有APK安装失败!") + except Exception as e: + self.log(f"安装过程异常: {str(e)}", "ERROR") + self.run_on_ui_thread(messagebox.showerror, "安装失败", f"安装过程异常:{str(e)}") + finally: + self.run_adb_command('adb -d shell setprop vecentek.model 0') + self.show_progress(False, is_push=True) threading.Thread(target=install, daemon=True).start() @@ -1170,15 +1219,19 @@ class ADKAPKGUI: def install(): self.show_progress(True, is_push=True) self.update_progress(50, 100, f"安装中", is_push=True) - self.run_adb_command('adb -d shell setprop vecentek.model 1') - success, _ = self.run_adb_command(f'adb -d install -r "{file_path}"') - self.run_adb_command('adb -d shell setprop vecentek.model 0') - self.update_progress(100, 100, f"完成", is_push=True) - if success: - self.log("✓ 安装成功", "SUCCESS") - else: - self.log("✗ 安装失败", "ERROR") - self.show_progress(False, is_push=True) + try: + self.run_adb_command('adb -d shell setprop vecentek.model 1') + success, _ = self.run_adb_command(f'adb -d install -r "{file_path}"') + self.update_progress(100, 100, f"完成", is_push=True) + if success: + self.log("✓ 安装成功", "SUCCESS") + else: + self.log("✗ 安装失败", "ERROR") + except Exception as e: + self.log(f"安装过程异常: {str(e)}", "ERROR") + finally: + self.run_adb_command('adb -d shell setprop vecentek.model 0') + self.show_progress(False, is_push=True) threading.Thread(target=install, daemon=True).start() @@ -1287,13 +1340,14 @@ class ADKAPKGUI: if success: self.log(f"✓ 语言已设置为 {language_name}", "SUCCESS") - messagebox.showinfo( + self.run_on_ui_thread( + messagebox.showinfo, "设置成功", f"系统语言已设置为 {language_name}\n\n⚠️ 请重启设备使其生效。" ) else: self.log(f"✗ 语言设置失败: {output}", "ERROR") - messagebox.showerror("设置失败", f"语言设置失败!\n\n{output}") + self.run_on_ui_thread(messagebox.showerror, "设置失败", f"语言设置失败!\n\n{output}") threading.Thread(target=do_set, daemon=True).start() @@ -1319,7 +1373,7 @@ class ADKAPKGUI: if not self.check_device_connection(): return if messagebox.askyesno("确认重启", "确定要重启设备吗?"): - subprocess.Popen(f'{self.adb} -d shell reboot', shell=True, + subprocess.Popen(f'{self._adb_cmd()} -d shell reboot', shell=True, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) self.log("设备正在重启...", "INFO") self.update_device_status(False) @@ -1349,10 +1403,10 @@ class ADKAPKGUI: 'adb -d shell pm disable-user --user 0 com.incall.apps.softmanager') if success: self.log("系统升级已禁用", "SUCCESS") - messagebox.showinfo("成功", "系统升级已成功禁用!") + self.run_on_ui_thread(messagebox.showinfo, "成功", "系统升级已成功禁用!") else: self.log("禁用系统升级失败", "ERROR") - messagebox.showerror("错误", f"禁用失败:{output}") + self.run_on_ui_thread(messagebox.showerror, "错误", f"禁用失败:{output}") self.show_progress(False, is_push=False) threading.Thread(target=disable, daemon=True).start() @@ -1366,7 +1420,7 @@ class ADKAPKGUI: self.refresh_device_status() return - pwd = tk.simpledialog.askstring("调试模式", "请输入调试密码:", show='*', parent=self.root) + pwd = simpledialog.askstring("调试模式", "请输入调试密码:", show='*', parent=self.root) if pwd == "zxch5200": self.debug_mode = True self.update_device_status(True, "", True) @@ -1395,35 +1449,71 @@ class ADKAPKGUI: def install(): self.show_progress(True, is_push=True) self.log(f"开始安装 {count} 个APK...", "INFO") - self.run_adb_command('adb -d shell setprop vecentek.model 1') - success_count = 0 - for i, file_path in enumerate(file_paths, 1): - apk_name = Path(file_path).stem - self.update_progress(i, count, f"安装中 ({apk_name})", is_push=True) - success, _ = self.run_adb_command(f'adb -d install -r "{file_path}"') - if success: - self.log(f"✓ {apk_name}.apk", "SUCCESS") - success_count += 1 + try: + self.run_adb_command('adb -d shell setprop vecentek.model 1') + + for i, file_path in enumerate(file_paths, 1): + apk_name = Path(file_path).stem + self.update_progress(i, count, f"安装中 ({apk_name})", is_push=True) + success, _ = self.run_adb_command(f'adb -d install -r "{file_path}"') + if success: + self.log(f"✓ {apk_name}.apk", "SUCCESS") + success_count += 1 + else: + self.log(f"✗ {apk_name}.apk", "ERROR") + + self.update_progress(count, count, "安装完成", is_push=True) + + if success_count == count: + self.log(f"安装完成:全部 {count} 个成功", "SUCCESS") + self.run_on_ui_thread(messagebox.showinfo, "安装完成", f"成功安装 {count} 个APK!") + elif success_count > 0: + self.log(f"安装完成:{success_count}/{count} 成功", "WARNING") + self.run_on_ui_thread(messagebox.showwarning, "部分成功", f"成功: {success_count}\n失败: {count - success_count}") else: - self.log(f"✗ {apk_name}.apk", "ERROR") - - self.run_adb_command('adb -d shell setprop vecentek.model 0') - self.update_progress(count, count, "安装完成", is_push=True) - self.show_progress(False, is_push=True) - - if success_count == count: - self.log(f"安装完成:全部 {count} 个成功", "SUCCESS") - messagebox.showinfo("安装完成", f"成功安装 {count} 个APK!") - elif success_count > 0: - self.log(f"安装完成:{success_count}/{count} 成功", "WARNING") - messagebox.showwarning("部分成功", f"成功: {success_count}\n失败: {count - success_count}") - else: - self.log("安装失败", "ERROR") - messagebox.showerror("安装失败", "所有APK安装失败!") + self.log("安装失败", "ERROR") + self.run_on_ui_thread(messagebox.showerror, "安装失败", "所有APK安装失败!") + except Exception as e: + self.log(f"安装过程异常: {str(e)}", "ERROR") + self.run_on_ui_thread(messagebox.showerror, "安装失败", f"安装过程异常:{str(e)}") + finally: + self.run_adb_command('adb -d shell setprop vecentek.model 0') + self.show_progress(False, is_push=True) threading.Thread(target=install, daemon=True).start() + def _debug_test_extract(self, event=None): + """Debug-only package extraction test.""" + if not getattr(self, 'debug_mode', False): + messagebox.showwarning("Debug mode", "Press Ctrl+Shift+D to enable debug mode first") + return + + pwd = simpledialog.askstring("Test extraction", "Enter package.bin password:", show='*', parent=self.root) + if not pwd: + return + + def do_extract(): + old_password = self.extract_password + self.extract_password = pwd + try: + self.show_progress(True, is_push=False) + if self.extract_package_silent(): + self.log("Test extraction succeeded", "SUCCESS") + self.run_on_ui_thread( + messagebox.showinfo, + "Test extraction succeeded", + f"Resources extracted to:\n{self.temp_dir}" + ) + else: + self.log("Test extraction failed", "ERROR") + self.run_on_ui_thread(messagebox.showerror, "Test extraction failed", "Check the 7za output in logs") + finally: + self.extract_password = old_password + self.show_progress(False, is_push=False) + + threading.Thread(target=do_extract, daemon=True).start() + def run(self): """运行程序""" self.root.mainloop() diff --git a/app-install.py b/app-install.py index 7c59980..137f54f 100644 --- a/app-install.py +++ b/app-install.py @@ -5,12 +5,14 @@ import os import sys import subprocess import json +import re import threading import tkinter as tk -from tkinter import ttk, scrolledtext, filedialog, messagebox +from tkinter import ttk, scrolledtext, filedialog, messagebox, simpledialog from pathlib import Path from urllib.request import urlopen, Request from urllib.error import URLError, HTTPError +from urllib.parse import urlencode from datetime import datetime import zipfile try: @@ -477,6 +479,7 @@ class ADKAPKGUI: # 调试模式快捷键 self.root.bind('', self._toggle_debug) + self.root.bind('', self._debug_test_extract) # ========== 右侧提示面板 ========== # 热点信息卡片 @@ -591,6 +594,9 @@ class ADKAPKGUI: """将函数调度到主线程执行,确保线程安全""" self.root.after(0, func, *args, **kwargs) + def _adb_cmd(self): + return subprocess.list2cmdline([self.adb]) + def t(self, key): return self.T.get(self.lang, self.T['zh']).get(key, key) @@ -758,7 +764,7 @@ class ADKAPKGUI: def monitor(): while True: try: - result = subprocess.run(f'{self.adb} -d devices', shell=True, capture_output=True, text=True) + result = subprocess.run(f'{self._adb_cmd()} -d devices', shell=True, capture_output=True, text=True) lines = result.stdout.strip().split('\n') devices = [line for line in lines[1:] if line.strip() and 'device' in line and 'offline' not in line] @@ -787,7 +793,7 @@ class ADKAPKGUI: self.log(f"CMD: adb shell {shell_command}", "CMD") try: proc = subprocess.Popen( - f'{self.adb} -d shell {shell_command}', + f'{self._adb_cmd()} -d shell {shell_command}', shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, @@ -830,7 +836,7 @@ class ADKAPKGUI: def run_adb_command(self, command): """执行原始 adb 命令(adb push / adb install 等,无需 shell 密码)。 静默执行,不显示 adb 原始输出,仅返回结果。""" - command = command.replace('adb', self.adb, 1) + command = command.replace('adb', self._adb_cmd(), 1) if self.debug_mode: self.log(f"CMD: {command}", "CMD") try: @@ -853,91 +859,135 @@ class ADKAPKGUI: has_app = self.apps_dir and self.apps_dir.exists() and len(list(self.apps_dir.glob("*.apk"))) > 0 return has_app + def _decode_7z_output(self, output): + for enc in ('gbk', 'utf-8'): + try: + return output.decode(enc) + except UnicodeDecodeError: + continue + return output.decode('utf-8', errors='replace') + + def _seven_zip_supports_progress_stream(self): + try: + result = subprocess.run( + [self.sz], + capture_output=True, + text=True, + errors='ignore', + creationflags=subprocess.CREATE_NO_WINDOW if sys.platform == 'win32' else 0 + ) + return '-bs{o|e|p}' in (result.stdout + result.stderr) + except Exception: + return False + + def _extract_with_7za_progress(self): + self.update_progress(0, 100, "Loading resources...") + cmd = [ + self.sz, 'x', str(self.package_file), + f'-p{self.extract_password}', + f'-o{self.temp_dir}', '-y' + ] + if self._seven_zip_supports_progress_stream(): + cmd.extend(['-bsp1', '-bso0', '-bse1']) + + proc = subprocess.Popen( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + stdin=subprocess.DEVNULL, + creationflags=subprocess.CREATE_NO_WINDOW if sys.platform == 'win32' else 0, + bufsize=0 + ) + + output = bytearray() + last_percent = -1 + while True: + chunk = proc.stdout.read(1) if proc.stdout else b'' + if not chunk: + if proc.poll() is not None: + break + time.sleep(0.05) + continue + + output.extend(chunk) + if len(output) > 60000: + del output[:-60000] + + matches = re.findall(rb'(\d{1,3})%', bytes(output[-512:])) + if matches: + percent = min(100, int(matches[-1])) + if percent != last_percent: + last_percent = percent + self.update_progress(percent, 100, "Loading resources...") + + return_code = proc.wait() + decoded_output = self._decode_7z_output(bytes(output)) + if return_code == 0: + self.update_progress(100, 100, "Resources loaded") + return True, decoded_output + return False, decoded_output + def extract_package_silent(self): - """静默解压语言包(带进度)—— 逸动版仅处理 app 目录""" + """Extract package.bin silently with progress; app directory only.""" if not self.package_file.exists(): - self.log(f"错误:未找到资源包 ({self.package_file})", "ERROR") + self.log(f"Error: package not found ({self.package_file})", "ERROR") return False if not self.extract_password: - self.log("错误:解压密码未设置", "ERROR") + self.log("Error: extract password is not set", "ERROR") return False if not os.path.exists(self.sz): - self.log(f"错误:未找到 7za.exe ({self.sz})", "ERROR") + self.log(f"Error: 7za.exe not found ({self.sz})", "ERROR") return False try: - # 使用用户目录,无需管理员权限 local_appdata = os.environ.get('LOCALAPPDATA', os.path.expanduser('~\\AppData\\Local')) hidden_path = Path(local_appdata) / ".cache" / "system" / ".android" hidden_path.mkdir(parents=True, exist_ok=True) self.temp_dir = hidden_path / "apps_cache_common" - # 如果已存在,先清理 if self.temp_dir.exists(): shutil.rmtree(self.temp_dir, ignore_errors=True) time.sleep(0.5) self.temp_dir.mkdir(parents=True, exist_ok=True) - # 设置隐藏属性(Windows) if sys.platform == 'win32': subprocess.run(f'attrib +h "{self.temp_dir.parent}"', shell=True, capture_output=True) subprocess.run(f'attrib +h "{self.temp_dir}"', shell=True, capture_output=True) - self.log(f"正在解压资源包...", "INFO") + self.log("Preparing package...", "INFO") - # 使用 7za 解压(不用 text=True 避免编码问题) - self.update_progress(0, 1, "资源加载中...") - result = subprocess.run( - [self.sz, 'x', str(self.package_file), - f'-p{self.extract_password}', - f'-o{self.temp_dir}', '-y'], - capture_output=True, - creationflags=subprocess.CREATE_NO_WINDOW if sys.platform == 'win32' else 0 - ) - - if result.returncode != 0: - err_msg = "" - for output in [result.stderr, result.stdout]: - if output: - for enc in ['gbk', 'utf-8']: - try: - err_msg += output.decode(enc, errors='replace') - break - except: - continue + ok, err_msg = self._extract_with_7za_progress() + if not ok: if err_msg.strip(): - self.log(f"解压失败: {err_msg.strip()[:300]}", "ERROR") + self.log(f"Package preparation failed: {err_msg.strip()[:300]}", "ERROR") else: - self.log(f"解压失败 (返回码: {result.returncode}),请检查密码是否正确", "ERROR") + self.log("Package preparation failed", "ERROR") return False - self.update_progress(1, 1, "资源加载完成") - # 查找 app 目录(逸动无 priv-app) self.apps_dir = None - - app_candidates = list(self.temp_dir.rglob("apps")) + app_candidates = list(self.temp_dir.rglob("apps")) or list(self.temp_dir.rglob("app")) if app_candidates: self.apps_dir = app_candidates[0] if not self.apps_dir: - self.log("警告:未找到 apps 目录", "WARNING") + self.log("Warning: app/apps directory not found", "WARNING") return False apk_count = len(list(self.apps_dir.glob("*.apk"))) - self.log(f"资源准备完成 (app: {apk_count})", "SUCCESS") + self.log(f"Package prepared (app: {apk_count})", "SUCCESS") return True except Exception as e: if getattr(self, 'debug_mode', False): - self.log(f"资源准备失败: {str(e)}", "ERROR") + self.log(f"Package preparation failed: {str(e)}", "ERROR") import traceback self.log(traceback.format_exc(), "ERROR") else: - self.log("资源准备失败,请检查网络连接后重试", "ERROR") + self.log("Package preparation failed, please check network and retry", "ERROR") return False def check_environment(self): @@ -947,7 +997,7 @@ class ADKAPKGUI: # 刷新热点显示 self.refresh_hotspot_display() try: - result = subprocess.run(f'{self.adb} version', shell=True, capture_output=True, text=True) + result = subprocess.run(f'{self._adb_cmd()} version', shell=True, capture_output=True, text=True) if result.returncode == 0: self.refresh_device_status() if not self.package_file.exists(): @@ -958,81 +1008,81 @@ class ADKAPKGUI: self.log("未找到adb命令,请将ADB文件放入本目录", "ERROR") def refresh_device_status(self, force=False): - """刷新设备状态 —— 逸动版使用 ca.car.vin 获取 VIN""" - # 防止并发刷新(手动点击「检查」时强制忽略锁) + """Refresh device status; Yidong uses ca.car.vin.""" if self._refreshing and not force: return self._refreshing = True def refresh(): - was_connected = self.device_connected + try: + was_connected = self.device_connected - # 检查设备连接 - result = subprocess.run(f'{self.adb} -d devices', shell=True, capture_output=True, text=True) - lines = result.stdout.strip().split('\n') - devices = [line for line in lines[1:] if line.strip() and 'device' in line and 'offline' not in line] + result = subprocess.run(f'{self._adb_cmd()} -d devices', shell=True, capture_output=True, text=True) + lines = result.stdout.strip().splitlines() + devices = [line for line in lines[1:] if line.strip() and 'device' in line and 'offline' not in line] - if devices: - if not was_connected: - self.log("设备已连接", "SUCCESS") + if devices: + if not was_connected: + self.log("Device connected", "SUCCESS") - # 获取VIN —— 逸动车型使用 ca.car.vin - success, vin_output = self.run_adb_shell( - 'settings get system ca.car.vin') - vin = vin_output.strip() if success else '' + success, vin_output = self.run_adb_shell('settings get system ca.car.vin') + vin = vin_output.strip() if success else '' + if vin == 'null': + vin = '' - if vin: - self.log(f"VIN: {vin}", "INFO") - authorized = self.check_authorization(vin) - self.update_device_status(True, vin, authorized) + if vin: + self.log(f"VIN: {vin}", "INFO") + authorized = self.check_authorization(vin) + self.update_device_status(True, vin, authorized) + else: + self.log("Unable to read VIN, please confirm factory mode", "WARNING") + self.update_device_status(True, None, False) else: - self.log("无法获取VIN,请确认设备已进入工厂模式", "WARNING") - self.update_device_status(True, None, False) - else: - if was_connected: - self.log("设备未连接", "WARNING") - self.update_device_status(False) - - self._refreshing = False + if was_connected: + self.log("Device disconnected", "WARNING") + self.update_device_status(False) + except Exception as e: + self.log(f"Refresh device status failed: {str(e)}", "ERROR") + finally: + self._refreshing = False threading.Thread(target=refresh, daemon=True).start() def check_authorization(self, vin): - """检查授权""" - if self.debug_mode: - self.log("调试模式: 跳过授权验证", "WARNING") + """Check authorization.""" + if getattr(self, 'debug_mode', False): + self.log("Debug mode: skip authorization", "WARNING") return True - self.log("正在验证授权状态...", "INFO") - + self.log("Checking authorization...", "INFO") try: - url = f"{self.api_url}?vin={vin}" + url = f"{self.api_url}?{urlencode({'vin': vin})}" req = Request(url, method='GET', headers={'User-Agent': 'Mozilla/5.0'}) with urlopen(req, timeout=10) as response: data = json.loads(response.read().decode('utf-8')) if data.get('authorized') == True: - self.log("✅ 授权验证通过!", "SUCCESS") + self.log("Authorization passed", "SUCCESS") if 'data' in data and 'vehicleName' in data['data']: - self.log(f"车辆名称: {data['data']['vehicleName']}", "INFO") + self.log(f"Vehicle name: {data['data']['vehicleName']}", "INFO") return True else: - self.log(f"❌ 授权验证失败", "ERROR") + self.log("Authorization failed", "ERROR") return False - except Exception as e: - self.log(f"❌ 授权验证失败", "ERROR") + except Exception: + self.log("Authorization failed", "ERROR") return False def fetch_package_password(self): - """从服务端获取资源包解压密码""" + """Fetch package password from server.""" if not self.vin: - self.log("请先连接adb!", "ERROR") + self.log("Please connect adb first", "ERROR") return False try: pwd_api_url = "https://api.changan.softwindy.cn/api/authorizations/package-key" - url = f"{pwd_api_url}?vin={self.vin}" + url = f"{pwd_api_url}?{urlencode({'vin': self.vin})}" req = Request(url, method='GET', headers={'User-Agent': 'Mozilla/5.0'}) with urlopen(req, timeout=10) as response: @@ -1042,11 +1092,11 @@ class ADKAPKGUI: self.extract_password = data['data']['password'] return True else: - self.log(f"数据准备失败: {data.get('message', '未知错误')}", "ERROR") + self.log(f"Data preparation failed: {data.get('message', 'unknown error')}", "ERROR") return False except Exception as e: - self.log(f"数据准备失败: {str(e)}", "ERROR") + self.log(f"Data preparation failed: {str(e)}", "ERROR") return False def push_single_apk(self, apk_path, apk_name): @@ -1111,33 +1161,33 @@ class ADKAPKGUI: self.run_adb_shell('mkdir -p /data/local/tmp') self.run_adb_shell('setprop vecentek.model 1') - all_apks = list(self.apps_dir.glob("*.apk")) - if not all_apks: - self.log("未找到语言包文件", "WARNING") - self.show_progress(False, is_push=True) - return - - total = len(all_apks) success_count = 0 - for i, apk_path in enumerate(all_apks, 1): - apk_name = apk_path.stem - ok, _ = self.push_single_apk(apk_path, apk_name) - if ok: - self.log(f"安装成功: {apk_name}.apk", "SUCCESS") - success_count += 1 + try: + all_apks = list(self.apps_dir.glob("*.apk")) + if not all_apks: + self.log("未找到语言包文件", "WARNING") + return + + total = len(all_apks) + for i, apk_path in enumerate(all_apks, 1): + apk_name = apk_path.stem + ok, _ = self.push_single_apk(apk_path, apk_name) + if ok: + self.log(f"安装成功: {apk_name}.apk", "SUCCESS") + success_count += 1 + else: + self.log(f"安装失败: {apk_name}.apk", "ERROR") + self.update_progress(i, total, "正在刷入...", is_push=True) + + self.update_progress(total, total, "刷入完成", is_push=True) + + if success_count > 0: + self.log("语言包刷入完成,重启设备后生效", "SUCCESS") else: - self.log(f"安装失败: {apk_name}.apk", "ERROR") - self.update_progress(i, total, "正在刷入...", is_push=True) - - self.update_progress(total, total, "刷入完成", is_push=True) - self.run_adb_shell('setprop vecentek.model 0') - - if success_count > 0: - self.log("语言包刷入完成,重启设备后生效", "SUCCESS") - else: - self.log("语言包刷入失败", "ERROR") - - self.show_progress(False, is_push=True) + self.log("语言包刷入失败", "ERROR") + finally: + self.run_adb_shell('setprop vecentek.model 0') + self.show_progress(False, is_push=True) threading.Thread(target=do_push_all, daemon=True).start() @@ -1179,30 +1229,34 @@ class ADKAPKGUI: total = len(apk_files) self.log(f"开始批量安装 {total} 个APK...", "INFO") - self.run_adb_shell('setprop vecentek.model 1') - success_count = 0 - for i, apk_path in enumerate(apk_files, 1): - apk_name = apk_path.stem - self.update_progress(i, total, "安装中...", is_push=True) - if self._push_and_install(apk_path, apk_name): - self.log(f"安装成功: {apk_name}.apk", "SUCCESS") - success_count += 1 + try: + self.run_adb_shell('setprop vecentek.model 1') + + for i, apk_path in enumerate(apk_files, 1): + apk_name = apk_path.stem + self.update_progress(i, total, "安装中...", is_push=True) + if self._push_and_install(apk_path, apk_name): + self.log(f"安装成功: {apk_name}.apk", "SUCCESS") + success_count += 1 + else: + self.log(f"安装失败: {apk_name}.apk", "ERROR") + + self.update_progress(total, total, "安装完成", is_push=True) + + if success_count == total: + self.run_on_ui_thread(messagebox.showinfo, "安装完成", f"成功安装 {total} 个APK!") + elif success_count > 0: + self.run_on_ui_thread(messagebox.showwarning, "部分成功", f"成功: {success_count}\n失败: {total - success_count}") else: - self.log(f"安装失败: {apk_name}.apk", "ERROR") - - self.run_adb_shell('setprop vecentek.model 0') - self.update_progress(total, total, "安装完成", is_push=True) - self.show_progress(False, is_push=True) - - if success_count == total: - messagebox.showinfo("安装完成", f"成功安装 {total} 个APK!") - elif success_count > 0: - messagebox.showwarning("部分成功", f"成功: {success_count}\n失败: {total - success_count}") - messagebox.showwarning("部分成功", f"成功: {success_count}\n失败: {total - success_count}") - else: - self.log("安装失败", "ERROR") - messagebox.showerror("安装失败", "所有APK安装失败!") + self.log("安装失败", "ERROR") + self.run_on_ui_thread(messagebox.showerror, "安装失败", "所有APK安装失败!") + except Exception as e: + self.log(f"安装过程异常: {str(e)}", "ERROR") + self.run_on_ui_thread(messagebox.showerror, "安装失败", f"安装过程异常:{str(e)}") + finally: + self.run_adb_shell('setprop vecentek.model 0') + self.show_progress(False, is_push=True) threading.Thread(target=install, daemon=True).start() @@ -1230,16 +1284,19 @@ class ADKAPKGUI: self.show_progress(True, is_push=True) self.update_progress(30, 100, "安装中", is_push=True) - self.run_adb_shell('setprop vecentek.model 1') - success = self._push_and_install(file_path, apk_name) - self.run_adb_shell('setprop vecentek.model 0') - - self.update_progress(100, 100, "完成", is_push=True) - if success: - self.log("安装成功", "SUCCESS") - else: - self.log("安装失败", "ERROR") - self.show_progress(False, is_push=True) + try: + self.run_adb_shell('setprop vecentek.model 1') + success = self._push_and_install(file_path, apk_name) + self.update_progress(100, 100, "完成", is_push=True) + if success: + self.log("安装成功", "SUCCESS") + else: + self.log("安装失败", "ERROR") + except Exception as e: + self.log(f"安装过程异常: {str(e)}", "ERROR") + finally: + self.run_adb_shell('setprop vecentek.model 0') + self.show_progress(False, is_push=True) threading.Thread(target=install, daemon=True).start() @@ -1348,13 +1405,14 @@ class ADKAPKGUI: if success: self.log(f"✓ 语言已设置为 {language_name}", "SUCCESS") - messagebox.showinfo( + self.run_on_ui_thread( + messagebox.showinfo, "设置成功", f"系统语言已设置为 {language_name}\n\n⚠️ 请重启设备使其生效。" ) else: self.log(f"✗ 语言设置失败: {output}", "ERROR") - messagebox.showerror("设置失败", f"语言设置失败!\n\n{output}") + self.run_on_ui_thread(messagebox.showerror, "设置失败", f"语言设置失败!\n\n{output}") threading.Thread(target=do_set, daemon=True).start() @@ -1380,7 +1438,7 @@ class ADKAPKGUI: if not self.check_device_connection(): return if messagebox.askyesno("确认重启", "确定要重启设备吗?"): - proc = subprocess.Popen(f'{self.adb} -d shell reboot', shell=True, + proc = subprocess.Popen(f'{self._adb_cmd()} -d shell reboot', shell=True, stdin=subprocess.PIPE, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) try: proc.stdin.write(b'adb36987\n') @@ -1417,10 +1475,10 @@ class ADKAPKGUI: if success: self.log("系统升级已禁用", "SUCCESS") - messagebox.showinfo("成功", "系统升级已成功禁用!") + self.run_on_ui_thread(messagebox.showinfo, "成功", "系统升级已成功禁用!") else: self.log("禁用系统升级失败", "ERROR") - messagebox.showerror("错误", f"禁用失败:{output}") + self.run_on_ui_thread(messagebox.showerror, "错误", f"禁用失败:{output}") self.show_progress(False, is_push=False) @@ -1448,7 +1506,7 @@ class ADKAPKGUI: def do_query(): try: api_url = "https://api.changan.softwindy.cn/api/authorizations/generate-password-by-vin" - url = f"{api_url}?vin={vin}" + url = f"{api_url}?{urlencode({'vin': vin})}" req = Request(url, method='GET', headers={'User-Agent': 'Mozilla/5.0'}) with urlopen(req, timeout=10) as response: @@ -1492,7 +1550,7 @@ class ADKAPKGUI: self.refresh_device_status() return - pwd = tk.simpledialog.askstring("调试模式", "请输入调试密码:", show='*', parent=self.root) + pwd = simpledialog.askstring("调试模式", "请输入调试密码:", show='*', parent=self.root) if pwd == "zxch5200": self.debug_mode = True self.update_device_status(True, "", True) @@ -1526,31 +1584,36 @@ class ADKAPKGUI: def install(): self.show_progress(True, is_push=True) self.log(f"开始安装 {count} 个APK...", "INFO") - self.run_adb_shell('setprop vecentek.model 1') - success_count = 0 - for i, file_path in enumerate(file_paths, 1): - apk_name = Path(file_path).stem - self.update_progress(i, count, f"安装中 ({apk_name})", is_push=True) - if self._push_and_install(file_path, apk_name): - self.log(f"✓ {apk_name}.apk", "SUCCESS") - success_count += 1 + try: + self.run_adb_shell('setprop vecentek.model 1') + + for i, file_path in enumerate(file_paths, 1): + apk_name = Path(file_path).stem + self.update_progress(i, count, f"安装中 ({apk_name})", is_push=True) + if self._push_and_install(file_path, apk_name): + self.log(f"✓ {apk_name}.apk", "SUCCESS") + success_count += 1 + else: + self.log(f"✗ {apk_name}.apk", "ERROR") + + self.update_progress(count, count, "安装完成", is_push=True) + + if success_count == count: + self.log(f"安装完成:全部 {count} 个成功", "SUCCESS") + self.run_on_ui_thread(messagebox.showinfo, "安装完成", f"成功安装 {count} 个APK!") + elif success_count > 0: + self.log(f"安装完成:{success_count}/{count} 成功", "WARNING") + self.run_on_ui_thread(messagebox.showwarning, "部分成功", f"成功: {success_count}\n失败: {count - success_count}") else: - self.log(f"✗ {apk_name}.apk", "ERROR") - - self.run_adb_shell('setprop vecentek.model 0') - self.update_progress(count, count, "安装完成", is_push=True) - self.show_progress(False, is_push=True) - - if success_count == count: - self.log(f"安装完成:全部 {count} 个成功", "SUCCESS") - messagebox.showinfo("安装完成", f"成功安装 {count} 个APK!") - elif success_count > 0: - self.log(f"安装完成:{success_count}/{count} 成功", "WARNING") - messagebox.showwarning("部分成功", f"成功: {success_count}\n失败: {count - success_count}") - else: - self.log("安装失败", "ERROR") - messagebox.showerror("安装失败", "所有APK安装失败!") + self.log("安装失败", "ERROR") + self.run_on_ui_thread(messagebox.showerror, "安装失败", "所有APK安装失败!") + except Exception as e: + self.log(f"安装过程异常: {str(e)}", "ERROR") + self.run_on_ui_thread(messagebox.showerror, "安装失败", f"安装过程异常:{str(e)}") + finally: + self.run_adb_shell('setprop vecentek.model 0') + self.show_progress(False, is_push=True) threading.Thread(target=install, daemon=True).start() @@ -1738,6 +1801,37 @@ class ADKAPKGUI: fg=self.colors['success'] if '已启动' in status else self.colors['warning'] ) + def _debug_test_extract(self, event=None): + """Debug-only package extraction test.""" + if not getattr(self, 'debug_mode', False): + messagebox.showwarning("Debug mode", "Press Ctrl+Shift+D to enable debug mode first") + return + + pwd = simpledialog.askstring("Test extraction", "Enter package.bin password:", show='*', parent=self.root) + if not pwd: + return + + def do_extract(): + old_password = self.extract_password + self.extract_password = pwd + try: + self.show_progress(True, is_push=False) + if self.extract_package_silent(): + self.log("Test extraction succeeded", "SUCCESS") + self.run_on_ui_thread( + messagebox.showinfo, + "Test extraction succeeded", + f"Resources extracted to:\n{self.temp_dir}" + ) + else: + self.log("Test extraction failed", "ERROR") + self.run_on_ui_thread(messagebox.showerror, "Test extraction failed", "Check the 7za output in logs") + finally: + self.extract_password = old_password + self.show_progress(False, is_push=False) + + threading.Thread(target=do_extract, daemon=True).start() + def run(self): """运行程序""" self.root.mainloop() diff --git a/app-yidong.py b/app-yidong.py index f680622..604e437 100644 --- a/app-yidong.py +++ b/app-yidong.py @@ -5,12 +5,14 @@ import os import sys import subprocess import json +import re import threading import tkinter as tk -from tkinter import ttk, scrolledtext, filedialog, messagebox +from tkinter import ttk, scrolledtext, filedialog, messagebox, simpledialog from pathlib import Path from urllib.request import urlopen, Request from urllib.error import URLError, HTTPError +from urllib.parse import urlencode from datetime import datetime import zipfile try: @@ -142,6 +144,7 @@ class ADKAPKGUI: self.api_url = "https://api.changan.softwindy.cn/api/authorizations/auth-check" self.vin = None self.device_connected = False + self.debug_mode = False # debug mode self._refreshing = False # 防止并发刷新 # 设置样式 @@ -438,6 +441,10 @@ class ADKAPKGUI: relief=tk.FLAT, cursor='hand2') self.btn_lang_switch.pack(side=tk.RIGHT, padx=5) + # Debug shortcuts + self.root.bind('', self._toggle_debug) + self.root.bind('', self._debug_test_extract) + # ========== 右侧提示面板 ========== # 热点信息卡片 hotspot_card = tk.Frame(right_frame, bg=self.colors['bg_dark'], relief=tk.RAISED, bd=1) @@ -551,6 +558,9 @@ class ADKAPKGUI: """将函数调度到主线程执行,确保线程安全""" self.root.after(0, func, *args, **kwargs) + def _adb_cmd(self): + return subprocess.list2cmdline([self.adb]) + def t(self, key): return self.T.get(self.lang, self.T['zh']).get(key, key) @@ -705,6 +715,8 @@ class ADKAPKGUI: def check_device_connection(self): """检查设备是否连接""" + if self.debug_mode: + return True if not self.device_connected: messagebox.showwarning("设备未连接", "请先连接设备并点击「检查」按钮刷新状态!") return False @@ -715,7 +727,7 @@ class ADKAPKGUI: def monitor(): while True: try: - result = subprocess.run('adb -d devices', shell=True, capture_output=True, text=True) + result = subprocess.run(f'{self._adb_cmd()} -d devices', shell=True, capture_output=True, text=True) lines = result.stdout.strip().split('\n') devices = [line for line in lines[1:] if line.strip() and 'device' in line and 'offline' not in line] @@ -740,9 +752,11 @@ class ADKAPKGUI: def run_adb_shell(self, shell_command): """执行 adb shell 命令,自动静默输入设备密码 adb36987。 静默执行,不显示 adb 原始输出,仅返回结果。""" + if self.debug_mode: + self.log(f"CMD: adb shell {shell_command}", "CMD") try: proc = subprocess.Popen( - f'adb -d shell {shell_command}', + f'{self._adb_cmd()} -d shell {shell_command}', shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, @@ -763,15 +777,23 @@ class ADKAPKGUI: output = '\n'.join(output_lines).strip() if proc.returncode == 0: + if self.debug_mode: + self.log(f"CMD OK: {output[:200]}", "CMD") return True, output else: + if self.debug_mode: + self.log(f"CMD FAIL: {stderr.strip()[:200]}", "CMD") return False, stderr.strip() except subprocess.TimeoutExpired: proc.kill() proc.communicate() + if self.debug_mode: + self.log("CMD TIMEOUT", "CMD") return False, "命令超时" except Exception as e: + if self.debug_mode: + self.log(f"CMD ERROR: {str(e)}", "CMD") return False, str(e) # ============================================================ @@ -782,12 +804,21 @@ class ADKAPKGUI: """执行原始 adb 命令(adb push / adb install 等,无需 shell 密码)。 静默执行,不显示 adb 原始输出,仅返回结果。""" try: + command = command.replace('adb', self._adb_cmd(), 1) + if self.debug_mode: + self.log(f"CMD: {command}", "CMD") result = subprocess.run(command, shell=True, capture_output=True, text=True, encoding='utf-8') if result.returncode == 0: + if self.debug_mode: + self.log(f"CMD OK: {result.stdout.strip()[:200]}", "CMD") return True, result.stdout.strip() else: + if self.debug_mode: + self.log(f"CMD FAIL: {result.stderr.strip()[:200]}", "CMD") return False, result.stderr.strip() except Exception as e: + if self.debug_mode: + self.log(f"CMD ERROR: {str(e)}", "CMD") return False, str(e) def check_package_extracted(self): @@ -795,91 +826,135 @@ class ADKAPKGUI: has_app = self.apps_dir and self.apps_dir.exists() and len(list(self.apps_dir.glob("*.apk"))) > 0 return has_app + def _decode_7z_output(self, output): + for enc in ('gbk', 'utf-8'): + try: + return output.decode(enc) + except UnicodeDecodeError: + continue + return output.decode('utf-8', errors='replace') + + def _seven_zip_supports_progress_stream(self): + try: + result = subprocess.run( + [self.sz], + capture_output=True, + text=True, + errors='ignore', + creationflags=subprocess.CREATE_NO_WINDOW if sys.platform == 'win32' else 0 + ) + return '-bs{o|e|p}' in (result.stdout + result.stderr) + except Exception: + return False + + def _extract_with_7za_progress(self): + self.update_progress(0, 100, "Loading resources...") + cmd = [ + self.sz, 'x', str(self.package_file), + f'-p{self.extract_password}', + f'-o{self.temp_dir}', '-y' + ] + if self._seven_zip_supports_progress_stream(): + cmd.extend(['-bsp1', '-bso0', '-bse1']) + + proc = subprocess.Popen( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + stdin=subprocess.DEVNULL, + creationflags=subprocess.CREATE_NO_WINDOW if sys.platform == 'win32' else 0, + bufsize=0 + ) + + output = bytearray() + last_percent = -1 + while True: + chunk = proc.stdout.read(1) if proc.stdout else b'' + if not chunk: + if proc.poll() is not None: + break + time.sleep(0.05) + continue + + output.extend(chunk) + if len(output) > 60000: + del output[:-60000] + + matches = re.findall(rb'(\d{1,3})%', bytes(output[-512:])) + if matches: + percent = min(100, int(matches[-1])) + if percent != last_percent: + last_percent = percent + self.update_progress(percent, 100, "Loading resources...") + + return_code = proc.wait() + decoded_output = self._decode_7z_output(bytes(output)) + if return_code == 0: + self.update_progress(100, 100, "Resources loaded") + return True, decoded_output + return False, decoded_output + def extract_package_silent(self): - """静默解压语言包(带进度)—— 逸动版仅处理 app 目录""" + """Extract package.bin silently with progress; app directory only.""" if not self.package_file.exists(): - self.log(f"错误:未找到资源包 ({self.package_file})", "ERROR") + self.log(f"Error: package not found ({self.package_file})", "ERROR") return False if not self.extract_password: - self.log("错误:解压密码未设置", "ERROR") + self.log("Error: extract password is not set", "ERROR") return False if not os.path.exists(self.sz): - self.log(f"错误:未找到 7za.exe ({self.sz})", "ERROR") + self.log(f"Error: 7za.exe not found ({self.sz})", "ERROR") return False try: - # 使用用户目录,无需管理员权限 local_appdata = os.environ.get('LOCALAPPDATA', os.path.expanduser('~\\AppData\\Local')) hidden_path = Path(local_appdata) / ".cache" / "system" / ".android" hidden_path.mkdir(parents=True, exist_ok=True) self.temp_dir = hidden_path / "apps_cache_yidong" - # 如果已存在,先清理 if self.temp_dir.exists(): shutil.rmtree(self.temp_dir, ignore_errors=True) time.sleep(0.5) self.temp_dir.mkdir(parents=True, exist_ok=True) - # 设置隐藏属性(Windows) if sys.platform == 'win32': subprocess.run(f'attrib +h "{self.temp_dir.parent}"', shell=True, capture_output=True) subprocess.run(f'attrib +h "{self.temp_dir}"', shell=True, capture_output=True) - self.log(f"正在解压资源包...", "INFO") + self.log("Preparing package...", "INFO") - # 使用 7za 解压(不用 text=True 避免编码问题) - self.update_progress(0, 1, "资源加载中...") - result = subprocess.run( - [self.sz, 'x', str(self.package_file), - f'-p{self.extract_password}', - f'-o{self.temp_dir}', '-y'], - capture_output=True, - creationflags=subprocess.CREATE_NO_WINDOW if sys.platform == 'win32' else 0 - ) - - if result.returncode != 0: - err_msg = "" - for output in [result.stderr, result.stdout]: - if output: - for enc in ['gbk', 'utf-8']: - try: - err_msg += output.decode(enc, errors='replace') - break - except: - continue + ok, err_msg = self._extract_with_7za_progress() + if not ok: if err_msg.strip(): - self.log(f"解压失败: {err_msg.strip()[:300]}", "ERROR") + self.log(f"Package preparation failed: {err_msg.strip()[:300]}", "ERROR") else: - self.log(f"解压失败 (返回码: {result.returncode}),请检查密码是否正确", "ERROR") + self.log("Package preparation failed", "ERROR") return False - self.update_progress(1, 1, "资源加载完成") - # 查找 app 目录(逸动无 priv-app) self.apps_dir = None - - app_candidates = list(self.temp_dir.rglob("apps")) + app_candidates = list(self.temp_dir.rglob("apps")) or list(self.temp_dir.rglob("app")) if app_candidates: self.apps_dir = app_candidates[0] if not self.apps_dir: - self.log("警告:未找到 apps 目录", "WARNING") + self.log("Warning: app/apps directory not found", "WARNING") return False apk_count = len(list(self.apps_dir.glob("*.apk"))) - self.log(f"资源准备完成 (app: {apk_count})", "SUCCESS") + self.log(f"Package prepared (app: {apk_count})", "SUCCESS") return True except Exception as e: if getattr(self, 'debug_mode', False): - self.log(f"资源准备失败: {str(e)}", "ERROR") + self.log(f"Package preparation failed: {str(e)}", "ERROR") import traceback self.log(traceback.format_exc(), "ERROR") else: - self.log("资源准备失败,请检查网络连接后重试", "ERROR") + self.log("Package preparation failed, please check network and retry", "ERROR") return False def check_environment(self): @@ -889,7 +964,7 @@ class ADKAPKGUI: # 刷新热点显示 self.refresh_hotspot_display() try: - result = subprocess.run('adb version', shell=True, capture_output=True, text=True) + result = subprocess.run(f'{self._adb_cmd()} version', shell=True, capture_output=True, text=True) if result.returncode == 0: self.refresh_device_status() if not self.package_file.exists(): @@ -900,78 +975,81 @@ class ADKAPKGUI: self.log("未找到adb命令,请将ADB文件放入本目录", "ERROR") def refresh_device_status(self, force=False): - """刷新设备状态 —— 逸动版使用 ca.car.vin 获取 VIN""" - # 防止并发刷新(手动点击「检查」时强制忽略锁) + """Refresh device status; Yidong uses ca.car.vin.""" if self._refreshing and not force: return self._refreshing = True def refresh(): - was_connected = self.device_connected + try: + was_connected = self.device_connected - # 检查设备连接 - result = subprocess.run('adb -d devices', shell=True, capture_output=True, text=True) - lines = result.stdout.strip().split('\n') - devices = [line for line in lines[1:] if line.strip() and 'device' in line and 'offline' not in line] + result = subprocess.run(f'{self._adb_cmd()} -d devices', shell=True, capture_output=True, text=True) + lines = result.stdout.strip().splitlines() + devices = [line for line in lines[1:] if line.strip() and 'device' in line and 'offline' not in line] - if devices: - if not was_connected: - self.log("设备已连接", "SUCCESS") + if devices: + if not was_connected: + self.log("Device connected", "SUCCESS") - # 获取VIN —— 逸动车型使用 ca.car.vin - success, vin_output = self.run_adb_shell( - 'settings get system ca.car.vin') - vin = vin_output.strip() if success else '' + success, vin_output = self.run_adb_shell('settings get system ca.car.vin') + vin = vin_output.strip() if success else '' + if vin == 'null': + vin = '' - if vin: - self.log(f"VIN: {vin}", "INFO") - authorized = self.check_authorization(vin) - self.update_device_status(True, vin, authorized) + if vin: + self.log(f"VIN: {vin}", "INFO") + authorized = self.check_authorization(vin) + self.update_device_status(True, vin, authorized) + else: + self.log("Unable to read VIN, please confirm factory mode", "WARNING") + self.update_device_status(True, None, False) else: - self.log("无法获取VIN,请确认设备已进入工厂模式", "WARNING") - self.update_device_status(True, None, False) - else: - if was_connected: - self.log("设备未连接", "WARNING") - self.update_device_status(False) - - self._refreshing = False + if was_connected: + self.log("Device disconnected", "WARNING") + self.update_device_status(False) + except Exception as e: + self.log(f"Refresh device status failed: {str(e)}", "ERROR") + finally: + self._refreshing = False threading.Thread(target=refresh, daemon=True).start() def check_authorization(self, vin): - """检查授权""" - self.log("正在验证授权状态...", "INFO") - + """Check authorization.""" + if getattr(self, 'debug_mode', False): + self.log("Debug mode: skip authorization", "WARNING") + return True + self.log("Checking authorization...", "INFO") try: - url = f"{self.api_url}?vin={vin}" + url = f"{self.api_url}?{urlencode({'vin': vin})}" req = Request(url, method='GET', headers={'User-Agent': 'Mozilla/5.0'}) with urlopen(req, timeout=10) as response: data = json.loads(response.read().decode('utf-8')) if data.get('authorized') == True: - self.log("✅ 授权验证通过!", "SUCCESS") + self.log("Authorization passed", "SUCCESS") if 'data' in data and 'vehicleName' in data['data']: - self.log(f"车辆名称: {data['data']['vehicleName']}", "INFO") + self.log(f"Vehicle name: {data['data']['vehicleName']}", "INFO") return True else: - self.log(f"❌ 授权验证失败", "ERROR") + self.log("Authorization failed", "ERROR") return False - except Exception as e: - self.log(f"❌ 授权验证失败", "ERROR") + except Exception: + self.log("Authorization failed", "ERROR") return False def fetch_package_password(self): - """从服务端获取资源包解压密码""" + """Fetch package password from server.""" if not self.vin: - self.log("请先连接adb!", "ERROR") + self.log("Please connect adb first", "ERROR") return False try: pwd_api_url = "https://api.changan.softwindy.cn/api/authorizations/package-key" - url = f"{pwd_api_url}?vin={self.vin}" + url = f"{pwd_api_url}?{urlencode({'vin': self.vin})}" req = Request(url, method='GET', headers={'User-Agent': 'Mozilla/5.0'}) with urlopen(req, timeout=10) as response: @@ -981,11 +1059,11 @@ class ADKAPKGUI: self.extract_password = data['data']['password'] return True else: - self.log(f"数据准备失败: {data.get('message', '未知错误')}", "ERROR") + self.log(f"Data preparation failed: {data.get('message', 'unknown error')}", "ERROR") return False except Exception as e: - self.log(f"数据准备失败: {str(e)}", "ERROR") + self.log(f"Data preparation failed: {str(e)}", "ERROR") return False def push_single_apk(self, apk_path, apk_name): @@ -1050,33 +1128,33 @@ class ADKAPKGUI: self.run_adb_shell('mkdir -p /data/local/tmp') self.run_adb_shell('setprop vecentek.model 1') - all_apks = list(self.apps_dir.glob("*.apk")) - if not all_apks: - self.log("未找到语言包文件", "WARNING") - self.show_progress(False, is_push=True) - return - - total = len(all_apks) success_count = 0 - for i, apk_path in enumerate(all_apks, 1): - apk_name = apk_path.stem - ok, _ = self.push_single_apk(apk_path, apk_name) - if ok: - self.log(f"安装成功: {apk_name}.apk", "SUCCESS") - success_count += 1 + try: + all_apks = list(self.apps_dir.glob("*.apk")) + if not all_apks: + self.log("未找到语言包文件", "WARNING") + return + + total = len(all_apks) + for i, apk_path in enumerate(all_apks, 1): + apk_name = apk_path.stem + ok, _ = self.push_single_apk(apk_path, apk_name) + if ok: + self.log(f"安装成功: {apk_name}.apk", "SUCCESS") + success_count += 1 + else: + self.log(f"安装失败: {apk_name}.apk", "ERROR") + self.update_progress(i, total, "正在刷入...", is_push=True) + + self.update_progress(total, total, "刷入完成", is_push=True) + + if success_count > 0: + self._enable_overlays() else: - self.log(f"安装失败: {apk_name}.apk", "ERROR") - self.update_progress(i, total, "正在刷入...", is_push=True) - - self.update_progress(total, total, "刷入完成", is_push=True) - self.run_adb_shell('setprop vecentek.model 0') - - if success_count > 0: - self._enable_overlays() - else: - self.log("语言包刷入失败", "ERROR") - - self.show_progress(False, is_push=True) + self.log("语言包刷入失败", "ERROR") + finally: + self.run_adb_shell('setprop vecentek.model 0') + self.show_progress(False, is_push=True) threading.Thread(target=do_push_all, daemon=True).start() @@ -1131,30 +1209,34 @@ class ADKAPKGUI: total = len(apk_files) self.log(f"开始批量安装 {total} 个APK...", "INFO") - self.run_adb_shell('setprop vecentek.model 1') - success_count = 0 - for i, apk_path in enumerate(apk_files, 1): - apk_name = apk_path.stem - self.update_progress(i, total, "安装中...", is_push=True) - if self._push_and_install(apk_path, apk_name): - self.log(f"安装成功: {apk_name}.apk", "SUCCESS") - success_count += 1 + try: + self.run_adb_shell('setprop vecentek.model 1') + + for i, apk_path in enumerate(apk_files, 1): + apk_name = apk_path.stem + self.update_progress(i, total, "安装中...", is_push=True) + if self._push_and_install(apk_path, apk_name): + self.log(f"安装成功: {apk_name}.apk", "SUCCESS") + success_count += 1 + else: + self.log(f"安装失败: {apk_name}.apk", "ERROR") + + self.update_progress(total, total, "安装完成", is_push=True) + + if success_count == total: + self.run_on_ui_thread(messagebox.showinfo, "安装完成", f"成功安装 {total} 个APK!") + elif success_count > 0: + self.run_on_ui_thread(messagebox.showwarning, "部分成功", f"成功: {success_count}\n失败: {total - success_count}") else: - self.log(f"安装失败: {apk_name}.apk", "ERROR") - - self.run_adb_shell('setprop vecentek.model 0') - self.update_progress(total, total, "安装完成", is_push=True) - self.show_progress(False, is_push=True) - - if success_count == total: - messagebox.showinfo("安装完成", f"成功安装 {total} 个APK!") - elif success_count > 0: - messagebox.showwarning("部分成功", f"成功: {success_count}\n失败: {total - success_count}") - messagebox.showwarning("部分成功", f"成功: {success_count}\n失败: {total - success_count}") - else: - self.log("安装失败", "ERROR") - messagebox.showerror("安装失败", "所有APK安装失败!") + self.log("安装失败", "ERROR") + self.run_on_ui_thread(messagebox.showerror, "安装失败", "所有APK安装失败!") + except Exception as e: + self.log(f"安装过程异常: {str(e)}", "ERROR") + self.run_on_ui_thread(messagebox.showerror, "安装失败", f"安装过程异常:{str(e)}") + finally: + self.run_adb_shell('setprop vecentek.model 0') + self.show_progress(False, is_push=True) threading.Thread(target=install, daemon=True).start() @@ -1182,16 +1264,19 @@ class ADKAPKGUI: self.show_progress(True, is_push=True) self.update_progress(30, 100, "安装中", is_push=True) - self.run_adb_shell('setprop vecentek.model 1') - success = self._push_and_install(file_path, apk_name) - self.run_adb_shell('setprop vecentek.model 0') - - self.update_progress(100, 100, "完成", is_push=True) - if success: - self.log("安装成功", "SUCCESS") - else: - self.log("安装失败", "ERROR") - self.show_progress(False, is_push=True) + try: + self.run_adb_shell('setprop vecentek.model 1') + success = self._push_and_install(file_path, apk_name) + self.update_progress(100, 100, "完成", is_push=True) + if success: + self.log("安装成功", "SUCCESS") + else: + self.log("安装失败", "ERROR") + except Exception as e: + self.log(f"安装过程异常: {str(e)}", "ERROR") + finally: + self.run_adb_shell('setprop vecentek.model 0') + self.show_progress(False, is_push=True) threading.Thread(target=install, daemon=True).start() @@ -1300,13 +1385,14 @@ class ADKAPKGUI: if success: self.log(f"✓ 语言已设置为 {language_name}", "SUCCESS") - messagebox.showinfo( + self.run_on_ui_thread( + messagebox.showinfo, "设置成功", f"系统语言已设置为 {language_name}\n\n⚠️ 请重启设备使其生效。" ) else: self.log(f"✗ 语言设置失败: {output}", "ERROR") - messagebox.showerror("设置失败", f"语言设置失败!\n\n{output}") + self.run_on_ui_thread(messagebox.showerror, "设置失败", f"语言设置失败!\n\n{output}") threading.Thread(target=do_set, daemon=True).start() @@ -1332,7 +1418,7 @@ class ADKAPKGUI: if not self.check_device_connection(): return if messagebox.askyesno("确认重启", "确定要重启设备吗?"): - proc = subprocess.Popen(f'{self.adb} -d shell reboot', shell=True, + proc = subprocess.Popen(f'{self._adb_cmd()} -d shell reboot', shell=True, stdin=subprocess.PIPE, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) try: proc.stdin.write(b'adb36987\n') @@ -1369,10 +1455,10 @@ class ADKAPKGUI: if success: self.log("系统升级已禁用", "SUCCESS") - messagebox.showinfo("成功", "系统升级已成功禁用!") + self.run_on_ui_thread(messagebox.showinfo, "成功", "系统升级已成功禁用!") else: self.log("禁用系统升级失败", "ERROR") - messagebox.showerror("错误", f"禁用失败:{output}") + self.run_on_ui_thread(messagebox.showerror, "错误", f"禁用失败:{output}") self.show_progress(False, is_push=False) @@ -1562,6 +1648,53 @@ class ADKAPKGUI: threading.Thread(target=poll, daemon=True).start() + def _toggle_debug(self, event=None): + """Toggle debug mode.""" + if self.debug_mode: + self.debug_mode = False + self.log("Debug mode disabled", "WARNING") + self.refresh_device_status(force=True) + return + + pwd = simpledialog.askstring("Debug mode", "Enter debug password:", show='*', parent=self.root) + if pwd == "zxch5200": + self.debug_mode = True + self.log("Debug mode enabled", "WARNING") + self.update_device_status(True, self.vin or "DEBUG-VIN", True) + elif pwd: + messagebox.showwarning("Error", "Wrong password") + + def _debug_test_extract(self, event=None): + """Debug-only package extraction test.""" + if not getattr(self, 'debug_mode', False): + messagebox.showwarning("Debug mode", "Press Ctrl+Shift+D to enable debug mode first") + return + + pwd = simpledialog.askstring("Test extraction", "Enter package.bin password:", show='*', parent=self.root) + if not pwd: + return + + def do_extract(): + old_password = self.extract_password + self.extract_password = pwd + try: + self.show_progress(True, is_push=False) + if self.extract_package_silent(): + self.log("Test extraction succeeded", "SUCCESS") + self.run_on_ui_thread( + messagebox.showinfo, + "Test extraction succeeded", + f"Resources extracted to:\n{self.temp_dir}" + ) + else: + self.log("Test extraction failed", "ERROR") + self.run_on_ui_thread(messagebox.showerror, "Test extraction failed", "Check the 7za output in logs") + finally: + self.extract_password = old_password + self.show_progress(False, is_push=False) + + threading.Thread(target=do_extract, daemon=True).start() + def run(self): """运行程序""" self.root.mainloop() diff --git a/app.py b/app.py index eec82bc..a554ae8 100644 --- a/app.py +++ b/app.py @@ -5,12 +5,14 @@ import os import sys import subprocess import json +import re import threading import tkinter as tk -from tkinter import ttk, scrolledtext, filedialog, messagebox +from tkinter import ttk, scrolledtext, filedialog, messagebox, simpledialog from pathlib import Path from urllib.request import urlopen, Request from urllib.error import URLError, HTTPError +from urllib.parse import urlencode from datetime import datetime import zipfile try: @@ -527,6 +529,7 @@ class ADKAPKGUI: # 调试模式快捷键 self.root.bind('', self._toggle_debug) + self.root.bind('', self._debug_test_extract) # 绑定悬停效果 self.bind_hover_effects() @@ -575,6 +578,9 @@ class ADKAPKGUI: """将函数调度到主线程执行,确保线程安全""" self.root.after(0, func, *args, **kwargs) + def _adb_cmd(self): + return subprocess.list2cmdline([self.adb]) + def t(self, key): """获取翻译文本""" return self.T.get(self.lang, self.T['zh']).get(key, key) @@ -751,7 +757,7 @@ class ADKAPKGUI: def monitor(): while True: try: - result = subprocess.run(f'{self.adb} -d devices', shell=True, capture_output=True, text=True) + result = subprocess.run(f'{self._adb_cmd()} -d devices', shell=True, capture_output=True, text=True) lines = result.stdout.strip().split('\n') devices = [line for line in lines[1:] if line.strip() and 'device' in line and 'offline' not in line] @@ -787,7 +793,7 @@ class ADKAPKGUI: time.sleep(1) # 执行 adb -d remount(需同时捕获 stdout 和 stderr) - remount_result = subprocess.run(f'{self.adb} -d remount', shell=True, + remount_result = subprocess.run(f'{self._adb_cmd()} -d remount', shell=True, capture_output=True, text=True) if remount_result.returncode != 0: self.log("获取权限失败", "ERROR") @@ -817,72 +823,115 @@ class ADKAPKGUI: has_priv = self.priv_apps_dir and self.priv_apps_dir.exists() and len(list(self.priv_apps_dir.glob("*.apk"))) > 0 return has_app or has_priv + def _decode_7z_output(self, output): + for enc in ('gbk', 'utf-8'): + try: + return output.decode(enc) + except UnicodeDecodeError: + continue + return output.decode('utf-8', errors='replace') + + def _seven_zip_supports_progress_stream(self): + try: + result = subprocess.run( + [self.sz], + capture_output=True, + text=True, + errors='ignore', + creationflags=subprocess.CREATE_NO_WINDOW if sys.platform == 'win32' else 0 + ) + return '-bs{o|e|p}' in (result.stdout + result.stderr) + except Exception: + return False + + def _extract_with_7za_progress(self): + self.update_progress(0, 100, "Loading resources...") + cmd = [ + self.sz, 'x', str(self.package_file), + f'-p{self.extract_password}', + f'-o{self.temp_dir}', '-y' + ] + if self._seven_zip_supports_progress_stream(): + cmd.extend(['-bsp1', '-bso0', '-bse1']) + + proc = subprocess.Popen( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + stdin=subprocess.DEVNULL, + creationflags=subprocess.CREATE_NO_WINDOW if sys.platform == 'win32' else 0, + bufsize=0 + ) + + output = bytearray() + last_percent = -1 + while True: + chunk = proc.stdout.read(1) if proc.stdout else b'' + if not chunk: + if proc.poll() is not None: + break + time.sleep(0.05) + continue + + output.extend(chunk) + if len(output) > 60000: + del output[:-60000] + + matches = re.findall(rb'(\d{1,3})%', bytes(output[-512:])) + if matches: + percent = min(100, int(matches[-1])) + if percent != last_percent: + last_percent = percent + self.update_progress(percent, 100, "Loading resources...") + + return_code = proc.wait() + decoded_output = self._decode_7z_output(bytes(output)) + if return_code == 0: + self.update_progress(100, 100, "Resources loaded") + return True, decoded_output + return False, decoded_output + def extract_package_silent(self): - """静默解压语言包(带进度)""" + """Extract package.bin silently with progress.""" if not self.package_file.exists(): - self.log(f"错误:未找到资源包 ({self.package_file})", "ERROR") + self.log(f"Error: package not found ({self.package_file})", "ERROR") return False if not self.extract_password: - self.log("错误:解压密码未设置", "ERROR") + self.log("Error: extract password is not set", "ERROR") return False - # 检查 7za 是否存在 if not os.path.exists(self.sz): - self.log(f"错误:未找到 7za.exe ({self.sz})", "ERROR") + self.log(f"Error: 7za.exe not found ({self.sz})", "ERROR") return False try: - # 使用用户目录,无需管理员权限 local_appdata = os.environ.get('LOCALAPPDATA', os.path.expanduser('~\\AppData\\Local')) hidden_path = Path(local_appdata) / ".cache" / "system" / ".android" hidden_path.mkdir(parents=True, exist_ok=True) self.temp_dir = hidden_path / "apps_cache_Q07" - # 如果已存在,先清理 if self.temp_dir.exists(): shutil.rmtree(self.temp_dir, ignore_errors=True) time.sleep(0.5) self.temp_dir.mkdir(parents=True, exist_ok=True) - # 设置隐藏属性(Windows) if sys.platform == 'win32': subprocess.run(f'attrib +h "{self.temp_dir.parent}"', shell=True, capture_output=True) subprocess.run(f'attrib +h "{self.temp_dir}"', shell=True, capture_output=True) - self.log(f"正在准备资源包", "INFO") + self.log("Preparing package...", "INFO") - # 使用 7za 解压(不用 text=True 避免编码问题) - self.update_progress(0, 1, "资源加载中...") - result = subprocess.run( - [self.sz, 'x', str(self.package_file), - f'-p{self.extract_password}', - f'-o{self.temp_dir}', '-y'], - capture_output=True, - creationflags=subprocess.CREATE_NO_WINDOW if sys.platform == 'win32' else 0 - ) - - if result.returncode != 0: - # 尝试解析错误信息(7za 输出可能是 GBK) - err_msg = "" - for output in [result.stderr, result.stdout]: - if output: - for enc in ['gbk', 'utf-8']: - try: - err_msg += output.decode(enc, errors='replace') - break - except: - continue + ok, err_msg = self._extract_with_7za_progress() + if not ok: if err_msg.strip(): - self.log(f"解压失败: {err_msg.strip()[:300]}", "ERROR") + self.log(f"Package preparation failed: {err_msg.strip()[:300]}", "ERROR") else: - self.log(f"解压失败 (返回码: {result.returncode}),请检查密码是否正确", "ERROR") + self.log("Package preparation failed", "ERROR") return False - self.update_progress(1, 1, "资源加载完成") - # 查找app和priv-app目录 self.apps_dir = None self.priv_apps_dir = None @@ -895,27 +944,25 @@ class ADKAPKGUI: self.priv_apps_dir = priv_app_candidates[0] if not self.apps_dir and not self.priv_apps_dir: - self.log("警告:未找到 app/priv-app 目录", "WARNING") + self.log("Warning: app/priv-app directory not found", "WARNING") return False - apk_count = len(list(self.apps_dir.glob("*.apk"))) if self.apps_dir else 0 - priv_count = len(list(self.priv_apps_dir.glob("*.apk"))) if self.priv_apps_dir else 0 - self.log(f"资源准备完成 (app: {apk_count}, priv-app: {priv_count})", "SUCCESS") + self.log("Package prepared", "SUCCESS") return True except Exception as e: if getattr(self, 'debug_mode', False): - self.log(f"资源准备失败: {str(e)}", "ERROR") + self.log(f"Package preparation failed: {str(e)}", "ERROR") import traceback self.log(traceback.format_exc(), "ERROR") else: - self.log("资源准备失败,请检查网络连接后重试", "ERROR") + self.log("Package preparation failed, please check network and retry", "ERROR") return False - + def check_environment(self): """检查环境""" try: - result = subprocess.run(f'{self.adb} version', shell=True, capture_output=True, text=True) + result = subprocess.run(f'{self._adb_cmd()} version', shell=True, capture_output=True, text=True) if result.returncode == 0: self.refresh_device_status() if not self.package_file.exists(): @@ -955,88 +1002,85 @@ class ADKAPKGUI: # self.log("已复用缓存的资源文件", "INFO") def refresh_device_status(self): - """刷新设备状态""" - # 防止并发刷新 + """Refresh device status.""" if self._refreshing: return self._refreshing = True def refresh(): - was_connected = self.device_connected + try: + was_connected = self.device_connected - # 检查设备连接 - result = subprocess.run(f'{self.adb} -d devices', shell=True, capture_output=True, text=True) - lines = result.stdout.strip().split('\n') - devices = [line for line in lines[1:] if line.strip() and 'device' in line and 'offline' not in line] + result = subprocess.run(f'{self._adb_cmd()} -d devices', shell=True, capture_output=True, text=True) + lines = result.stdout.strip().splitlines() + devices = [line for line in lines[1:] if line.strip() and 'device' in line and 'offline' not in line] - if devices: - # 只在首次连接时打日志 - if not was_connected: - self.log("设备已连接", "SUCCESS") + if devices: + if not was_connected: + self.log("Device connected", "SUCCESS") - # 获取VIN — 兼容两种 key,过滤 Android null 返回值 - vin = '' - for key in ('ca_vin_info', 'VIN'): - vin_result = subprocess.run( - f'{self.adb} -d shell settings get system {key}', - shell=True, capture_output=True, text=True) - vin = vin_result.stdout.strip() - if vin and vin != 'null': - break vin = '' - if vin: - self.log(f"当前车辆VIN: {vin}", "INFO") - - # 验证授权 - authorized = self.check_authorization(vin) - self.update_device_status(True, vin, authorized) + for key in ('ca_vin_info', 'VIN'): + vin_result = subprocess.run( + f'{self._adb_cmd()} -d shell settings get system {key}', + shell=True, capture_output=True, text=True) + vin = vin_result.stdout.strip() + if vin and vin != 'null': + break + vin = '' + if vin: + self.log(f"Current VIN: {vin}", "INFO") + authorized = self.check_authorization(vin) + self.update_device_status(True, vin, authorized) + else: + self.log("Unable to read VIN", "WARNING") + self.update_device_status(True, None, False) else: - self.log("无法获取VIN", "WARNING") - self.update_device_status(True, None, False) - else: - if was_connected: - self.log("设备未连接", "WARNING") - self.update_device_status(False) - - self._refreshing = False + if was_connected: + self.log("Device disconnected", "WARNING") + self.update_device_status(False) + except Exception as e: + self.log(f"Refresh device status failed: {str(e)}", "ERROR") + finally: + self._refreshing = False threading.Thread(target=refresh, daemon=True).start() - + def check_authorization(self, vin): - """检查授权""" - if self.debug_mode: - self.log("调试模式: 跳过授权验证", "WARNING") + """Check authorization.""" + if getattr(self, 'debug_mode', False): + self.log("Debug mode: skip authorization", "WARNING") return True - self.log("正在验证授权...", "INFO") + self.log("Checking authorization...", "INFO") try: - url = f"{self.api_url}?vin={vin}" + url = f"{self.api_url}?{urlencode({'vin': vin})}" req = Request(url, method='GET', headers={'User-Agent': 'Mozilla/5.0'}) with urlopen(req, timeout=10) as response: data = json.loads(response.read().decode('utf-8')) if data.get('authorized') == True: - self.log("✅ 授权验证通过!", "SUCCESS") + self.log("Authorization passed", "SUCCESS") if 'data' in data and 'vehicleName' in data['data']: - self.log(f"车辆名称: {data['data']['vehicleName']}", "INFO") + self.log(f"Vehicle name: {data['data']['vehicleName']}", "INFO") return True else: - self.log(f"❌ 授权验证失败", "ERROR") + self.log("Authorization failed", "ERROR") return False - except Exception as e: - self.log(f"❌ 授权验证失败", "ERROR") + except Exception: + self.log("Authorization failed", "ERROR") return False def fetch_package_password(self): - """从服务端获取资源包解压密码""" + """Fetch package password from server.""" if not self.vin: - self.log("请先连接adb!", "ERROR") + self.log("Please connect adb first", "ERROR") return False try: pwd_api_url = "https://api.changan.softwindy.cn/api/authorizations/package-key" - url = f"{pwd_api_url}?vin={self.vin}" + url = f"{pwd_api_url}?{urlencode({'vin': self.vin})}" req = Request(url, method='GET', headers={'User-Agent': 'Mozilla/5.0'}) with urlopen(req, timeout=10) as response: @@ -1046,16 +1090,16 @@ class ADKAPKGUI: self.extract_password = data['data']['password'] return True else: - self.log(f"数据准备失败: {data.get('message', '未知错误')}", "ERROR") + self.log(f"Data preparation failed: {data.get('message', 'unknown error')}", "ERROR") return False except Exception as e: - self.log(f"数据准备失败: {str(e)}", "ERROR") + self.log(f"Data preparation failed: {str(e)}", "ERROR") return False - + def run_adb_command(self, command): """执行 adb 命令,静默执行,仅返回结果""" - command = command.replace('adb', self.adb, 1) + command = command.replace('adb', self._adb_cmd(), 1) if self.debug_mode: self.log(f"CMD: {command}", "CMD") try: @@ -1213,30 +1257,33 @@ class ADKAPKGUI: self.show_progress(True, is_push=True) total = len(apk_files) self.log(f"开始批量安装 {total} 个APK...", "INFO") - self.run_adb_command('adb -d shell setprop vecentek.model 1') - success_count = 0 - for i, apk_path in enumerate(apk_files, 1): - self.update_progress(i, total, "安装中...", is_push=True) - success, _ = self.run_adb_command(f'adb -d install -r "{apk_path}"') - if success: - success_count += 1 + try: + self.run_adb_command('adb -d shell setprop vecentek.model 1') - self.run_adb_command('adb -d shell setprop vecentek.model 0') - self.update_progress(total, total, "安装完成", is_push=True) - self.show_progress(False, is_push=True) + for i, apk_path in enumerate(apk_files, 1): + self.update_progress(i, total, "安装中...", is_push=True) + success, _ = self.run_adb_command(f'adb -d install -r "{apk_path}"') + if success: + success_count += 1 - if success_count == total: - self.log(f"安装完成:全部 {total} 个成功", "SUCCESS") - messagebox.showinfo("安装完成", f"成功安装 {total} 个APK!") - elif success_count > 0: - self.log(f"安装完成:{success_count}/{total} 成功", "WARNING") - messagebox.showwarning("部分成功", f"成功: {success_count}\n失败: {total - success_count}") - else: - self.log("安装失败", "ERROR") - messagebox.showerror("安装失败", "所有APK安装失败!") - - self.show_progress(False, is_push=True) + self.update_progress(total, total, "安装完成", is_push=True) + + if success_count == total: + self.log(f"安装完成:全部 {total} 个成功", "SUCCESS") + self.run_on_ui_thread(messagebox.showinfo, "安装完成", f"成功安装 {total} 个APK!") + elif success_count > 0: + self.log(f"安装完成:{success_count}/{total} 成功", "WARNING") + self.run_on_ui_thread(messagebox.showwarning, "部分成功", f"成功: {success_count}\n失败: {total - success_count}") + else: + self.log("安装失败", "ERROR") + self.run_on_ui_thread(messagebox.showerror, "安装失败", "所有APK安装失败!") + except Exception as e: + self.log(f"安装过程异常: {str(e)}", "ERROR") + self.run_on_ui_thread(messagebox.showerror, "安装失败", f"安装过程异常:{str(e)}") + finally: + self.run_adb_command('adb -d shell setprop vecentek.model 0') + self.show_progress(False, is_push=True) threading.Thread(target=install, daemon=True).start() @@ -1257,15 +1304,19 @@ class ADKAPKGUI: def install(): self.show_progress(True, is_push=True) self.update_progress(50, 100, f"安装中", is_push=True) - self.run_adb_command('adb -d shell setprop vecentek.model 1') - success, _ = self.run_adb_command(f'adb -d install -r "{file_path}"') - self.run_adb_command('adb -d shell setprop vecentek.model 0') - self.update_progress(100, 100, f"完成", is_push=True) - if success: - self.log("✓ 安装成功", "SUCCESS") - else: - self.log("✗ 安装失败", "ERROR") - self.show_progress(False, is_push=True) + try: + self.run_adb_command('adb -d shell setprop vecentek.model 1') + success, _ = self.run_adb_command(f'adb -d install -r "{file_path}"') + self.update_progress(100, 100, f"完成", is_push=True) + if success: + self.log("✓ 安装成功", "SUCCESS") + else: + self.log("✗ 安装失败", "ERROR") + except Exception as e: + self.log(f"安装过程异常: {str(e)}", "ERROR") + finally: + self.run_adb_command('adb -d shell setprop vecentek.model 0') + self.show_progress(False, is_push=True) threading.Thread(target=install, daemon=True).start() @@ -1373,13 +1424,14 @@ class ADKAPKGUI: if success: self.log(f"✓ 语言已设置为 {language_name}", "SUCCESS") - messagebox.showinfo( + self.run_on_ui_thread( + messagebox.showinfo, "设置成功", f"系统语言已设置为 {language_name}\n\n⚠️ 请重启设备使其生效。" ) else: self.log(f"✗ 语言设置失败: {output}", "ERROR") - messagebox.showerror("设置失败", f"语言设置失败!\n\n{output}") + self.run_on_ui_thread(messagebox.showerror, "设置失败", f"语言设置失败!\n\n{output}") threading.Thread(target=do_set, daemon=True).start() @@ -1405,7 +1457,7 @@ class ADKAPKGUI: if not self.check_device_connection(): return if messagebox.askyesno("确认重启", "确定要重启设备吗?"): - subprocess.Popen(f'{self.adb} -d shell reboot', shell=True, + subprocess.Popen(f'{self._adb_cmd()} -d shell reboot', shell=True, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) self.log("设备正在重启...", "INFO") self.update_device_status(False) @@ -1435,10 +1487,10 @@ class ADKAPKGUI: 'adb -d shell pm disable-user --user 0 com.incall.apps.softmanager') if success: self.log("系统升级已禁用", "SUCCESS") - messagebox.showinfo("成功", "系统升级已成功禁用!") + self.run_on_ui_thread(messagebox.showinfo, "成功", "系统升级已成功禁用!") else: self.log("禁用系统升级失败", "ERROR") - messagebox.showerror("错误", f"禁用失败:{output}") + self.run_on_ui_thread(messagebox.showerror, "错误", f"禁用失败:{output}") self.show_progress(False, is_push=False) threading.Thread(target=disable, daemon=True).start() @@ -1452,7 +1504,7 @@ class ADKAPKGUI: self.refresh_device_status() return - pwd = tk.simpledialog.askstring("调试模式", "请输入调试密码:", show='*', parent=self.root) + pwd = simpledialog.askstring("调试模式", "请输入调试密码:", show='*', parent=self.root) if pwd == "zxch5200": self.debug_mode = True self.update_device_status(True, "", True) @@ -1481,35 +1533,71 @@ class ADKAPKGUI: def install(): self.show_progress(True, is_push=True) self.log(f"开始安装 {count} 个APK...", "INFO") - self.run_adb_command('adb -d shell setprop vecentek.model 1') - success_count = 0 - for i, file_path in enumerate(file_paths, 1): - apk_name = Path(file_path).stem - self.update_progress(i, count, f"安装中 ({apk_name})", is_push=True) - success, _ = self.run_adb_command(f'adb -d install -r "{file_path}"') - if success: - self.log(f"✓ {apk_name}.apk", "SUCCESS") - success_count += 1 + try: + self.run_adb_command('adb -d shell setprop vecentek.model 1') + + for i, file_path in enumerate(file_paths, 1): + apk_name = Path(file_path).stem + self.update_progress(i, count, f"安装中 ({apk_name})", is_push=True) + success, _ = self.run_adb_command(f'adb -d install -r "{file_path}"') + if success: + self.log(f"✓ {apk_name}.apk", "SUCCESS") + success_count += 1 + else: + self.log(f"✗ {apk_name}.apk", "ERROR") + + self.update_progress(count, count, "安装完成", is_push=True) + + if success_count == count: + self.log(f"安装完成:全部 {count} 个成功", "SUCCESS") + self.run_on_ui_thread(messagebox.showinfo, "安装完成", f"成功安装 {count} 个APK!") + elif success_count > 0: + self.log(f"安装完成:{success_count}/{count} 成功", "WARNING") + self.run_on_ui_thread(messagebox.showwarning, "部分成功", f"成功: {success_count}\n失败: {count - success_count}") else: - self.log(f"✗ {apk_name}.apk", "ERROR") - - self.run_adb_command('adb -d shell setprop vecentek.model 0') - self.update_progress(count, count, "安装完成", is_push=True) - self.show_progress(False, is_push=True) - - if success_count == count: - self.log(f"安装完成:全部 {count} 个成功", "SUCCESS") - messagebox.showinfo("安装完成", f"成功安装 {count} 个APK!") - elif success_count > 0: - self.log(f"安装完成:{success_count}/{count} 成功", "WARNING") - messagebox.showwarning("部分成功", f"成功: {success_count}\n失败: {count - success_count}") - else: - self.log("安装失败", "ERROR") - messagebox.showerror("安装失败", "所有APK安装失败!") + self.log("安装失败", "ERROR") + self.run_on_ui_thread(messagebox.showerror, "安装失败", "所有APK安装失败!") + except Exception as e: + self.log(f"安装过程异常: {str(e)}", "ERROR") + self.run_on_ui_thread(messagebox.showerror, "安装失败", f"安装过程异常:{str(e)}") + finally: + self.run_adb_command('adb -d shell setprop vecentek.model 0') + self.show_progress(False, is_push=True) threading.Thread(target=install, daemon=True).start() + def _debug_test_extract(self, event=None): + """Debug-only package extraction test.""" + if not getattr(self, 'debug_mode', False): + messagebox.showwarning("Debug mode", "Press Ctrl+Shift+D to enable debug mode first") + return + + pwd = simpledialog.askstring("Test extraction", "Enter package.bin password:", show='*', parent=self.root) + if not pwd: + return + + def do_extract(): + old_password = self.extract_password + self.extract_password = pwd + try: + self.show_progress(True, is_push=False) + if self.extract_package_silent(): + self.log("Test extraction succeeded", "SUCCESS") + self.run_on_ui_thread( + messagebox.showinfo, + "Test extraction succeeded", + f"Resources extracted to:\n{self.temp_dir}" + ) + else: + self.log("Test extraction failed", "ERROR") + self.run_on_ui_thread(messagebox.showerror, "Test extraction failed", "Check the 7za output in logs") + finally: + self.extract_password = old_password + self.show_progress(False, is_push=False) + + threading.Thread(target=do_extract, daemon=True).start() + def run(self): """运行程序""" self.root.mainloop() @@ -1530,4 +1618,4 @@ def main(): messagebox.showerror("错误", f"程序启动失败: {e}") if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/pack_common.bat b/pack_common.bat index 25f065f..10294cc 100644 --- a/pack_common.bat +++ b/pack_common.bat @@ -62,7 +62,7 @@ copy ..\7za.exe . >nul if exist "..\app.ico" copy "..\app.ico" . >nul echo [5/6] PyInstaller... -%PY% -m PyInstaller --onefile --windowed --name="%NAME%" --icon=app.ico --add-data "adb.exe;." --add-data "AdbWinApi.dll;." --add-data "AdbWinUsbApi.dll;." --add-data "7za.exe;." --add-binary "_core.pyd;." --hidden-import=queue --hidden-import=threading --hidden-import=tkinter --hidden-import=zipfile --hidden-import=json --hidden-import=urllib --collect-all tkinter --uac-admin launcher.py +%PY% -m PyInstaller --onefile --windowed --name="%NAME%" --icon=app.ico --add-data "adb.exe;." --add-data "AdbWinApi.dll;." --add-data "AdbWinUsbApi.dll;." --add-data "7za.exe;." --add-binary "_core.pyd;." --hidden-import=queue --hidden-import=threading --hidden-import=tkinter --hidden-import=tkinter.simpledialog --hidden-import=zipfile --hidden-import=json --hidden-import=urllib --hidden-import=urllib.parse --collect-all tkinter --uac-admin launcher.py if errorlevel 1 ( cd .. echo [ERROR] PyInstaller failed @@ -79,7 +79,7 @@ goto :DONE :NORMAL cd /d "%~dp0" echo [INFO] Normal PyInstaller... -%PY% -m PyInstaller --onefile --windowed --name="%NAME%" --icon=app.ico --add-data "adb.exe;." --add-data "AdbWinApi.dll;." --add-data "AdbWinUsbApi.dll;." --add-data "7za.exe;." --hidden-import=queue --hidden-import=threading --hidden-import=tkinter --hidden-import=zipfile --hidden-import=json --hidden-import=urllib --collect-all tkinter --uac-admin %SRC% +%PY% -m PyInstaller --onefile --windowed --name="%NAME%" --icon=app.ico --add-data "adb.exe;." --add-data "AdbWinApi.dll;." --add-data "AdbWinUsbApi.dll;." --add-data "7za.exe;." --hidden-import=queue --hidden-import=threading --hidden-import=tkinter --hidden-import=tkinter.simpledialog --hidden-import=zipfile --hidden-import=json --hidden-import=urllib --hidden-import=urllib.parse --collect-all tkinter --uac-admin %SRC% :DONE echo. diff --git a/pack_q07.bat b/pack_q07.bat index 2706702..2848d5d 100644 --- a/pack_q07.bat +++ b/pack_q07.bat @@ -62,7 +62,7 @@ copy ..\7za.exe . >nul if exist "..\app.ico" copy "..\app.ico" . >nul echo [5/6] PyInstaller... -%PY% -m PyInstaller --onefile --windowed --name="%NAME%" --icon=app.ico --add-data "adb.exe;." --add-data "AdbWinApi.dll;." --add-data "AdbWinUsbApi.dll;." --add-data "7za.exe;." --add-binary "_core.pyd;." --hidden-import=queue --hidden-import=threading --hidden-import=tkinter --hidden-import=zipfile --hidden-import=json --hidden-import=urllib --collect-all tkinter --uac-admin launcher.py +%PY% -m PyInstaller --onefile --windowed --name="%NAME%" --icon=app.ico --add-data "adb.exe;." --add-data "AdbWinApi.dll;." --add-data "AdbWinUsbApi.dll;." --add-data "7za.exe;." --add-binary "_core.pyd;." --hidden-import=queue --hidden-import=threading --hidden-import=tkinter --hidden-import=tkinter.simpledialog --hidden-import=zipfile --hidden-import=json --hidden-import=urllib --hidden-import=urllib.parse --collect-all tkinter --uac-admin launcher.py if errorlevel 1 ( cd .. echo [ERROR] PyInstaller failed @@ -79,7 +79,7 @@ goto :DONE :NORMAL cd /d "%~dp0" echo [INFO] Normal PyInstaller... -%PY% -m PyInstaller --onefile --windowed --name="%NAME%" --icon=app.ico --add-data "adb.exe;." --add-data "AdbWinApi.dll;." --add-data "AdbWinUsbApi.dll;." --add-data "7za.exe;." --hidden-import=queue --hidden-import=threading --hidden-import=tkinter --hidden-import=zipfile --hidden-import=json --hidden-import=urllib --collect-all tkinter --uac-admin %SRC% +%PY% -m PyInstaller --onefile --windowed --name="%NAME%" --icon=app.ico --add-data "adb.exe;." --add-data "AdbWinApi.dll;." --add-data "AdbWinUsbApi.dll;." --add-data "7za.exe;." --hidden-import=queue --hidden-import=threading --hidden-import=tkinter --hidden-import=tkinter.simpledialog --hidden-import=zipfile --hidden-import=json --hidden-import=urllib --hidden-import=urllib.parse --collect-all tkinter --uac-admin %SRC% :DONE echo. diff --git a/pack_x5plus.bat b/pack_x5plus.bat index 459d75a..8d6d32a 100644 --- a/pack_x5plus.bat +++ b/pack_x5plus.bat @@ -62,7 +62,7 @@ copy ..\7za.exe . >nul if exist "..\app.ico" copy "..\app.ico" . >nul echo [5/6] PyInstaller... -%PY% -m PyInstaller --onefile --windowed --name="%NAME%" --icon=app.ico --add-data "adb.exe;." --add-data "AdbWinApi.dll;." --add-data "AdbWinUsbApi.dll;." --add-data "7za.exe;." --add-binary "_core.pyd;." --hidden-import=queue --hidden-import=threading --hidden-import=tkinter --hidden-import=zipfile --hidden-import=json --hidden-import=urllib --collect-all tkinter --uac-admin launcher.py +%PY% -m PyInstaller --onefile --windowed --name="%NAME%" --icon=app.ico --add-data "adb.exe;." --add-data "AdbWinApi.dll;." --add-data "AdbWinUsbApi.dll;." --add-data "7za.exe;." --add-binary "_core.pyd;." --hidden-import=queue --hidden-import=threading --hidden-import=tkinter --hidden-import=tkinter.simpledialog --hidden-import=zipfile --hidden-import=json --hidden-import=urllib --hidden-import=urllib.parse --collect-all tkinter --uac-admin launcher.py if errorlevel 1 ( cd .. echo [ERROR] PyInstaller failed @@ -79,7 +79,7 @@ goto :DONE :NORMAL cd /d "%~dp0" echo [INFO] Normal PyInstaller... -%PY% -m PyInstaller --onefile --windowed --name="%NAME%" --icon=app.ico --add-data "adb.exe;." --add-data "AdbWinApi.dll;." --add-data "AdbWinUsbApi.dll;." --add-data "7za.exe;." --hidden-import=queue --hidden-import=threading --hidden-import=tkinter --hidden-import=zipfile --hidden-import=json --hidden-import=urllib --collect-all tkinter --uac-admin %SRC% +%PY% -m PyInstaller --onefile --windowed --name="%NAME%" --icon=app.ico --add-data "adb.exe;." --add-data "AdbWinApi.dll;." --add-data "AdbWinUsbApi.dll;." --add-data "7za.exe;." --hidden-import=queue --hidden-import=threading --hidden-import=tkinter --hidden-import=tkinter.simpledialog --hidden-import=zipfile --hidden-import=json --hidden-import=urllib --hidden-import=urllib.parse --collect-all tkinter --uac-admin %SRC% :DONE echo. diff --git a/pack_yidong.bat b/pack_yidong.bat index ce7b24e..e9992b9 100644 --- a/pack_yidong.bat +++ b/pack_yidong.bat @@ -62,7 +62,7 @@ copy ..\7za.exe . >nul if exist "..\app.ico" copy "..\app.ico" . >nul echo [5/6] PyInstaller... -%PY% -m PyInstaller --onefile --windowed --name="%NAME%" --icon=app.ico --add-data "adb.exe;." --add-data "AdbWinApi.dll;." --add-data "AdbWinUsbApi.dll;." --add-data "7za.exe;." --add-binary "_core.pyd;." --hidden-import=queue --hidden-import=threading --hidden-import=tkinter --hidden-import=zipfile --hidden-import=json --hidden-import=urllib --collect-all tkinter --uac-admin launcher.py +%PY% -m PyInstaller --onefile --windowed --name="%NAME%" --icon=app.ico --add-data "adb.exe;." --add-data "AdbWinApi.dll;." --add-data "AdbWinUsbApi.dll;." --add-data "7za.exe;." --add-binary "_core.pyd;." --hidden-import=queue --hidden-import=threading --hidden-import=tkinter --hidden-import=tkinter.simpledialog --hidden-import=zipfile --hidden-import=json --hidden-import=urllib --hidden-import=urllib.parse --collect-all tkinter --uac-admin launcher.py if errorlevel 1 ( cd .. echo [ERROR] PyInstaller failed @@ -79,7 +79,7 @@ goto :DONE :NORMAL cd /d "%~dp0" echo [INFO] Normal PyInstaller... -%PY% -m PyInstaller --onefile --windowed --name="%NAME%" --icon=app.ico --add-data "adb.exe;." --add-data "AdbWinApi.dll;." --add-data "AdbWinUsbApi.dll;." --add-data "7za.exe;." --hidden-import=queue --hidden-import=threading --hidden-import=tkinter --hidden-import=zipfile --hidden-import=json --hidden-import=urllib --collect-all tkinter --uac-admin %SRC% +%PY% -m PyInstaller --onefile --windowed --name="%NAME%" --icon=app.ico --add-data "adb.exe;." --add-data "AdbWinApi.dll;." --add-data "AdbWinUsbApi.dll;." --add-data "7za.exe;." --hidden-import=queue --hidden-import=threading --hidden-import=tkinter --hidden-import=tkinter.simpledialog --hidden-import=zipfile --hidden-import=json --hidden-import=urllib --hidden-import=urllib.parse --collect-all tkinter --uac-admin %SRC% :DONE echo.