feat: 支持图片密钥配置与内存扫描

This commit is contained in:
电摇小子
2026-07-14 10:20:23 +08:00
committed by 电摇小子
parent 8d82fabf57
commit 71e970c55c
11 changed files with 490 additions and 67 deletions
@@ -12,6 +12,9 @@ interface AppSettings {
apiEnabled: boolean
apiHost: string
apiPort: number
imageKeyRoot: string
imageXorKey: string
imageAesKey: string
}
interface ApiState {
@@ -51,6 +54,10 @@ export const SettingsPanel: React.FC<SettingsPanelProps> = ({
{ kind: 'idle' | 'ok' | 'fail'; message: string; wxid?: string; accountRoot?: string }
>({ kind: 'idle', message: '' })
const [reopenStatus, setReopenStatus] = useState<string>('')
const [imageKeyStatus, setImageKeyStatus] = useState<{
kind: 'idle' | 'ok' | 'fail'
message: string
}>({ kind: 'idle', message: '' })
const [busy, setBusy] = useState(false)
useEffect(() => {
@@ -58,6 +65,13 @@ export const SettingsPanel: React.FC<SettingsPanelProps> = ({
void refresh()
}, [open])
useEffect(() => {
if (!open) return
return window.api.onImageKeyStatus(({ message }) => {
setImageKeyStatus({ kind: 'idle', message })
})
}, [open])
async function refresh(): Promise<void> {
const [{ settings, settingsPath }, api] = await Promise.all([
window.api.getSettings(),
@@ -140,6 +154,37 @@ export const SettingsPanel: React.FC<SettingsPanelProps> = ({
setBusy(false)
}
async function handleAutoGetImageKey(): Promise<void> {
if (!settings) return
setBusy(true)
setImageKeyStatus({ kind: 'idle', message: '正在扫描微信内存获取图片密钥...' })
try {
const result = await window.api.autoGetImageKey()
if (!result.success || !result.aesKey) {
setImageKeyStatus({ kind: 'fail', message: result.error || '图片密钥获取失败' })
return
}
const imageXorKey =
result.imageXorKey ||
(typeof result.xorKey === 'number'
? `0x${result.xorKey.toString(16).toUpperCase().padStart(2, '0')}`
: settings.imageXorKey)
const imageAesKey = result.imageAesKey || result.aesKey
setSettings(result.settings || { ...settings, imageXorKey, imageAesKey })
setImageKeyStatus({
kind: 'ok',
message: result.verified ? '图片密钥已获取并校验通过' : '图片密钥已获取,未完成模板校验'
})
} catch (error) {
setImageKeyStatus({
kind: 'fail',
message: error instanceof Error ? error.message : String(error)
})
} finally {
setBusy(false)
}
}
return (
<div className="settings-overlay" onClick={onClose}>
<div className="settings-modal" onClick={(e) => e.stopPropagation()}>
@@ -216,6 +261,60 @@ export const SettingsPanel: React.FC<SettingsPanelProps> = ({
</div>
</section>
{/* 图片解密密钥 */}
<section className="settings-section">
<div className="settings-section-title"></div>
<div className="settings-row">
<input
type="text"
className="settings-input"
value={settings?.imageKeyRoot || settings?.dbRoot || ''}
onChange={(e) =>
setSettings(settings ? { ...settings, imageKeyRoot: e.target.value } : null)
}
onBlur={(e) => handleSave({ imageKeyRoot: e.target.value })}
placeholder={dbRootPlaceholder}
spellCheck={false}
/>
</div>
<div className="settings-row">
<input
type="text"
className="settings-input settings-input-quarter"
value={settings?.imageXorKey ?? ''}
onChange={(e) =>
setSettings(settings ? { ...settings, imageXorKey: e.target.value } : null)
}
onBlur={(e) => handleSave({ imageXorKey: e.target.value })}
placeholder="XOR Key,如 0x40"
spellCheck={false}
/>
<input
type="text"
className="settings-input"
value={settings?.imageAesKey ?? ''}
onChange={(e) =>
setSettings(settings ? { ...settings, imageAesKey: e.target.value } : null)
}
onBlur={(e) => handleSave({ imageAesKey: e.target.value })}
placeholder="AES Key16 位字符"
spellCheck={false}
/>
<button className="settings-btn" onClick={handleAutoGetImageKey} disabled={busy}>
</button>
</div>
{imageKeyStatus.message && (
<div className={`settings-status ${imageKeyStatus.kind}`}>
{imageKeyStatus.kind === 'ok' ? '✓ ' : imageKeyStatus.kind === 'fail' ? '✗ ' : ''}
{imageKeyStatus.message}
</div>
)}
<div className="settings-hint">
使Windows 2-3
</div>
</section>
{/* 数据库根目录 */}
<section className="settings-section">
<div className="settings-section-title"></div>