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