diff --git a/.spec b/.spec index 9905e9b..3b258bd 100644 --- a/.spec +++ b/.spec @@ -1,47 +1,47 @@ -# -*- mode: python ; coding: utf-8 -*- -from PyInstaller.utils.hooks import collect_all - -datas = [('adb.exe', '.'), ('AdbWinApi.dll', '.'), ('AdbWinUsbApi.dll', '.'), ('7za.exe', '.')] -binaries = [] -hiddenimports = ['queue', 'threading', 'tkinter', 'zipfile', 'json', 'urllib'] -tmp_ret = collect_all('tkinter') -datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2] - - -a = Analysis( - ['app-yidong.py'], - pathex=[], - binaries=binaries, - datas=datas, - hiddenimports=hiddenimports, - hookspath=[], - hooksconfig={}, - runtime_hooks=[], - excludes=[], - noarchive=False, - optimize=0, -) -pyz = PYZ(a.pure) - -exe = EXE( - pyz, - a.scripts, - a.binaries, - a.datas, - [], - name='', - debug=False, - bootloader_ignore_signals=False, - strip=False, - upx=True, - upx_exclude=[], - runtime_tmpdir=None, - console=False, - disable_windowed_traceback=False, - argv_emulation=False, - target_arch=None, - codesign_identity=None, - entitlements_file=None, - uac_admin=True, - icon=['app.ico'], -) +# -*- mode: python ; coding: utf-8 -*- +from PyInstaller.utils.hooks import collect_all + +datas = [('adb.exe', '.'), ('AdbWinApi.dll', '.'), ('AdbWinUsbApi.dll', '.'), ('7za.exe', '.')] +binaries = [] +hiddenimports = ['queue', 'threading', 'tkinter', 'tkinter.simpledialog', 'zipfile', 'json', 'urllib', 'urllib.parse'] +tmp_ret = collect_all('tkinter') +datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2] + + +a = Analysis( + ['S05_fixed.py'], + pathex=[], + binaries=binaries, + datas=datas, + hiddenimports=hiddenimports, + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=[], + noarchive=False, + optimize=0, +) +pyz = PYZ(a.pure) + +exe = EXE( + pyz, + a.scripts, + a.binaries, + a.datas, + [], + name='', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + upx_exclude=[], + runtime_tmpdir=None, + console=False, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, + uac_admin=True, + icon=['app.ico'], +) diff --git a/7-zip.chm b/7-zip.chm new file mode 100644 index 0000000..51ed3a7 Binary files /dev/null and b/7-zip.chm differ diff --git a/7za.dll b/7za.dll new file mode 100644 index 0000000..17a3e94 Binary files /dev/null and b/7za.dll differ diff --git a/7zxa.dll b/7zxa.dll new file mode 100644 index 0000000..499d578 Binary files /dev/null and b/7zxa.dll differ diff --git a/CS55-Q05_Installer.exe b/CS55-Q05_Installer.exe new file mode 100644 index 0000000..cf70267 Binary files /dev/null and b/CS55-Q05_Installer.exe differ diff --git a/Changan_Yidong-Installer.exe b/Changan_Yidong-Installer.exe new file mode 100644 index 0000000..2cdfb24 Binary files /dev/null and b/Changan_Yidong-Installer.exe differ diff --git a/Deepal_S05-Installer.exe b/Deepal_S05-Installer.exe new file mode 100644 index 0000000..d045c3c Binary files /dev/null and b/Deepal_S05-Installer.exe differ diff --git a/Far/7-ZipEng.hlf b/Far/7-ZipEng.hlf new file mode 100644 index 0000000..87b8df8 --- /dev/null +++ b/Far/7-ZipEng.hlf @@ -0,0 +1,85 @@ +.Language=English,English +.PluginContents=7-Zip Plugin + +@Contents +$^#7-Zip Plugin 26.01# +$^#Copyright (c) 1999-2026 Igor Pavlov# + This FAR module performs transparent #archive# processing. +Files in the archive are handled in the same manner as if they +were in a folder. + + ~Extracting from the archive~@Extract@ + + ~Add files to the archive~@Update@ + + ~7-Zip Plugin configuration~@Config@ + + + Web site: #www.7-zip.org# + +@Extract +$ #Extracting from the archive# + +In this dialog you may enter extracting mode. + + Path mode + + #Full pathnames# Extract files with full pathnames. + + #Current pathnames# Extract files with all relative paths. + + #No pathnames# Extract files without folder paths. + + + Overwrite mode + + #Ask before overwrite# Ask before overwriting existing files. + + #Overwrite without prompt# Overwrite existing files without prompt. + + #Skip existing files# Skip extracting of existing files. + + + Files + + #Selected files# Extract only selected files. + + #All files# Extract all files from archive. + +@Update +$ #Add files to the archive# + +This dialog allows you to specify options for process of updating archive. + + + Compression method + + #Store# Files will be copied to archive without compression. + + #Normal# Files will be compressed. + + #Maximum# Files will be compressed with method that gives + maximum compression ratio. + + + Update mode + + #Add and replace files# Add all specified files to the archive. + + #Update and add files# Update older files in the archive and add + files that are new to the archive. + + #Freshen existing files# Update specified files in the archive that + are older than the selected disk files. + + #Synchronize files# Replace specified files only if + added files are newer. Always add those + files, which are not present in the + archive. Delete from archive those files, + which are not present on the disk. + +@Config +$ #7-Zip Plugin configuration# + In this dialog you may change following parameters: + + #Plugin is used by default# Plugin is used by default. diff --git a/Far/7-ZipEng.lng b/Far/7-ZipEng.lng new file mode 100644 index 0000000..a79a856 --- /dev/null +++ b/Far/7-ZipEng.lng @@ -0,0 +1,220 @@ +.Language=English,English + +"Ok" +"&Cancel" + +"Warning" +"Error" + +"Format" + +"Properties" + +"Yes" +"No" + +"Get password" +"Enter password" + +"Extract" +"&Extract to" + +"Path mode" +"&Full pathnames" +"C&urrent pathnames" +"&No pathnames" + +"Overwrite mode" +"As&k before overwrite" +"&Overwrite without prompt" +"Sk&ip existing files" +"A&uto rename" +"A&uto rename existing files" + +"Extract" +"&Selected files" +"A&ll files" + +"&Password" + +"Extr&act" +"&Cancel" + +"Can not open output file '%s'." + +"Unsupported compression method for '%s'." +"CRC failed in '%s'." +"Data error in '%s'." +"CRC failed in encrypted file '%s'. Wrong password?" +"Data error in encrypted file '%s'. Wrong password?" + +"Confirm File Replace" +"Destination folder already contains processed file." +"Would you like to replace the existing file" +"with this one" + +"bytes" +"modified on" + + +"&Yes" +"Yes to &All" +"&No" +"No to A&ll" +"A&uto rename" +"&Cancel" + + +"Update operations are not supported for this archive." + + +"Delete from archive" +"Delete \"%.40s\" from the archive" +"Delete selected files from the archive" +"Delete %d files from the archive" +"Delete" +"Cancel" + +"Add files to archive" + +"Add to %s a&rchive:" + +"Compression method" +"&Store" +"Fas&test" +"&Fast" +"&Normal" +"&Maximum" +"&Ultra" + +"Update mode" +"A&dd and replace files" +"&Update and add files" +"&Freshen existing files" +"S&ynchronize files" + +"&Add" +"Se&lect archiver" + +"Select archive format" + +"Wait" +"Reading the archive" +"Extracting from the archive" +"Deleting from the archive" +"Updating the archive" + +"Move operation is not supported" + +"7-Zip" +"7-Zip (add to archive)" + +"7-Zip" + +"Plugin is used by default" + +"0" +"1" +"2" +"Path" +"Name" +"Extension" +"Is Folder" +"Size" +"Packed Size" +"Attributes" +"Created" +"Accessed" +"Modified" +"Solid" +"Commented" +"Encrypted" +"Splited Before" +"Splited After" +"Dictionary Size" +"CRC" +"Type" +"Anti" +"Method" +"Host OS" +"File System" +"User" +"Group" +"Block" +"Comment" +"Position" +"Path Prefix" +"Folders" +"Files" +"Version" +"Volume" +"Multivolume" +"Offset" +"Links" +"Blocks" +"Volumes" +"Time Type" +"64-bit" +"Big-endian" +"CPU" +"Physical Size" +"Headers Size" +"Checksum" +"Characteristics" +"Virtual Address" +"ID" +"Short Name" +"Creator Application" +"Sector Size" +"Mode" +"Symbolic Link" +"Error" +"Total Size" +"Free Space" +"Cluster Size" +"Label" +"Local Name" +"Provider" +"NT Security" +"Alternate Stream" +"Aux" +"Deleted" +"Tree" +"SHA-1" +"SHA-256" +"Error Type" +"Errors" +"Errors" +"Warnings" +"Warning" +"Streams" +"Alternate Streams" +"Alternate Streams Size" +"Virtual Size" +"Unpack Size" +"Total Physical Size" +"Volume Index" +"SubType" +"Short Comment" +"Code Page" +"Is not archive type" +"Physical Size can't be detected" +"Zeros Tail Is Allowed" +"Tail Size" +"Embedded Stub Size" +"Link" +"Hard Link" +"iNode" +"Stream ID" +"Read-only" +"Out Name" +"Copy Link" +"Arc File Name" +"Is Hash" +"Metadata Changed" +"User ID" +"Group ID" +"Device Major" +"Device Minor" +"Dev Major" +"Dev Minor" diff --git a/Far/7-ZipFar.dll b/Far/7-ZipFar.dll new file mode 100644 index 0000000..0cd3547 Binary files /dev/null and b/Far/7-ZipFar.dll differ diff --git a/Far/7-ZipFar64.dll b/Far/7-ZipFar64.dll new file mode 100644 index 0000000..6c6dba1 Binary files /dev/null and b/Far/7-ZipFar64.dll differ diff --git a/Far/7-ZipRus.hlf b/Far/7-ZipRus.hlf new file mode 100644 index 0000000..fb83fd6 --- /dev/null +++ b/Far/7-ZipRus.hlf @@ -0,0 +1,84 @@ +.Language=Russian,Russian (᪨) +.PluginContents= 7-Zip + +@Contents +$^#7-Zip Plugin 26.01# +$^#Copyright (c) 1999-2026 Igor Pavlov# + FAR ࠡ #娢#. 짮⥫ +䠩 娢 ⫨ 䠩 . + + + ~ᯠ 䠩 娢~@Extract@ + + ~ 䠩 娢~@Update@ + + ~ࠬ ࠡ 娢~@Config@ + + + Web site: #www.7-zip.org# + +@Extract +$ #ᯠ 䠩 娢# + ⮬ ᯠ 䠩 +० ᯠ. + + + + # # ᯠ 䠩 묨 ﬨ. + + #⭮⥫ # ᯠ ⭮⥫묨 ﬨ. + + # ⥩# ᯠ ⥩. + + + १ + + #訢 ⢥ত# 訢 ⢥ত + १ 饣 䠩. + + # ⢥ত# 騩 䠩 + ⢥ত. + + #ய᪠# ய᪠ 騥 䠩. + + + ᯠ + + #࠭ 䠩# ᯠ ⮫쪮 뤥 䠩 娢. + + # 䠩# ᯠ 䠩 娢. + +@Update +$ # 䠩 娢# + + ⮬ ० 㯠. + + + ⮤ ᦠ: + + # ᦠ# ᪮஢ ᦠ. + + #ଠ쭮 ᦠ⨥# ᦠ. + + #ᨬ쭮 ᦠ⨥# ᦠ ᨬ쭮 + ⥯ ᦠ. + + + : + + # # ࠭ 䠩 娢. + + # # ॢ訥 䠩 娢 + 䠩, 娢. + + ## ॢ訥 䠩 娢. + + #஭஢# ஭஢ ᮤন 娢 + ࠭묨 䠩. + + +@Config +$ #ࠬ ࠡ 7-Zip# + ⮬ ᫥騥 ࠬ: + + # ᯮ 㬮砭# ᯮ 㬮砭 diff --git a/Far/7-ZipRus.lng b/Far/7-ZipRus.lng new file mode 100644 index 0000000..f989945 --- /dev/null +++ b/Far/7-ZipRus.lng @@ -0,0 +1,220 @@ +.Language=Russian,Russian (᪨) + +"த" +"&⬥" + +"।०" +"訡" + +"ଠ" + +"⢠" + +"" +"" + +" ஫" +" ஫" + +"ᯠ" +"&ᯠ " + +"" +"& " +"&⭮⥫ " +"& ⥩" + +"१" +"&訢 ⢥ত" +"& ⢥ত" +"ய&᪠" +"२ ⮬." +"२. ⮬. ." + +"ᯠ" +"&࠭ 䠩" +" &䠩" + +"&஫" + +"&ᯠ" +"&⬥" + +" 䠩 '%s'." + +"ন ⮤ ᦠ 䠩 '%s'." +"訡 CRC '%s'." +"訡 '%s'." +"訡 CRC ஢ 䠩 '%s'. ஫?" +"訡 ஢ 䠩 '%s'. ஫?" + +"⢥न 䠩" +" 㦥 ᮤন ࠡ뢠 䠩." +" 騩 䠩" +"᫥騬 䠩" + +"" +"" + + +"&" +" &" +"&" +" &" +"२ ⮬᪨" +"&⬥" + + +" ⮣ 娢 樨 ন." + + +" 娢" +" \"%.40s\" 娢" +" ࠭ 䠩 娢" +" %d 䠩 娢" +"" +"⬥" + +" 䠩 娢" + +" %s &娢" + +"⮤ ᦠ" +" ᦠ" +"⭮" +"" +"ଠ" +"ᨬ" +"" + +" " +" " +" " +"" +"஭஢" + +"&" +"&娢" + +"롮 娢 ଠ" + +"" +"⥭ 娢" +"ᯠ" +"" +"" + +"६饭 䠩 ন" + +"7-Zip" +"7-Zip ( 娢)" + +"7-Zip configuration" + +" ᯮ 㬮砭" + +"0" +"1" +"2" +"" +"" +"७" +"" +"" +"" +"ਡ" +"" +"" +"" +"뢭" +"਩" +"஢" +" " +" ᫥" +"" +"CRC" +"" +"" +"⮤" +"⥬" +" ⥬" +"짮⥫" +"㯯" +"" +"਩" +"" +"" +"" +"" +"" +"" +"⮬" +"饭" +"뫮" +"" +"" +"Time Type" +"64-bit" +"Big-endian" +"" +"᪨ " +" " +". 㬬" +"ࠪ⨪" +"㠫 " +"ID" +"⪮ " +"ணࠬ" +" ᥪ" +"" +"쭠 뫪" +"訡" +"" +"" +" " +"⪠" +"쭮 " +"஢" +"NT ᭮" +"ୠ⨢ ⮪" +"Aux" +"" +"ॢ" +"SHA-1" +"SHA-256" +" 訡" +"訡" +"訡" +"।०" +"।०" +"⮪" +"ୠ⨢ ⮪" +" ୠ⨢ ⮪" +"㠫 " +"ᯠ " +"騩 ᪨ " +" " +"⨯" +"⪨ ਩" +" ࠭" +"Is not archive type" +"Physical Size can't be detected" +"Zeros Tail Is Allowed" +" ⪠" +" ஥ " +"뫪" +"⪠ 뫪" +"iNode" +"ID ⮪" +"쪮 ⥭" +"Out Name" +"뫪 ஢" +"Arc File Name" +"Is Hash" +" ⠤" +"User ID" +"Group ID" +"Device Major" +"Device Minor" +"Dev Major" +"Dev Minor" diff --git a/Far/7zToFar.ini b/Far/7zToFar.ini new file mode 100644 index 0000000..0cd8336 --- /dev/null +++ b/Far/7zToFar.ini @@ -0,0 +1,67 @@ +; 7z supporting for MutiArc in Far +; Append the following strings to file +; ..\Program Files\Far\Plugins\MultiArc\Formats\Custom.ini + +[7z] +TypeName=7z +ID=37 7A BC AF 27 1C +IDPos= +IDOnly=1 +Extension=7z +List=7z l -- %%AQ +Start="^-----" +End="^-----" +Format0="yyyy tt dd hh mm ss aaaaa zzzzzzzzzzzz pppppppppppp nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn" +Extract=7z x {-p%%P} -r0 -y -scsDOS -i@%%LQMN -- %%A +ExtractWithoutPath=7z e {-p%%P} -r0 -y -scsDOS -i@%%LQMN -- %%A +Test=7z t {-p%%P} -r0 -scsDOS -i@%%LQMN -- %%A +Delete=7z d {-p%%P} -r0 -ms=off -scsDOS -i@%%LQMN -- %%A +Add=7z a {-p%%P} -r0 -t7z {%%S} -scsDOS -i@%%LQMN -- %%A +AddRecurse=7z a {-p%%P} -r0 -t7z {%%S} -scsDOS -i@%%LQMN -- %%A +AllFilesMask="*" + +[rpm] +TypeName=rpm +ID=ED AB EE DB +IDPos= +IDOnly=1 +Extension=rpm +List=7z l -- %%AQ +Start="^-----" +End="^-----" +Format0="yyyy tt dd hh mm ss aaaaa zzzzzzzzzzzz pppppppppppp nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn" +Extract=7z x {-p%%P} -r0 -y -scsDOS -i@%%LQMN -- %%A +ExtractWithoutPath=7z e {-p%%P} -r0 -y -scsDOS -i@%%LQMN -- %%A +Test=7z t {-p%%P} -r0 -scsDOS -i@%%LQMN -- %%A +AllFilesMask="*" + +[cpio] +TypeName=cpio +ID= +IDPos= +IDOnly=0 +Extension=cpio +List=7z l -- %%AQ +Start="^-----" +End="^-----" +Format0="yyyy tt dd hh mm ss aaaaa zzzzzzzzzzzz pppppppppppp nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn" +Extract=7z x {-p%%P} -r0 -y -scsDOS -i@%%LQMN -- %%A +ExtractWithoutPath=7z e {-p%%P} -r0 -y -scsDOS -i@%%LQMN -- %%A +Test=7z t {-p%%P} -r0 -scsDOS -i@%%LQMN -- %%A +AllFilesMask="*" + +[deb] +TypeName=deb +ID= +IDPos= +IDOnly=0 +Extension=deb +List=7z l -- %%AQ +Start="^-----" +End="^-----" +Format0="yyyy tt dd hh mm ss aaaaa zzzzzzzzzzzz pppppppppppp nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn" +Extract=7z x {-p%%P} -r0 -y -scsDOS -i@%%LQMN -- %%A +ExtractWithoutPath=7z e {-p%%P} -r0 -y -scsDOS -i@%%LQMN -- %%A +Test=7z t {-p%%P} -r0 -scsDOS -i@%%LQMN -- %%A +AllFilesMask="*" + diff --git a/Far/far7z.reg b/Far/far7z.reg new file mode 100644 index 0000000..dd4610d --- /dev/null +++ b/Far/far7z.reg @@ -0,0 +1,67 @@ +REGEDIT4 + +[HKEY_LOCAL_MACHINE\SOFTWARE\Far\Plugins\MultiArc\ZIP] +"Extract"="7z x {-p%%P} -r0 -y {-w%%W} -scsDOS -i@%%LQMN -- %%A" +"ExtractWithoutPath"="7z e {-p%%P} -r0 -y {-w%%W} -scsDOS -i@%%LQMN -- %%A" +"Test"="7z t {-p%%P} -r0 -scsDOS -i@%%LQMN -- %%A" +"Delete"="7z d {-p%%P} -r0 {-w%%W} -scsDOS -i@%%LQMN -- %%A" +"Add"="7z a {-p%%P} -r0 -tzip {-w%%W} {%%S} -scsDOS -i@%%LQMN -- %%A" +"AddRecurse"="7z a {-p%%P} -r0 -tzip {-w%%W} {%%S} -scsDOS -i@%%LQMN -- %%A" +"AllFilesMask"="*" + +[HKEY_LOCAL_MACHINE\SOFTWARE\Far\Plugins\MultiArc\TAR] +"Extract"="7z x -r0 -y {-w%%W} -scsDOS -i@%%LQMN -- %%A" +"ExtractWithoutPath"="7z e -r0 -y {-w%%W} -scsDOS -i@%%LQMN -- %%A" +"Test"="7z t -r0 -scsDOS -i@%%LQMN -- %%A" +"Delete"="7z d -r0 {-w%%W} -scsDOS -i@%%LQMN -- %%A" +"Add"="7z a -r0 -y -ttar {-w%%W} {%%S} -scsDOS -i@%%LQMN -- %%A" +"AddRecurse"="7z a -r0 -y -ttar {-w%%W} {%%S} -scsDOS -i@%%LQMN -- %%A" +"AllFilesMask"="*" + +[HKEY_LOCAL_MACHINE\SOFTWARE\Far\Plugins\MultiArc\GZIP] +"Extract"="7z x -r0 -y {-w%%W} -scsDOS -i@%%LQMN -- %%A" +"ExtractWithoutPath"="7z e -r0 -y {-w%%W} -scsDOS -i@%%LQMN -- %%A" +"Test"="7z t -r0 -scsDOS -i@%%LQMN -- %%A" +"Delete"="7z d -r0 {-w%%W} -scsDOS -i@%%LQMN -- %%A" +"Add"="7z a -r0 -tgzip {-w%%W} {%%S} -scsDOS -i@%%LQMN -- %%A" +"AddRecurse"="7z a -r0 -tgzip {-w%%W} {%%S} -scsDOS -i@%%LQMN -- %%A" +"AllFilesMask"="*" + +[HKEY_LOCAL_MACHINE\SOFTWARE\Far\Plugins\MultiArc\BZIP] +"Extract"="7z x -r0 -y {-w%%W} -scsDOS -i@%%LQMN -- %%A" +"ExtractWithoutPath"="7z e -r0 -y {-w%%W} -scsDOS -i@%%LQMN -- %%A" +"Test"="7z t -r0 -scsDOS -i@%%LQMN -- %%A" +"Delete"="7z d -r0 {-w%%W} -scsDOS -i@%%LQMN -- %%A" +"Add"="7z a -r0 -tbzip2 {-w%%W} {%%S} -scsDOS -i@%%LQMN -- %%A" +"AddRecurse"="7z a -r0 -tbzip2 {-w%%W} {%%S} -scsDOS -i@%%LQMN -- %%A" +"AllFilesMask"="*" + +[HKEY_LOCAL_MACHINE\SOFTWARE\Far\Plugins\MultiArc\ARJ] +"Extract"="7z x -r0 -y {-w%%W} -scsDOS -i@%%LQMN -- %%A" +"ExtractWithoutPath"="7z e -r0 -y {-w%%W} -scsDOS -i@%%LQMN -- %%A" +"Test"="7z t -r0 -scsDOS -i@%%LQMN -- %%A" +"AllFilesMask"="*" + +[HKEY_LOCAL_MACHINE\SOFTWARE\Far\Plugins\MultiArc\CAB] +"Extract"="7z x -r0 -y {-w%%W} -scsDOS -i@%%LQMN -- %%A" +"ExtractWithoutPath"="7z e -r0 -y {-w%%W} -scsDOS -i@%%LQMN -- %%A" +"Test"="7z t -r0 -scsDOS -i@%%LQMN -- %%A" +"AllFilesMask"="*" + +[HKEY_LOCAL_MACHINE\SOFTWARE\Far\Plugins\MultiArc\LZH] +"Extract"="7z x -r0 -y {-w%%W} -scsDOS -i@%%LQMN -- %%A" +"ExtractWithoutPath"="7z e -r0 -y {-w%%W} -scsDOS -i@%%LQMN -- %%A" +"Test"="7z t -r0 -scsDOS -i@%%LQMN -- %%A" +"AllFilesMask"="*" + +[HKEY_LOCAL_MACHINE\SOFTWARE\Far\Plugins\MultiArc\RAR] +"Extract"="7z x -r0 -y {-w%%W} -scsDOS -i@%%LQMN -- %%A" +"ExtractWithoutPath"="7z e -r0 -y {-w%%W} -scsDOS -i@%%LQMN -- %%A" +"Test"="7z t -r0 -scsDOS -i@%%LQMN -- %%A" +"AllFilesMask"="*" + +[HKEY_LOCAL_MACHINE\SOFTWARE\Far\Plugins\MultiArc\Z(Unix)] +"Extract"="7z x -r0 -y {-w%%W} -scsDOS -i@%%LQMN -- %%A" +"ExtractWithoutPath"="7z e -r0 -y {-w%%W} -scsDOS -i@%%LQMN -- %%A" +"Test"="7z t -r0 -scsDOS -i@%%LQMN -- %%A" +"AllFilesMask"="*" diff --git a/Far/far7z.txt b/Far/far7z.txt new file mode 100644 index 0000000..0ddbb59 --- /dev/null +++ b/Far/far7z.txt @@ -0,0 +1,73 @@ +7-Zip Plugin for FAR Manager +---------------------------- + +FAR Manager is a file manager working in text mode. +You can download "FAR Manager" from site: +http://www.farmanager.com + +Files: + +far7z.txt - This file +far7z.reg - Regisrty file for MultiArc Plugin +7zToFar.ini - Supporting 7z for MultiArc Plugin +7-ZipFar.dll - 7-Zip Plugin for FAR Manager +7-ZipEng.hlf - Help file in English for FAR Manager +7-ZipRus.hlf - Help file in Russian for FAR Manager +7-ZipEng.lng - Plugin message strings in English for FAR Manager +7-ZipRus.lng - Plugin message strings in Russian for FAR Manager + +There are two ways to use 7-Zip with FAR Manager: + + 1) Via 7-Zip FAR Plugin (it's recommended way). + 2) Via standard MultiArc Plugin. + + +7-Zip FAR Plugin +~~~~~~~~~~~~~~~~ + +7-Zip FAR Plugin is first level plugin for FAR Manager, like MultiArc plugin. +It very fast extracts and updates files in archive, since it doesn't use +external programs. It supports all formats supported by 7-Zip: +7z, ZIP, RAR, CAB, ARJ, GZIP, BZIP2, Z, TAR, CPIO, RPM and DEB. + +To install 7-Zip FAR Plugin: + 1) Create "7-Zip" folder in ...\Program Files\Far\Plugins folder. + 2) Copy all files from "FAR" folder of this package to created folder. + 3) Install 7-Zip, or copy 7z.dll from 7-Zip to Program Files\Far\Plugins\7-Zip\ + 4) Restart FAR. + +Also you must enable "OEM plugin support" option in FAR Manager: + + 1) F9 / Options / Plugins manager settings / check on "OEM plugin support". + 2) F9 / Options / Save setup. + 4) Restart FAR + +You can open archives with one of the following ways: + * Pressing Enter. + * Pressing Ctrl-PgDown. + * Pressing F11 and selecting 7-Zip item. + + +You can create new archives with 7-Zip by pressing F11 and +selecting 7-Zip (add to archive) item. + +If you think that some operations with archives is better to do with MultiArc Plugin, +you can disable 7-Zip plugin via Options / Pligin configuration / 7-Zip. In such mode +opening archives by pressing Enter and Ctrl-PgDown will start MultiArc Plugin. And +if you want to open archive with 7-Zip, press F11 and select 7-Zip item. + + +Using command line 7-Zip via MultiArc Plugin +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you want to use 7-Zip via MultiArc Plugin, you must +register file far7z.reg. + +If you want to use 7z archives via MultiArc Plugin, you must +append contents of file Far\7zToFar.ini to file +..\Program Files\Far\Plugins\MultiArc\Formats\Custom.ini. + + +If you want to cancel using 7-Zip by MultiArc, just remove lines that contain +7-Zip (7z) program name from HKEY_LOCAL_MACHINE\SOFTWARE\Far\Plugins\MultiArc\ZIP +registry key. diff --git a/License.txt b/License.txt new file mode 100644 index 0000000..65ff908 --- /dev/null +++ b/License.txt @@ -0,0 +1,123 @@ + 7-Zip Extra + ~~~~~~~~~~~ + License for use and distribution + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Copyright (C) 1999-2026 Igor Pavlov. + + The licenses for files are: + + - 7za.exe: + - The "GNU LGPL" as main license for most of the code + - The "BSD 3-clause License" for some code + - The "BSD 2-clause License" for some code + - All other files: the "GNU LGPL". + + Redistributions in binary form must reproduce related license information from this file. + + Note: + You can use 7-Zip Extra on any computer, including a computer in a commercial + organization. You don't need to register or pay for 7-Zip. + + It is allowed to digitally sign DLL and EXE files included into this package + with arbitrary signatures of third parties. + + +GNU LGPL information +-------------------- + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You can receive a copy of the GNU Lesser General Public License from + http://www.gnu.org/ + + + +BSD 3-clause License in 7-Zip code +---------------------------------- + + The "BSD 3-clause License" is used for the following code in 7za.exe + - ZSTD data decompression. + that code was developed using original zstd decoder code as reference code. + The original zstd decoder code was developed by Facebook Inc, + that also uses the "BSD 3-clause License". + + Copyright (c) Facebook, Inc. All rights reserved. + Copyright (c) 2023-2025 Igor Pavlov. + +Text of the "BSD 3-clause License" +---------------------------------- + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors may + be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +--- + + + + +BSD 2-clause License in 7-Zip code +---------------------------------- + + The "BSD 2-clause License" is used for the XXH64 code in 7za.exe. + + XXH64 code in 7-Zip was derived from the original XXH64 code developed by Yann Collet. + + Copyright (c) 2012-2021 Yann Collet. + Copyright (c) 2023-2025 Igor Pavlov. + +Text of the "BSD 2-clause License" +---------------------------------- + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +--- diff --git a/S05.py b/S05.py index ad77e37..4692f1f 100644 --- a/S05.py +++ b/S05.py @@ -28,7 +28,7 @@ class ADKAPKGUI: self.root.resizable(True, True) # 设置颜色主题 - self.colors = { + self.colors_dark = { 'bg_dark': '#1e1e2e', 'bg_light': '#2a2a3e', 'accent': '#6c5ce7', @@ -41,6 +41,92 @@ class ADKAPKGUI: 'text_secondary': '#b2bec3', 'border': '#3d3d5e' } + self.colors_light = { + 'bg_dark': '#f5f5f5', + 'bg_light': '#ffffff', + 'accent': '#6c5ce7', + 'accent_hover': '#5b4bc4', + 'success': '#00b894', + 'error': '#d63031', + 'warning': '#e17055', + 'info': '#0984e3', + 'text': '#2d3436', + 'text_secondary': '#636e72', + 'border': '#dfe6e9' + } + self.colors = dict(self.colors_dark) + self.theme = 'dark' + + # 多语言 + self.lang = 'zh' + self.T = { + 'zh': { + 'title': '深蓝S05多语言安装', + 'btn_root': '🔓 获取权限', + 'btn_push': '📦 刷入语言包', + 'btn_install': '📱 安装App', + 'btn_language': '🌐 语言设置', + 'btn_timezone': '⏰ 时区设置', + 'btn_settings': '⚙️ 安卓设置', + 'btn_reboot': '🔄 重启设备', + 'btn_disable_upgrade': '❌ 禁用升级', + 'btn_clear_log': '🗑 清空日志', + 'device_label': '设备:', + 'vin_label': 'VIN码:', + 'auth_label': '授权:', + 'log_title': '📋 运行日志', + 'status_ready': '就绪', + 'status_connected': '已连接', + 'status_disconnected': '未连接', + 'status_detecting': '未检测', + 'vin_none': '未获取', + 'auth_none': '未验证', + 'auth_yes': '已授权', + 'auth_no': '未授权', + 'btn_refresh': '🔄 检查', + 'hint_factory': '🔧 工厂模式:拨号 *#*#888 ,密码:369875', + 'theme_dark': '🌙 暗色', + 'theme_light': '☀️ 亮色', + 'lang_zh': '中', + 'lang_en': 'EN', + 'switch_lang': '语言 / Language', + 'switch_theme': '切换主题', + 'about_company': '宜宾科宜科技有限公司 - 出口改装一站式服务', + }, + 'en': { + 'title': 'Shenlan S05 Multi-Language', + 'btn_root': '🔓 Get Root', + 'btn_push': '📦 Flash Lang Pkg', + 'btn_install': '📱 Install App', + 'btn_language': '🌐 Language', + 'btn_timezone': '⏰ Timezone', + 'btn_settings': '⚙️ Settings', + 'btn_reboot': '🔄 Reboot', + 'btn_disable_upgrade': '❌ Disable OTA', + 'btn_clear_log': '🗑 Clear Log', + 'device_label': 'Device:', + 'vin_label': 'VIN:', + 'auth_label': 'Auth:', + 'log_title': '📋 Log', + 'status_ready': 'Ready', + 'status_connected': 'Connected', + 'status_disconnected': 'Disconnected', + 'status_detecting': 'Detecting', + 'vin_none': 'None', + 'auth_none': 'Unknown', + 'auth_yes': 'Authorized', + 'auth_no': 'Unauthorized', + 'btn_refresh': '🔄 Check', + 'hint_factory': '🔧 Factory Mode: dial *#*#888, password: 369875', + 'theme_dark': '🌙 Dark', + 'theme_light': '☀️ Light', + 'lang_zh': '中', + 'lang_en': 'EN', + 'switch_lang': 'Language', + 'switch_theme': 'Theme', + 'about_company': 'Yibin Keyi Technology - Export Modification Service', + } + } # 从 exe/py 所在目录查找资源文件 self.base_dir = Path(sys.executable).parent if getattr(sys, 'frozen', False) else Path(__file__).parent @@ -393,6 +479,25 @@ class ADKAPKGUI: bg=self.colors['bg_light']) self.status_text.pack(side=tk.LEFT, padx=10) + # 主题和语言切换按钮 + self.btn_theme_switch = tk.Button(bottom_status, text="🌙 暗色", + command=self.toggle_theme, + font=('Microsoft YaHei', 8), + fg=self.colors['accent'], + bg=self.colors['bg_light'], + relief=tk.FLAT, + cursor='hand2') + self.btn_theme_switch.pack(side=tk.RIGHT, padx=5) + + self.btn_lang_switch = tk.Button(bottom_status, text="EN", + command=self.toggle_lang, + font=('Microsoft YaHei', 8, 'bold'), + fg=self.colors['accent'], + bg=self.colors['bg_light'], + relief=tk.FLAT, + cursor='hand2') + self.btn_lang_switch.pack(side=tk.RIGHT, padx=5) + # 调试模式快捷键 self.root.bind('', self._toggle_debug) @@ -443,6 +548,81 @@ class ADKAPKGUI: """将函数调度到主线程执行,确保线程安全""" self.root.after(0, func, *args, **kwargs) + def t(self, key): + """获取翻译文本""" + return self.T.get(self.lang, self.T['zh']).get(key, key) + + def toggle_lang(self): + """切换语言""" + self.lang = 'en' if self.lang == 'zh' else 'zh' + self.btn_lang_switch.config(text=self.t('lang_en') if self.lang == 'zh' else self.t('lang_zh')) + self._refresh_ui_texts() + self.log(f"语言已切换为 {'English' if self.lang == 'en' else '中文'}", "INFO") + + def toggle_theme(self): + """切换主题""" + if self.theme == 'dark': + self.colors = dict(self.colors_light) + self.theme = 'light' + self.btn_theme_switch.config(text=self.t('theme_dark')) + else: + self.colors = dict(self.colors_dark) + self.theme = 'dark' + self.btn_theme_switch.config(text=self.t('theme_light')) + self._apply_theme() + + def _apply_theme(self): + """应用当前主题到所有控件""" + c = self.colors + self.root.configure(bg=c['bg_dark']) + style = ttk.Style() + style.configure('TFrame', background=c['bg_dark']) + style.configure('TLabel', background=c['bg_dark'], foreground=c['text']) + style.configure('TLabelframe', background=c['bg_dark'], foreground=c['text']) + style.configure('TLabelframe.Label', background=c['bg_dark'], foreground=c['accent']) + style.configure('TProgressbar', background=c['accent'], troughcolor=c['bg_light'], borderwidth=0) + self.log_text.tag_config('INFO', foreground='#74b9ff') + self.log_text.tag_config('SUCCESS', foreground='#55efc4') + self.log_text.tag_config('ERROR', foreground='#ff7675') + self.log_text.tag_config('WARNING', foreground='#ffeaa7') + self.log_text.tag_config('CMD', foreground='#a29bfe') + if self.theme == 'light': + self.log_text.configure(bg='#ffffff', fg='#2d3436') + else: + self.log_text.configure(bg='#2d2d3d', fg='#e0e0e0') + + def _refresh_ui_texts(self): + """刷新所有UI文本""" + t = self.t + widgets = [ + (getattr(self, 'title_label', None), 'title', None), + (getattr(self, 'subtitle_label', None), 'about_company', None), + (getattr(self, 'btn_root', None), 'btn_root', None), + (getattr(self, 'btn_push', None), 'btn_push', None), + (getattr(self, 'btn_install_all', None), 'btn_install', None), + (getattr(self, 'btn_language', None), 'btn_language', None), + (getattr(self, 'btn_timezone', None), 'btn_timezone', None), + (getattr(self, 'btn_settings', None), 'btn_settings', None), + (getattr(self, 'btn_reboot', None), 'btn_reboot', None), + (getattr(self, 'btn_exit', None), 'btn_disable_upgrade', None), + (getattr(self, 'btn_clear', None), 'btn_clear_log', None), + (getattr(self, 'log_title_label', None), 'log_title', None), + (getattr(self, 'status_text', None), 'status_ready', None), + (getattr(self, 'device_label', None), 'device_label', None), + (getattr(self, 'vin_label_title', None), 'vin_label', None), + (getattr(self, 'auth_label_title', None), 'auth_label', None), + (getattr(self, 'btn_refresh', None), 'btn_refresh', None), + (getattr(self, 'hint_label', None), 'hint_factory', None), + ] + for w, key, _ in widgets: + if w: + w.config(text=t(key)) + self.btn_theme_switch.config(text=t('theme_light') if self.theme == 'dark' else t('theme_dark')) + self.btn_lang_switch.config(text=t('lang_en') if self.lang == 'zh' else t('lang_zh')) + if self.vin: + self._update_device_status_impl(self.device_connected, self.vin, + getattr(self, '_last_authorized', False)) + def _log_impl(self, message, level="INFO"): """日志写入的实际实现(必须在主线程调用)""" timestamp = datetime.now().strftime("%H:%M:%S") @@ -507,23 +687,25 @@ class ADKAPKGUI: def _update_device_status_impl(self, connected, vin, authorized): """设备状态UI更新的实际实现(必须在主线程调用)""" + self._last_authorized = authorized + t = self.t if connected: self.status_indicator.itemconfig(self.status_dot, fill=self.colors['success']) - self.device_status_label.config(text="已连接", fg=self.colors['success']) + self.device_status_label.config(text=t('status_connected'), fg=self.colors['success']) if vin: self.vin_label.config(text=vin, fg=self.colors['success']) if authorized: - self.auth_label.config(text="已授权", fg=self.colors['success']) + self.auth_label.config(text=t('auth_yes'), fg=self.colors['success']) else: - self.auth_label.config(text="未授权", fg=self.colors['error']) + self.auth_label.config(text=t('auth_no'), fg=self.colors['error']) else: - self.vin_label.config(text="未获取", fg=self.colors['error']) - self.auth_label.config(text="未验证", fg=self.colors['error']) + self.vin_label.config(text=t('vin_none'), fg=self.colors['error']) + self.auth_label.config(text=t('auth_none'), fg=self.colors['error']) else: self.status_indicator.itemconfig(self.status_dot, fill=self.colors['error']) - self.device_status_label.config(text="未连接", fg=self.colors['error']) - self.vin_label.config(text="未获取", fg=self.colors['error']) - self.auth_label.config(text="未验证", fg=self.colors['error']) + self.device_status_label.config(text=t('status_disconnected'), fg=self.colors['error']) + self.vin_label.config(text=t('vin_none'), fg=self.colors['error']) + self.auth_label.config(text=t('auth_none'), fg=self.colors['error']) def check_device_connection(self): """检查设备是否连接""" @@ -608,7 +790,15 @@ class ADKAPKGUI: def extract_package_silent(self): """静默解压语言包(带进度)""" if not self.package_file.exists(): - self.log(f"未找到资源包 ({self.package_file.name})", "ERROR") + self.log(f"错误:未找到资源包 ({self.package_file})", "ERROR") + return False + + if not self.extract_password: + self.log("错误:解压密码未设置", "ERROR") + return False + + if not os.path.exists(self.sz): + self.log(f"错误:未找到 7za.exe ({self.sz})", "ERROR") return False try: @@ -631,19 +821,32 @@ class ADKAPKGUI: 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") - # 使用 7za 高速解压 + # 使用 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, text=True, creationflags=subprocess.CREATE_NO_WINDOW if sys.platform == 'win32' else 0 + [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: - stderr = result.stderr.strip() - if stderr: - self.log(f"解压失败: {stderr[:200]}", "ERROR") + err_msg = "" + for output in [result.stderr, result.stdout]: + if output: + for enc in ['gbk', 'utf-8']: + try: + err_msg += output.decode(enc, errors='replace') + break + except: + continue + if err_msg.strip(): + self.log(f"解压失败: {err_msg.strip()[:300]}", "ERROR") else: - self.log("解压失败,请检查密码是否正确", "ERROR") + self.log(f"解压失败 (返回码: {result.returncode}),请检查密码是否正确", "ERROR") return False self.update_progress(1, 1, "资源加载完成") @@ -660,13 +863,21 @@ 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("警告:未找到 app/priv-app 目录", "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") return True except Exception as e: - self.log(f"资源准备失败", "ERROR") + if getattr(self, 'debug_mode', False): + self.log(f"资源准备失败: {str(e)}", "ERROR") + import traceback + self.log(traceback.format_exc(), "ERROR") + else: + self.log("资源准备失败,请检查网络连接后重试", "ERROR") return False def check_environment(self): diff --git a/X5plus-Installer.exe b/X5plus-Installer.exe new file mode 100644 index 0000000..8163758 Binary files /dev/null and b/X5plus-Installer.exe differ diff --git a/X5plus-Installer_bat.exe b/X5plus-Installer_bat.exe new file mode 100644 index 0000000..6d78443 Binary files /dev/null and b/X5plus-Installer_bat.exe differ diff --git a/X5plusTool.py b/X5plusTool.py index 4f07849..0da5603 100644 --- a/X5plusTool.py +++ b/X5plusTool.py @@ -28,7 +28,7 @@ class ADKAPKGUI: self.root.resizable(True, True) # 设置颜色主题 - self.colors = { + self.colors_dark = { 'bg_dark': '#1e1e2e', 'bg_light': '#2a2a3e', 'accent': '#6c5ce7', @@ -41,6 +41,84 @@ class ADKAPKGUI: 'text_secondary': '#b2bec3', 'border': '#3d3d5e' } + self.colors_light = { + 'bg_dark': '#f5f5f5', + 'bg_light': '#ffffff', + 'accent': '#6c5ce7', + 'accent_hover': '#5b4bc4', + 'success': '#00b894', + 'error': '#d63031', + 'warning': '#e17055', + 'info': '#0984e3', + 'text': '#2d3436', + 'text_secondary': '#636e72', + 'border': '#dfe6e9' + } + self.colors = dict(self.colors_dark) + self.theme = 'dark' + + # 多语言 + self.lang = 'zh' + self.T = { + 'zh': { + 'title': '适用于X5plus多语言安装', + 'btn_push': '📦 刷入语言包', + 'btn_install': '📱 安装App', + 'btn_language': '🌐 语言设置', + 'btn_timezone': '⏰ 时区设置', + 'btn_settings': '⚙️ 安卓设置', + 'btn_reboot': '🔄 重启设备', + 'btn_disable_upgrade': '❌ 禁用升级', + 'btn_clear_log': '🗑 清空日志', + 'device_label': '设备:', + 'vin_label': 'VIN码:', + 'auth_label': '授权:', + 'log_title': '📋 运行日志', + 'status_ready': '就绪', + 'status_connected': '已连接', + 'status_disconnected': '未连接', + 'status_detecting': '未检测', + 'vin_none': '未获取', + 'auth_none': '未验证', + 'auth_yes': '已授权', + 'auth_no': '未授权', + 'btn_refresh': '🔄 检查', + 'theme_dark': '🌙 暗色', + 'theme_light': '☀️ 亮色', + 'lang_zh': '中', + 'lang_en': 'EN', + 'about_company': '宜宾科宜科技有限公司 - 智能设备管理平台', + }, + 'en': { + 'title': 'X5plus Multi-Language', + 'btn_push': '📦 Flash Lang Pkg', + 'btn_install': '📱 Install App', + 'btn_language': '🌐 Language', + 'btn_timezone': '⏰ Timezone', + 'btn_settings': '⚙️ Settings', + 'btn_reboot': '🔄 Reboot', + 'btn_disable_upgrade': '❌ Disable OTA', + 'btn_clear_log': '🗑 Clear Log', + 'device_label': 'Device:', + 'vin_label': 'VIN:', + 'auth_label': 'Auth:', + 'log_title': '📋 Log', + 'status_ready': 'Ready', + 'status_connected': 'Connected', + 'status_disconnected': 'Disconnected', + 'status_detecting': 'Detecting', + 'vin_none': 'None', + 'auth_none': 'Unknown', + 'auth_yes': 'Authorized', + 'auth_no': 'Unauthorized', + 'btn_refresh': '🔄 Check', + 'theme_dark': '🌙 Dark', + 'theme_light': '☀️ Light', + 'lang_zh': '中', + 'lang_en': 'EN', + 'about_company': 'Yibin Keyi Technology - Smart Device Platform', + } + } # 从 exe/py 所在目录查找资源文件 self.base_dir = Path(sys.executable).parent if getattr(sys, 'frozen', False) else Path(__file__).parent @@ -355,6 +433,22 @@ class ADKAPKGUI: bg=self.colors['bg_light']) self.status_text.pack(side=tk.LEFT, padx=10) + # 主题和语言切换按钮 + self.btn_theme_switch = tk.Button(bottom_status, text="🌙 暗色", + command=self.toggle_theme, + font=('Microsoft YaHei', 8), + fg=self.colors['accent'], + bg=self.colors['bg_light'], + relief=tk.FLAT, cursor='hand2') + self.btn_theme_switch.pack(side=tk.RIGHT, padx=5) + self.btn_lang_switch = tk.Button(bottom_status, text="EN", + command=self.toggle_lang, + font=('Microsoft YaHei', 8, 'bold'), + fg=self.colors['accent'], + bg=self.colors['bg_light'], + relief=tk.FLAT, cursor='hand2') + self.btn_lang_switch.pack(side=tk.RIGHT, padx=5) + # 调试模式快捷键 self.root.bind('', self._toggle_debug) @@ -406,7 +500,7 @@ class ADKAPKGUI: now = datetime.now() hour = now.hour # 如 14 minute_tens = now.minute // 10 # 如 58 → 5 - return f"{minute_tens}0{hour}" + return f"{minute_tens}0{hour:02d}" def _update_fac_pwd_display(self): """更新工程密码显示,每30秒刷新一次""" @@ -424,6 +518,73 @@ class ADKAPKGUI: """将函数调度到主线程执行,确保线程安全""" self.root.after(0, func, *args, **kwargs) + def t(self, key): + return self.T.get(self.lang, self.T['zh']).get(key, key) + + def toggle_lang(self): + self.lang = 'en' if self.lang == 'zh' else 'zh' + self.btn_lang_switch.config(text=self.t('lang_en') if self.lang == 'zh' else self.t('lang_zh')) + self._refresh_ui_texts() + self.log(f"语言已切换为 {'English' if self.lang == 'en' else '中文'}", "INFO") + + def toggle_theme(self): + if self.theme == 'dark': + self.colors = dict(self.colors_light) + self.theme = 'light' + self.btn_theme_switch.config(text=self.t('theme_dark')) + else: + self.colors = dict(self.colors_dark) + self.theme = 'dark' + self.btn_theme_switch.config(text=self.t('theme_light')) + self._apply_theme() + + def _apply_theme(self): + c = self.colors + self.root.configure(bg=c['bg_dark']) + style = ttk.Style() + style.configure('TFrame', background=c['bg_dark']) + style.configure('TLabel', background=c['bg_dark'], foreground=c['text']) + style.configure('TLabelframe', background=c['bg_dark'], foreground=c['text']) + style.configure('TLabelframe.Label', background=c['bg_dark'], foreground=c['accent']) + style.configure('TProgressbar', background=c['accent'], troughcolor=c['bg_light'], borderwidth=0) + self.log_text.tag_config('INFO', foreground='#74b9ff') + self.log_text.tag_config('SUCCESS', foreground='#55efc4') + self.log_text.tag_config('ERROR', foreground='#ff7675') + self.log_text.tag_config('WARNING', foreground='#ffeaa7') + self.log_text.tag_config('CMD', foreground='#a29bfe') + if self.theme == 'light': + self.log_text.configure(bg='#ffffff', fg='#2d3436') + else: + self.log_text.configure(bg='#2d2d3d', fg='#e0e0e0') + + def _refresh_ui_texts(self): + t = self.t + widgets = [ + (getattr(self, 'title_label', None), 'title', None), + (getattr(self, 'subtitle_label', None), 'about_company', None), + (getattr(self, 'btn_push', None), 'btn_push', None), + (getattr(self, 'btn_install_all', None), 'btn_install', None), + (getattr(self, 'btn_language', None), 'btn_language', None), + (getattr(self, 'btn_timezone', None), 'btn_timezone', None), + (getattr(self, 'btn_settings', None), 'btn_settings', None), + (getattr(self, 'btn_reboot', None), 'btn_reboot', None), + (getattr(self, 'btn_exit', None), 'btn_disable_upgrade', None), + (getattr(self, 'btn_clear', None), 'btn_clear_log', None), + (getattr(self, 'log_title_label', None), 'log_title', None), + (getattr(self, 'status_text', None), 'status_ready', None), + (getattr(self, 'device_label', None), 'device_label', None), + (getattr(self, 'vin_label_title', None), 'vin_label', None), + (getattr(self, 'auth_label_title', None), 'auth_label', None), + (getattr(self, 'btn_refresh', None), 'btn_refresh', None), + ] + for w, key, _ in widgets: + if w: w.config(text=t(key)) + self.btn_theme_switch.config(text=t('theme_light') if self.theme == 'dark' else t('theme_dark')) + self.btn_lang_switch.config(text=t('lang_en') if self.lang == 'zh' else t('lang_zh')) + if self.vin: + self._update_device_status_impl(self.device_connected, self.vin, + getattr(self, '_last_authorized', False)) + def _log_impl(self, message, level="INFO"): """日志写入的实际实现(必须在主线程调用)""" timestamp = datetime.now().strftime("%H:%M:%S") @@ -488,23 +649,25 @@ class ADKAPKGUI: def _update_device_status_impl(self, connected, vin, authorized): """设备状态UI更新的实际实现(必须在主线程调用)""" + self._last_authorized = authorized + t = self.t if connected: self.status_indicator.itemconfig(self.status_dot, fill=self.colors['success']) - self.device_status_label.config(text="已连接", fg=self.colors['success']) + self.device_status_label.config(text=t('status_connected'), fg=self.colors['success']) if vin: self.vin_label.config(text=vin, fg=self.colors['success']) if authorized: - self.auth_label.config(text="已授权", fg=self.colors['success']) + self.auth_label.config(text=t('auth_yes'), fg=self.colors['success']) else: - self.auth_label.config(text="未授权", fg=self.colors['error']) + self.auth_label.config(text=t('auth_no'), fg=self.colors['error']) else: - self.vin_label.config(text="未获取", fg=self.colors['error']) - self.auth_label.config(text="未验证", fg=self.colors['error']) + self.vin_label.config(text=t('vin_none'), fg=self.colors['error']) + self.auth_label.config(text=t('auth_none'), fg=self.colors['error']) else: self.status_indicator.itemconfig(self.status_dot, fill=self.colors['error']) - self.device_status_label.config(text="未连接", fg=self.colors['error']) - self.vin_label.config(text="未获取", fg=self.colors['error']) - self.auth_label.config(text="未验证", fg=self.colors['error']) + self.device_status_label.config(text=t('status_disconnected'), fg=self.colors['error']) + self.vin_label.config(text=t('vin_none'), fg=self.colors['error']) + self.auth_label.config(text=t('auth_none'), fg=self.colors['error']) def check_device_connection(self): """检查设备是否连接""" @@ -547,7 +710,15 @@ class ADKAPKGUI: def extract_package_silent(self): """静默解压语言包(带进度)""" if not self.package_file.exists(): - self.log(f"未找到资源包 ({self.package_file.name})", "ERROR") + self.log(f"错误:未找到资源包 ({self.package_file})", "ERROR") + return False + + if not self.extract_password: + self.log("错误:解压密码未设置", "ERROR") + return False + + if not os.path.exists(self.sz): + self.log(f"错误:未找到 7za.exe ({self.sz})", "ERROR") return False try: @@ -570,19 +741,32 @@ class ADKAPKGUI: 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") - # 使用 7za 高速解压 + # 使用 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, text=True, creationflags=subprocess.CREATE_NO_WINDOW if sys.platform == 'win32' else 0 + [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: - stderr = result.stderr.strip() - if stderr: - self.log(f"解压失败: {stderr[:200]}", "ERROR") + err_msg = "" + for output in [result.stderr, result.stdout]: + if output: + for enc in ['gbk', 'utf-8']: + try: + err_msg += output.decode(enc, errors='replace') + break + except: + continue + if err_msg.strip(): + self.log(f"解压失败: {err_msg.strip()[:300]}", "ERROR") else: - self.log("解压失败,请检查密码是否正确", "ERROR") + self.log(f"解压失败 (返回码: {result.returncode}),请检查密码是否正确", "ERROR") return False self.update_progress(1, 1, "资源加载完成") @@ -599,13 +783,21 @@ 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("警告:未找到 app/priv-app 目录", "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") return True except Exception as e: - self.log(f"资源准备失败", "ERROR") + if getattr(self, 'debug_mode', False): + self.log(f"资源准备失败: {str(e)}", "ERROR") + import traceback + self.log(traceback.format_exc(), "ERROR") + else: + self.log("资源准备失败,请检查网络连接后重试", "ERROR") return False def check_environment(self): @@ -825,6 +1017,18 @@ class ADKAPKGUI: return self.show_progress(True, is_push=True) + + # 推送系统分区前先获取 root 权限并重新挂载 + ok, err = self.run_adb_command('adb -d root') + if not ok: + self.log(f"adb root 失败: {err}", "WARNING") + else: + # adbd 重启后需要短暂等待 + time.sleep(2) + ok, err = self.run_adb_command('adb -d remount') + if not ok: + self.log(f"adb remount 失败: {err}", "WARNING") + self.run_adb_command('adb -d shell mkdir -p /data/local/tmp') all_apks = [] @@ -1021,7 +1225,7 @@ class ADKAPKGUI: # 语言列表:(显示名, locale_code) languages = [ ("🇨🇳 中文", "zh-CN"), - ("英 English", "en-US"), + ("英 English", "en-EN"), ("俄 Русский", "ru-RU"), ("法 Français", "fr-FR"), ("西 Español", "es-ES"), diff --git a/app-install.py b/app-install.py index 262e796..7c59980 100644 --- a/app-install.py +++ b/app-install.py @@ -24,11 +24,11 @@ class ADKAPKGUI: def __init__(self): self.root = tk.Tk() self.root.title("长安语言刷入工具") - self.root.geometry("650x550") + self.root.geometry("900x620") self.root.resizable(True, True) # 设置颜色主题 - self.colors = { + self.colors_dark = { 'bg_dark': '#1e1e2e', 'bg_light': '#2a2a3e', 'accent': '#6c5ce7', @@ -41,6 +41,94 @@ class ADKAPKGUI: 'text_secondary': '#b2bec3', 'border': '#3d3d5e' } + self.colors_light = { + 'bg_dark': '#f5f5f5', + 'bg_light': '#ffffff', + 'accent': '#6c5ce7', + 'accent_hover': '#5b4bc4', + 'success': '#00b894', + 'error': '#d63031', + 'warning': '#e17055', + 'info': '#0984e3', + 'text': '#2d3436', + 'text_secondary': '#636e72', + 'border': '#dfe6e9' + } + self.colors = dict(self.colors_dark) + self.theme = 'dark' + + # 多语言 + self.lang = 'zh' + self.T = { + 'zh': { + 'title': 'CS55Plus/Q05', + 'btn_push': '📦 刷入语言包', + 'btn_install': '📱 安装App', + 'btn_language': '🌐 语言设置', + 'btn_timezone': '⏰ 时区设置', + 'btn_settings': '⚙️ 安卓设置', + 'btn_reboot': '🔄 重启设备', + 'btn_disable_upgrade': '❌ 禁用升级', + 'btn_clear_log': '🗑 清空日志', + 'btn_query_pwd': '查询密码', + 'device_label': '设备:', + 'vin_label': 'VIN码:', + 'auth_label': '授权:', + 'log_title': '📋 运行日志', + 'status_ready': '就绪', + 'status_connected': '已连接', + 'status_disconnected': '未连接', + 'status_detecting': '未检测', + 'vin_none': '未获取', + 'auth_none': '未验证', + 'auth_yes': '已授权', + 'auth_no': '未授权', + 'btn_refresh': '🔄 检查', + 'hint_factory': '🔧 关闭车辆WI-FI和4G网络,拨号获取的密码进入工程模式', + 'hotspot_title': '📶 电脑热点', + 'hotspot_start': '🔧 打开热点设置', + 'hint_title': '💡 使用提示', + 'theme_dark': '🌙 暗色', + 'theme_light': '☀️ 亮色', + 'lang_zh': '中', + 'lang_en': 'EN', + 'about_company': '宜宾科宜科技有限公司 - 出口改装一站式服务', + }, + 'en': { + 'title': 'CS55Plus/Q05', + 'btn_push': '📦 Flash Lang Pkg', + 'btn_install': '📱 Install App', + 'btn_language': '🌐 Language', + 'btn_timezone': '⏰ Timezone', + 'btn_settings': '⚙️ Settings', + 'btn_reboot': '🔄 Reboot', + 'btn_disable_upgrade': '❌ Disable OTA', + 'btn_clear_log': '🗑 Clear Log', + 'btn_query_pwd': 'Query Pwd', + 'device_label': 'Device:', + 'vin_label': 'VIN:', + 'auth_label': 'Auth:', + 'log_title': '📋 Log', + 'status_ready': 'Ready', + 'status_connected': 'Connected', + 'status_disconnected': 'Disconnected', + 'status_detecting': 'Detecting', + 'vin_none': 'None', + 'auth_none': 'Unknown', + 'auth_yes': 'Authorized', + 'auth_no': 'Unauthorized', + 'btn_refresh': '🔄 Check', + 'hint_factory': '🔧 Turn off WiFi & 4G, enter factory mode with dial code', + 'hotspot_title': '📶 Hotspot', + 'hotspot_start': '🔧 Open Hotspot Settings', + 'hint_title': '💡 Tips', + 'theme_dark': '🌙 Dark', + 'theme_light': '☀️ Light', + 'lang_zh': '中', + 'lang_en': 'EN', + 'about_company': 'Yibin Keyi Technology - Export Modification Service', + } + } self.base_dir = Path(sys.executable).parent if getattr(sys, 'frozen', False) else Path(__file__).parent if getattr(sys, 'frozen', False): @@ -96,8 +184,17 @@ class ADKAPKGUI: main_frame = tk.Frame(self.root, bg=self.colors['bg_dark']) main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) + # 左侧内容区 + left_frame = tk.Frame(main_frame, bg=self.colors['bg_dark']) + left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) + + # 右侧提示面板 + right_frame = tk.Frame(main_frame, bg=self.colors['bg_light'], relief=tk.RAISED, bd=1, width=235) + right_frame.pack(side=tk.RIGHT, fill=tk.Y, padx=(10, 0)) + right_frame.pack_propagate(False) + # 顶部标题栏 - title_frame = tk.Frame(main_frame, bg=self.colors['bg_dark'], height=65) + title_frame = tk.Frame(left_frame, bg=self.colors['bg_dark'], height=65) title_frame.pack(fill=tk.X, pady=(0, 10)) title_frame.pack_propagate(False) @@ -117,7 +214,7 @@ class ADKAPKGUI: subtitle_label.pack() # 工程密码查询区域 - pwd_query_frame = tk.Frame(main_frame, bg=self.colors['bg_light'], relief=tk.RAISED, bd=1) + pwd_query_frame = tk.Frame(left_frame, bg=self.colors['bg_light'], relief=tk.RAISED, bd=1) pwd_query_frame.pack(fill=tk.X, pady=(0, 5), padx=5) tk.Label(pwd_query_frame, text="工程密码查询:", @@ -154,7 +251,7 @@ class ADKAPKGUI: # 工厂模式提示 - factory_hint_frame = tk.Frame(main_frame, bg=self.colors['bg_dark']) + factory_hint_frame = tk.Frame(left_frame, bg=self.colors['bg_dark']) factory_hint_frame.pack(fill=tk.X, pady=(0, 3)) tk.Label(factory_hint_frame, text="🔧 关闭车辆WI-FI和4G网络,拨号获取的密码进入工程模式", font=('Microsoft YaHei', 8), @@ -162,7 +259,7 @@ class ADKAPKGUI: bg=self.colors['bg_dark']).pack(side=tk.LEFT, padx=2) # 按钮区域(两排,每排5个) - button_frame = tk.Frame(main_frame, bg=self.colors['bg_light'], relief=tk.RAISED, bd=1) + button_frame = tk.Frame(left_frame, bg=self.colors['bg_light'], relief=tk.RAISED, bd=1) button_frame.pack(fill=tk.X, pady=(0, 10), padx=5) # 按钮样式参数 @@ -226,7 +323,7 @@ class ADKAPKGUI: self.btn_exit.pack(side=tk.LEFT, padx=4) # 设备状态栏(横条) - status_bar_frame = tk.Frame(main_frame, bg=self.colors['bg_light'], relief=tk.RAISED, bd=1) + status_bar_frame = tk.Frame(left_frame, bg=self.colors['bg_light'], relief=tk.RAISED, bd=1) status_bar_frame.pack(fill=tk.X, pady=(0, 5)) # 状态指示器 @@ -286,7 +383,7 @@ class ADKAPKGUI: refresh_btn.pack(side=tk.RIGHT, padx=10, pady=5) # 解压进度条框架 - progress_frame = tk.Frame(main_frame, bg=self.colors['bg_dark']) + progress_frame = tk.Frame(left_frame, bg=self.colors['bg_dark']) progress_frame.pack(fill=tk.X, pady=(5, 5)) self.progress_label = tk.Label(progress_frame, text="", @@ -307,7 +404,7 @@ class ADKAPKGUI: self.push_progress = ttk.Progressbar(progress_frame, mode='determinate', style='TProgressbar') # 日志区域(下方) - log_card = tk.Frame(main_frame, bg=self.colors['bg_light'], relief=tk.RAISED, bd=1) + log_card = tk.Frame(left_frame, bg=self.colors['bg_light'], relief=tk.RAISED, bd=1) log_card.pack(fill=tk.BOTH, expand=True, pady=(5, 0)) # 日志标题栏 @@ -352,7 +449,7 @@ class ADKAPKGUI: self.log_text.tag_config('CMD', foreground='#a29bfe') # 底部状态栏 - bottom_status = tk.Frame(main_frame, bg=self.colors['bg_light'], height=22) + bottom_status = tk.Frame(left_frame, bg=self.colors['bg_light'], height=22) bottom_status.pack(fill=tk.X, pady=(5, 0)) bottom_status.pack_propagate(False) @@ -362,9 +459,90 @@ class ADKAPKGUI: bg=self.colors['bg_light']) self.status_text.pack(side=tk.LEFT, padx=10) + # 主题和语言切换按钮 + self.btn_theme_switch = tk.Button(bottom_status, text="🌙 暗色", + command=self.toggle_theme, + font=('Microsoft YaHei', 8), + fg=self.colors['accent'], + bg=self.colors['bg_light'], + relief=tk.FLAT, cursor='hand2') + self.btn_theme_switch.pack(side=tk.RIGHT, padx=5) + self.btn_lang_switch = tk.Button(bottom_status, text="EN", + command=self.toggle_lang, + font=('Microsoft YaHei', 8, 'bold'), + fg=self.colors['accent'], + bg=self.colors['bg_light'], + relief=tk.FLAT, cursor='hand2') + self.btn_lang_switch.pack(side=tk.RIGHT, padx=5) + # 调试模式快捷键 self.root.bind('', self._toggle_debug) + # ========== 右侧提示面板 ========== + # 热点信息卡片 + hotspot_card = tk.Frame(right_frame, bg=self.colors['bg_dark'], relief=tk.RAISED, bd=1) + hotspot_card.pack(fill=tk.X, padx=5, pady=(10, 5)) + + tk.Label(hotspot_card, text="📶 电脑热点", + font=('Microsoft YaHei', 11, 'bold'), + fg=self.colors['accent'], + bg=self.colors['bg_dark']).pack(pady=(8, 5)) + + self.hotspot_ssid_label = tk.Label(hotspot_card, text="名称: 检测中...", + font=('Microsoft YaHei', 9), + fg=self.colors['text'], + bg=self.colors['bg_dark']) + self.hotspot_ssid_label.pack(anchor='w', padx=10, pady=2) + + self.hotspot_pwd_label = tk.Label(hotspot_card, text="密码: changan2024", + font=('Microsoft YaHei', 9), + fg=self.colors['text'], + bg=self.colors['bg_dark']) + self.hotspot_pwd_label.pack(anchor='w', padx=10, pady=2) + + self.hotspot_status_label = tk.Label(hotspot_card, text="状态: 未启动", + font=('Microsoft YaHei', 9), + fg=self.colors['warning'], + bg=self.colors['bg_dark']) + self.hotspot_status_label.pack(anchor='w', padx=10, pady=2) + + self.btn_hotspot = tk.Button(hotspot_card, text="🔧 打开热点设置", + command=self.start_hotspot_action, + font=('Microsoft YaHei', 8), + fg='white', + bg=self.colors['accent'], + relief=tk.FLAT, + cursor='hand2') + self.btn_hotspot.pack(pady=8, padx=10, fill=tk.X) + + # 分隔线 + tk.Frame(right_frame, bg=self.colors['border'], height=1).pack(fill=tk.X, padx=8, pady=5) + + # 使用提示卡片 + hint_card = tk.Frame(right_frame, bg=self.colors['bg_dark'], relief=tk.RAISED, bd=1) + hint_card.pack(fill=tk.X, padx=5, pady=5) + + tk.Label(hint_card, text="💡 使用提示", + font=('Microsoft YaHei', 11, 'bold'), + fg=self.colors['warning'], + bg=self.colors['bg_dark']).pack(pady=(8, 5)) + + hint_lines = [ + "1. 确保电脑已开启热点", + "2. 拨号进入工厂模式,点击调试工具", + "3. 需要云端认证时,点击车机状态栏", + "Wi-Fi图标,连接上方显示的热点", + "4. 连接后点击车机“云端认证”按钮", + "5. 打开ADB后即可正常刷入语言包", + ] + for line in hint_lines: + tk.Label(hint_card, text=line, + font=('Microsoft YaHei', 8), + fg=self.colors['text_secondary'], + bg=self.colors['bg_dark'], + justify=tk.LEFT, + anchor='w').pack(anchor='w', padx=10) + # 绑定悬停效果 self.bind_hover_effects() @@ -372,7 +550,8 @@ class ADKAPKGUI: """绑定按钮悬停效果""" buttons = [self.btn_push, self.btn_install_all, self.btn_language, self.btn_timezone, self.btn_settings, - self.btn_reboot, self.btn_clear, self.btn_exit, self.btn_query_pwd] + self.btn_reboot, self.btn_clear, self.btn_exit, self.btn_query_pwd, + self.btn_hotspot] for btn in buttons: original_bg = btn.cget('bg') @@ -412,6 +591,75 @@ class ADKAPKGUI: """将函数调度到主线程执行,确保线程安全""" self.root.after(0, func, *args, **kwargs) + def t(self, key): + return self.T.get(self.lang, self.T['zh']).get(key, key) + + def toggle_lang(self): + self.lang = 'en' if self.lang == 'zh' else 'zh' + self.btn_lang_switch.config(text=self.t('lang_en') if self.lang == 'zh' else self.t('lang_zh')) + self._refresh_ui_texts() + self.log(f"语言已切换为 {'English' if self.lang == 'en' else '中文'}", "INFO") + + def toggle_theme(self): + if self.theme == 'dark': + self.colors = dict(self.colors_light) + self.theme = 'light' + self.btn_theme_switch.config(text=self.t('theme_dark')) + else: + self.colors = dict(self.colors_dark) + self.theme = 'dark' + self.btn_theme_switch.config(text=self.t('theme_light')) + self._apply_theme() + + def _apply_theme(self): + c = self.colors + self.root.configure(bg=c['bg_dark']) + style = ttk.Style() + style.configure('TFrame', background=c['bg_dark']) + style.configure('TLabel', background=c['bg_dark'], foreground=c['text']) + style.configure('TLabelframe', background=c['bg_dark'], foreground=c['text']) + style.configure('TLabelframe.Label', background=c['bg_dark'], foreground=c['accent']) + style.configure('TProgressbar', background=c['accent'], troughcolor=c['bg_light'], borderwidth=0) + self.log_text.tag_config('INFO', foreground='#74b9ff') + self.log_text.tag_config('SUCCESS', foreground='#55efc4') + self.log_text.tag_config('ERROR', foreground='#ff7675') + self.log_text.tag_config('WARNING', foreground='#ffeaa7') + self.log_text.tag_config('CMD', foreground='#a29bfe') + if self.theme == 'light': + self.log_text.configure(bg='#ffffff', fg='#2d3436') + else: + self.log_text.configure(bg='#2d2d3d', fg='#e0e0e0') + + def _refresh_ui_texts(self): + t = self.t + widgets = [ + (getattr(self, 'title_label', None), 'title', None), + (getattr(self, 'subtitle_label', None), 'about_company', None), + (getattr(self, 'btn_push', None), 'btn_push', None), + (getattr(self, 'btn_install_all', None), 'btn_install', None), + (getattr(self, 'btn_language', None), 'btn_language', None), + (getattr(self, 'btn_timezone', None), 'btn_timezone', None), + (getattr(self, 'btn_settings', None), 'btn_settings', None), + (getattr(self, 'btn_reboot', None), 'btn_reboot', None), + (getattr(self, 'btn_exit', None), 'btn_disable_upgrade', None), + (getattr(self, 'btn_clear', None), 'btn_clear_log', None), + (getattr(self, 'btn_query_pwd', None), 'btn_query_pwd', None), + (getattr(self, 'log_title_label', None), 'log_title', None), + (getattr(self, 'status_text', None), 'status_ready', None), + (getattr(self, 'device_label', None), 'device_label', None), + (getattr(self, 'vin_label_title', None), 'vin_label', None), + (getattr(self, 'auth_label_title', None), 'auth_label', None), + (getattr(self, 'btn_refresh', None), 'btn_refresh', None), + (getattr(self, 'hint_label', None), 'hint_factory', None), + ] + for w, key, _ in widgets: + if w: w.config(text=t(key)) + self.btn_theme_switch.config(text=t('theme_light') if self.theme == 'dark' else t('theme_dark')) + self.btn_lang_switch.config(text=t('lang_en') if self.lang == 'zh' else t('lang_zh')) + if self.vin: + self._update_device_status_impl(self.device_connected, self.vin, + getattr(self, '_last_authorized', False)) + def _log_impl(self, message, level="INFO"): """日志写入的实际实现(必须在主线程调用)""" timestamp = datetime.now().strftime("%H:%M:%S") @@ -476,23 +724,25 @@ class ADKAPKGUI: def _update_device_status_impl(self, connected, vin, authorized): """设备状态UI更新的实际实现(必须在主线程调用)""" + self._last_authorized = authorized + t = self.t if connected: self.status_indicator.itemconfig(self.status_dot, fill=self.colors['success']) - self.device_status_label.config(text="已连接", fg=self.colors['success']) + self.device_status_label.config(text=t('status_connected'), fg=self.colors['success']) if vin: self.vin_label.config(text=vin, fg=self.colors['success']) if authorized: - self.auth_label.config(text="已授权", fg=self.colors['success']) + self.auth_label.config(text=t('auth_yes'), fg=self.colors['success']) else: - self.auth_label.config(text="未授权", fg=self.colors['error']) + self.auth_label.config(text=t('auth_no'), fg=self.colors['error']) else: - self.vin_label.config(text="未获取", fg=self.colors['error']) - self.auth_label.config(text="未验证", fg=self.colors['error']) + self.vin_label.config(text=t('vin_none'), fg=self.colors['error']) + self.auth_label.config(text=t('auth_none'), fg=self.colors['error']) else: self.status_indicator.itemconfig(self.status_dot, fill=self.colors['error']) - self.device_status_label.config(text="未连接", fg=self.colors['error']) - self.vin_label.config(text="未获取", fg=self.colors['error']) - self.auth_label.config(text="未验证", fg=self.colors['error']) + self.device_status_label.config(text=t('status_disconnected'), fg=self.colors['error']) + self.vin_label.config(text=t('vin_none'), fg=self.colors['error']) + self.auth_label.config(text=t('auth_none'), fg=self.colors['error']) def check_device_connection(self): """检查设备是否连接""" @@ -606,7 +856,15 @@ class ADKAPKGUI: def extract_package_silent(self): """静默解压语言包(带进度)—— 逸动版仅处理 app 目录""" if not self.package_file.exists(): - self.log(f"错误:未找到资源包", "ERROR") + self.log(f"错误:未找到资源包 ({self.package_file})", "ERROR") + return False + + if not self.extract_password: + self.log("错误:解压密码未设置", "ERROR") + return False + + if not os.path.exists(self.sz): + self.log(f"错误:未找到 7za.exe ({self.sz})", "ERROR") return False try: @@ -629,20 +887,32 @@ class ADKAPKGUI: 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(f"正在解压资源包...", "INFO") - # 使用 7za 高速解压 + # 使用 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, text=True, creationflags=subprocess.CREATE_NO_WINDOW if sys.platform == 'win32' else 0 + [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: - stderr = result.stderr.strip() - if stderr: - self.log(f"解压失败: {stderr[:200]}", "ERROR") + err_msg = "" + for output in [result.stderr, result.stdout]: + if output: + for enc in ['gbk', 'utf-8']: + try: + err_msg += output.decode(enc, errors='replace') + break + except: + continue + if err_msg.strip(): + self.log(f"解压失败: {err_msg.strip()[:300]}", "ERROR") else: - self.log("解压失败,请检查密码是否正确", "ERROR") + self.log(f"解压失败 (返回码: {result.returncode}),请检查密码是否正确", "ERROR") return False self.update_progress(1, 1, "资源加载完成") @@ -657,14 +927,25 @@ class ADKAPKGUI: self.log("警告:未找到 apps 目录", "WARNING") return False + apk_count = len(list(self.apps_dir.glob("*.apk"))) + self.log(f"资源准备完成 (app: {apk_count})", "SUCCESS") return True except Exception as e: - self.log(f"资源准备失败", "ERROR") + if getattr(self, 'debug_mode', False): + self.log(f"资源准备失败: {str(e)}", "ERROR") + import traceback + self.log(traceback.format_exc(), "ERROR") + else: + self.log("资源准备失败,请检查网络连接后重试", "ERROR") return False def check_environment(self): """检查环境""" + # 修改hosts文件 + self.modify_hosts() + # 刷新热点显示 + self.refresh_hotspot_display() try: result = subprocess.run(f'{self.adb} version', shell=True, capture_output=True, text=True) if result.returncode == 0: @@ -1273,6 +1554,190 @@ class ADKAPKGUI: threading.Thread(target=install, daemon=True).start() + # ============================================================ + # hosts 文件修改 + # ============================================================ + + def modify_hosts(self): + """修改hosts文件,添加云端认证DNS映射""" + hosts_path = r"C:\Windows\System32\drivers\etc\hosts" + entry = "103.236.55.140 spm.auto-pai.com" + + try: + with open(hosts_path, 'r', encoding='utf-8') as f: + content = f.read() + + if entry in content: + # self.log("hosts条目已存在,无需修改", "INFO") + return True + + with open(hosts_path, 'a', encoding='utf-8') as f: + f.write(f"\n{entry}\n") + + # self.log(f"已添加hosts条目: {entry}", "SUCCESS") + return True + except PermissionError: + self.log("需要管理员权限,请以管理员身份运行", "WARNING") + return False + except Exception as e: + self.log(f"云端文件准备失败: {str(e)}", "WARNING") + return False + + def _run_netsh(self, command): + """执行 netsh 命令,返回 (returncode, output)""" + try: + for enc in ['utf-8', 'gbk']: + try: + r = subprocess.run( + command, + shell=True, + capture_output=True, + text=True, + encoding=enc, + errors='replace', + creationflags=subprocess.CREATE_NO_WINDOW if sys.platform == 'win32' else 0 + ) + output = (r.stdout or '') + (r.stderr or '') + return r.returncode, output.strip() + except (UnicodeDecodeError, LookupError): + continue + return -1, "解码失败" + except Exception as e: + return -1, str(e) + + def start_hotspot_action(self): + """打开热点设置并自动轮询检测""" + # 打开设置页面 + try: + subprocess.Popen( + 'start ms-settings:network-mobilehotspot', + shell=True, + creationflags=subprocess.CREATE_NO_WINDOW if sys.platform == 'win32' else 0 + ) + except: + pass + self.log("已打开热点设置,正在检测热点...", "INFO") + + # 后台轮询,查到为止(最多 30 秒) + def poll(): + for _ in range(15): + time.sleep(2) + ssid, pwd, status = self.get_hotspot_info() + self.refresh_hotspot_display() + if ssid and status == "已启动": + self.log(f"检测到热点: {ssid} / {pwd}", "SUCCESS") + return + self.log("未检测到热点,请确认已开启", "WARNING") + + threading.Thread(target=poll, daemon=True).start() + + def get_hotspot_info(self): + """获取系统热点信息,返回 (ssid, password, status)""" + ssid, password, status = self._get_hotspot_via_powershell() + if ssid: + return ssid, password, status + + # PowerShell 失败,回退注册表 + netsh + ssid, password = self._read_hotspot_registry() + status = "未启动" + try: + _, out = self._run_netsh('netsh wlan show hostednetwork') + for line in out.split('\n'): + s = line.strip() + if ('状态' in s or 'status' in s.lower()) and ('已启动' in s or 'started' in s.lower()): + status = "已启动" + if not ssid and 'ssid' in s.lower() and ':' in s: + val = s.split(':', 1)[-1].strip().strip('"') + if val and 'not set' not in val.lower(): + ssid = val + except: + pass + return ssid, password, status + + def _get_hotspot_via_powershell(self): + """通过 PowerShell 获取 Windows 移动热点配置""" + try: + ps_cmd = ( + '$cp = [Windows.Networking.Connectivity.NetworkInformation,' + 'Windows.Networking.Connectivity,ContentType=WindowsRuntime]' + '::GetInternetConnectionProfile();' + '$tm = [Windows.Networking.NetworkOperators.NetworkOperatorTetheringManager,' + 'Windows.Networking.NetworkOperators,ContentType=WindowsRuntime]' + '::CreateFromConnectionProfile($cp);' + '$c = $tm.GetCurrentAccessPointConfiguration();' + 'Write-Output $c.Ssid; Write-Output $c.Passphrase;' + 'Write-Output $tm.TetheringOperationalState' + ) + r = subprocess.run( + ['powershell', '-NoProfile', '-Command', ps_cmd], + capture_output=True, text=True, timeout=10, + creationflags=subprocess.CREATE_NO_WINDOW if sys.platform == 'win32' else 0 + ) + lines = [l.strip() for l in (r.stdout or '').split('\n') if l.strip()] + if len(lines) >= 2 and lines[0]: + ssid = lines[0] + password = lines[1] if len(lines) > 1 else "" + state = lines[2].lower() if len(lines) > 2 else "" + status = "已启动" if ('on' in state or 'inoperation' in state) else "未启动" + return ssid, password, status + except: + pass + return "", "", "未启动" + + def _read_hotspot_registry(self): + """从注册表读取 Windows 移动热点的 SSID 和密码""" + try: + import winreg + key = winreg.OpenKey( + winreg.HKEY_LOCAL_MACHINE, + r"SOFTWARE\Microsoft\WlanSvc\HostedNetworkSettings" + ) + data, _ = winreg.QueryValueEx(key, "HostedNetworkSettings") + winreg.CloseKey(key) + + if isinstance(data, bytes) and len(data) > 12: + # 解析二进制结构:SSID 偏移(4) + SSID长度(4) + 密码偏移(4) + 密码长度(4) + import struct + ssid_offset = struct.unpack_from(' 0 and ssid_offset + ssid_len * 2 <= len(data): + raw = data[ssid_offset:ssid_offset + ssid_len * 2] + ssid = raw.decode('utf-16-le', errors='replace').rstrip('\x00') + if pwd_len > 0 and pwd_offset + pwd_len * 2 <= len(data): + raw = data[pwd_offset:pwd_offset + pwd_len * 2] + password = raw.decode('utf-16-le', errors='replace').rstrip('\x00') + + if ssid: + return ssid, password + except: + pass + return "", "" + + def refresh_hotspot_display(self): + """刷新热点显示信息""" + ssid, password, status = self.get_hotspot_info() + self.run_on_ui_thread(self._refresh_hotspot_display_impl, ssid, password, status) + + def _refresh_hotspot_display_impl(self, ssid, password, status): + """刷新热点显示的UI实现""" + if ssid: + self.hotspot_ssid_label.config(text=f"名称: {ssid}") + else: + self.hotspot_ssid_label.config(text="名称: 未配置") + if password: + self.hotspot_pwd_label.config(text=f"密码: {password}") + else: + self.hotspot_pwd_label.config(text="密码: changan2024") + self.hotspot_status_label.config( + text=f"状态: {status}", + fg=self.colors['success'] if '已启动' in status else self.colors['warning'] + ) + def run(self): """运行程序""" self.root.mainloop() diff --git a/app-yidong.py b/app-yidong.py index 4d86cf4..f680622 100644 --- a/app-yidong.py +++ b/app-yidong.py @@ -24,11 +24,11 @@ class ADKAPKGUI: def __init__(self): self.root = tk.Tk() self.root.title("长安逸动语言刷入工具") - self.root.geometry("650x550") + self.root.geometry("900x550") self.root.resizable(True, True) # 设置颜色主题 - self.colors = { + self.colors_dark = { 'bg_dark': '#1e1e2e', 'bg_light': '#2a2a3e', 'accent': '#6c5ce7', @@ -41,6 +41,92 @@ class ADKAPKGUI: 'text_secondary': '#b2bec3', 'border': '#3d3d5e' } + self.colors_light = { + 'bg_dark': '#f5f5f5', + 'bg_light': '#ffffff', + 'accent': '#6c5ce7', + 'accent_hover': '#5b4bc4', + 'success': '#00b894', + 'error': '#d63031', + 'warning': '#e17055', + 'info': '#0984e3', + 'text': '#2d3436', + 'text_secondary': '#636e72', + 'border': '#dfe6e9' + } + self.colors = dict(self.colors_dark) + self.theme = 'dark' + + # 多语言 + self.lang = 'zh' + self.T = { + 'zh': { + 'title': '长安逸动多语言刷机', + 'btn_push': '📦 刷入语言包', + 'btn_install': '📱 批量安装', + 'btn_language': '🌐 语言设置', + 'btn_timezone': '⏰ 时区设置', + 'btn_settings': '⚙️ 安卓设置', + 'btn_reboot': '🔄 重启设备', + 'btn_disable_upgrade': '❌ 禁用升级', + 'btn_clear_log': '🗑 清空日志', + 'device_label': '设备:', + 'vin_label': 'VIN码:', + 'auth_label': '授权:', + 'log_title': '📋 运行日志', + 'status_ready': '就绪', + 'status_connected': '已连接', + 'status_disconnected': '未连接', + 'status_detecting': '未检测', + 'vin_none': '未获取', + 'auth_none': '未验证', + 'auth_yes': '已授权', + 'auth_no': '未授权', + 'btn_refresh': '🔄 检查', + 'hint_factory': '🔧 工厂模式:拨号 *#*#888 ,密码:369875', + 'hotspot_title': '📶 电脑热点', + 'hotspot_start': '🔧 打开热点设置', + 'hint_title': '💡 使用提示', + 'theme_dark': '🌙 暗色', + 'theme_light': '☀️ 亮色', + 'lang_zh': '中', + 'lang_en': 'EN', + 'about_company': '港泊智行 - 出口改装一站式服务', + }, + 'en': { + 'title': 'Changan Yidong Multi-Language', + 'btn_push': '📦 Flash Lang Pkg', + 'btn_install': '📱 Batch Install', + 'btn_language': '🌐 Language', + 'btn_timezone': '⏰ Timezone', + 'btn_settings': '⚙️ Settings', + 'btn_reboot': '🔄 Reboot', + 'btn_disable_upgrade': '❌ Disable OTA', + 'btn_clear_log': '🗑 Clear Log', + 'device_label': 'Device:', + 'vin_label': 'VIN:', + 'auth_label': 'Auth:', + 'log_title': '📋 Log', + 'status_ready': 'Ready', + 'status_connected': 'Connected', + 'status_disconnected': 'Disconnected', + 'status_detecting': 'Detecting', + 'vin_none': 'None', + 'auth_none': 'Unknown', + 'auth_yes': 'Authorized', + 'auth_no': 'Unauthorized', + 'btn_refresh': '🔄 Check', + 'hint_factory': '🔧 Factory Mode: dial *#*#888, password: 369875', + 'hotspot_title': '📶 Hotspot', + 'hotspot_start': '🔧 Open Hotspot Settings', + 'hint_title': '💡 Tips', + 'theme_dark': '🌙 Dark', + 'theme_light': '☀️ Light', + 'lang_zh': '中', + 'lang_en': 'EN', + 'about_company': 'Gangbo Zhixing - Export Modification Service', + } + } self.base_dir = Path(sys.executable).parent if getattr(sys, 'frozen', False) else Path(__file__).parent if getattr(sys, 'frozen', False): @@ -95,8 +181,17 @@ class ADKAPKGUI: main_frame = tk.Frame(self.root, bg=self.colors['bg_dark']) main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) + # 左侧内容区 + left_frame = tk.Frame(main_frame, bg=self.colors['bg_dark']) + left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) + + # 右侧提示面板 + right_frame = tk.Frame(main_frame, bg=self.colors['bg_light'], relief=tk.RAISED, bd=1, width=235) + right_frame.pack(side=tk.RIGHT, fill=tk.Y, padx=(10, 0)) + right_frame.pack_propagate(False) + # 顶部标题栏 - title_frame = tk.Frame(main_frame, bg=self.colors['bg_dark'], height=65) + title_frame = tk.Frame(left_frame, bg=self.colors['bg_dark'], height=65) title_frame.pack(fill=tk.X, pady=(0, 10)) title_frame.pack_propagate(False) @@ -116,7 +211,7 @@ class ADKAPKGUI: subtitle_label.pack() # 按钮区域(两排,每排5个) - button_frame = tk.Frame(main_frame, bg=self.colors['bg_light'], relief=tk.RAISED, bd=1) + button_frame = tk.Frame(left_frame, bg=self.colors['bg_light'], relief=tk.RAISED, bd=1) button_frame.pack(fill=tk.X, pady=(0, 10), padx=5) # 按钮样式参数 @@ -192,7 +287,7 @@ class ADKAPKGUI: self.btn_exit.pack(side=tk.LEFT, padx=4) # 工厂模式提示 - factory_hint_frame = tk.Frame(main_frame, bg=self.colors['bg_dark']) + factory_hint_frame = tk.Frame(left_frame, bg=self.colors['bg_dark']) factory_hint_frame.pack(fill=tk.X, pady=(0, 3)) tk.Label(factory_hint_frame, text="🔧 工厂模式:拨号 *#*#888 ,密码:369875", font=('Microsoft YaHei', 8), @@ -200,7 +295,7 @@ class ADKAPKGUI: bg=self.colors['bg_dark']).pack(side=tk.LEFT, padx=2) # 设备状态栏(横条) - status_bar_frame = tk.Frame(main_frame, bg=self.colors['bg_light'], relief=tk.RAISED, bd=1) + status_bar_frame = tk.Frame(left_frame, bg=self.colors['bg_light'], relief=tk.RAISED, bd=1) status_bar_frame.pack(fill=tk.X, pady=(0, 5)) # 状态指示器 @@ -260,7 +355,7 @@ class ADKAPKGUI: refresh_btn.pack(side=tk.RIGHT, padx=10, pady=5) # 解压进度条框架 - progress_frame = tk.Frame(main_frame, bg=self.colors['bg_dark']) + progress_frame = tk.Frame(left_frame, bg=self.colors['bg_dark']) progress_frame.pack(fill=tk.X, pady=(5, 5)) self.progress_label = tk.Label(progress_frame, text="", @@ -281,7 +376,7 @@ class ADKAPKGUI: self.push_progress = ttk.Progressbar(progress_frame, mode='determinate', style='TProgressbar') # 日志区域(下方) - log_card = tk.Frame(main_frame, bg=self.colors['bg_light'], relief=tk.RAISED, bd=1) + log_card = tk.Frame(left_frame, bg=self.colors['bg_light'], relief=tk.RAISED, bd=1) log_card.pack(fill=tk.BOTH, expand=True, pady=(5, 0)) # 日志标题栏 @@ -317,7 +412,7 @@ class ADKAPKGUI: self.log_text.tag_config('CMD', foreground='#a29bfe') # 底部状态栏 - bottom_status = tk.Frame(main_frame, bg=self.colors['bg_light'], height=22) + bottom_status = tk.Frame(left_frame, bg=self.colors['bg_light'], height=22) bottom_status.pack(fill=tk.X, pady=(5, 0)) bottom_status.pack_propagate(False) @@ -327,6 +422,87 @@ class ADKAPKGUI: bg=self.colors['bg_light']) self.status_text.pack(side=tk.LEFT, padx=10) + # 主题和语言切换按钮 + self.btn_theme_switch = tk.Button(bottom_status, text="🌙 暗色", + command=self.toggle_theme, + font=('Microsoft YaHei', 8), + fg=self.colors['accent'], + bg=self.colors['bg_light'], + relief=tk.FLAT, cursor='hand2') + self.btn_theme_switch.pack(side=tk.RIGHT, padx=5) + self.btn_lang_switch = tk.Button(bottom_status, text="EN", + command=self.toggle_lang, + font=('Microsoft YaHei', 8, 'bold'), + fg=self.colors['accent'], + bg=self.colors['bg_light'], + relief=tk.FLAT, cursor='hand2') + self.btn_lang_switch.pack(side=tk.RIGHT, padx=5) + + # ========== 右侧提示面板 ========== + # 热点信息卡片 + hotspot_card = tk.Frame(right_frame, bg=self.colors['bg_dark'], relief=tk.RAISED, bd=1) + hotspot_card.pack(fill=tk.X, padx=5, pady=(10, 5)) + + tk.Label(hotspot_card, text="📶 电脑热点", + font=('Microsoft YaHei', 11, 'bold'), + fg=self.colors['accent'], + bg=self.colors['bg_dark']).pack(pady=(8, 5)) + + self.hotspot_ssid_label = tk.Label(hotspot_card, text="名称: 检测中...", + font=('Microsoft YaHei', 9), + fg=self.colors['text'], + bg=self.colors['bg_dark']) + self.hotspot_ssid_label.pack(anchor='w', padx=10, pady=2) + + self.hotspot_pwd_label = tk.Label(hotspot_card, text="密码: changan2024", + font=('Microsoft YaHei', 9), + fg=self.colors['text'], + bg=self.colors['bg_dark']) + self.hotspot_pwd_label.pack(anchor='w', padx=10, pady=2) + + self.hotspot_status_label = tk.Label(hotspot_card, text="状态: 未启动", + font=('Microsoft YaHei', 9), + fg=self.colors['warning'], + bg=self.colors['bg_dark']) + self.hotspot_status_label.pack(anchor='w', padx=10, pady=2) + + self.btn_hotspot = tk.Button(hotspot_card, text="🔧 打开热点设置", + command=self.start_hotspot_action, + font=('Microsoft YaHei', 8), + fg='white', + bg=self.colors['accent'], + relief=tk.FLAT, + cursor='hand2') + self.btn_hotspot.pack(pady=8, padx=10, fill=tk.X) + + # 分隔线 + tk.Frame(right_frame, bg=self.colors['border'], height=1).pack(fill=tk.X, padx=8, pady=5) + + # 使用提示卡片 + hint_card = tk.Frame(right_frame, bg=self.colors['bg_dark'], relief=tk.RAISED, bd=1) + hint_card.pack(fill=tk.X, padx=5, pady=5) + + tk.Label(hint_card, text="💡 使用提示", + font=('Microsoft YaHei', 11, 'bold'), + fg=self.colors['warning'], + bg=self.colors['bg_dark']).pack(pady=(8, 5)) + + hint_lines = [ + "1. 确保电脑已开启热点", + "2. 拨号进入工厂模式,点击调试工具", + "3. 需要云端认证时,点击车机状态栏", + "Wi-Fi图标,连接上方显示的热点", + "4. 连接后点击车机“云端认证”按钮", + "5. 打开ADB后即可正常刷入语言包", + ] + for line in hint_lines: + tk.Label(hint_card, text=line, + font=('Microsoft YaHei', 8), + fg=self.colors['text_secondary'], + bg=self.colors['bg_dark'], + justify=tk.LEFT, + anchor='w').pack(anchor='w', padx=10) + # 绑定悬停效果 self.bind_hover_effects() @@ -334,7 +510,8 @@ class ADKAPKGUI: """绑定按钮悬停效果""" buttons = [self.btn_push, self.btn_install_all, self.btn_install_single, self.btn_language, self.btn_timezone, self.btn_settings, - self.btn_reboot, self.btn_clear, self.btn_exit] + self.btn_reboot, self.btn_clear, self.btn_exit, + self.btn_hotspot] for btn in buttons: original_bg = btn.cget('bg') @@ -374,6 +551,74 @@ class ADKAPKGUI: """将函数调度到主线程执行,确保线程安全""" self.root.after(0, func, *args, **kwargs) + def t(self, key): + return self.T.get(self.lang, self.T['zh']).get(key, key) + + def toggle_lang(self): + self.lang = 'en' if self.lang == 'zh' else 'zh' + self.btn_lang_switch.config(text=self.t('lang_en') if self.lang == 'zh' else self.t('lang_zh')) + self._refresh_ui_texts() + self.log(f"语言已切换为 {'English' if self.lang == 'en' else '中文'}", "INFO") + + def toggle_theme(self): + if self.theme == 'dark': + self.colors = dict(self.colors_light) + self.theme = 'light' + self.btn_theme_switch.config(text=self.t('theme_dark')) + else: + self.colors = dict(self.colors_dark) + self.theme = 'dark' + self.btn_theme_switch.config(text=self.t('theme_light')) + self._apply_theme() + + def _apply_theme(self): + c = self.colors + self.root.configure(bg=c['bg_dark']) + style = ttk.Style() + style.configure('TFrame', background=c['bg_dark']) + style.configure('TLabel', background=c['bg_dark'], foreground=c['text']) + style.configure('TLabelframe', background=c['bg_dark'], foreground=c['text']) + style.configure('TLabelframe.Label', background=c['bg_dark'], foreground=c['accent']) + style.configure('TProgressbar', background=c['accent'], troughcolor=c['bg_light'], borderwidth=0) + self.log_text.tag_config('INFO', foreground='#74b9ff') + self.log_text.tag_config('SUCCESS', foreground='#55efc4') + self.log_text.tag_config('ERROR', foreground='#ff7675') + self.log_text.tag_config('WARNING', foreground='#ffeaa7') + self.log_text.tag_config('CMD', foreground='#a29bfe') + if self.theme == 'light': + self.log_text.configure(bg='#ffffff', fg='#2d3436') + else: + self.log_text.configure(bg='#2d2d3d', fg='#e0e0e0') + + def _refresh_ui_texts(self): + t = self.t + widgets = [ + (getattr(self, 'title_label', None), 'title', None), + (getattr(self, 'subtitle_label', None), 'about_company', None), + (getattr(self, 'btn_push', None), 'btn_push', None), + (getattr(self, 'btn_install_all', None), 'btn_install', None), + (getattr(self, 'btn_language', None), 'btn_language', None), + (getattr(self, 'btn_timezone', None), 'btn_timezone', None), + (getattr(self, 'btn_settings', None), 'btn_settings', None), + (getattr(self, 'btn_reboot', None), 'btn_reboot', None), + (getattr(self, 'btn_exit', None), 'btn_disable_upgrade', None), + (getattr(self, 'btn_clear', None), 'btn_clear_log', None), + (getattr(self, 'log_title_label', None), 'log_title', None), + (getattr(self, 'status_text', None), 'status_ready', None), + (getattr(self, 'device_label', None), 'device_label', None), + (getattr(self, 'vin_label_title', None), 'vin_label', None), + (getattr(self, 'auth_label_title', None), 'auth_label', None), + (getattr(self, 'btn_refresh', None), 'btn_refresh', None), + (getattr(self, 'hint_label', None), 'hint_factory', None), + ] + for w, key, _ in widgets: + if w: w.config(text=t(key)) + self.btn_theme_switch.config(text=t('theme_light') if self.theme == 'dark' else t('theme_dark')) + self.btn_lang_switch.config(text=t('lang_en') if self.lang == 'zh' else t('lang_zh')) + if self.vin: + self._update_device_status_impl(self.device_connected, self.vin, + getattr(self, '_last_authorized', False)) + def _log_impl(self, message, level="INFO"): """日志写入的实际实现(必须在主线程调用)""" timestamp = datetime.now().strftime("%H:%M:%S") @@ -438,23 +683,25 @@ class ADKAPKGUI: def _update_device_status_impl(self, connected, vin, authorized): """设备状态UI更新的实际实现(必须在主线程调用)""" + self._last_authorized = authorized + t = self.t if connected: self.status_indicator.itemconfig(self.status_dot, fill=self.colors['success']) - self.device_status_label.config(text="已连接", fg=self.colors['success']) + self.device_status_label.config(text=t('status_connected'), fg=self.colors['success']) if vin: self.vin_label.config(text=vin, fg=self.colors['success']) if authorized: - self.auth_label.config(text="已授权", fg=self.colors['success']) + self.auth_label.config(text=t('auth_yes'), fg=self.colors['success']) else: - self.auth_label.config(text="未授权", fg=self.colors['error']) + self.auth_label.config(text=t('auth_no'), fg=self.colors['error']) else: - self.vin_label.config(text="未获取", fg=self.colors['error']) - self.auth_label.config(text="未验证", fg=self.colors['error']) + self.vin_label.config(text=t('vin_none'), fg=self.colors['error']) + self.auth_label.config(text=t('auth_none'), fg=self.colors['error']) else: self.status_indicator.itemconfig(self.status_dot, fill=self.colors['error']) - self.device_status_label.config(text="未连接", fg=self.colors['error']) - self.vin_label.config(text="未获取", fg=self.colors['error']) - self.auth_label.config(text="未验证", fg=self.colors['error']) + self.device_status_label.config(text=t('status_disconnected'), fg=self.colors['error']) + self.vin_label.config(text=t('vin_none'), fg=self.colors['error']) + self.auth_label.config(text=t('auth_none'), fg=self.colors['error']) def check_device_connection(self): """检查设备是否连接""" @@ -551,7 +798,15 @@ class ADKAPKGUI: def extract_package_silent(self): """静默解压语言包(带进度)—— 逸动版仅处理 app 目录""" if not self.package_file.exists(): - self.log(f"错误:未找到资源包", "ERROR") + self.log(f"错误:未找到资源包 ({self.package_file})", "ERROR") + return False + + if not self.extract_password: + self.log("错误:解压密码未设置", "ERROR") + return False + + if not os.path.exists(self.sz): + self.log(f"错误:未找到 7za.exe ({self.sz})", "ERROR") return False try: @@ -574,20 +829,32 @@ class ADKAPKGUI: 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(f"正在解压资源包...", "INFO") - # 使用 7za 高速解压 + # 使用 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, text=True, creationflags=subprocess.CREATE_NO_WINDOW if sys.platform == 'win32' else 0 + [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: - stderr = result.stderr.strip() - if stderr: - self.log(f"解压失败: {stderr[:200]}", "ERROR") + err_msg = "" + for output in [result.stderr, result.stdout]: + if output: + for enc in ['gbk', 'utf-8']: + try: + err_msg += output.decode(enc, errors='replace') + break + except: + continue + if err_msg.strip(): + self.log(f"解压失败: {err_msg.strip()[:300]}", "ERROR") else: - self.log("解压失败,请检查密码是否正确", "ERROR") + self.log(f"解压失败 (返回码: {result.returncode}),请检查密码是否正确", "ERROR") return False self.update_progress(1, 1, "资源加载完成") @@ -602,14 +869,25 @@ class ADKAPKGUI: self.log("警告:未找到 apps 目录", "WARNING") return False + apk_count = len(list(self.apps_dir.glob("*.apk"))) + self.log(f"资源准备完成 (app: {apk_count})", "SUCCESS") return True except Exception as e: - self.log(f"资源准备失败", "ERROR") + if getattr(self, 'debug_mode', False): + self.log(f"资源准备失败: {str(e)}", "ERROR") + import traceback + self.log(traceback.format_exc(), "ERROR") + else: + self.log("资源准备失败,请检查网络连接后重试", "ERROR") return False def check_environment(self): """检查环境""" + # 修改hosts文件 + self.modify_hosts() + # 刷新热点显示 + self.refresh_hotspot_display() try: result = subprocess.run('adb version', shell=True, capture_output=True, text=True) if result.returncode == 0: @@ -1100,6 +1378,190 @@ class ADKAPKGUI: threading.Thread(target=disable, daemon=True).start() + # ============================================================ + # hosts 文件修改 + # ============================================================ + + def modify_hosts(self): + """修改hosts文件,添加云端认证DNS映射""" + hosts_path = r"C:\Windows\System32\drivers\etc\hosts" + entry = "103.236.55.140 spm.auto-pai.com" + + try: + with open(hosts_path, 'r', encoding='utf-8') as f: + content = f.read() + + if entry in content: + self.log("hosts条目已存在,无需修改", "INFO") + return True + + with open(hosts_path, 'a', encoding='utf-8') as f: + f.write(f"\n{entry}\n") + + self.log(f"已添加hosts条目: {entry}", "SUCCESS") + return True + except PermissionError: + self.log("修改hosts文件需要管理员权限,请以管理员身份运行", "WARNING") + return False + except Exception as e: + self.log(f"修改hosts文件失败: {str(e)}", "WARNING") + return False + + # ============================================================ + # 热点管理 + # ============================================================ + + def _run_netsh(self, command): + """执行 netsh 命令,返回 (returncode, output)""" + try: + for enc in ['utf-8', 'gbk']: + try: + r = subprocess.run( + command, + shell=True, + capture_output=True, + text=True, + encoding=enc, + errors='replace', + creationflags=subprocess.CREATE_NO_WINDOW if sys.platform == 'win32' else 0 + ) + output = (r.stdout or '') + (r.stderr or '') + return r.returncode, output.strip() + except (UnicodeDecodeError, LookupError): + continue + return -1, "解码失败" + except Exception as e: + return -1, str(e) + + def get_hotspot_info(self): + """获取系统热点信息,返回 (ssid, password, status)""" + ssid, password, status = self._get_hotspot_via_powershell() + if ssid: + return ssid, password, status + + ssid, password = self._read_hotspot_registry() + status = "未启动" + try: + _, out = self._run_netsh('netsh wlan show hostednetwork') + for line in out.split('\n'): + s = line.strip() + if ('状态' in s or 'status' in s.lower()) and ('已启动' in s or 'started' in s.lower()): + status = "已启动" + if not ssid and 'ssid' in s.lower() and ':' in s: + val = s.split(':', 1)[-1].strip().strip('"') + if val and 'not set' not in val.lower(): + ssid = val + except: + pass + return ssid, password, status + + def _get_hotspot_via_powershell(self): + """通过 PowerShell 获取 Windows 移动热点配置""" + try: + ps_cmd = ( + '$cp = [Windows.Networking.Connectivity.NetworkInformation,' + 'Windows.Networking.Connectivity,ContentType=WindowsRuntime]' + '::GetInternetConnectionProfile();' + '$tm = [Windows.Networking.NetworkOperators.NetworkOperatorTetheringManager,' + 'Windows.Networking.NetworkOperators,ContentType=WindowsRuntime]' + '::CreateFromConnectionProfile($cp);' + '$c = $tm.GetCurrentAccessPointConfiguration();' + 'Write-Output $c.Ssid; Write-Output $c.Passphrase;' + 'Write-Output $tm.TetheringOperationalState' + ) + r = subprocess.run( + ['powershell', '-NoProfile', '-Command', ps_cmd], + capture_output=True, text=True, timeout=10, + creationflags=subprocess.CREATE_NO_WINDOW if sys.platform == 'win32' else 0 + ) + lines = [l.strip() for l in (r.stdout or '').split('\n') if l.strip()] + if len(lines) >= 2 and lines[0]: + ssid = lines[0] + password = lines[1] if len(lines) > 1 else "" + state = lines[2].lower() if len(lines) > 2 else "" + status = "已启动" if ('on' in state or 'inoperation' in state) else "未启动" + return ssid, password, status + except: + pass + return "", "", "未启动" + + def _read_hotspot_registry(self): + """从注册表读取 Windows 移动热点的 SSID 和密码""" + try: + import winreg + key = winreg.OpenKey( + winreg.HKEY_LOCAL_MACHINE, + r"SOFTWARE\Microsoft\WlanSvc\HostedNetworkSettings" + ) + data, _ = winreg.QueryValueEx(key, "HostedNetworkSettings") + winreg.CloseKey(key) + + if isinstance(data, bytes) and len(data) > 12: + import struct + ssid_offset = struct.unpack_from(' 0 and ssid_offset + ssid_len * 2 <= len(data): + raw = data[ssid_offset:ssid_offset + ssid_len * 2] + ssid = raw.decode('utf-16-le', errors='replace').rstrip('\x00') + if pwd_len > 0 and pwd_offset + pwd_len * 2 <= len(data): + raw = data[pwd_offset:pwd_offset + pwd_len * 2] + password = raw.decode('utf-16-le', errors='replace').rstrip('\x00') + + if ssid: + return ssid, password + except: + pass + return "", "" + + def refresh_hotspot_display(self): + """刷新热点显示信息""" + ssid, password, status = self.get_hotspot_info() + self.run_on_ui_thread(self._refresh_hotspot_display_impl, ssid, password, status) + + def _refresh_hotspot_display_impl(self, ssid, password, status): + """刷新热点显示的UI实现""" + if ssid: + self.hotspot_ssid_label.config(text=f"名称: {ssid}") + else: + self.hotspot_ssid_label.config(text="名称: 未配置") + if password: + self.hotspot_pwd_label.config(text=f"密码: {password}") + else: + self.hotspot_pwd_label.config(text="密码: changan2024") + self.hotspot_status_label.config( + text=f"状态: {status}", + fg=self.colors['success'] if '已启动' in status else self.colors['warning'] + ) + + def start_hotspot_action(self): + """打开热点设置并自动轮询检测""" + try: + subprocess.Popen( + 'start ms-settings:network-mobilehotspot', + shell=True, + creationflags=subprocess.CREATE_NO_WINDOW if sys.platform == 'win32' else 0 + ) + except: + pass + self.log("已打开热点设置,正在检测热点...", "INFO") + + def poll(): + for _ in range(15): + time.sleep(2) + ssid, pwd, status = self.get_hotspot_info() + self.refresh_hotspot_display() + if ssid and status == "已启动": + self.log(f"检测到热点: {ssid} / {pwd}", "SUCCESS") + return + self.log("未检测到热点,请确认已开启", "WARNING") + + threading.Thread(target=poll, daemon=True).start() + def run(self): """运行程序""" self.root.mainloop() diff --git a/app.py b/app.py index c5fbcbe..eec82bc 100644 --- a/app.py +++ b/app.py @@ -28,7 +28,7 @@ class ADKAPKGUI: self.root.resizable(True, True) # 设置颜色主题 - self.colors = { + self.colors_dark = { 'bg_dark': '#1e1e2e', 'bg_light': '#2a2a3e', 'accent': '#6c5ce7', @@ -41,6 +41,160 @@ class ADKAPKGUI: 'text_secondary': '#b2bec3', 'border': '#3d3d5e' } + self.colors_light = { + 'bg_dark': '#f5f5f5', + 'bg_light': '#ffffff', + 'accent': '#6c5ce7', + 'accent_hover': '#5b4bc4', + 'success': '#00b894', + 'error': '#d63031', + 'warning': '#e17055', + 'info': '#0984e3', + 'text': '#2d3436', + 'text_secondary': '#636e72', + 'border': '#dfe6e9' + } + self.colors = dict(self.colors_dark) + self.theme = 'dark' + + # 多语言 + self.lang = 'zh' + self.T = { + 'zh': { + 'title': '适用于启源Q07多语言安装', + 'btn_root': '🔓 获取权限', + 'btn_push': '📦 刷入语言包', + 'btn_install': '📱 安装App', + 'btn_language': '🌐 语言设置', + 'btn_timezone': '⏰ 时区设置', + 'btn_settings': '⚙️ 安卓设置', + 'btn_reboot': '🔄 重启设备', + 'btn_disable_upgrade': '❌ 禁用升级', + 'btn_clear_log': '🗑 清空日志', + 'device_label': '设备:', + 'vin_label': 'VIN码:', + 'auth_label': '授权:', + 'log_title': '📋 运行日志', + 'status_ready': '就绪', + 'status_connected': '已连接', + 'status_disconnected': '未连接', + 'status_detecting': '未检测', + 'vin_none': '未获取', + 'auth_none': '未验证', + 'auth_yes': '已授权', + 'auth_no': '未授权', + 'btn_refresh': '🔄 检查', + 'hint_factory': '🔧 关闭车辆WI-FI和4G网络,拨号获取的密码进入工程模式', + 'warn_no_device': '设备未连接', + 'warn_connect_first': '请先连接设备并点击「检查」按钮刷新状态!', + 'warn_no_vin': '请先刷新设备状态并获取VIN码', + 'err_auth_fail': '授权失败', + 'err_device_not_auth': '设备未授权', + 'err_resource_fail': '资源准备失败!', + 'err_no_resource_dir': '资源目录未找到', + 'info_flash_start': '开始刷入语言包...', + 'info_flash_done': '语言包刷入完成,重启设备后生效', + 'info_flash_fail': '语言包刷入失败', + 'warn_no_apk': '未找到语言包文件', + 'info_installing': '安装中...', + 'info_install_done': '安装完成', + 'info_log_cleared': '日志已清空', + 'confirm_reboot': '确定要重启设备吗?', + 'info_rebooting': '设备正在重启...', + 'warn_device_disconnected': '设备已断开连接', + 'info_device_connected': '设备已连接', + 'info_checking_auth': '正在验证授权状态...', + 'info_auth_pass': '✅ 授权验证通过!', + 'info_auth_fail': '❌ 授权验证失败', + 'info_preparing': '正在准备资源...', + 'err_no_package': '错误:未找到资源包', + 'err_no_password': '错误:解压密码未设置', + 'err_no_7za': '错误:未找到 7za.exe', + 'err_extract_fail': '解压失败', + 'info_extracting': '正在解压资源包...', + 'info_extract_done': '资源准备完成', + 'err_extract_user': '资源准备失败,请检查网络连接后重试', + 'warn_no_app_dir': '警告:未找到 app/priv-app 目录', + 'theme_dark': '🌙 暗色', + 'theme_light': '☀️ 亮色', + 'lang_zh': '中', + 'lang_en': 'EN', + 'switch_lang': '语言 / Language', + 'switch_theme': '切换主题', + 'about_company': '宜宾科宜科技有限公司 - 智能设备管理平台', + 'warn_flash_warning': '⚠️ 重要提示', + 'warn_flash_msg': '刷入过程中请勿:\n ● 重启车机\n ● 退出本程序\n ● 关闭电脑\n\n否则可能导致车机系统损坏!', + 'err_wrong_password': '请检查密码是否正确', + 'title_pop_lang': '快捷语言设置', + }, + 'en': { + 'title': 'Qiyuan Q07 Multi-Language', + 'btn_root': '🔓 Get Root', + 'btn_push': '📦 Flash Lang Pkg', + 'btn_install': '📱 Install App', + 'btn_language': '🌐 Language', + 'btn_timezone': '⏰ Timezone', + 'btn_settings': '⚙️ Settings', + 'btn_reboot': '🔄 Reboot', + 'btn_disable_upgrade': '❌ Disable OTA', + 'btn_clear_log': '🗑 Clear Log', + 'device_label': 'Device:', + 'vin_label': 'VIN:', + 'auth_label': 'Auth:', + 'log_title': '📋 Log', + 'status_ready': 'Ready', + 'status_connected': 'Connected', + 'status_disconnected': 'Disconnected', + 'status_detecting': 'Detecting', + 'vin_none': 'None', + 'auth_none': 'Unknown', + 'auth_yes': 'Authorized', + 'auth_no': 'Unauthorized', + 'btn_refresh': '🔄 Check', + 'hint_factory': '🔧 Turn off WiFi & 4G, enter factory mode with dial code', + 'warn_no_device': 'Device not connected', + 'warn_connect_first': 'Please connect device and click Check button!', + 'warn_no_vin': 'Please refresh device status and get VIN', + 'err_auth_fail': 'Authorization failed', + 'err_device_not_auth': 'Device not authorized', + 'err_resource_fail': 'Resource preparation failed!', + 'err_no_resource_dir': 'Resource directory not found', + 'info_flash_start': 'Starting language pack flashing...', + 'info_flash_done': 'Flashing complete, reboot device to take effect', + 'info_flash_fail': 'Flashing failed', + 'warn_no_apk': 'No APK files found', + 'info_installing': 'Installing...', + 'info_install_done': 'Install complete', + 'info_log_cleared': 'Log cleared', + 'confirm_reboot': 'Are you sure you want to reboot?', + 'info_rebooting': 'Device rebooting...', + 'warn_device_disconnected': 'Device disconnected', + 'info_device_connected': 'Device connected', + 'info_checking_auth': 'Verifying authorization...', + 'info_auth_pass': '✅ Authorization passed!', + 'info_auth_fail': '❌ Authorization failed', + 'info_preparing': 'Preparing resources...', + 'err_no_package': 'Error: package.bin not found', + 'err_no_password': 'Error: password not set', + 'err_no_7za': 'Error: 7za.exe not found', + 'err_extract_fail': 'Extraction failed', + 'info_extracting': 'Extracting package...', + 'info_extract_done': 'Resource preparation complete', + 'err_extract_user': 'Resource preparation failed, check network and retry', + 'warn_no_app_dir': 'Warning: app/priv-app directory not found', + 'theme_dark': '🌙 Dark', + 'theme_light': '☀️ Light', + 'lang_zh': '中', + 'lang_en': 'EN', + 'switch_lang': 'Language', + 'switch_theme': 'Theme', + 'about_company': 'Yibin Keyi Technology - Smart Device Platform', + 'warn_flash_warning': '⚠️ Warning', + 'warn_flash_msg': 'During flashing, DO NOT:\n ● Reboot vehicle\n ● Close this app\n ● Power off PC\n\nSystem damage may occur!', + 'err_wrong_password': 'Please check password', + 'title_pop_lang': 'Quick Language Setting', + } + } # 从 exe/py 所在目录查找资源文件 self.base_dir = Path(sys.executable).parent if getattr(sys, 'frozen', False) else Path(__file__).parent @@ -104,19 +258,19 @@ class ADKAPKGUI: title_frame.pack_propagate(False) # 标题 - title_label = tk.Label(title_frame, - text="🚀 适用于启源Q07多语言安装", + self.title_label = tk.Label(title_frame, + text="🚀 适用于启源Q07多语言安装", font=('Microsoft YaHei', 18, 'bold'), fg=self.colors['accent'], bg=self.colors['bg_dark']) - title_label.pack() - - subtitle_label = tk.Label(title_frame, + self.title_label.pack() + + self.subtitle_label = tk.Label(title_frame, text="宜宾科宜科技有限公司 - 智能设备管理平台", font=('Microsoft YaHei', 9), fg=self.colors['text_secondary'], bg=self.colors['bg_dark']) - subtitle_label.pack() + self.subtitle_label.pack() # 按钮区域(两排,每排5个) button_frame = tk.Frame(main_frame, bg=self.colors['bg_light'], relief=tk.RAISED, bd=1) @@ -201,10 +355,11 @@ class ADKAPKGUI: self.status_indicator.pack(side=tk.LEFT) self.status_dot = self.status_indicator.create_oval(2, 2, 8, 8, fill='#636e72') - tk.Label(status_indicator_frame, text="设备:", + self.device_label = tk.Label(status_indicator_frame, text="设备:", font=('Microsoft YaHei', 9), fg=self.colors['text'], - bg=self.colors['bg_light']).pack(side=tk.LEFT, padx=(5, 3)) + bg=self.colors['bg_light']) + self.device_label.pack(side=tk.LEFT, padx=(5, 3)) self.device_status_label = tk.Label(status_indicator_frame, text="未检测", font=('Microsoft YaHei', 9, 'bold'), @@ -216,10 +371,11 @@ class ADKAPKGUI: # VIN信息 vin_frame = tk.Frame(status_bar_frame, bg=self.colors['bg_light']) vin_frame.pack(side=tk.LEFT, padx=20, pady=5) - tk.Label(vin_frame, text="VIN码:", + self.vin_label_title = tk.Label(vin_frame, text="VIN码:", font=('Microsoft YaHei', 9), fg=self.colors['text'], - bg=self.colors['bg_light']).pack(side=tk.LEFT) + bg=self.colors['bg_light']) + self.vin_label_title.pack(side=tk.LEFT) self.vin_label = tk.Label(vin_frame, text="未获取", font=('Microsoft YaHei', 9, 'bold'), fg='#636e72', @@ -230,10 +386,11 @@ class ADKAPKGUI: # 授权状态 auth_frame = tk.Frame(status_bar_frame, bg=self.colors['bg_light']) auth_frame.pack(side=tk.LEFT, padx=20, pady=5) - tk.Label(auth_frame, text="授权:", + self.auth_label_title = tk.Label(auth_frame, text="授权:", font=('Microsoft YaHei', 9), fg=self.colors['text'], - bg=self.colors['bg_light']).pack(side=tk.LEFT) + bg=self.colors['bg_light']) + self.auth_label_title.pack(side=tk.LEFT) self.auth_label = tk.Label(auth_frame, text="未验证", font=('Microsoft YaHei', 9, 'bold'), fg='#636e72', @@ -242,14 +399,14 @@ class ADKAPKGUI: self.auth_label.pack(side=tk.LEFT, padx=(5, 0)) # 刷新按钮 - refresh_btn = tk.Button(status_bar_frame, text="🔄 检查", + self.btn_refresh = tk.Button(status_bar_frame, text="🔄 检查", command=self.refresh_device_status, font=('Microsoft YaHei', 8), fg=self.colors['accent'], bg=self.colors['bg_light'], relief=tk.FLAT, cursor='hand2') - refresh_btn.pack(side=tk.RIGHT, padx=10, pady=5) + self.btn_refresh.pack(side=tk.RIGHT, padx=10, pady=5) # 提示信息区域(设备状态下方) tips_frame = tk.Frame(main_frame, bg=self.colors['bg_light'], relief=tk.RAISED, bd=1) @@ -301,10 +458,11 @@ class ADKAPKGUI: log_title_frame.pack(fill=tk.X) log_title_frame.pack_propagate(False) - tk.Label(log_title_frame, text="📋 运行日志", + self.log_title_label = tk.Label(log_title_frame, text="📋 运行日志", font=('Microsoft YaHei', 10, 'bold'), fg=self.colors['accent'], - bg=self.colors['bg_dark']).pack(side=tk.LEFT, padx=10) + bg=self.colors['bg_dark']) + self.log_title_label.pack(side=tk.LEFT, padx=10) self.btn_clear = tk.Button(log_title_frame, text="🗑 清空日志", command=self.clear_log, @@ -347,7 +505,26 @@ class ADKAPKGUI: fg=self.colors['text_secondary'], bg=self.colors['bg_light']) self.status_text.pack(side=tk.LEFT, padx=10) - + + # 主题和语言切换按钮 + self.btn_theme_switch = tk.Button(bottom_status, text="🌙 暗色", + command=self.toggle_theme, + font=('Microsoft YaHei', 8), + fg=self.colors['accent'], + bg=self.colors['bg_light'], + relief=tk.FLAT, + cursor='hand2') + self.btn_theme_switch.pack(side=tk.RIGHT, padx=5) + + self.btn_lang_switch = tk.Button(bottom_status, text="EN", + command=self.toggle_lang, + font=('Microsoft YaHei', 8, 'bold'), + fg=self.colors['accent'], + bg=self.colors['bg_light'], + relief=tk.FLAT, + cursor='hand2') + self.btn_lang_switch.pack(side=tk.RIGHT, padx=5) + # 调试模式快捷键 self.root.bind('', self._toggle_debug) @@ -398,6 +575,84 @@ class ADKAPKGUI: """将函数调度到主线程执行,确保线程安全""" self.root.after(0, func, *args, **kwargs) + def t(self, key): + """获取翻译文本""" + return self.T.get(self.lang, self.T['zh']).get(key, key) + + def toggle_lang(self): + """切换语言""" + self.lang = 'en' if self.lang == 'zh' else 'zh' + self.btn_lang_switch.config(text=self.t('lang_en') if self.lang == 'zh' else self.t('lang_zh')) + self._refresh_ui_texts() + self.log(f"语言已切换为 {'English' if self.lang == 'en' else '中文'}", "INFO") + + def toggle_theme(self): + """切换主题""" + if self.theme == 'dark': + self.colors = dict(self.colors_light) + self.theme = 'light' + self.btn_theme_switch.config(text=self.t('theme_dark')) + else: + self.colors = dict(self.colors_dark) + self.theme = 'dark' + self.btn_theme_switch.config(text=self.t('theme_light')) + self._apply_theme() + + def _apply_theme(self): + """应用当前主题到所有控件""" + c = self.colors + self.root.configure(bg=c['bg_dark']) + # 更新 ttk 样式 + style = ttk.Style() + style.configure('TFrame', background=c['bg_dark']) + style.configure('TLabel', background=c['bg_dark'], foreground=c['text']) + style.configure('TLabelframe', background=c['bg_dark'], foreground=c['text']) + style.configure('TLabelframe.Label', background=c['bg_dark'], foreground=c['accent']) + style.configure('TProgressbar', background=c['accent'], troughcolor=c['bg_light'], borderwidth=0) + # 更新日志颜色 + self.log_text.tag_config('INFO', foreground='#74b9ff') + self.log_text.tag_config('SUCCESS', foreground='#55efc4') + self.log_text.tag_config('ERROR', foreground='#ff7675') + self.log_text.tag_config('WARNING', foreground='#ffeaa7') + self.log_text.tag_config('CMD', foreground='#a29bfe') + if self.theme == 'light': + self.log_text.configure(bg='#ffffff', fg='#2d3436') + else: + self.log_text.configure(bg='#2d2d3d', fg='#e0e0e0') + + def _refresh_ui_texts(self): + """刷新所有UI文本""" + t = self.t + widgets = [ + (getattr(self, 'title_label', None), 'title', None), + (getattr(self, 'subtitle_label', None), 'about_company', None), + (getattr(self, 'btn_root', None), 'btn_root', None), + (getattr(self, 'btn_push', None), 'btn_push', None), + (getattr(self, 'btn_install_all', None), 'btn_install', None), + (getattr(self, 'btn_language', None), 'btn_language', None), + (getattr(self, 'btn_timezone', None), 'btn_timezone', None), + (getattr(self, 'btn_settings', None), 'btn_settings', None), + (getattr(self, 'btn_reboot', None), 'btn_reboot', None), + (getattr(self, 'btn_exit', None), 'btn_disable_upgrade', None), + (getattr(self, 'btn_clear', None), 'btn_clear_log', None), + (getattr(self, 'log_title_label', None), 'log_title', None), + (getattr(self, 'status_text', None), 'status_ready', None), + (getattr(self, 'device_label', None), 'device_label', None), + (getattr(self, 'vin_label_title', None), 'vin_label', None), + (getattr(self, 'auth_label_title', None), 'auth_label', None), + (getattr(self, 'btn_refresh', None), 'btn_refresh', None), + (getattr(self, 'hint_label', None), 'hint_factory', None), + ] + for w, key, _ in widgets: + if w: + w.config(text=t(key)) + self.btn_theme_switch.config(text=t('theme_light') if self.theme == 'dark' else t('theme_dark')) + self.btn_lang_switch.config(text=t('lang_en') if self.lang == 'zh' else t('lang_zh')) + # 刷新设备状态显示 + if self.vin: + self._update_device_status_impl(self.device_connected, self.vin, + getattr(self, '_last_authorized', False)) + def _log_impl(self, message, level="INFO"): """日志写入的实际实现(必须在主线程调用)""" timestamp = datetime.now().strftime("%H:%M:%S") @@ -462,23 +717,25 @@ class ADKAPKGUI: def _update_device_status_impl(self, connected, vin, authorized): """设备状态UI更新的实际实现(必须在主线程调用)""" + self._last_authorized = authorized + t = self.t if connected: self.status_indicator.itemconfig(self.status_dot, fill=self.colors['success']) - self.device_status_label.config(text="已连接", fg=self.colors['success']) + self.device_status_label.config(text=t('status_connected'), fg=self.colors['success']) if vin: self.vin_label.config(text=vin, fg=self.colors['success']) if authorized: - self.auth_label.config(text="已授权", fg=self.colors['success']) + self.auth_label.config(text=t('auth_yes'), fg=self.colors['success']) else: - self.auth_label.config(text="未授权", fg=self.colors['error']) + self.auth_label.config(text=t('auth_no'), fg=self.colors['error']) else: - self.vin_label.config(text="未获取", fg=self.colors['error']) - self.auth_label.config(text="未验证", fg=self.colors['error']) + self.vin_label.config(text=t('vin_none'), fg=self.colors['error']) + self.auth_label.config(text=t('auth_none'), fg=self.colors['error']) else: self.status_indicator.itemconfig(self.status_dot, fill=self.colors['error']) - self.device_status_label.config(text="未连接", fg=self.colors['error']) - self.vin_label.config(text="未获取", fg=self.colors['error']) - self.auth_label.config(text="未验证", fg=self.colors['error']) + self.device_status_label.config(text=t('status_disconnected'), fg=self.colors['error']) + self.vin_label.config(text=t('vin_none'), fg=self.colors['error']) + self.auth_label.config(text=t('auth_none'), fg=self.colors['error']) def check_device_connection(self): """检查设备是否连接""" @@ -563,9 +820,18 @@ class ADKAPKGUI: def extract_package_silent(self): """静默解压语言包(带进度)""" if not self.package_file.exists(): - self.log(f"未找到资源包 ({self.package_file.name})", "ERROR") + self.log(f"错误:未找到资源包 ({self.package_file})", "ERROR") return False - + + if not self.extract_password: + self.log("错误:解压密码未设置", "ERROR") + return False + + # 检查 7za 是否存在 + if not os.path.exists(self.sz): + self.log(f"错误:未找到 7za.exe ({self.sz})", "ERROR") + return False + try: # 使用用户目录,无需管理员权限 local_appdata = os.environ.get('LOCALAPPDATA', os.path.expanduser('~\\AppData\\Local')) @@ -573,35 +839,49 @@ class ADKAPKGUI: 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) - - - # 使用 7za 高速解压 + + self.log(f"正在准备资源包", "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, text=True, creationflags=subprocess.CREATE_NO_WINDOW if sys.platform == 'win32' else 0 + [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: - stderr = result.stderr.strip() - if stderr: - self.log(f"解压失败: {stderr[:200]}", "ERROR") + # 尝试解析错误信息(7za 输出可能是 GBK) + err_msg = "" + for output in [result.stderr, result.stdout]: + if output: + for enc in ['gbk', 'utf-8']: + try: + err_msg += output.decode(enc, errors='replace') + break + except: + continue + if err_msg.strip(): + self.log(f"解压失败: {err_msg.strip()[:300]}", "ERROR") else: - self.log("解压失败,请检查密码是否正确", "ERROR") + self.log(f"解压失败 (返回码: {result.returncode}),请检查密码是否正确", "ERROR") return False self.update_progress(1, 1, "资源加载完成") - + # 查找app和priv-app目录 self.apps_dir = None self.priv_apps_dir = None @@ -615,13 +895,21 @@ 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("警告:未找到 app/priv-app 目录", "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") return True - + except Exception as e: - self.log(f"资源准备失败", "ERROR") + if getattr(self, 'debug_mode', False): + self.log(f"资源准备失败: {str(e)}", "ERROR") + import traceback + self.log(traceback.format_exc(), "ERROR") + else: + self.log("资源准备失败,请检查网络连接后重试", "ERROR") return False def check_environment(self): @@ -1160,7 +1448,7 @@ class ADKAPKGUI: if self.debug_mode: self.debug_mode = False self.log("调试模式已关闭", "WARNING") - self.status_text.config(text="就绪") + self.status_text.config(text=self.t('status_ready')) self.refresh_device_status() return diff --git a/arm64/7-ZipFar.dll b/arm64/7-ZipFar.dll new file mode 100644 index 0000000..54673d5 Binary files /dev/null and b/arm64/7-ZipFar.dll differ diff --git a/arm64/7za.dll b/arm64/7za.dll new file mode 100644 index 0000000..abad43f Binary files /dev/null and b/arm64/7za.dll differ diff --git a/arm64/7za.exe b/arm64/7za.exe new file mode 100644 index 0000000..b8c0cba Binary files /dev/null and b/arm64/7za.exe differ diff --git a/arm64/7zxa.dll b/arm64/7zxa.dll new file mode 100644 index 0000000..08792cd Binary files /dev/null and b/arm64/7zxa.dll differ diff --git a/history.txt b/history.txt new file mode 100644 index 0000000..6aba84b --- /dev/null +++ b/history.txt @@ -0,0 +1,368 @@ +7-Zip Extra history +------------------- + +This file contains only information about changes related to that package exclusively. +The full history of changes is listed in history.txt in main 7-Zip program. + +26.01 2026-04-27 +------------------------- +- new -spo[d|c|r] switch specifies the path generation mode for the output directory + for archive extraction. The output directory path is generated from the path specified + in the -o{dir_path} switch and the name of the archive being unpacked. + -spod : for Linux/Posix/macOS: -o{dir_path} specifies the direct path to the output directory. + The asterisk (*) character in {dir_path} will not be replaced by the archive name. + -spoc : 7-Zip will concatenate the path specified in -o{dir_path} with the archive name + to form the final path to the output directory. + -spor : 7-Zip will replace asterisk (*) character in the path specified in the -o{dir_path} + with the archive name. This is the default option. +- some bugs were fixed. + + +26.00 2026-02-12 +------------------------- +- improved code for ZIP support. +- the bug was fixed: 7-Zip could not correctly extract TAR archives containing sparse files. +- some bugs were fixed. + + +25.01 2025-08-03 +------------------------- +- The code for handling symbolic links has been changed + to provide greater security when extracting files from archives. + Command line switch -snld20 can be used to bypass default security + checks when creating symbolic links. + + +25.00 2025-07-05 +------------------------- +- 7-Zip for Windows can now use more than 64 CPU threads for compression + to zip/7z/xz archives and for the 7-Zip benchmark. + If there are more than one processor group in Windows (on systems with more than + 64 cpu threads), 7-Zip distributes running CPU threads across different processor groups. +- bzip2 compression speed was increased by 15-40%. +- deflate (zip/gz) compression speed was increased by 1-3%. +- improved support for zip, cpio and fat archives. +- fixed some bugs and vulnerabilities. + + +24.09 2024-11-29 +------------------------- +- The default dictionary size values for LZMA/LZMA2 compression methods were increased: + dictionary size compression level + v24.08 v24.09 v24.09 + 32-bit 64-bit + 8 MB 16 MB 16 MB -mx4 + 16 MB 32 MB 32 MB -mx5 : Normal + 32 MB 64 MB 64 MB -mx6 + 32 MB 64 MB 128 MB -mx7 : Maximum + 64 MB 64 MB 256 MB -mx8 + 64 MB 64 MB 256 MB -mx9 : Ultra + The default dictionary size values for 32-bit versions of LZMA/LZMA2 don't exceed 64 MB. +- If an archive update operation uses a temporary archive folder and + the archive is moved to the destination folder, 7-Zip shows the progress of moving + the archive file, as this operation can take a long time if the archive is large. +- Some bugs were fixed. + + +24.08 2024-08-11 +------------------------- +- The bug in 7-Zip 24.00-24.07 was fixed: + For creating a zip archive: 7-Zip could write extra zero bytes after the end of the archive, + if a file included to archive cannot be compressed to a size smaller than original. + The created zip archive is correct except for the useless zero bytes after the end of the archive. + When unpacking such a zip archive, 7-Zip displays a warning: + "WARNING: There are data after the end of archive". + + +24.07 2024-06-19 +------------------------- +- The bug was fixed: 7-Zip could crash for some incorrect ZSTD archives. + + +24.06 2024-05-26 +------------------------- +- The bug was fixed: 7-Zip could not unpack some ZSTD archives. + + +24.05 2024-05-14 +------------------------- +- New switch -myv={MMNN} to set decoder compatibility version for 7z archive creating. + {MMNN} is 4-digit number that represents the version of 7-Zip without a dot. + If -myv={MMNN} switch is specified, 7-Zip will only use compression methods that can + be decoded by the specified version {MMNN} of 7-Zip and newer versions. + If -myv={MMNN} switch is not specified, -myv=2300 is used, and 7-Zip will only + use compression methods that can be decoded by 7-Zip 23.00 and newer versions. +- New switch -myfa={FilterID} to allow 7-Zip to use the specified filter method for 7z archive creating. +- New switch -myfd={FilterID} to disallow 7-Zip to use the specified filter method for 7z archive creating. + + +24.03 2024-03-23 +------------------------- +- 7-Zip now can use new RISCV filter for compression to 7z and xz archives. + RISCV filter can increase compression ratio for data containing executable + files compiled for RISC-V architecture. +- The speed for LZMA and LZMA2 decompression in ARM64 version for Windows + was increased by 20%-60%. +- new switch -smemx{size}g : to set allowed memory usage limit for RAR archive unpacking. + RAR archives can use dictionary up 64 GB. Default allowed limit for RAR unpacking is 4 GB. +- -slmu switch : to show timestamps as UTC instead of LOCAL TIME. +- -slsl switch : in console 7-Zip for Windows : to show file paths with + linux path separator slash '/' instead of backslash separator '\'. +- 7-Zip supports .sha256 files that use backslash path separator '\'. +- Some bugs were fixed. + + +24.01 2024-01-31 +------------------------- +- 7-Zip now can unpack ZSTD archives (.zst filename extension). +- 7-Zip now can unpack ZIP archives that use ZSTD compression method. +- 7-Zip now supports fast hash algorithm XXH64 that is used in ZSTD. +- Speed optimizations for archive unpacking: zip, gz, cab. +- Speed optimizations for hash caclulation: CRC-32, CRC-64. +- arm64 binaries are included to 7-Zip Extra package. +- The bug was fixed: 7-Zip for Linux could fail for multivolume creation in some cases. +- Some bugs were fixed. + + +23.01 2023-06-20 +------------------------- +- Some bugs were fixed. + + +23.00 2023-05-07 +------------------------- +- 7-Zip now can use new ARM64 filter for compression to 7z and xz archives. + ARM64 filter can increase compression ratio for data containing executable + files compiled for ARM64 (AArch64) architecture. + Also 7-Zip now parses executable files (that have exe and dll filename extensions) + before compressing, and it selects appropriate filter for each parsed file: + - BCJ or BCJ2 filter for x86 executable files, + - ARM64 filter for ARM64 executable files. + Previous versions by default used x86 filter BCJ or BCJ2 for all exe/dll files. +- Default section size for BCJ2 filter was changed from 64 MiB to 240 MiB. + It can increase compression ratio for executable files larger than 64 MiB. +- When new 7-Zip creates multivolume archive, 7-Zip keeps in open state + only volumes that still can be changed. Previous versions kept all volumes + in open state until the end of the archive creation. +- 7-Zip for Linux and macOS now can reduce the number of simultaneously open files, + when 7-Zip opens, extracts or creates multivolume archive. It allows to avoid + the failures for cases with big number of volumes, bacause there is a limitation + for number of open files allowed for a single program in Linux and macOS. +- The bugs were fixed: + - ZIP archives: if multithreaded zip compression was performed with more than one + file to stdout stream (-so switch), 7-zip didn't write "data descriptor" for some files. + - Some another bugs were fixed. + + +22.00 2022-06-16 +------------------------- +- 7-Zip now can create TAR archives in POSIX (pax) tar format with the switches + -ttar -mm=pax or -ttar -mm=posix +- 7-Zip now can store additional file timestamps with high precision (1 ns in Linux) + in tar/pax archives with the following switches: + -ttar -mm=pax -mtp=3 -mtc -mta + + +21.07 2021-12-26 +------------------------- +- New switches: -spm and -im!{file_path} to exclude directories from processing + for specified paths that don't contain path separator character at the end of path. +- The sorting order of files in archives was slightly changed to be more consistent + for cases where the name of some directory is the same as the prefix part of the name + of another directory or file. +- TAR archives created by 7-Zip now are more consistent with archives created by GNU TAR program. + + +21.06 2021-11-24 +------------------------- +- New switch -mmemuse={N}g / -mmemuse=p{N} to set a limit on memory usage (RAM) + for compressing and decompressing. +- Bug in versions 21.00-21.05 was fixed: + 7-Zip didn't set attributes of directories during archive extracting. +- Some bugs were fixed. + + +21.04 beta 2021-11-02 +------------------------- +- 7-Zip now reduces the number of working CPU threads for compression, + if RAM size is not enough for compression with big LZMA2 dictionary. +- 7-Zip now can create and check "file.sha256" text files that contain the list + of file names and SHA-256 checksums in format compatible with sha256sum program. + 7-Zip can work with such checksum files as with archives, + but these files don't contain real file data. + The context menu commands for command line version:: + 7z a -thash file.sha256 *.txt + 7z t -thash file.sha256 + 7z t -thash -shd. file.sha256 + New -shd{dir_path} switch to set the directory that is used to check files + referenced by "file.sha256" file for "Test" operation. + If -shd{dir_path} is not specified, 7-Zip uses the directory where "file.sha256" is stored. +- New -xtd switch to exclude directory metadata records from processing. + + +21.03 beta 2021-07-20 +------------------------- +- The maximum dictionary size for LZMA/LZMA2 compressing was increased to 4 GB (3840 MiB). +- Minor speed optimizations in LZMA/LZMA2 compressing. + + +21.02 alpha 2021-05-06 +------------------------- +- 7-Zip now writes additional field for filename in UTF-8 encoding to zip archives. + It allows to extract correct file name from zip archives on different systems. +- Some changes and improvements in ZIP and TAR code. + + +21.01 alpha 2021-03-09 +------------------------- +- The improvements for speed of ARM64 version using hardware CPU instructions + for AES, CRC-32, SHA-1 and SHA-256. +- The bug in versions 18.02 - 21.00 was fixed: + 7-Zip could not correctly extract some ZIP archives created with xz compression method. +- Some bugs were fixed. + + +20.02 alpha 2020-08-08 +------------------------- +- The default number of LZMA2 chunks per solid block in 7z archive was increased to 64. + It allows to increase the compression speed for big 7z archives, if there is a big number + of CPU cores and threads. +- The speed of PPMd compressing/decompressing was increased for 7z/ZIP archives. +- The new -ssp switch. If the switch -ssp is specified, 7-Zip doesn't allow the system + to modify "Last Access Time" property of source files for archiving and hashing operations. +- Some bugs were fixed. + + +20.00 alpha 2020-02-06 +------------------------- +- 7-Zip now supports new optional match finders for LZMA/LZMA2 compression: bt5 and hc5, + that can work faster than bt4 and hc4 match finders for the data with big redundancy. +- The compression ratio was improved for Fast and Fastest compression levels with the + following default settings: + - Fastest level (-mx1) : hc5 match finder with 256 KB dictionary. + - Fast level (-mx3) : hc5 match finder with 4 MB dictionary. +- Minor speed optimizations in multithreaded LZMA/LZMA2 compression for Normal/Maximum/Ultra + compression levels. +- bzip2 decoding code was updated to support bzip2 archives, created by lbzip2 program. + + +19.02 alpha 2019-09-05 +------------------------- +- 7-Zip now can use new x86/x64 hardware instructions for SHA-1 and SHA-256, supported + by AMD Ryzen and latest Intel CPUs: Ice Lake and Goldmont. + It increases + - the speed of SHA-1/SHA-256 hash value calculation, + - the speed of encryption/decryption in zip AES, + - the speed of key derivation for encryption/decryption in 7z/zip/rar archives. +- The speed of zip AES encryption and 7z/zip/rar AES decryption was increased with + the following improvements: + - 7-Zip now can use new x86/x64 VAES (AVX Vector AES) instructions, supported by + Intel Ice Lake CPU. + - The existing code of x86/x64 AES-NI was improved also. +- Some bugs were fixed. + + +19.00 2019-02-21 +------------------------- +- Encryption strength for 7z archives was increased: + the size of random initialization vector was increased from 64-bit to 128-bit, + and the pseudo-random number generator was improved. +- Some bugs were fixed. + + +18.06 2018-12-30 +------------------------- +- The speed for LZMA/LZMA2 compressing was increased by 3-10%, + and there are minor changes in compression ratio. +- Some bugs were fixed. + + +18.05 2018-04-30 +------------------------- +- The speed for LZMA/LZMA2 compressing was increased + by 8% for fastest/fast compression levels and + by 3% for normal/maximum compression levels. + + +18.03 beta 2018-03-04 +------------------------- +- The speed for single-thread LZMA/LZMA2 decoding + was increased by 30% in x64 version and by 3% in x86 version. +- 7-Zip now can use multi-threading for 7z/LZMA2 decoding, + if there are multiple independent data chunks in LZMA2 stream. + + +9.35 beta 2014-12-07 +------------------------------ + - SFX modules were moved to LZMA SDK package. + + +9.34 alpha 2014-06-22 +------------------------------ + - Minimum supported system now is Windows 2000 for EXE and DLL files. + - all EXE and DLL files use msvcrt.dll. + - 7zr.exe now support AES encryption. + + +9.18 2010-11-02 +------------------------------ + - New small SFX module for installers. + + +9.17 2010-10-04 +------------------------------ + - New 7-Zip plugin for FAR Manager x64. + + +9.10 2009-12-30 +------------------------------ + - 7-Zip for installers now supports LZMA2. + + +9.09 2009-12-12 +------------------------------ + - LZMA2 compression method support. + - Some bugs were fixed. + + +4.65 2009-02-03 +------------------------------ + - Some bugs were fixed. + + +4.38 beta 2006-04-13 +------------------------------ + - SFX for installers now supports new properties in config file: + Progress, Directory, ExecuteFile, ExecuteParameters. + + +4.34 beta 2006-02-27 +------------------------------ + - ISetProperties::SetProperties: + it's possible to specify desirable number of CPU threads: + PROPVARIANT: name=L"mt", vt = VT_UI4, ulVal = NumberOfThreads + If "mt" is not defined, 7za.dll will check number of processors in system to set + number of desirable threads. + Now 7za.dll can use: + 2 threads for LZMA compressing + N threads for BZip2 compressing + 4 threads for BZip2 decompressing + Other codecs use only one thread. + Note: 7za.dll can use additional "small" threads with low CPU load. + - It's possible to call ISetProperties::SetProperties to specify "mt" property for decoder. + + +4.33 beta 2006-02-05 +------------------------------ + - Compressing speed and Memory requirements were increased. + Default dictionary size was increased: Fastest: 64 KB, Fast: 1 MB, + Normal: 4 MB, Max: 16 MB, Ultra: 64 MB. + - 7z/LZMA now can use only these match finders: HC4, BT2, BT3, BT4 + + +4.27 2005-09-21 +------------------------------ + - Some GUIDs/interfaces were changed. + IStream.h: + ISequentialInStream::Read now works as old ReadPart + ISequentialOutStream::Write now works as old WritePart diff --git a/readme.txt b/readme.txt new file mode 100644 index 0000000..82664e7 --- /dev/null +++ b/readme.txt @@ -0,0 +1,126 @@ +7-Zip Extra 26.01 +----------------- + +7-Zip Extra is package of extra modules of 7-Zip. + +7-Zip Copyright (C) 1999-2026 Igor Pavlov. + +7-Zip is free software. Read License.txt for more information about license. + +Source code of binaries can be found at: + http://www.7-zip.org/ + +This package contains the following files: + +7za.exe - standalone console version of 7-Zip with reduced formats support. +7za.dll - library for working with 7z archives +7zxa.dll - library for extracting from 7z archives +License.txt - license information +readme.txt - this file +7-zip.chm - Help file + +Far\ - plugin for Far Manager +x64\ - binaries for x64 +arm64\ - binaries for arm64 + + +All 32-bit binaries can work in: + Windows 2000 / 2003 / 2008 / XP / Vista / 7 / 8 / 10 + and in any Windows x64 version with WoW64 support. +All x64 binaries can work in any Windows x64 version. + +All x86 and x64 binaries use msvcrt.dll. + +7za.exe +------- + +7za.exe - is a standalone console version of 7-Zip with reduced formats support. + + Extra: 7za.exe : support for only some formats of 7-Zip. + 7-Zip: 7z.exe with 7z.dll : support for all formats of 7-Zip. + +7za.exe and 7z.exe from 7-Zip have same command line interface. +7za.exe doesn't use external DLL files. + +You can read Help File (7-zip.chm) from 7-Zip package for description +of all commands and switches for 7za.exe and 7z.exe. + +7za.exe features: + + - High compression ratio in 7z format + - Supported formats: + - Packing / unpacking: 7z, xz, ZIP, GZIP, BZIP2 and TAR + - Unpacking only: lzma, CAB, ZSTD. + - Highest compression ratio for ZIP and GZIP formats. + - Fast compression and decompression + - Strong AES-256 encryption in 7z and ZIP formats. + +Note: another package "LZMA SDK" contains 7zr.exe - that is reduced version of 7za.exe. +But you can use 7zr.exe as "public domain" code. + + + +DLL files +--------- + +7za.dll and 7zxa.dll are reduced versions of 7z.dll from 7-Zip. +7za.dll and 7zxa.dll support only 7z format. +Note: 7z.dll is main DLL file that works with all archive types in 7-Zip. + +7za.dll and 7zxa.dll support the following decoding methods: + - LZMA, LZMA2, PPMD, BCJ, BCJ2, COPY, 7zAES, BZip2, Deflate. + +7za.dll also supports 7z encoding with the following encoding methods: + - LZMA, LZMA2, PPMD, BCJ, BCJ2, COPY, 7zAES. + +7za.dll and 7zxa.dll work via COM interfaces. +But these DLLs don't use standard COM interfaces for objects creating. + +Look also example code that calls DLL functions (in source code of 7-Zip): + + 7zip\UI\Client7z + +Another example of binary that uses these interface is 7-Zip itself. +The following binaries from 7-Zip use 7z.dll: + - 7z.exe (console version) + - 7zG.exe (GUI version) + - 7zFM.exe (7-Zip File Manager) + +Note: The source code of LZMA SDK also contains the code for similar DLLs +(DLLs without BZip2, Deflate support). And these files from LZMA SDK can be +used as "public domain" code. If you use LZMA SDK files, you don't need to +follow GNU LGPL rules, if you want to change the code. + + + + +License FAQ +----------- + +Can I use the EXE or DLL files from 7-Zip in a commercial application? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Yes, but you are required to specify in documentation for your application: + (1) that you used parts of the 7-Zip program, + (2) that 7-Zip is licensed under the GNU LGPL license and + (3) you must give a link to www.7-zip.org, where the source code can be found. + + +Can I use the source code of 7-Zip in a commercial application? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Since 7-Zip is licensed under the GNU LGPL you must follow the rules of that license. +In brief, it means that any LGPL'ed code must remain licensed under the LGPL. +For instance, you can change the code from 7-Zip or write a wrapper for some +code from 7-Zip and compile it into a DLL; but, the source code of that DLL +(including your modifications / additions / wrapper) must be licensed under +the LGPL or GPL. +Any other code in your application can be licensed as you wish. This scheme allows +users and developers to change LGPL'ed code and recompile that DLL. That is the +idea of free software. Read more here: http://www.gnu.org/. + + + +Note: You can look also LZMA SDK, which is available under a more liberal license. + + +--- +End of document diff --git a/x64/7za.dll b/x64/7za.dll new file mode 100644 index 0000000..0aaad70 Binary files /dev/null and b/x64/7za.dll differ diff --git a/x64/7za.exe b/x64/7za.exe new file mode 100644 index 0000000..2577379 Binary files /dev/null and b/x64/7za.exe differ diff --git a/x64/7zxa.dll b/x64/7zxa.dll new file mode 100644 index 0000000..faafd2f Binary files /dev/null and b/x64/7zxa.dll differ