From 5b3b8b5bc314691d6d75855239f1369ca51f136e Mon Sep 17 00:00:00 2001 From: H3CoF6 <1707889225@qq.com> Date: Sat, 28 Feb 2026 05:00:42 +0800 Subject: [PATCH] feat: add progress --- electron/services/keyService.ts | 19 ++++------ src/pages/SettingsPage.scss | 67 +++++++++++++++++++++++++++++++++ src/pages/SettingsPage.tsx | 55 +++++++++++++++++++++------ 3 files changed, 119 insertions(+), 22 deletions(-) diff --git a/electron/services/keyService.ts b/electron/services/keyService.ts index f3c2d2b..2da4539 100644 --- a/electron/services/keyService.ts +++ b/electron/services/keyService.ts @@ -4,7 +4,6 @@ import { existsSync, readdirSync, readFileSync, statSync, copyFileSync, mkdirSyn import { execFile, spawn } from 'child_process' import { promisify } from 'util' import { Worker } from 'worker_threads' -import crypto from 'crypto' import os from 'os' const execFileAsync = promisify(execFile) @@ -936,7 +935,8 @@ export class KeyService { if (msg.type === 'progress') { totalScanned += msg.scanned const percent = ((totalScanned / totalCombinations) * 100).toFixed(1) - onProgress?.(`多核爆破中: 已尝试 ${(totalScanned / 10000).toFixed(0)} 万次 (${percent}%)`) + // 优化文案,并确保包含 (xx.x%) 供前端解析 + onProgress?.(`多核爆破引擎运行中:已扫描 ${(totalScanned / 10000).toFixed(0)} 万个密钥空间 (${percent}%)`) } else if (msg.type === 'success' && !resolved) { resolved = true cleanup() @@ -966,31 +966,28 @@ export class KeyService { manualDir?: string, onProgress?: (message: string) => void ): Promise { - onProgress?.('正在定位微信账号目录...') + onProgress?.('正在定位微信账号数据目录...') const accountDir = this.resolveAccountDir(manualDir) if (!accountDir) return { success: false, error: '未找到微信账号目录' } - // 精确提取 wxid,直接剥离 _f1c4 类似的 4 位十六进制校验码 let wxid = basename(accountDir) wxid = wxid.replace(/_[0-9a-fA-F]{4}$/, '') - onProgress?.('正在收集模板文件...') + onProgress?.('正在收集并分析加密模板文件...') const templateFiles = this.findTemplateDatFiles(accountDir) if (!templateFiles.length) return { success: false, error: '未找到模板文件' } - onProgress?.('正在计算 XOR 密钥...') + onProgress?.('正在计算特征 XOR 密钥...') const xorKey = this.getXorKey(templateFiles) if (xorKey == null) return { success: false, error: '无法计算 XOR 密钥' } - onProgress?.('正在读取加密模板数据...') + onProgress?.('正在读取加密模板区块...') const ciphertexts = this.getCiphertextsFromTemplate(templateFiles) if (ciphertexts.length === 0) return { success: false, error: '无法读取加密模板数据' } - // 提示收集到的样本数量 - onProgress?.(`提取到 ${ciphertexts.length} 个特征样本,开始进行交叉校验...`) + onProgress?.(`成功提取 ${ciphertexts.length} 个特征样本,准备交叉校验...`) + onProgress?.(`准备启动 ${os.cpus().length || 4} 线程并发爆破引擎 (基于 wxid: ${wxid})...`) - onProgress?.(`正在利用多核爆破 AES 密钥 (基于 wxid: ${wxid})...`) - // 注意这里传入的是 ciphertexts 数组 const aesKey = await this.bruteForceAesKey(xorKey, wxid, ciphertexts, (msg) => { onProgress?.(msg) }) diff --git a/src/pages/SettingsPage.scss b/src/pages/SettingsPage.scss index eb54f95..a27d74d 100644 --- a/src/pages/SettingsPage.scss +++ b/src/pages/SettingsPage.scss @@ -2172,4 +2172,71 @@ width: 100%; margin-top: 12px; } +} + +.brute-force-progress { + margin-top: 12px; + padding: 14px 16px; + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: 12px; + animation: slideUp 0.3s ease; + + .status-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 10px; + + .status-text { + font-size: 13px; + color: var(--text-primary); + font-weight: 500; + margin: 0; + // 增加文字呼吸灯效果,表明正在运行 + animation: pulse 2s ease-in-out infinite; + } + + .percent { + font-size: 14px; + color: var(--primary); + font-weight: 700; + font-family: var(--font-mono); + } + } + + .progress-bar-container { + width: 100%; + height: 8px; + background: var(--bg-primary); + border-radius: 4px; + overflow: hidden; + border: 1px solid var(--border-color); + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.05); + + .fill { + height: 100%; + background: linear-gradient(90deg, var(--primary) 0%, color-mix(in srgb, var(--primary) 60%, white) 100%); + border-radius: 4px; + transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1); + position: relative; + + // 流光扫过的高亮特效 + &::after { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient( + 90deg, + transparent, + rgba(255, 255, 255, 0.3), + transparent + ); + animation: progress-shimmer 1.5s infinite linear; + } + } + } } \ No newline at end of file diff --git a/src/pages/SettingsPage.tsx b/src/pages/SettingsPage.tsx index 85dbee0..faa558f 100644 --- a/src/pages/SettingsPage.tsx +++ b/src/pages/SettingsPage.tsx @@ -82,6 +82,8 @@ function SettingsPage() { const exportExcelColumnsDropdownRef = useRef(null) const exportConcurrencyDropdownRef = useRef(null) const [cachePath, setCachePath] = useState('') + const [imageKeyProgress, setImageKeyProgress] = useState(0) + const [imageKeyPercent, setImageKeyPercent] = useState(null) const [logEnabled, setLogEnabled] = useState(false) const [whisperModelName, setWhisperModelName] = useState('base') @@ -222,8 +224,28 @@ function SettingsPage() { const removeDb = window.electronAPI.key.onDbKeyStatus((payload: { message: string; level: number }) => { setDbKeyStatus(payload.message) }) - const removeImage = window.electronAPI.key.onImageKeyStatus((payload: { message: string }) => { - setImageKeyStatus(payload.message) + + const removeImage = window.electronAPI.key.onImageKeyStatus((payload: { message: string, percent?: number }) => { + let msg = payload.message; + let pct = payload.percent; + + // 如果后端没有显式传 percent,则用正则从字符串中提取如 "(12.5%)" + if (pct === undefined) { + const match = msg.match(/\(([\d.]+)%\)/); + if (match) { + pct = parseFloat(match[1]); + // 将百分比从文本中剥离,让 UI 更清爽 + msg = msg.replace(/\s*\([\d.]+%\)/, ''); + } + } + + setImageKeyStatus(msg); + if (pct !== undefined) { + setImageKeyPercent(pct); + } else if (msg.includes('启动多核') || msg.includes('定位') || msg.includes('准备')) { + // 预热阶段 + setImageKeyPercent(0); + } }) return () => { removeDb?.() @@ -745,15 +767,18 @@ function SettingsPage() { } const handleAutoGetImageKey = async () => { - if (isFetchingImageKey) return + if (isFetchingImageKey) return; if (!dbPath) { - showMessage('请先选择数据库目录', false) - return + showMessage('请先选择数据库目录', false); + return; } - setIsFetchingImageKey(true) - setImageKeyStatus('正在准备获取图片密钥...') + setIsFetchingImageKey(true); + setImageKeyPercent(0) + setImageKeyStatus('正在初始化多核爆破引擎...'); + setImageKeyProgress(0); // 重置进度 + try { - const accountPath = wxid ? `${dbPath}/${wxid}` : dbPath + const accountPath = wxid ? `${dbPath}/${wxid}` : dbPath; const result = await window.electronAPI.key.autoGetImageKey(accountPath) if (result.success && result.aesKey) { if (typeof result.xorKey === 'number') { @@ -1352,11 +1377,19 @@ function SettingsPage() { {isFetchingImageKey ? '获取中...' : '自动获取图片密钥'} {isFetchingImageKey ? ( -
- {imageKeyStatus || '正在启动多核爆破引擎...'} +
+
+ {imageKeyStatus || '正在启动多核爆破引擎...'} + {imageKeyPercent !== null && {imageKeyPercent.toFixed(1)}%} +
+ {imageKeyPercent !== null && ( +
+
+
+ )}
) : ( - imageKeyStatus &&
{imageKeyStatus}
+ imageKeyStatus &&
{imageKeyStatus}
)}