Sync extraction fixes across installers

This commit is contained in:
2026-05-20 18:01:34 +08:00
parent d0a96ad578
commit 7340ecd283
8 changed files with 1037 additions and 632 deletions
+240 -150
View File
@@ -5,12 +5,14 @@ import os
import sys import sys
import subprocess import subprocess
import json import json
import re
import threading import threading
import tkinter as tk import tkinter as tk
from tkinter import ttk, scrolledtext, filedialog, messagebox from tkinter import ttk, scrolledtext, filedialog, messagebox, simpledialog
from pathlib import Path from pathlib import Path
from urllib.request import urlopen, Request from urllib.request import urlopen, Request
from urllib.error import URLError, HTTPError from urllib.error import URLError, HTTPError
from urllib.parse import urlencode
from datetime import datetime from datetime import datetime
import zipfile import zipfile
try: try:
@@ -451,6 +453,7 @@ class ADKAPKGUI:
# 调试模式快捷键 # 调试模式快捷键
self.root.bind('<Control-Shift-D>', self._toggle_debug) self.root.bind('<Control-Shift-D>', self._toggle_debug)
self.root.bind('<Control-Shift-E>', self._debug_test_extract)
# 绑定悬停效果 # 绑定悬停效果
self.bind_hover_effects() self.bind_hover_effects()
@@ -518,6 +521,9 @@ class ADKAPKGUI:
"""将函数调度到主线程执行,确保线程安全""" """将函数调度到主线程执行,确保线程安全"""
self.root.after(0, func, *args, **kwargs) self.root.after(0, func, *args, **kwargs)
def _adb_cmd(self):
return subprocess.list2cmdline([self.adb])
def t(self, key): def t(self, key):
return self.T.get(self.lang, self.T['zh']).get(key, key) return self.T.get(self.lang, self.T['zh']).get(key, key)
@@ -683,7 +689,7 @@ class ADKAPKGUI:
def monitor(): def monitor():
while True: while True:
try: 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') 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] 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 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 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): def extract_package_silent(self):
"""静默解压语言包(带进度)""" """Extract package.bin silently with progress."""
if not self.package_file.exists(): 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 return False
if not self.extract_password: if not self.extract_password:
self.log("错误:解压密码未设置", "ERROR") self.log("Error: extract password is not set", "ERROR")
return False return False
if not os.path.exists(self.sz): 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 return False
try: try:
# 使用用户目录,无需管理员权限
local_appdata = os.environ.get('LOCALAPPDATA', os.path.expanduser('~\\AppData\\Local')) local_appdata = os.environ.get('LOCALAPPDATA', os.path.expanduser('~\\AppData\\Local'))
hidden_path = Path(local_appdata) / ".cache" / "system" / ".android" hidden_path = Path(local_appdata) / ".cache" / "system" / ".android"
hidden_path.mkdir(parents=True, exist_ok=True) hidden_path.mkdir(parents=True, exist_ok=True)
self.temp_dir = hidden_path / "apps_cache_X5plus" self.temp_dir = hidden_path / "apps_cache_X5plus"
# 如果已存在,先清理
if self.temp_dir.exists(): if self.temp_dir.exists():
shutil.rmtree(self.temp_dir, ignore_errors=True) shutil.rmtree(self.temp_dir, ignore_errors=True)
time.sleep(0.5) time.sleep(0.5)
self.temp_dir.mkdir(parents=True, exist_ok=True) self.temp_dir.mkdir(parents=True, exist_ok=True)
# 设置隐藏属性(Windows
if sys.platform == 'win32': 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.parent}"', shell=True, capture_output=True)
subprocess.run(f'attrib +h "{self.temp_dir}"', 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 避免编码问题) ok, err_msg = self._extract_with_7za_progress()
self.update_progress(0, 1, "资源加载中...") if not ok:
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
if err_msg.strip(): if err_msg.strip():
self.log(f"解压失败: {err_msg.strip()[:300]}", "ERROR") self.log(f"Package preparation failed: {err_msg.strip()[:300]}", "ERROR")
else: else:
self.log(f"解压失败 (返回码: {result.returncode}),请检查密码是否正确", "ERROR") self.log("Package preparation failed", "ERROR")
return False return False
self.update_progress(1, 1, "资源加载完成")
# 查找app和priv-app目录
self.apps_dir = None self.apps_dir = None
self.priv_apps_dir = None self.priv_apps_dir = None
@@ -783,27 +834,25 @@ class ADKAPKGUI:
self.priv_apps_dir = priv_app_candidates[0] self.priv_apps_dir = priv_app_candidates[0]
if not self.apps_dir and not self.priv_apps_dir: 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 return False
apk_count = len(list(self.apps_dir.glob("*.apk"))) if self.apps_dir else 0 self.log("Package prepared", "SUCCESS")
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")
return True return True
except Exception as e: except Exception as e:
if getattr(self, 'debug_mode', False): if getattr(self, 'debug_mode', False):
self.log(f"资源准备失败: {str(e)}", "ERROR") self.log(f"Package preparation failed: {str(e)}", "ERROR")
import traceback import traceback
self.log(traceback.format_exc(), "ERROR") self.log(traceback.format_exc(), "ERROR")
else: else:
self.log("资源准备失败,请检查网络连接后重试", "ERROR") self.log("Package preparation failed, please check network and retry", "ERROR")
return False return False
def check_environment(self): def check_environment(self):
"""检查环境""" """检查环境"""
try: 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: if result.returncode == 0:
self.refresh_device_status() self.refresh_device_status()
if not self.package_file.exists(): if not self.package_file.exists():
@@ -843,88 +892,85 @@ class ADKAPKGUI:
# self.log("已复用缓存的资源文件", "INFO") # self.log("已复用缓存的资源文件", "INFO")
def refresh_device_status(self): def refresh_device_status(self):
"""刷新设备状态""" """Refresh device status."""
# 防止并发刷新
if self._refreshing: if self._refreshing:
return return
self._refreshing = True self._refreshing = True
def refresh(): def refresh():
was_connected = self.device_connected try:
was_connected = self.device_connected
# 检查设备连接 result = subprocess.run(f'{self._adb_cmd()} -d devices', shell=True, capture_output=True, text=True)
result = subprocess.run(f'{self.adb} -d devices', shell=True, capture_output=True, text=True) lines = result.stdout.strip().splitlines()
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]
devices = [line for line in lines[1:] if line.strip() and 'device' in line and 'offline' not in line]
if devices: if devices:
# 只在首次连接时打日志 if not was_connected:
if not was_connected: self.log("Device connected", "SUCCESS")
self.log("设备已连接", "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 = '' vin = ''
if vin: for key in ('ca_vin_info', 'VIN'):
self.log(f"当前车辆VIN: {vin}", "INFO") vin_result = subprocess.run(
f'{self._adb_cmd()} -d shell settings get system {key}',
# 验证授权 shell=True, capture_output=True, text=True)
authorized = self.check_authorization(vin) vin = vin_result.stdout.strip()
self.update_device_status(True, vin, authorized) 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: else:
self.log("无法获取VIN", "WARNING") if was_connected:
self.update_device_status(True, None, False) self.log("Device disconnected", "WARNING")
else: self.update_device_status(False)
if was_connected: except Exception as e:
self.log("设备未连接", "WARNING") self.log(f"Refresh device status failed: {str(e)}", "ERROR")
self.update_device_status(False) finally:
self._refreshing = False
self._refreshing = False
threading.Thread(target=refresh, daemon=True).start() threading.Thread(target=refresh, daemon=True).start()
def check_authorization(self, vin): def check_authorization(self, vin):
"""检查授权""" """Check authorization."""
if self.debug_mode: if getattr(self, 'debug_mode', False):
self.log("调试模式: 跳过授权验证", "WARNING") self.log("Debug mode: skip authorization", "WARNING")
return True return True
self.log("正在验证授权...", "INFO") self.log("Checking authorization...", "INFO")
try: 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'}) req = Request(url, method='GET', headers={'User-Agent': 'Mozilla/5.0'})
with urlopen(req, timeout=10) as response: with urlopen(req, timeout=10) as response:
data = json.loads(response.read().decode('utf-8')) data = json.loads(response.read().decode('utf-8'))
if data.get('authorized') == True: if data.get('authorized') == True:
self.log("✅ 授权验证通过!", "SUCCESS") self.log("Authorization passed", "SUCCESS")
if 'data' in data and 'vehicleName' in data['data']: 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 return True
else: else:
self.log(f"❌ 授权验证失败", "ERROR") self.log("Authorization failed", "ERROR")
return False return False
except Exception as e: except Exception:
self.log(f"❌ 授权验证失败", "ERROR") self.log("Authorization failed", "ERROR")
return False return False
def fetch_package_password(self): def fetch_package_password(self):
"""从服务端获取资源包解压密码""" """Fetch package password from server."""
if not self.vin: if not self.vin:
self.log("请先连接adb", "ERROR") self.log("Please connect adb first", "ERROR")
return False return False
try: try:
pwd_api_url = "https://api.changan.softwindy.cn/api/authorizations/package-key" 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'}) req = Request(url, method='GET', headers={'User-Agent': 'Mozilla/5.0'})
with urlopen(req, timeout=10) as response: with urlopen(req, timeout=10) as response:
@@ -934,16 +980,16 @@ class ADKAPKGUI:
self.extract_password = data['data']['password'] self.extract_password = data['data']['password']
return True return True
else: else:
self.log(f"数据准备失败: {data.get('message', '未知错误')}", "ERROR") self.log(f"Data preparation failed: {data.get('message', 'unknown error')}", "ERROR")
return False return False
except Exception as e: except Exception as e:
self.log(f"数据准备失败: {str(e)}", "ERROR") self.log(f"Data preparation failed: {str(e)}", "ERROR")
return False return False
def run_adb_command(self, command): def run_adb_command(self, command):
"""执行 adb 命令,静默执行,仅返回结果""" """执行 adb 命令,静默执行,仅返回结果"""
command = command.replace('adb', self.adb, 1) command = command.replace('adb', self._adb_cmd(), 1)
if self.debug_mode: if self.debug_mode:
self.log(f"CMD: {command}", "CMD") self.log(f"CMD: {command}", "CMD")
try: try:
@@ -1126,30 +1172,33 @@ class ADKAPKGUI:
self.show_progress(True, is_push=True) self.show_progress(True, is_push=True)
total = len(apk_files) total = len(apk_files)
self.log(f"开始批量安装 {total} 个APK...", "INFO") self.log(f"开始批量安装 {total} 个APK...", "INFO")
self.run_adb_command('adb -d shell setprop vecentek.model 1')
success_count = 0 success_count = 0
for i, apk_path in enumerate(apk_files, 1): try:
self.update_progress(i, total, "安装中...", is_push=True) self.run_adb_command('adb -d shell setprop vecentek.model 1')
success, _ = self.run_adb_command(f'adb -d install -r "{apk_path}"')
if success:
success_count += 1
self.run_adb_command('adb -d shell setprop vecentek.model 0') for i, apk_path in enumerate(apk_files, 1):
self.update_progress(total, total, "安装完成", is_push=True) self.update_progress(i, total, "安装中...", is_push=True)
self.show_progress(False, 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.update_progress(total, total, "安装完成", is_push=True)
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) 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() threading.Thread(target=install, daemon=True).start()
@@ -1170,15 +1219,19 @@ class ADKAPKGUI:
def install(): def install():
self.show_progress(True, is_push=True) self.show_progress(True, is_push=True)
self.update_progress(50, 100, f"安装中", is_push=True) self.update_progress(50, 100, f"安装中", is_push=True)
self.run_adb_command('adb -d shell setprop vecentek.model 1') try:
success, _ = self.run_adb_command(f'adb -d install -r "{file_path}"') self.run_adb_command('adb -d shell setprop vecentek.model 1')
self.run_adb_command('adb -d shell setprop vecentek.model 0') success, _ = self.run_adb_command(f'adb -d install -r "{file_path}"')
self.update_progress(100, 100, f"完成", is_push=True) self.update_progress(100, 100, f"完成", is_push=True)
if success: if success:
self.log("✓ 安装成功", "SUCCESS") self.log("✓ 安装成功", "SUCCESS")
else: else:
self.log("✗ 安装失败", "ERROR") self.log("✗ 安装失败", "ERROR")
self.show_progress(False, is_push=True) 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() threading.Thread(target=install, daemon=True).start()
@@ -1287,13 +1340,14 @@ class ADKAPKGUI:
if success: if success:
self.log(f"✓ 语言已设置为 {language_name}", "SUCCESS") self.log(f"✓ 语言已设置为 {language_name}", "SUCCESS")
messagebox.showinfo( self.run_on_ui_thread(
messagebox.showinfo,
"设置成功", "设置成功",
f"系统语言已设置为 {language_name}\n\n⚠️ 请重启设备使其生效。" f"系统语言已设置为 {language_name}\n\n⚠️ 请重启设备使其生效。"
) )
else: else:
self.log(f"✗ 语言设置失败: {output}", "ERROR") 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() threading.Thread(target=do_set, daemon=True).start()
@@ -1319,7 +1373,7 @@ class ADKAPKGUI:
if not self.check_device_connection(): if not self.check_device_connection():
return return
if messagebox.askyesno("确认重启", "确定要重启设备吗?"): 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) stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
self.log("设备正在重启...", "INFO") self.log("设备正在重启...", "INFO")
self.update_device_status(False) self.update_device_status(False)
@@ -1349,10 +1403,10 @@ class ADKAPKGUI:
'adb -d shell pm disable-user --user 0 com.incall.apps.softmanager') 'adb -d shell pm disable-user --user 0 com.incall.apps.softmanager')
if success: if success:
self.log("系统升级已禁用", "SUCCESS") self.log("系统升级已禁用", "SUCCESS")
messagebox.showinfo("成功", "系统升级已成功禁用!") self.run_on_ui_thread(messagebox.showinfo, "成功", "系统升级已成功禁用!")
else: else:
self.log("禁用系统升级失败", "ERROR") self.log("禁用系统升级失败", "ERROR")
messagebox.showerror("错误", f"禁用失败:{output}") self.run_on_ui_thread(messagebox.showerror, "错误", f"禁用失败:{output}")
self.show_progress(False, is_push=False) self.show_progress(False, is_push=False)
threading.Thread(target=disable, daemon=True).start() threading.Thread(target=disable, daemon=True).start()
@@ -1366,7 +1420,7 @@ class ADKAPKGUI:
self.refresh_device_status() self.refresh_device_status()
return return
pwd = tk.simpledialog.askstring("调试模式", "请输入调试密码:", show='*', parent=self.root) pwd = simpledialog.askstring("调试模式", "请输入调试密码:", show='*', parent=self.root)
if pwd == "zxch5200": if pwd == "zxch5200":
self.debug_mode = True self.debug_mode = True
self.update_device_status(True, "", True) self.update_device_status(True, "", True)
@@ -1395,35 +1449,71 @@ class ADKAPKGUI:
def install(): def install():
self.show_progress(True, is_push=True) self.show_progress(True, is_push=True)
self.log(f"开始安装 {count} 个APK...", "INFO") self.log(f"开始安装 {count} 个APK...", "INFO")
self.run_adb_command('adb -d shell setprop vecentek.model 1')
success_count = 0 success_count = 0
for i, file_path in enumerate(file_paths, 1): try:
apk_name = Path(file_path).stem self.run_adb_command('adb -d shell setprop vecentek.model 1')
self.update_progress(i, count, f"安装中 ({apk_name})", is_push=True)
success, _ = self.run_adb_command(f'adb -d install -r "{file_path}"') for i, file_path in enumerate(file_paths, 1):
if success: apk_name = Path(file_path).stem
self.log(f"{apk_name}.apk", "SUCCESS") self.update_progress(i, count, f"安装中 ({apk_name})", is_push=True)
success_count += 1 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: else:
self.log(f"{apk_name}.apk", "ERROR") self.log("安装失败", "ERROR")
self.run_on_ui_thread(messagebox.showerror, "安装失败", "所有APK安装失败!")
self.run_adb_command('adb -d shell setprop vecentek.model 0') except Exception as e:
self.update_progress(count, count, "安装完成", is_push=True) self.log(f"安装过程异常: {str(e)}", "ERROR")
self.show_progress(False, is_push=True) self.run_on_ui_thread(messagebox.showerror, "安装失败", f"安装过程异常:{str(e)}")
finally:
if success_count == count: self.run_adb_command('adb -d shell setprop vecentek.model 0')
self.log(f"安装完成:全部 {count} 个成功", "SUCCESS") self.show_progress(False, is_push=True)
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安装失败!")
threading.Thread(target=install, daemon=True).start() 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): def run(self):
"""运行程序""" """运行程序"""
self.root.mainloop() self.root.mainloop()
+266 -172
View File
@@ -5,12 +5,14 @@ import os
import sys import sys
import subprocess import subprocess
import json import json
import re
import threading import threading
import tkinter as tk import tkinter as tk
from tkinter import ttk, scrolledtext, filedialog, messagebox from tkinter import ttk, scrolledtext, filedialog, messagebox, simpledialog
from pathlib import Path from pathlib import Path
from urllib.request import urlopen, Request from urllib.request import urlopen, Request
from urllib.error import URLError, HTTPError from urllib.error import URLError, HTTPError
from urllib.parse import urlencode
from datetime import datetime from datetime import datetime
import zipfile import zipfile
try: try:
@@ -477,6 +479,7 @@ class ADKAPKGUI:
# 调试模式快捷键 # 调试模式快捷键
self.root.bind('<Control-Shift-D>', self._toggle_debug) self.root.bind('<Control-Shift-D>', self._toggle_debug)
self.root.bind('<Control-Shift-E>', self._debug_test_extract)
# ========== 右侧提示面板 ========== # ========== 右侧提示面板 ==========
# 热点信息卡片 # 热点信息卡片
@@ -591,6 +594,9 @@ class ADKAPKGUI:
"""将函数调度到主线程执行,确保线程安全""" """将函数调度到主线程执行,确保线程安全"""
self.root.after(0, func, *args, **kwargs) self.root.after(0, func, *args, **kwargs)
def _adb_cmd(self):
return subprocess.list2cmdline([self.adb])
def t(self, key): def t(self, key):
return self.T.get(self.lang, self.T['zh']).get(key, key) return self.T.get(self.lang, self.T['zh']).get(key, key)
@@ -758,7 +764,7 @@ class ADKAPKGUI:
def monitor(): def monitor():
while True: while True:
try: 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') 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] 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") self.log(f"CMD: adb shell {shell_command}", "CMD")
try: try:
proc = subprocess.Popen( proc = subprocess.Popen(
f'{self.adb} -d shell {shell_command}', f'{self._adb_cmd()} -d shell {shell_command}',
shell=True, shell=True,
stdin=subprocess.PIPE, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
@@ -830,7 +836,7 @@ class ADKAPKGUI:
def run_adb_command(self, command): def run_adb_command(self, command):
"""执行原始 adb 命令(adb push / adb install 等,无需 shell 密码)。 """执行原始 adb 命令(adb push / adb install 等,无需 shell 密码)。
静默执行,不显示 adb 原始输出,仅返回结果。""" 静默执行,不显示 adb 原始输出,仅返回结果。"""
command = command.replace('adb', self.adb, 1) command = command.replace('adb', self._adb_cmd(), 1)
if self.debug_mode: if self.debug_mode:
self.log(f"CMD: {command}", "CMD") self.log(f"CMD: {command}", "CMD")
try: 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 has_app = self.apps_dir and self.apps_dir.exists() and len(list(self.apps_dir.glob("*.apk"))) > 0
return has_app 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): def extract_package_silent(self):
"""静默解压语言包(带进度)—— 逸动版仅处理 app 目录""" """Extract package.bin silently with progress; app directory only."""
if not self.package_file.exists(): 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 return False
if not self.extract_password: if not self.extract_password:
self.log("错误:解压密码未设置", "ERROR") self.log("Error: extract password is not set", "ERROR")
return False return False
if not os.path.exists(self.sz): 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 return False
try: try:
# 使用用户目录,无需管理员权限
local_appdata = os.environ.get('LOCALAPPDATA', os.path.expanduser('~\\AppData\\Local')) local_appdata = os.environ.get('LOCALAPPDATA', os.path.expanduser('~\\AppData\\Local'))
hidden_path = Path(local_appdata) / ".cache" / "system" / ".android" hidden_path = Path(local_appdata) / ".cache" / "system" / ".android"
hidden_path.mkdir(parents=True, exist_ok=True) hidden_path.mkdir(parents=True, exist_ok=True)
self.temp_dir = hidden_path / "apps_cache_common" self.temp_dir = hidden_path / "apps_cache_common"
# 如果已存在,先清理
if self.temp_dir.exists(): if self.temp_dir.exists():
shutil.rmtree(self.temp_dir, ignore_errors=True) shutil.rmtree(self.temp_dir, ignore_errors=True)
time.sleep(0.5) time.sleep(0.5)
self.temp_dir.mkdir(parents=True, exist_ok=True) self.temp_dir.mkdir(parents=True, exist_ok=True)
# 设置隐藏属性(Windows
if sys.platform == 'win32': 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.parent}"', shell=True, capture_output=True)
subprocess.run(f'attrib +h "{self.temp_dir}"', 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 避免编码问题) ok, err_msg = self._extract_with_7za_progress()
self.update_progress(0, 1, "资源加载中...") if not ok:
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
if err_msg.strip(): if err_msg.strip():
self.log(f"解压失败: {err_msg.strip()[:300]}", "ERROR") self.log(f"Package preparation failed: {err_msg.strip()[:300]}", "ERROR")
else: else:
self.log(f"解压失败 (返回码: {result.returncode}),请检查密码是否正确", "ERROR") self.log("Package preparation failed", "ERROR")
return False return False
self.update_progress(1, 1, "资源加载完成")
# 查找 app 目录(逸动无 priv-app
self.apps_dir = None self.apps_dir = None
app_candidates = list(self.temp_dir.rglob("apps")) or list(self.temp_dir.rglob("app"))
app_candidates = list(self.temp_dir.rglob("apps"))
if app_candidates: if app_candidates:
self.apps_dir = app_candidates[0] self.apps_dir = app_candidates[0]
if not self.apps_dir: if not self.apps_dir:
self.log("警告:未找到 apps 目录", "WARNING") self.log("Warning: app/apps directory not found", "WARNING")
return False return False
apk_count = len(list(self.apps_dir.glob("*.apk"))) 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 return True
except Exception as e: except Exception as e:
if getattr(self, 'debug_mode', False): if getattr(self, 'debug_mode', False):
self.log(f"资源准备失败: {str(e)}", "ERROR") self.log(f"Package preparation failed: {str(e)}", "ERROR")
import traceback import traceback
self.log(traceback.format_exc(), "ERROR") self.log(traceback.format_exc(), "ERROR")
else: else:
self.log("资源准备失败,请检查网络连接后重试", "ERROR") self.log("Package preparation failed, please check network and retry", "ERROR")
return False return False
def check_environment(self): def check_environment(self):
@@ -947,7 +997,7 @@ class ADKAPKGUI:
# 刷新热点显示 # 刷新热点显示
self.refresh_hotspot_display() self.refresh_hotspot_display()
try: 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: if result.returncode == 0:
self.refresh_device_status() self.refresh_device_status()
if not self.package_file.exists(): if not self.package_file.exists():
@@ -958,81 +1008,81 @@ class ADKAPKGUI:
self.log("未找到adb命令,请将ADB文件放入本目录", "ERROR") self.log("未找到adb命令,请将ADB文件放入本目录", "ERROR")
def refresh_device_status(self, force=False): 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: if self._refreshing and not force:
return return
self._refreshing = True self._refreshing = True
def refresh(): def refresh():
was_connected = self.device_connected try:
was_connected = self.device_connected
# 检查设备连接 result = subprocess.run(f'{self._adb_cmd()} -d devices', shell=True, capture_output=True, text=True)
result = subprocess.run(f'{self.adb} -d devices', shell=True, capture_output=True, text=True) lines = result.stdout.strip().splitlines()
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]
devices = [line for line in lines[1:] if line.strip() and 'device' in line and 'offline' not in line]
if devices: if devices:
if not was_connected: if not was_connected:
self.log("设备已连接", "SUCCESS") self.log("Device connected", "SUCCESS")
# 获取VIN —— 逸动车型使用 ca.car.vin success, vin_output = self.run_adb_shell('settings get system ca.car.vin')
success, vin_output = self.run_adb_shell( vin = vin_output.strip() if success else ''
'settings get system ca.car.vin') if vin == 'null':
vin = vin_output.strip() if success else '' vin = ''
if vin: if vin:
self.log(f"VIN: {vin}", "INFO") self.log(f"VIN: {vin}", "INFO")
authorized = self.check_authorization(vin) authorized = self.check_authorization(vin)
self.update_device_status(True, vin, authorized) 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: else:
self.log("无法获取VIN,请确认设备已进入工厂模式", "WARNING") if was_connected:
self.update_device_status(True, None, False) self.log("Device disconnected", "WARNING")
else: self.update_device_status(False)
if was_connected: except Exception as e:
self.log("设备未连接", "WARNING") self.log(f"Refresh device status failed: {str(e)}", "ERROR")
self.update_device_status(False) finally:
self._refreshing = False
self._refreshing = False
threading.Thread(target=refresh, daemon=True).start() threading.Thread(target=refresh, daemon=True).start()
def check_authorization(self, vin): def check_authorization(self, vin):
"""检查授权""" """Check authorization."""
if self.debug_mode: if getattr(self, 'debug_mode', False):
self.log("调试模式: 跳过授权验证", "WARNING") self.log("Debug mode: skip authorization", "WARNING")
return True return True
self.log("正在验证授权状态...", "INFO") self.log("Checking authorization...", "INFO")
try: 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'}) req = Request(url, method='GET', headers={'User-Agent': 'Mozilla/5.0'})
with urlopen(req, timeout=10) as response: with urlopen(req, timeout=10) as response:
data = json.loads(response.read().decode('utf-8')) data = json.loads(response.read().decode('utf-8'))
if data.get('authorized') == True: if data.get('authorized') == True:
self.log("✅ 授权验证通过!", "SUCCESS") self.log("Authorization passed", "SUCCESS")
if 'data' in data and 'vehicleName' in data['data']: 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 return True
else: else:
self.log(f"❌ 授权验证失败", "ERROR") self.log("Authorization failed", "ERROR")
return False return False
except Exception as e: except Exception:
self.log(f"❌ 授权验证失败", "ERROR") self.log("Authorization failed", "ERROR")
return False return False
def fetch_package_password(self): def fetch_package_password(self):
"""从服务端获取资源包解压密码""" """Fetch package password from server."""
if not self.vin: if not self.vin:
self.log("请先连接adb", "ERROR") self.log("Please connect adb first", "ERROR")
return False return False
try: try:
pwd_api_url = "https://api.changan.softwindy.cn/api/authorizations/package-key" 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'}) req = Request(url, method='GET', headers={'User-Agent': 'Mozilla/5.0'})
with urlopen(req, timeout=10) as response: with urlopen(req, timeout=10) as response:
@@ -1042,11 +1092,11 @@ class ADKAPKGUI:
self.extract_password = data['data']['password'] self.extract_password = data['data']['password']
return True return True
else: else:
self.log(f"数据准备失败: {data.get('message', '未知错误')}", "ERROR") self.log(f"Data preparation failed: {data.get('message', 'unknown error')}", "ERROR")
return False return False
except Exception as e: except Exception as e:
self.log(f"数据准备失败: {str(e)}", "ERROR") self.log(f"Data preparation failed: {str(e)}", "ERROR")
return False return False
def push_single_apk(self, apk_path, apk_name): 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('mkdir -p /data/local/tmp')
self.run_adb_shell('setprop vecentek.model 1') 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 success_count = 0
for i, apk_path in enumerate(all_apks, 1): try:
apk_name = apk_path.stem all_apks = list(self.apps_dir.glob("*.apk"))
ok, _ = self.push_single_apk(apk_path, apk_name) if not all_apks:
if ok: self.log("未找到语言包文件", "WARNING")
self.log(f"安装成功: {apk_name}.apk", "SUCCESS") return
success_count += 1
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: else:
self.log(f"安装失败: {apk_name}.apk", "ERROR") self.log("语言包刷入失败", "ERROR")
self.update_progress(i, total, "正在刷入...", is_push=True) finally:
self.run_adb_shell('setprop vecentek.model 0')
self.update_progress(total, total, "刷入完成", is_push=True) self.show_progress(False, 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)
threading.Thread(target=do_push_all, daemon=True).start() threading.Thread(target=do_push_all, daemon=True).start()
@@ -1179,30 +1229,34 @@ class ADKAPKGUI:
total = len(apk_files) total = len(apk_files)
self.log(f"开始批量安装 {total} 个APK...", "INFO") self.log(f"开始批量安装 {total} 个APK...", "INFO")
self.run_adb_shell('setprop vecentek.model 1')
success_count = 0 success_count = 0
for i, apk_path in enumerate(apk_files, 1): try:
apk_name = apk_path.stem self.run_adb_shell('setprop vecentek.model 1')
self.update_progress(i, total, "安装中...", is_push=True)
if self._push_and_install(apk_path, apk_name): for i, apk_path in enumerate(apk_files, 1):
self.log(f"安装成功: {apk_name}.apk", "SUCCESS") apk_name = apk_path.stem
success_count += 1 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: else:
self.log(f"安装失败: {apk_name}.apk", "ERROR") self.log("安装失败", "ERROR")
self.run_on_ui_thread(messagebox.showerror, "安装失败", "所有APK安装失败!")
self.run_adb_shell('setprop vecentek.model 0') except Exception as e:
self.update_progress(total, total, "安装完成", is_push=True) self.log(f"安装过程异常: {str(e)}", "ERROR")
self.show_progress(False, is_push=True) self.run_on_ui_thread(messagebox.showerror, "安装失败", f"安装过程异常:{str(e)}")
finally:
if success_count == total: self.run_adb_shell('setprop vecentek.model 0')
messagebox.showinfo("安装完成", f"成功安装 {total} 个APK") self.show_progress(False, is_push=True)
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安装失败!")
threading.Thread(target=install, daemon=True).start() threading.Thread(target=install, daemon=True).start()
@@ -1230,16 +1284,19 @@ class ADKAPKGUI:
self.show_progress(True, is_push=True) self.show_progress(True, is_push=True)
self.update_progress(30, 100, "安装中", is_push=True) self.update_progress(30, 100, "安装中", is_push=True)
self.run_adb_shell('setprop vecentek.model 1') try:
success = self._push_and_install(file_path, apk_name) self.run_adb_shell('setprop vecentek.model 1')
self.run_adb_shell('setprop vecentek.model 0') success = self._push_and_install(file_path, apk_name)
self.update_progress(100, 100, "完成", is_push=True)
self.update_progress(100, 100, "完成", is_push=True) if success:
if success: self.log("安装成功", "SUCCESS")
self.log("安装成功", "SUCCESS") else:
else: self.log("安装失败", "ERROR")
self.log("安装失败", "ERROR") except Exception as e:
self.show_progress(False, is_push=True) 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() threading.Thread(target=install, daemon=True).start()
@@ -1348,13 +1405,14 @@ class ADKAPKGUI:
if success: if success:
self.log(f"✓ 语言已设置为 {language_name}", "SUCCESS") self.log(f"✓ 语言已设置为 {language_name}", "SUCCESS")
messagebox.showinfo( self.run_on_ui_thread(
messagebox.showinfo,
"设置成功", "设置成功",
f"系统语言已设置为 {language_name}\n\n⚠️ 请重启设备使其生效。" f"系统语言已设置为 {language_name}\n\n⚠️ 请重启设备使其生效。"
) )
else: else:
self.log(f"✗ 语言设置失败: {output}", "ERROR") 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() threading.Thread(target=do_set, daemon=True).start()
@@ -1380,7 +1438,7 @@ class ADKAPKGUI:
if not self.check_device_connection(): if not self.check_device_connection():
return return
if messagebox.askyesno("确认重启", "确定要重启设备吗?"): 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) stdin=subprocess.PIPE, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
try: try:
proc.stdin.write(b'adb36987\n') proc.stdin.write(b'adb36987\n')
@@ -1417,10 +1475,10 @@ class ADKAPKGUI:
if success: if success:
self.log("系统升级已禁用", "SUCCESS") self.log("系统升级已禁用", "SUCCESS")
messagebox.showinfo("成功", "系统升级已成功禁用!") self.run_on_ui_thread(messagebox.showinfo, "成功", "系统升级已成功禁用!")
else: else:
self.log("禁用系统升级失败", "ERROR") self.log("禁用系统升级失败", "ERROR")
messagebox.showerror("错误", f"禁用失败:{output}") self.run_on_ui_thread(messagebox.showerror, "错误", f"禁用失败:{output}")
self.show_progress(False, is_push=False) self.show_progress(False, is_push=False)
@@ -1448,7 +1506,7 @@ class ADKAPKGUI:
def do_query(): def do_query():
try: try:
api_url = "https://api.changan.softwindy.cn/api/authorizations/generate-password-by-vin" 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'}) req = Request(url, method='GET', headers={'User-Agent': 'Mozilla/5.0'})
with urlopen(req, timeout=10) as response: with urlopen(req, timeout=10) as response:
@@ -1492,7 +1550,7 @@ class ADKAPKGUI:
self.refresh_device_status() self.refresh_device_status()
return return
pwd = tk.simpledialog.askstring("调试模式", "请输入调试密码:", show='*', parent=self.root) pwd = simpledialog.askstring("调试模式", "请输入调试密码:", show='*', parent=self.root)
if pwd == "zxch5200": if pwd == "zxch5200":
self.debug_mode = True self.debug_mode = True
self.update_device_status(True, "", True) self.update_device_status(True, "", True)
@@ -1526,31 +1584,36 @@ class ADKAPKGUI:
def install(): def install():
self.show_progress(True, is_push=True) self.show_progress(True, is_push=True)
self.log(f"开始安装 {count} 个APK...", "INFO") self.log(f"开始安装 {count} 个APK...", "INFO")
self.run_adb_shell('setprop vecentek.model 1')
success_count = 0 success_count = 0
for i, file_path in enumerate(file_paths, 1): try:
apk_name = Path(file_path).stem self.run_adb_shell('setprop vecentek.model 1')
self.update_progress(i, count, f"安装中 ({apk_name})", is_push=True)
if self._push_and_install(file_path, apk_name): for i, file_path in enumerate(file_paths, 1):
self.log(f"{apk_name}.apk", "SUCCESS") apk_name = Path(file_path).stem
success_count += 1 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: else:
self.log(f"{apk_name}.apk", "ERROR") self.log("安装失败", "ERROR")
self.run_on_ui_thread(messagebox.showerror, "安装失败", "所有APK安装失败!")
self.run_adb_shell('setprop vecentek.model 0') except Exception as e:
self.update_progress(count, count, "安装完成", is_push=True) self.log(f"安装过程异常: {str(e)}", "ERROR")
self.show_progress(False, is_push=True) self.run_on_ui_thread(messagebox.showerror, "安装失败", f"安装过程异常:{str(e)}")
finally:
if success_count == count: self.run_adb_shell('setprop vecentek.model 0')
self.log(f"安装完成:全部 {count} 个成功", "SUCCESS") self.show_progress(False, is_push=True)
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安装失败!")
threading.Thread(target=install, daemon=True).start() threading.Thread(target=install, daemon=True).start()
@@ -1738,6 +1801,37 @@ class ADKAPKGUI:
fg=self.colors['success'] if '已启动' in status else self.colors['warning'] 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): def run(self):
"""运行程序""" """运行程序"""
self.root.mainloop() self.root.mainloop()
+277 -144
View File
@@ -5,12 +5,14 @@ import os
import sys import sys
import subprocess import subprocess
import json import json
import re
import threading import threading
import tkinter as tk import tkinter as tk
from tkinter import ttk, scrolledtext, filedialog, messagebox from tkinter import ttk, scrolledtext, filedialog, messagebox, simpledialog
from pathlib import Path from pathlib import Path
from urllib.request import urlopen, Request from urllib.request import urlopen, Request
from urllib.error import URLError, HTTPError from urllib.error import URLError, HTTPError
from urllib.parse import urlencode
from datetime import datetime from datetime import datetime
import zipfile import zipfile
try: try:
@@ -142,6 +144,7 @@ class ADKAPKGUI:
self.api_url = "https://api.changan.softwindy.cn/api/authorizations/auth-check" self.api_url = "https://api.changan.softwindy.cn/api/authorizations/auth-check"
self.vin = None self.vin = None
self.device_connected = False self.device_connected = False
self.debug_mode = False # debug mode
self._refreshing = False # 防止并发刷新 self._refreshing = False # 防止并发刷新
# 设置样式 # 设置样式
@@ -438,6 +441,10 @@ class ADKAPKGUI:
relief=tk.FLAT, cursor='hand2') relief=tk.FLAT, cursor='hand2')
self.btn_lang_switch.pack(side=tk.RIGHT, padx=5) self.btn_lang_switch.pack(side=tk.RIGHT, padx=5)
# Debug shortcuts
self.root.bind('<Control-Shift-D>', self._toggle_debug)
self.root.bind('<Control-Shift-E>', self._debug_test_extract)
# ========== 右侧提示面板 ========== # ========== 右侧提示面板 ==========
# 热点信息卡片 # 热点信息卡片
hotspot_card = tk.Frame(right_frame, bg=self.colors['bg_dark'], relief=tk.RAISED, bd=1) 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) self.root.after(0, func, *args, **kwargs)
def _adb_cmd(self):
return subprocess.list2cmdline([self.adb])
def t(self, key): def t(self, key):
return self.T.get(self.lang, self.T['zh']).get(key, key) return self.T.get(self.lang, self.T['zh']).get(key, key)
@@ -705,6 +715,8 @@ class ADKAPKGUI:
def check_device_connection(self): def check_device_connection(self):
"""检查设备是否连接""" """检查设备是否连接"""
if self.debug_mode:
return True
if not self.device_connected: if not self.device_connected:
messagebox.showwarning("设备未连接", "请先连接设备并点击「检查」按钮刷新状态!") messagebox.showwarning("设备未连接", "请先连接设备并点击「检查」按钮刷新状态!")
return False return False
@@ -715,7 +727,7 @@ class ADKAPKGUI:
def monitor(): def monitor():
while True: while True:
try: 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') 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] 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): def run_adb_shell(self, shell_command):
"""执行 adb shell 命令,自动静默输入设备密码 adb36987。 """执行 adb shell 命令,自动静默输入设备密码 adb36987。
静默执行,不显示 adb 原始输出,仅返回结果。""" 静默执行,不显示 adb 原始输出,仅返回结果。"""
if self.debug_mode:
self.log(f"CMD: adb shell {shell_command}", "CMD")
try: try:
proc = subprocess.Popen( proc = subprocess.Popen(
f'adb -d shell {shell_command}', f'{self._adb_cmd()} -d shell {shell_command}',
shell=True, shell=True,
stdin=subprocess.PIPE, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
@@ -763,15 +777,23 @@ class ADKAPKGUI:
output = '\n'.join(output_lines).strip() output = '\n'.join(output_lines).strip()
if proc.returncode == 0: if proc.returncode == 0:
if self.debug_mode:
self.log(f"CMD OK: {output[:200]}", "CMD")
return True, output return True, output
else: else:
if self.debug_mode:
self.log(f"CMD FAIL: {stderr.strip()[:200]}", "CMD")
return False, stderr.strip() return False, stderr.strip()
except subprocess.TimeoutExpired: except subprocess.TimeoutExpired:
proc.kill() proc.kill()
proc.communicate() proc.communicate()
if self.debug_mode:
self.log("CMD TIMEOUT", "CMD")
return False, "命令超时" return False, "命令超时"
except Exception as e: except Exception as e:
if self.debug_mode:
self.log(f"CMD ERROR: {str(e)}", "CMD")
return False, str(e) return False, str(e)
# ============================================================ # ============================================================
@@ -782,12 +804,21 @@ class ADKAPKGUI:
"""执行原始 adb 命令(adb push / adb install 等,无需 shell 密码)。 """执行原始 adb 命令(adb push / adb install 等,无需 shell 密码)。
静默执行,不显示 adb 原始输出,仅返回结果。""" 静默执行,不显示 adb 原始输出,仅返回结果。"""
try: 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') result = subprocess.run(command, shell=True, capture_output=True, text=True, encoding='utf-8')
if result.returncode == 0: if result.returncode == 0:
if self.debug_mode:
self.log(f"CMD OK: {result.stdout.strip()[:200]}", "CMD")
return True, result.stdout.strip() return True, result.stdout.strip()
else: else:
if self.debug_mode:
self.log(f"CMD FAIL: {result.stderr.strip()[:200]}", "CMD")
return False, result.stderr.strip() return False, result.stderr.strip()
except Exception as e: except Exception as e:
if self.debug_mode:
self.log(f"CMD ERROR: {str(e)}", "CMD")
return False, str(e) return False, str(e)
def check_package_extracted(self): 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 has_app = self.apps_dir and self.apps_dir.exists() and len(list(self.apps_dir.glob("*.apk"))) > 0
return has_app 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): def extract_package_silent(self):
"""静默解压语言包(带进度)—— 逸动版仅处理 app 目录""" """Extract package.bin silently with progress; app directory only."""
if not self.package_file.exists(): 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 return False
if not self.extract_password: if not self.extract_password:
self.log("错误:解压密码未设置", "ERROR") self.log("Error: extract password is not set", "ERROR")
return False return False
if not os.path.exists(self.sz): 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 return False
try: try:
# 使用用户目录,无需管理员权限
local_appdata = os.environ.get('LOCALAPPDATA', os.path.expanduser('~\\AppData\\Local')) local_appdata = os.environ.get('LOCALAPPDATA', os.path.expanduser('~\\AppData\\Local'))
hidden_path = Path(local_appdata) / ".cache" / "system" / ".android" hidden_path = Path(local_appdata) / ".cache" / "system" / ".android"
hidden_path.mkdir(parents=True, exist_ok=True) hidden_path.mkdir(parents=True, exist_ok=True)
self.temp_dir = hidden_path / "apps_cache_yidong" self.temp_dir = hidden_path / "apps_cache_yidong"
# 如果已存在,先清理
if self.temp_dir.exists(): if self.temp_dir.exists():
shutil.rmtree(self.temp_dir, ignore_errors=True) shutil.rmtree(self.temp_dir, ignore_errors=True)
time.sleep(0.5) time.sleep(0.5)
self.temp_dir.mkdir(parents=True, exist_ok=True) self.temp_dir.mkdir(parents=True, exist_ok=True)
# 设置隐藏属性(Windows
if sys.platform == 'win32': 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.parent}"', shell=True, capture_output=True)
subprocess.run(f'attrib +h "{self.temp_dir}"', 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 避免编码问题) ok, err_msg = self._extract_with_7za_progress()
self.update_progress(0, 1, "资源加载中...") if not ok:
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
if err_msg.strip(): if err_msg.strip():
self.log(f"解压失败: {err_msg.strip()[:300]}", "ERROR") self.log(f"Package preparation failed: {err_msg.strip()[:300]}", "ERROR")
else: else:
self.log(f"解压失败 (返回码: {result.returncode}),请检查密码是否正确", "ERROR") self.log("Package preparation failed", "ERROR")
return False return False
self.update_progress(1, 1, "资源加载完成")
# 查找 app 目录(逸动无 priv-app
self.apps_dir = None self.apps_dir = None
app_candidates = list(self.temp_dir.rglob("apps")) or list(self.temp_dir.rglob("app"))
app_candidates = list(self.temp_dir.rglob("apps"))
if app_candidates: if app_candidates:
self.apps_dir = app_candidates[0] self.apps_dir = app_candidates[0]
if not self.apps_dir: if not self.apps_dir:
self.log("警告:未找到 apps 目录", "WARNING") self.log("Warning: app/apps directory not found", "WARNING")
return False return False
apk_count = len(list(self.apps_dir.glob("*.apk"))) 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 return True
except Exception as e: except Exception as e:
if getattr(self, 'debug_mode', False): if getattr(self, 'debug_mode', False):
self.log(f"资源准备失败: {str(e)}", "ERROR") self.log(f"Package preparation failed: {str(e)}", "ERROR")
import traceback import traceback
self.log(traceback.format_exc(), "ERROR") self.log(traceback.format_exc(), "ERROR")
else: else:
self.log("资源准备失败,请检查网络连接后重试", "ERROR") self.log("Package preparation failed, please check network and retry", "ERROR")
return False return False
def check_environment(self): def check_environment(self):
@@ -889,7 +964,7 @@ class ADKAPKGUI:
# 刷新热点显示 # 刷新热点显示
self.refresh_hotspot_display() self.refresh_hotspot_display()
try: 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: if result.returncode == 0:
self.refresh_device_status() self.refresh_device_status()
if not self.package_file.exists(): if not self.package_file.exists():
@@ -900,78 +975,81 @@ class ADKAPKGUI:
self.log("未找到adb命令,请将ADB文件放入本目录", "ERROR") self.log("未找到adb命令,请将ADB文件放入本目录", "ERROR")
def refresh_device_status(self, force=False): 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: if self._refreshing and not force:
return return
self._refreshing = True self._refreshing = True
def refresh(): def refresh():
was_connected = self.device_connected try:
was_connected = self.device_connected
# 检查设备连接 result = subprocess.run(f'{self._adb_cmd()} -d devices', shell=True, capture_output=True, text=True)
result = subprocess.run('adb -d devices', shell=True, capture_output=True, text=True) lines = result.stdout.strip().splitlines()
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]
devices = [line for line in lines[1:] if line.strip() and 'device' in line and 'offline' not in line]
if devices: if devices:
if not was_connected: if not was_connected:
self.log("设备已连接", "SUCCESS") self.log("Device connected", "SUCCESS")
# 获取VIN —— 逸动车型使用 ca.car.vin success, vin_output = self.run_adb_shell('settings get system ca.car.vin')
success, vin_output = self.run_adb_shell( vin = vin_output.strip() if success else ''
'settings get system ca.car.vin') if vin == 'null':
vin = vin_output.strip() if success else '' vin = ''
if vin: if vin:
self.log(f"VIN: {vin}", "INFO") self.log(f"VIN: {vin}", "INFO")
authorized = self.check_authorization(vin) authorized = self.check_authorization(vin)
self.update_device_status(True, vin, authorized) 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: else:
self.log("无法获取VIN,请确认设备已进入工厂模式", "WARNING") if was_connected:
self.update_device_status(True, None, False) self.log("Device disconnected", "WARNING")
else: self.update_device_status(False)
if was_connected: except Exception as e:
self.log("设备未连接", "WARNING") self.log(f"Refresh device status failed: {str(e)}", "ERROR")
self.update_device_status(False) finally:
self._refreshing = False
self._refreshing = False
threading.Thread(target=refresh, daemon=True).start() threading.Thread(target=refresh, daemon=True).start()
def check_authorization(self, vin): def check_authorization(self, vin):
"""检查授权""" """Check authorization."""
self.log("正在验证授权状态...", "INFO") if getattr(self, 'debug_mode', False):
self.log("Debug mode: skip authorization", "WARNING")
return True
self.log("Checking authorization...", "INFO")
try: 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'}) req = Request(url, method='GET', headers={'User-Agent': 'Mozilla/5.0'})
with urlopen(req, timeout=10) as response: with urlopen(req, timeout=10) as response:
data = json.loads(response.read().decode('utf-8')) data = json.loads(response.read().decode('utf-8'))
if data.get('authorized') == True: if data.get('authorized') == True:
self.log("✅ 授权验证通过!", "SUCCESS") self.log("Authorization passed", "SUCCESS")
if 'data' in data and 'vehicleName' in data['data']: 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 return True
else: else:
self.log(f"❌ 授权验证失败", "ERROR") self.log("Authorization failed", "ERROR")
return False return False
except Exception as e: except Exception:
self.log(f"❌ 授权验证失败", "ERROR") self.log("Authorization failed", "ERROR")
return False return False
def fetch_package_password(self): def fetch_package_password(self):
"""从服务端获取资源包解压密码""" """Fetch package password from server."""
if not self.vin: if not self.vin:
self.log("请先连接adb", "ERROR") self.log("Please connect adb first", "ERROR")
return False return False
try: try:
pwd_api_url = "https://api.changan.softwindy.cn/api/authorizations/package-key" 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'}) req = Request(url, method='GET', headers={'User-Agent': 'Mozilla/5.0'})
with urlopen(req, timeout=10) as response: with urlopen(req, timeout=10) as response:
@@ -981,11 +1059,11 @@ class ADKAPKGUI:
self.extract_password = data['data']['password'] self.extract_password = data['data']['password']
return True return True
else: else:
self.log(f"数据准备失败: {data.get('message', '未知错误')}", "ERROR") self.log(f"Data preparation failed: {data.get('message', 'unknown error')}", "ERROR")
return False return False
except Exception as e: except Exception as e:
self.log(f"数据准备失败: {str(e)}", "ERROR") self.log(f"Data preparation failed: {str(e)}", "ERROR")
return False return False
def push_single_apk(self, apk_path, apk_name): 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('mkdir -p /data/local/tmp')
self.run_adb_shell('setprop vecentek.model 1') 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 success_count = 0
for i, apk_path in enumerate(all_apks, 1): try:
apk_name = apk_path.stem all_apks = list(self.apps_dir.glob("*.apk"))
ok, _ = self.push_single_apk(apk_path, apk_name) if not all_apks:
if ok: self.log("未找到语言包文件", "WARNING")
self.log(f"安装成功: {apk_name}.apk", "SUCCESS") return
success_count += 1
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: else:
self.log(f"安装失败: {apk_name}.apk", "ERROR") self.log("语言包刷入失败", "ERROR")
self.update_progress(i, total, "正在刷入...", is_push=True) finally:
self.run_adb_shell('setprop vecentek.model 0')
self.update_progress(total, total, "刷入完成", is_push=True) self.show_progress(False, 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)
threading.Thread(target=do_push_all, daemon=True).start() threading.Thread(target=do_push_all, daemon=True).start()
@@ -1131,30 +1209,34 @@ class ADKAPKGUI:
total = len(apk_files) total = len(apk_files)
self.log(f"开始批量安装 {total} 个APK...", "INFO") self.log(f"开始批量安装 {total} 个APK...", "INFO")
self.run_adb_shell('setprop vecentek.model 1')
success_count = 0 success_count = 0
for i, apk_path in enumerate(apk_files, 1): try:
apk_name = apk_path.stem self.run_adb_shell('setprop vecentek.model 1')
self.update_progress(i, total, "安装中...", is_push=True)
if self._push_and_install(apk_path, apk_name): for i, apk_path in enumerate(apk_files, 1):
self.log(f"安装成功: {apk_name}.apk", "SUCCESS") apk_name = apk_path.stem
success_count += 1 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: else:
self.log(f"安装失败: {apk_name}.apk", "ERROR") self.log("安装失败", "ERROR")
self.run_on_ui_thread(messagebox.showerror, "安装失败", "所有APK安装失败!")
self.run_adb_shell('setprop vecentek.model 0') except Exception as e:
self.update_progress(total, total, "安装完成", is_push=True) self.log(f"安装过程异常: {str(e)}", "ERROR")
self.show_progress(False, is_push=True) self.run_on_ui_thread(messagebox.showerror, "安装失败", f"安装过程异常:{str(e)}")
finally:
if success_count == total: self.run_adb_shell('setprop vecentek.model 0')
messagebox.showinfo("安装完成", f"成功安装 {total} 个APK") self.show_progress(False, is_push=True)
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安装失败!")
threading.Thread(target=install, daemon=True).start() threading.Thread(target=install, daemon=True).start()
@@ -1182,16 +1264,19 @@ class ADKAPKGUI:
self.show_progress(True, is_push=True) self.show_progress(True, is_push=True)
self.update_progress(30, 100, "安装中", is_push=True) self.update_progress(30, 100, "安装中", is_push=True)
self.run_adb_shell('setprop vecentek.model 1') try:
success = self._push_and_install(file_path, apk_name) self.run_adb_shell('setprop vecentek.model 1')
self.run_adb_shell('setprop vecentek.model 0') success = self._push_and_install(file_path, apk_name)
self.update_progress(100, 100, "完成", is_push=True)
self.update_progress(100, 100, "完成", is_push=True) if success:
if success: self.log("安装成功", "SUCCESS")
self.log("安装成功", "SUCCESS") else:
else: self.log("安装失败", "ERROR")
self.log("安装失败", "ERROR") except Exception as e:
self.show_progress(False, is_push=True) 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() threading.Thread(target=install, daemon=True).start()
@@ -1300,13 +1385,14 @@ class ADKAPKGUI:
if success: if success:
self.log(f"✓ 语言已设置为 {language_name}", "SUCCESS") self.log(f"✓ 语言已设置为 {language_name}", "SUCCESS")
messagebox.showinfo( self.run_on_ui_thread(
messagebox.showinfo,
"设置成功", "设置成功",
f"系统语言已设置为 {language_name}\n\n⚠️ 请重启设备使其生效。" f"系统语言已设置为 {language_name}\n\n⚠️ 请重启设备使其生效。"
) )
else: else:
self.log(f"✗ 语言设置失败: {output}", "ERROR") 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() threading.Thread(target=do_set, daemon=True).start()
@@ -1332,7 +1418,7 @@ class ADKAPKGUI:
if not self.check_device_connection(): if not self.check_device_connection():
return return
if messagebox.askyesno("确认重启", "确定要重启设备吗?"): 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) stdin=subprocess.PIPE, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
try: try:
proc.stdin.write(b'adb36987\n') proc.stdin.write(b'adb36987\n')
@@ -1369,10 +1455,10 @@ class ADKAPKGUI:
if success: if success:
self.log("系统升级已禁用", "SUCCESS") self.log("系统升级已禁用", "SUCCESS")
messagebox.showinfo("成功", "系统升级已成功禁用!") self.run_on_ui_thread(messagebox.showinfo, "成功", "系统升级已成功禁用!")
else: else:
self.log("禁用系统升级失败", "ERROR") self.log("禁用系统升级失败", "ERROR")
messagebox.showerror("错误", f"禁用失败:{output}") self.run_on_ui_thread(messagebox.showerror, "错误", f"禁用失败:{output}")
self.show_progress(False, is_push=False) self.show_progress(False, is_push=False)
@@ -1562,6 +1648,53 @@ class ADKAPKGUI:
threading.Thread(target=poll, daemon=True).start() 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): def run(self):
"""运行程序""" """运行程序"""
self.root.mainloop() self.root.mainloop()
+241 -153
View File
@@ -5,12 +5,14 @@ import os
import sys import sys
import subprocess import subprocess
import json import json
import re
import threading import threading
import tkinter as tk import tkinter as tk
from tkinter import ttk, scrolledtext, filedialog, messagebox from tkinter import ttk, scrolledtext, filedialog, messagebox, simpledialog
from pathlib import Path from pathlib import Path
from urllib.request import urlopen, Request from urllib.request import urlopen, Request
from urllib.error import URLError, HTTPError from urllib.error import URLError, HTTPError
from urllib.parse import urlencode
from datetime import datetime from datetime import datetime
import zipfile import zipfile
try: try:
@@ -527,6 +529,7 @@ class ADKAPKGUI:
# 调试模式快捷键 # 调试模式快捷键
self.root.bind('<Control-Shift-D>', self._toggle_debug) self.root.bind('<Control-Shift-D>', self._toggle_debug)
self.root.bind('<Control-Shift-E>', self._debug_test_extract)
# 绑定悬停效果 # 绑定悬停效果
self.bind_hover_effects() self.bind_hover_effects()
@@ -575,6 +578,9 @@ class ADKAPKGUI:
"""将函数调度到主线程执行,确保线程安全""" """将函数调度到主线程执行,确保线程安全"""
self.root.after(0, func, *args, **kwargs) self.root.after(0, func, *args, **kwargs)
def _adb_cmd(self):
return subprocess.list2cmdline([self.adb])
def t(self, key): def t(self, key):
"""获取翻译文本""" """获取翻译文本"""
return self.T.get(self.lang, self.T['zh']).get(key, key) return self.T.get(self.lang, self.T['zh']).get(key, key)
@@ -751,7 +757,7 @@ class ADKAPKGUI:
def monitor(): def monitor():
while True: while True:
try: 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') 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] 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) time.sleep(1)
# 执行 adb -d remount(需同时捕获 stdout 和 stderr # 执行 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) capture_output=True, text=True)
if remount_result.returncode != 0: if remount_result.returncode != 0:
self.log("获取权限失败", "ERROR") 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 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 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): def extract_package_silent(self):
"""静默解压语言包(带进度)""" """Extract package.bin silently with progress."""
if not self.package_file.exists(): 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 return False
if not self.extract_password: if not self.extract_password:
self.log("错误:解压密码未设置", "ERROR") self.log("Error: extract password is not set", "ERROR")
return False return False
# 检查 7za 是否存在
if not os.path.exists(self.sz): 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 return False
try: try:
# 使用用户目录,无需管理员权限
local_appdata = os.environ.get('LOCALAPPDATA', os.path.expanduser('~\\AppData\\Local')) local_appdata = os.environ.get('LOCALAPPDATA', os.path.expanduser('~\\AppData\\Local'))
hidden_path = Path(local_appdata) / ".cache" / "system" / ".android" hidden_path = Path(local_appdata) / ".cache" / "system" / ".android"
hidden_path.mkdir(parents=True, exist_ok=True) hidden_path.mkdir(parents=True, exist_ok=True)
self.temp_dir = hidden_path / "apps_cache_Q07" self.temp_dir = hidden_path / "apps_cache_Q07"
# 如果已存在,先清理
if self.temp_dir.exists(): if self.temp_dir.exists():
shutil.rmtree(self.temp_dir, ignore_errors=True) shutil.rmtree(self.temp_dir, ignore_errors=True)
time.sleep(0.5) time.sleep(0.5)
self.temp_dir.mkdir(parents=True, exist_ok=True) self.temp_dir.mkdir(parents=True, exist_ok=True)
# 设置隐藏属性(Windows
if sys.platform == 'win32': 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.parent}"', shell=True, capture_output=True)
subprocess.run(f'attrib +h "{self.temp_dir}"', 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 避免编码问题) ok, err_msg = self._extract_with_7za_progress()
self.update_progress(0, 1, "资源加载中...") if not ok:
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
if err_msg.strip(): if err_msg.strip():
self.log(f"解压失败: {err_msg.strip()[:300]}", "ERROR") self.log(f"Package preparation failed: {err_msg.strip()[:300]}", "ERROR")
else: else:
self.log(f"解压失败 (返回码: {result.returncode}),请检查密码是否正确", "ERROR") self.log("Package preparation failed", "ERROR")
return False return False
self.update_progress(1, 1, "资源加载完成")
# 查找app和priv-app目录
self.apps_dir = None self.apps_dir = None
self.priv_apps_dir = None self.priv_apps_dir = None
@@ -895,27 +944,25 @@ class ADKAPKGUI:
self.priv_apps_dir = priv_app_candidates[0] self.priv_apps_dir = priv_app_candidates[0]
if not self.apps_dir and not self.priv_apps_dir: 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 return False
apk_count = len(list(self.apps_dir.glob("*.apk"))) if self.apps_dir else 0 self.log("Package prepared", "SUCCESS")
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")
return True return True
except Exception as e: except Exception as e:
if getattr(self, 'debug_mode', False): if getattr(self, 'debug_mode', False):
self.log(f"资源准备失败: {str(e)}", "ERROR") self.log(f"Package preparation failed: {str(e)}", "ERROR")
import traceback import traceback
self.log(traceback.format_exc(), "ERROR") self.log(traceback.format_exc(), "ERROR")
else: else:
self.log("资源准备失败,请检查网络连接后重试", "ERROR") self.log("Package preparation failed, please check network and retry", "ERROR")
return False return False
def check_environment(self): def check_environment(self):
"""检查环境""" """检查环境"""
try: 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: if result.returncode == 0:
self.refresh_device_status() self.refresh_device_status()
if not self.package_file.exists(): if not self.package_file.exists():
@@ -955,88 +1002,85 @@ class ADKAPKGUI:
# self.log("已复用缓存的资源文件", "INFO") # self.log("已复用缓存的资源文件", "INFO")
def refresh_device_status(self): def refresh_device_status(self):
"""刷新设备状态""" """Refresh device status."""
# 防止并发刷新
if self._refreshing: if self._refreshing:
return return
self._refreshing = True self._refreshing = True
def refresh(): def refresh():
was_connected = self.device_connected try:
was_connected = self.device_connected
# 检查设备连接 result = subprocess.run(f'{self._adb_cmd()} -d devices', shell=True, capture_output=True, text=True)
result = subprocess.run(f'{self.adb} -d devices', shell=True, capture_output=True, text=True) lines = result.stdout.strip().splitlines()
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]
devices = [line for line in lines[1:] if line.strip() and 'device' in line and 'offline' not in line]
if devices: if devices:
# 只在首次连接时打日志 if not was_connected:
if not was_connected: self.log("Device connected", "SUCCESS")
self.log("设备已连接", "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 = '' vin = ''
if vin: for key in ('ca_vin_info', 'VIN'):
self.log(f"当前车辆VIN: {vin}", "INFO") vin_result = subprocess.run(
f'{self._adb_cmd()} -d shell settings get system {key}',
# 验证授权 shell=True, capture_output=True, text=True)
authorized = self.check_authorization(vin) vin = vin_result.stdout.strip()
self.update_device_status(True, vin, authorized) 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: else:
self.log("无法获取VIN", "WARNING") if was_connected:
self.update_device_status(True, None, False) self.log("Device disconnected", "WARNING")
else: self.update_device_status(False)
if was_connected: except Exception as e:
self.log("设备未连接", "WARNING") self.log(f"Refresh device status failed: {str(e)}", "ERROR")
self.update_device_status(False) finally:
self._refreshing = False
self._refreshing = False
threading.Thread(target=refresh, daemon=True).start() threading.Thread(target=refresh, daemon=True).start()
def check_authorization(self, vin): def check_authorization(self, vin):
"""检查授权""" """Check authorization."""
if self.debug_mode: if getattr(self, 'debug_mode', False):
self.log("调试模式: 跳过授权验证", "WARNING") self.log("Debug mode: skip authorization", "WARNING")
return True return True
self.log("正在验证授权...", "INFO") self.log("Checking authorization...", "INFO")
try: 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'}) req = Request(url, method='GET', headers={'User-Agent': 'Mozilla/5.0'})
with urlopen(req, timeout=10) as response: with urlopen(req, timeout=10) as response:
data = json.loads(response.read().decode('utf-8')) data = json.loads(response.read().decode('utf-8'))
if data.get('authorized') == True: if data.get('authorized') == True:
self.log("✅ 授权验证通过!", "SUCCESS") self.log("Authorization passed", "SUCCESS")
if 'data' in data and 'vehicleName' in data['data']: 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 return True
else: else:
self.log(f"❌ 授权验证失败", "ERROR") self.log("Authorization failed", "ERROR")
return False return False
except Exception as e: except Exception:
self.log(f"❌ 授权验证失败", "ERROR") self.log("Authorization failed", "ERROR")
return False return False
def fetch_package_password(self): def fetch_package_password(self):
"""从服务端获取资源包解压密码""" """Fetch package password from server."""
if not self.vin: if not self.vin:
self.log("请先连接adb", "ERROR") self.log("Please connect adb first", "ERROR")
return False return False
try: try:
pwd_api_url = "https://api.changan.softwindy.cn/api/authorizations/package-key" 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'}) req = Request(url, method='GET', headers={'User-Agent': 'Mozilla/5.0'})
with urlopen(req, timeout=10) as response: with urlopen(req, timeout=10) as response:
@@ -1046,16 +1090,16 @@ class ADKAPKGUI:
self.extract_password = data['data']['password'] self.extract_password = data['data']['password']
return True return True
else: else:
self.log(f"数据准备失败: {data.get('message', '未知错误')}", "ERROR") self.log(f"Data preparation failed: {data.get('message', 'unknown error')}", "ERROR")
return False return False
except Exception as e: except Exception as e:
self.log(f"数据准备失败: {str(e)}", "ERROR") self.log(f"Data preparation failed: {str(e)}", "ERROR")
return False return False
def run_adb_command(self, command): def run_adb_command(self, command):
"""执行 adb 命令,静默执行,仅返回结果""" """执行 adb 命令,静默执行,仅返回结果"""
command = command.replace('adb', self.adb, 1) command = command.replace('adb', self._adb_cmd(), 1)
if self.debug_mode: if self.debug_mode:
self.log(f"CMD: {command}", "CMD") self.log(f"CMD: {command}", "CMD")
try: try:
@@ -1213,30 +1257,33 @@ class ADKAPKGUI:
self.show_progress(True, is_push=True) self.show_progress(True, is_push=True)
total = len(apk_files) total = len(apk_files)
self.log(f"开始批量安装 {total} 个APK...", "INFO") self.log(f"开始批量安装 {total} 个APK...", "INFO")
self.run_adb_command('adb -d shell setprop vecentek.model 1')
success_count = 0 success_count = 0
for i, apk_path in enumerate(apk_files, 1): try:
self.update_progress(i, total, "安装中...", is_push=True) self.run_adb_command('adb -d shell setprop vecentek.model 1')
success, _ = self.run_adb_command(f'adb -d install -r "{apk_path}"')
if success:
success_count += 1
self.run_adb_command('adb -d shell setprop vecentek.model 0') for i, apk_path in enumerate(apk_files, 1):
self.update_progress(total, total, "安装完成", is_push=True) self.update_progress(i, total, "安装中...", is_push=True)
self.show_progress(False, 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.update_progress(total, total, "安装完成", is_push=True)
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) 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() threading.Thread(target=install, daemon=True).start()
@@ -1257,15 +1304,19 @@ class ADKAPKGUI:
def install(): def install():
self.show_progress(True, is_push=True) self.show_progress(True, is_push=True)
self.update_progress(50, 100, f"安装中", is_push=True) self.update_progress(50, 100, f"安装中", is_push=True)
self.run_adb_command('adb -d shell setprop vecentek.model 1') try:
success, _ = self.run_adb_command(f'adb -d install -r "{file_path}"') self.run_adb_command('adb -d shell setprop vecentek.model 1')
self.run_adb_command('adb -d shell setprop vecentek.model 0') success, _ = self.run_adb_command(f'adb -d install -r "{file_path}"')
self.update_progress(100, 100, f"完成", is_push=True) self.update_progress(100, 100, f"完成", is_push=True)
if success: if success:
self.log("✓ 安装成功", "SUCCESS") self.log("✓ 安装成功", "SUCCESS")
else: else:
self.log("✗ 安装失败", "ERROR") self.log("✗ 安装失败", "ERROR")
self.show_progress(False, is_push=True) 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() threading.Thread(target=install, daemon=True).start()
@@ -1373,13 +1424,14 @@ class ADKAPKGUI:
if success: if success:
self.log(f"✓ 语言已设置为 {language_name}", "SUCCESS") self.log(f"✓ 语言已设置为 {language_name}", "SUCCESS")
messagebox.showinfo( self.run_on_ui_thread(
messagebox.showinfo,
"设置成功", "设置成功",
f"系统语言已设置为 {language_name}\n\n⚠️ 请重启设备使其生效。" f"系统语言已设置为 {language_name}\n\n⚠️ 请重启设备使其生效。"
) )
else: else:
self.log(f"✗ 语言设置失败: {output}", "ERROR") 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() threading.Thread(target=do_set, daemon=True).start()
@@ -1405,7 +1457,7 @@ class ADKAPKGUI:
if not self.check_device_connection(): if not self.check_device_connection():
return return
if messagebox.askyesno("确认重启", "确定要重启设备吗?"): 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) stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
self.log("设备正在重启...", "INFO") self.log("设备正在重启...", "INFO")
self.update_device_status(False) self.update_device_status(False)
@@ -1435,10 +1487,10 @@ class ADKAPKGUI:
'adb -d shell pm disable-user --user 0 com.incall.apps.softmanager') 'adb -d shell pm disable-user --user 0 com.incall.apps.softmanager')
if success: if success:
self.log("系统升级已禁用", "SUCCESS") self.log("系统升级已禁用", "SUCCESS")
messagebox.showinfo("成功", "系统升级已成功禁用!") self.run_on_ui_thread(messagebox.showinfo, "成功", "系统升级已成功禁用!")
else: else:
self.log("禁用系统升级失败", "ERROR") self.log("禁用系统升级失败", "ERROR")
messagebox.showerror("错误", f"禁用失败:{output}") self.run_on_ui_thread(messagebox.showerror, "错误", f"禁用失败:{output}")
self.show_progress(False, is_push=False) self.show_progress(False, is_push=False)
threading.Thread(target=disable, daemon=True).start() threading.Thread(target=disable, daemon=True).start()
@@ -1452,7 +1504,7 @@ class ADKAPKGUI:
self.refresh_device_status() self.refresh_device_status()
return return
pwd = tk.simpledialog.askstring("调试模式", "请输入调试密码:", show='*', parent=self.root) pwd = simpledialog.askstring("调试模式", "请输入调试密码:", show='*', parent=self.root)
if pwd == "zxch5200": if pwd == "zxch5200":
self.debug_mode = True self.debug_mode = True
self.update_device_status(True, "", True) self.update_device_status(True, "", True)
@@ -1481,35 +1533,71 @@ class ADKAPKGUI:
def install(): def install():
self.show_progress(True, is_push=True) self.show_progress(True, is_push=True)
self.log(f"开始安装 {count} 个APK...", "INFO") self.log(f"开始安装 {count} 个APK...", "INFO")
self.run_adb_command('adb -d shell setprop vecentek.model 1')
success_count = 0 success_count = 0
for i, file_path in enumerate(file_paths, 1): try:
apk_name = Path(file_path).stem self.run_adb_command('adb -d shell setprop vecentek.model 1')
self.update_progress(i, count, f"安装中 ({apk_name})", is_push=True)
success, _ = self.run_adb_command(f'adb -d install -r "{file_path}"') for i, file_path in enumerate(file_paths, 1):
if success: apk_name = Path(file_path).stem
self.log(f"{apk_name}.apk", "SUCCESS") self.update_progress(i, count, f"安装中 ({apk_name})", is_push=True)
success_count += 1 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: else:
self.log(f"{apk_name}.apk", "ERROR") self.log("安装失败", "ERROR")
self.run_on_ui_thread(messagebox.showerror, "安装失败", "所有APK安装失败!")
self.run_adb_command('adb -d shell setprop vecentek.model 0') except Exception as e:
self.update_progress(count, count, "安装完成", is_push=True) self.log(f"安装过程异常: {str(e)}", "ERROR")
self.show_progress(False, is_push=True) self.run_on_ui_thread(messagebox.showerror, "安装失败", f"安装过程异常:{str(e)}")
finally:
if success_count == count: self.run_adb_command('adb -d shell setprop vecentek.model 0')
self.log(f"安装完成:全部 {count} 个成功", "SUCCESS") self.show_progress(False, is_push=True)
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安装失败!")
threading.Thread(target=install, daemon=True).start() 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): def run(self):
"""运行程序""" """运行程序"""
self.root.mainloop() self.root.mainloop()
+2 -2
View File
@@ -62,7 +62,7 @@ copy ..\7za.exe . >nul
if exist "..\app.ico" copy "..\app.ico" . >nul if exist "..\app.ico" copy "..\app.ico" . >nul
echo [5/6] PyInstaller... 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 ( if errorlevel 1 (
cd .. cd ..
echo [ERROR] PyInstaller failed echo [ERROR] PyInstaller failed
@@ -79,7 +79,7 @@ goto :DONE
:NORMAL :NORMAL
cd /d "%~dp0" cd /d "%~dp0"
echo [INFO] Normal PyInstaller... 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 :DONE
echo. echo.
+2 -2
View File
@@ -62,7 +62,7 @@ copy ..\7za.exe . >nul
if exist "..\app.ico" copy "..\app.ico" . >nul if exist "..\app.ico" copy "..\app.ico" . >nul
echo [5/6] PyInstaller... 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 ( if errorlevel 1 (
cd .. cd ..
echo [ERROR] PyInstaller failed echo [ERROR] PyInstaller failed
@@ -79,7 +79,7 @@ goto :DONE
:NORMAL :NORMAL
cd /d "%~dp0" cd /d "%~dp0"
echo [INFO] Normal PyInstaller... 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 :DONE
echo. echo.
+2 -2
View File
@@ -62,7 +62,7 @@ copy ..\7za.exe . >nul
if exist "..\app.ico" copy "..\app.ico" . >nul if exist "..\app.ico" copy "..\app.ico" . >nul
echo [5/6] PyInstaller... 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 ( if errorlevel 1 (
cd .. cd ..
echo [ERROR] PyInstaller failed echo [ERROR] PyInstaller failed
@@ -79,7 +79,7 @@ goto :DONE
:NORMAL :NORMAL
cd /d "%~dp0" cd /d "%~dp0"
echo [INFO] Normal PyInstaller... 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 :DONE
echo. echo.
+2 -2
View File
@@ -62,7 +62,7 @@ copy ..\7za.exe . >nul
if exist "..\app.ico" copy "..\app.ico" . >nul if exist "..\app.ico" copy "..\app.ico" . >nul
echo [5/6] PyInstaller... 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 ( if errorlevel 1 (
cd .. cd ..
echo [ERROR] PyInstaller failed echo [ERROR] PyInstaller failed
@@ -79,7 +79,7 @@ goto :DONE
:NORMAL :NORMAL
cd /d "%~dp0" cd /d "%~dp0"
echo [INFO] Normal PyInstaller... 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 :DONE
echo. echo.