550 lines
25 KiB
C#
550 lines
25 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Collections.ObjectModel;
|
||
using System.Globalization;
|
||
using System.Linq;
|
||
using System.Threading;
|
||
using System.Threading.Tasks;
|
||
using System.Windows;
|
||
using System.Windows.Data;
|
||
using Microsoft.Win32;
|
||
using MemScanEDR.Models;
|
||
using MemScanEDR.Services;
|
||
|
||
namespace MemScanEDR;
|
||
|
||
// ─── Value Converter: List<string> → comma-separated string ───
|
||
public class ListToStringConverter : IValueConverter
|
||
{
|
||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) =>
|
||
value is List<string> list && list.Count > 0 ? string.Join(", ", list) : "—";
|
||
|
||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) =>
|
||
throw new NotSupportedException();
|
||
}
|
||
|
||
// ─── Simple language helper ──────────────────────────────────
|
||
public static class Lang
|
||
{
|
||
private static bool _isChinese = true;
|
||
public static bool IsChinese => _isChinese;
|
||
|
||
public static void SetChinese(bool zh) => _isChinese = zh;
|
||
|
||
public static string T(string zh, string en) => _isChinese ? zh : en;
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════════
|
||
// MAIN WINDOW
|
||
// ═══════════════════════════════════════════════════════════════
|
||
public partial class MainWindow : Window
|
||
{
|
||
// ── Services ───────────────────────────────────────────────
|
||
private readonly ProcessCollector _processCollector = new();
|
||
private readonly MemoryScanner _memoryScanner = new();
|
||
private readonly CeMcpManager _ceMcp = new();
|
||
private AIAnalyzer? _aiAnalyzer;
|
||
|
||
// ── Application state ──────────────────────────────────────
|
||
private AppSettings _settings = null!;
|
||
private readonly ObservableCollection<ScanResult> _results = new();
|
||
private readonly ObservableCollection<string> _scanLog = new();
|
||
private List<Signature> _signatures = new();
|
||
private CancellationTokenSource? _cts;
|
||
private bool _isScanning;
|
||
|
||
// ═══════════════════════════════════════════════════════════
|
||
// CONSTRUCTOR
|
||
// ═══════════════════════════════════════════════════════════
|
||
public MainWindow()
|
||
{
|
||
try
|
||
{
|
||
InitializeComponent();
|
||
|
||
// Bind observable collections to DataGrids / ListBox
|
||
DgAlerts.ItemsSource = _results;
|
||
DgProcesses.ItemsSource = _results;
|
||
LstScanLog.ItemsSource = _scanLog;
|
||
|
||
// Load settings & initialize AI analyzer
|
||
_settings = AppSettings.Load();
|
||
_aiAnalyzer = new AIAnalyzer(_settings.AI);
|
||
|
||
// Load signature database
|
||
_signatures = _memoryScanner.LoadSignatures(_settings.RulesDir);
|
||
|
||
// Apply saved settings to the Settings view
|
||
LoadSettingsToUI();
|
||
ApplyLanguage();
|
||
|
||
AddLog(Lang.T("[init] MemScan-EDR 控制台已加载", "[init] MemScan-EDR console loaded"));
|
||
|
||
// Start CE-MCP server at launch (background)
|
||
Task.Run(() =>
|
||
{
|
||
if (_settings.CeMcpPath != "disabled")
|
||
{
|
||
Dispatcher.Invoke(() => AddLog(Lang.T("[mcp] 正在启动 CE-MCP 服务器...", "[mcp] Starting CE-MCP server...")));
|
||
var ok = _ceMcp.Start(_settings.CeMcpCommand, _settings.CeMcpPath == "auto" ? null : _settings.CeMcpPath);
|
||
if (ok)
|
||
{
|
||
Thread.Sleep(1500);
|
||
Dispatcher.Invoke(() => AddLog(Lang.T("[mcp] CE-MCP 服务器已启动", "[mcp] CE-MCP server started")));
|
||
}
|
||
else
|
||
{
|
||
Dispatcher.Invoke(() => AddLog(Lang.T("[mcp] CE-MCP 服务器未找到,将跳过", "[mcp] CE-MCP server not found — skipped")));
|
||
}
|
||
}
|
||
});
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
System.IO.File.AppendAllText(
|
||
System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "crash.log"),
|
||
$"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] MainWindow init error\n{ex}\n\n");
|
||
System.Windows.MessageBox.Show($"Init error (see crash.log):\n\n{ex.Message}", "MemScan-EDR",
|
||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||
}
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════
|
||
// LANGUAGE TOGGLE
|
||
// ═══════════════════════════════════════════════════════════
|
||
private void BtnLangToggle_Click(object sender, RoutedEventArgs e)
|
||
{
|
||
Lang.SetChinese(!Lang.IsChinese);
|
||
ApplyLanguage();
|
||
}
|
||
|
||
private void ApplyLanguage()
|
||
{
|
||
// Title bar
|
||
BtnLang.Content = Lang.T("EN", "中");
|
||
|
||
// Sidebar
|
||
TbMonHeader.Text = Lang.T("监控", "MONITORING");
|
||
TbSysHeader.Text = Lang.T("系统", "SYSTEM");
|
||
TbNavDashboard.Text = Lang.T("仪表盘", "Dashboard");
|
||
TbNavProcess.Text = Lang.T("进程列表", "Processes");
|
||
TbNavScan.Text = Lang.T("扫描进度", "Scan Progress");
|
||
TbNavSettings.Text = Lang.T("设置", "Settings");
|
||
TbQuickScan.Text = Lang.T("快速扫描", "Quick Scan");
|
||
TbQuickScanDesc.Text = Lang.T("立即扫描所有进程", "Scan all processes now");
|
||
BtnStartScan.Content = Lang.T("开始扫描", "Start Scan");
|
||
|
||
// Dashboard
|
||
TbDashboardTitle.Text = Lang.T("安全仪表盘", "Security Dashboard");
|
||
TbStatTotal.Text = Lang.T("已扫描", "TOTAL SCANNED");
|
||
TbStatTotalDesc.Text = Lang.T("进程", "Processes");
|
||
TbStatHigh.Text = Lang.T("高风险", "HIGH RISK");
|
||
TbStatHighDesc.Text = Lang.T("严重告警", "Critical Alerts");
|
||
TbStatSusp.Text = Lang.T("可疑", "SUSPICIOUS");
|
||
TbStatSuspDesc.Text = Lang.T("警告", "Warnings");
|
||
TbStatSafe.Text = Lang.T("安全", "SAFE");
|
||
TbStatSafeDesc.Text = Lang.T("正常", "Clean");
|
||
TbRecentAlerts.Text = Lang.T("最近告警", "Recent Alerts");
|
||
|
||
// Scan view
|
||
TbScanTitle.Text = Lang.T("扫描进度", "Scan Progress");
|
||
BtnStopScan.Content = Lang.T("停止扫描", "Stop Scan");
|
||
TbScanLog.Text = Lang.T("扫描日志", "Scan Log");
|
||
|
||
// Process view
|
||
TbProcTitle.Text = Lang.T("扫描进程", "Scanned Processes");
|
||
|
||
// Settings
|
||
TbSettingsTitle.Text = Lang.T("设置", "Settings");
|
||
TbAiConfig.Text = Lang.T("AI 配置", "AI Configuration");
|
||
TbAiProvider.Text = Lang.T("AI 提供商", "AI Provider");
|
||
TbBaseUrl.Text = Lang.T("API 地址", "API Base URL");
|
||
TbApiKey.Text = Lang.T("API 密钥", "API Key");
|
||
TbModel.Text = Lang.T("模型名称", "Model Name");
|
||
TbPathConfig.Text = Lang.T("路径配置", "Path Configuration");
|
||
TbCeMcpPath.Text = Lang.T("CE-MCP 服务器路径", "CE-MCP Server Path");
|
||
TbRulesDir.Text = Lang.T("扫描规则目录", "Scan Rules Directory");
|
||
TbLogDir.Text = Lang.T("日志目录", "Log Directory");
|
||
TbScanProfile.Text = Lang.T("扫描配置", "Scan Profile");
|
||
BtnSaveSettings.Content = Lang.T("保存设置", "Save Settings");
|
||
|
||
// ComboBox items
|
||
CmbAIProvider.Items[0] = new System.Windows.Controls.ComboBoxItem { Content = Lang.T("启发式规则 (无需 API)", "Heuristic (rule-based, no API)"), Tag = "Heuristic" };
|
||
CmbAIProvider.Items[1] = new System.Windows.Controls.ComboBoxItem { Content = Lang.T("OpenAI 兼容 (OpenAI / DeepSeek / 通义等)", "OpenAI-Compatible (OpenAI / DeepSeek / etc.)"), Tag = "OpenAICompatible" };
|
||
CmbAIProvider.Items[2] = new System.Windows.Controls.ComboBoxItem { Content = Lang.T("Claude (Anthropic API)", "Claude (Anthropic API)"), Tag = "Claude" };
|
||
CmbAIProvider.Items[3] = new System.Windows.Controls.ComboBoxItem { Content = Lang.T("本地模型 (Ollama / LM Studio 等)", "Local (Ollama / LM Studio / etc.)"), Tag = "Local" };
|
||
|
||
CmbScanProfile.Items[0] = new System.Windows.Controls.ComboBoxItem { Content = Lang.T("快速 - 简单扫描", "fast - Quick scan"), Tag = "fast" };
|
||
CmbScanProfile.Items[1] = new System.Windows.Controls.ComboBoxItem { Content = Lang.T("平衡 - 默认扫描深度", "balanced - Default scan depth"), Tag = "balanced" };
|
||
CmbScanProfile.Items[2] = new System.Windows.Controls.ComboBoxItem { Content = Lang.T("深度 - 完整内存 + AI 分析", "deep - Full memory + AI analysis"), Tag = "deep" };
|
||
|
||
// Restore selected index
|
||
LoadSettingsToUI();
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════
|
||
// SIDEBAR NAVIGATION
|
||
// ═══════════════════════════════════════════════════════════
|
||
private void NavChanged(object sender, RoutedEventArgs e)
|
||
{
|
||
if (DashboardView == null || ProcessView == null || ScanView == null || SettingsView == null)
|
||
return;
|
||
|
||
DashboardView.Visibility = Visibility.Collapsed;
|
||
ProcessView.Visibility = Visibility.Collapsed;
|
||
ScanView.Visibility = Visibility.Collapsed;
|
||
SettingsView.Visibility = Visibility.Collapsed;
|
||
|
||
if (sender == NavDashboard) DashboardView.Visibility = Visibility.Visible;
|
||
else if (sender == NavProcess) ProcessView.Visibility = Visibility.Visible;
|
||
else if (sender == NavScan) ScanView.Visibility = Visibility.Visible;
|
||
else if (sender == NavSettings) SettingsView.Visibility = Visibility.Visible;
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════
|
||
// WINDOW CHROME BUTTONS
|
||
// ═══════════════════════════════════════════════════════════
|
||
private void TitleBar_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
|
||
{
|
||
if (e.ClickCount == 2)
|
||
BtnMaximize(sender, e);
|
||
else
|
||
DragMove();
|
||
}
|
||
|
||
private void BtnMinimize(object sender, RoutedEventArgs e) =>
|
||
WindowState = WindowState.Minimized;
|
||
|
||
private void BtnMaximize(object sender, RoutedEventArgs e) =>
|
||
WindowState = WindowState == WindowState.Maximized
|
||
? WindowState.Normal
|
||
: WindowState.Maximized;
|
||
|
||
private void BtnClose(object sender, RoutedEventArgs e)
|
||
{
|
||
_cts?.Cancel();
|
||
_ceMcp.Stop();
|
||
Close();
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════
|
||
// SCAN PROGRESS REPORTING MODEL
|
||
// ═══════════════════════════════════════════════════════════
|
||
private class ScanProgress
|
||
{
|
||
public string ProcessName { get; init; } = "";
|
||
public string Message { get; init; } = "";
|
||
public double Progress { get; init; }
|
||
public ScanResult? Result { get; init; }
|
||
public bool IsComplete { get; init; }
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════
|
||
// START SCAN
|
||
// ═══════════════════════════════════════════════════════════
|
||
private async void BtnStartScan_Click(object sender, RoutedEventArgs e)
|
||
{
|
||
if (_isScanning) return;
|
||
|
||
_isScanning = true;
|
||
_cts = new CancellationTokenSource();
|
||
var ct = _cts.Token;
|
||
|
||
BtnStartScan.IsEnabled = false;
|
||
BtnStopScan.IsEnabled = true;
|
||
_scanLog.Clear();
|
||
_results.Clear();
|
||
UpdateStats();
|
||
ScanProgressBar.Value = 0;
|
||
LblCurrentProcess.Text = Lang.T("初始化中...", "Initializing...");
|
||
|
||
NavScan.IsChecked = true;
|
||
|
||
SetScanStatus(Lang.T("扫描中", "SCANNING"), true);
|
||
AddLog(Lang.T($"[scan] 扫描开始 — {DateTime.Now:yyyy-MM-dd HH:mm:ss}", $"[scan] Scan started — {DateTime.Now:yyyy-MM-dd HH:mm:ss}"));
|
||
|
||
var progress = new Progress<ScanProgress>(OnScanProgress);
|
||
|
||
try
|
||
{
|
||
await Task.Run(() => RunScanAsync(progress, ct), ct);
|
||
}
|
||
catch (OperationCanceledException)
|
||
{
|
||
AddLog(Lang.T("[scan] 扫描已取消", "[scan] Scan cancelled by user"));
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
AddLog($"[error] {ex.Message}");
|
||
}
|
||
finally
|
||
{
|
||
_isScanning = false;
|
||
BtnStartScan.IsEnabled = true;
|
||
BtnStopScan.IsEnabled = false;
|
||
SetScanStatus(Lang.T("空闲", "IDLE"), false);
|
||
AddLog(Lang.T($"[scan] 扫描完成 — {DateTime.Now:yyyy-MM-dd HH:mm:ss}", $"[scan] Scan completed — {DateTime.Now:yyyy-MM-dd HH:mm:ss}"));
|
||
}
|
||
}
|
||
|
||
private void BtnStopScan_Click(object sender, RoutedEventArgs e)
|
||
{
|
||
_cts?.Cancel();
|
||
AddLog(Lang.T("[scan] 正在停止...", "[scan] Stop requested..."));
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════
|
||
// CORE SCAN LOGIC (background)
|
||
// ═══════════════════════════════════════════════════════════
|
||
private async Task RunScanAsync(IProgress<ScanProgress> progress, CancellationToken ct)
|
||
{
|
||
// CE-MCP already started at launch — just check if it's running
|
||
if (_settings.CeMcpPath != "disabled" && !_ceMcp.IsRunning)
|
||
{
|
||
progress.Report(new ScanProgress { Message = Lang.T("[mcp] CE-MCP 未运行,尝试启动...", "[mcp] CE-MCP not running, attempting start...") });
|
||
_ceMcp.Start(_settings.CeMcpCommand, _settings.CeMcpPath == "auto" ? null : _settings.CeMcpPath);
|
||
await Task.Delay(1000, ct);
|
||
}
|
||
|
||
// Enumerate processes
|
||
progress.Report(new ScanProgress { Message = Lang.T("[proc] 正在枚举进程...", "[proc] Enumerating processes...") });
|
||
var processes = _processCollector.CollectAll();
|
||
progress.Report(new ScanProgress { Message = Lang.T($"[proc] 发现 {processes.Count} 个进程", $"[proc] Found {processes.Count} processes") });
|
||
|
||
var total = processes.Count;
|
||
var scanned = 0;
|
||
|
||
foreach (var proc in processes)
|
||
{
|
||
ct.ThrowIfCancellationRequested();
|
||
|
||
scanned++;
|
||
var pct = (double)scanned / total * 100.0;
|
||
|
||
progress.Report(new ScanProgress
|
||
{
|
||
ProcessName = $"{proc.Name} (PID {proc.Pid})",
|
||
Message = Lang.T($"[{scanned}/{total}] 扫描 {proc.Name} (PID {proc.Pid})...", $"[{scanned}/{total}] Scanning {proc.Name} (PID {proc.Pid})..."),
|
||
Progress = pct
|
||
});
|
||
|
||
try
|
||
{
|
||
var matches = _memoryScanner.ScanProcess(proc.Pid, _signatures);
|
||
var suspiciousRegions = _memoryScanner.GetSuspiciousRegions(proc.Pid);
|
||
var result = await _aiAnalyzer!.AnalyzeProcessAsync(proc, matches, suspiciousRegions);
|
||
|
||
var riskTag = result.RiskLevel switch
|
||
{
|
||
RiskLevel.High => Lang.T("高危", "HIGH"),
|
||
RiskLevel.Suspicious => Lang.T("可疑", "SUSP"),
|
||
_ => Lang.T("安全", "SAFE")
|
||
};
|
||
|
||
progress.Report(new ScanProgress
|
||
{
|
||
Message = $"[{scanned}/{total}] [{riskTag}] {proc.Name} score={result.RiskScore:F2} ({result.AIProvider})",
|
||
Progress = pct,
|
||
Result = result
|
||
});
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
progress.Report(new ScanProgress
|
||
{
|
||
Message = $"[warn] {proc.Name} — {ex.Message}",
|
||
Progress = pct
|
||
});
|
||
}
|
||
|
||
await Task.Delay(20, ct);
|
||
}
|
||
|
||
progress.Report(new ScanProgress
|
||
{
|
||
Message = Lang.T($"[done] 扫描完成 — 共分析 {total} 个进程", $"[done] Scan complete — {total} processes analyzed"),
|
||
Progress = 100,
|
||
IsComplete = true
|
||
});
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════
|
||
// PROGRESS CALLBACK (UI thread)
|
||
// ═══════════════════════════════════════════════════════════
|
||
private void OnScanProgress(ScanProgress sp)
|
||
{
|
||
if (sp.Progress > 0)
|
||
ScanProgressBar.Value = sp.Progress;
|
||
|
||
if (!string.IsNullOrEmpty(sp.ProcessName))
|
||
LblCurrentProcess.Text = sp.ProcessName;
|
||
|
||
if (!string.IsNullOrEmpty(sp.Message))
|
||
AddLog(sp.Message);
|
||
|
||
if (sp.Result != null)
|
||
{
|
||
_results.Add(sp.Result);
|
||
UpdateStats();
|
||
}
|
||
|
||
if (LstScanLog.Items.Count > 0)
|
||
LstScanLog.ScrollIntoView(LstScanLog.Items[^1]);
|
||
|
||
if (sp.IsComplete)
|
||
{
|
||
LblCurrentProcess.Text = Lang.T("扫描完成", "Scan complete");
|
||
LblProcCount.Text = Lang.T($" — {_results.Count} 条结果", $" — {_results.Count} results");
|
||
}
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════
|
||
// UI HELPERS
|
||
// ═══════════════════════════════════════════════════════════
|
||
private void AddLog(string message)
|
||
{
|
||
var ts = DateTime.Now.ToString("HH:mm:ss");
|
||
_scanLog.Add($"{ts} {message}");
|
||
}
|
||
|
||
private void UpdateStats()
|
||
{
|
||
var total = _results.Count;
|
||
var high = _results.Count(r => r.RiskLevel == RiskLevel.High);
|
||
var susp = _results.Count(r => r.RiskLevel == RiskLevel.Suspicious);
|
||
var safe = total - high - susp;
|
||
|
||
StatTotal.Text = total.ToString();
|
||
StatHigh.Text = high.ToString();
|
||
StatSuspicious.Text = susp.ToString();
|
||
StatSafe.Text = safe.ToString();
|
||
|
||
if (high > 0)
|
||
{
|
||
LblDashboardStatus.Text = Lang.T($"● {high} 条告警", $"● {high} ALERT{(high > 1 ? "S" : "")}");
|
||
LblDashboardStatus.Foreground = TryFindResource("AccentRedBrush") as System.Windows.Media.Brush;
|
||
}
|
||
else if (susp > 0)
|
||
{
|
||
LblDashboardStatus.Text = Lang.T($"● {susp} 条警告", $"● {susp} WARNING{(susp > 1 ? "S" : "")}");
|
||
LblDashboardStatus.Foreground = TryFindResource("AccentYellowBrush") as System.Windows.Media.Brush;
|
||
}
|
||
else
|
||
{
|
||
LblDashboardStatus.Text = Lang.T("● 监控中", "● MONITORING");
|
||
LblDashboardStatus.Foreground = TryFindResource("AccentBlueBrush") as System.Windows.Media.Brush;
|
||
}
|
||
}
|
||
|
||
private void SetScanStatus(string text, bool isScanning)
|
||
{
|
||
LblScanStatus.Text = $"● {text}";
|
||
if (isScanning)
|
||
{
|
||
LblScanStatus.Foreground = TryFindResource("AccentBlueBrush") as System.Windows.Media.Brush;
|
||
ScanStatusBadge.Background = TryFindResource("HighlightBlueBrush") as System.Windows.Media.Brush;
|
||
}
|
||
else
|
||
{
|
||
LblScanStatus.Foreground = TryFindResource("AccentGreenBrush") as System.Windows.Media.Brush;
|
||
ScanStatusBadge.Background = new System.Windows.Media.SolidColorBrush(
|
||
System.Windows.Media.Color.FromRgb(0xf0, 0xfd, 0xf4));
|
||
}
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════
|
||
// SETTINGS VIEW
|
||
// ═══════════════════════════════════════════════════════════
|
||
private void LoadSettingsToUI()
|
||
{
|
||
CmbAIProvider.SelectedIndex = _settings.AI.Provider switch
|
||
{
|
||
AIProvider.OpenAICompatible => 1,
|
||
AIProvider.Claude => 2,
|
||
AIProvider.Local => 3,
|
||
_ => 0
|
||
};
|
||
|
||
TxtApiKey.Password = _settings.AI.ApiKey;
|
||
TxtBaseUrl.Text = _settings.AI.BaseUrl;
|
||
TxtModel.Text = _settings.AI.Model;
|
||
|
||
TxtCeMcpPath.Text = _settings.CeMcpPath;
|
||
TxtRulesDir.Text = _settings.RulesDir;
|
||
TxtLogDir.Text = _settings.LogDir;
|
||
|
||
CmbScanProfile.SelectedIndex = _settings.ScanProfile switch
|
||
{
|
||
"fast" => 0,
|
||
"deep" => 2,
|
||
_ => 1
|
||
};
|
||
}
|
||
|
||
private void BtnSaveSettings_Click(object sender, RoutedEventArgs e)
|
||
{
|
||
var providerTag = (CmbAIProvider.SelectedItem as System.Windows.Controls.ComboBoxItem)?.Tag?.ToString() ?? "Heuristic";
|
||
var provider = providerTag switch
|
||
{
|
||
"OpenAICompatible" => AIProvider.OpenAICompatible,
|
||
"Claude" => AIProvider.Claude,
|
||
"Local" => AIProvider.Local,
|
||
_ => AIProvider.Heuristic
|
||
};
|
||
|
||
_settings.AI = AIConfig.DefaultFor(provider);
|
||
_settings.AI.ApiKey = TxtApiKey.Password;
|
||
_settings.AI.BaseUrl = TxtBaseUrl.Text.Trim();
|
||
_settings.AI.Model = TxtModel.Text.Trim();
|
||
|
||
_settings.CeMcpPath = TxtCeMcpPath.Text.Trim();
|
||
_settings.RulesDir = TxtRulesDir.Text.Trim();
|
||
_settings.LogDir = TxtLogDir.Text.Trim();
|
||
|
||
var profileTag = (CmbScanProfile.SelectedItem as System.Windows.Controls.ComboBoxItem)?.Tag?.ToString() ?? "balanced";
|
||
_settings.ScanProfile = profileTag;
|
||
|
||
_settings.Save();
|
||
|
||
_aiAnalyzer = new AIAnalyzer(_settings.AI);
|
||
_signatures = _memoryScanner.LoadSignatures(_settings.RulesDir);
|
||
|
||
AddLog(Lang.T("[settings] 设置已保存", "[settings] Settings saved"));
|
||
|
||
System.Windows.MessageBox.Show(
|
||
Lang.T("设置已保存,AI 分析器和签名库已重新加载。", "Settings saved. AI analyzer and signatures reloaded."),
|
||
"MemScan-EDR",
|
||
MessageBoxButton.OK,
|
||
MessageBoxImage.Information);
|
||
}
|
||
|
||
private void BtnBrowseCeMcp_Click(object sender, RoutedEventArgs e)
|
||
{
|
||
var dlg = new OpenFileDialog
|
||
{
|
||
Title = Lang.T("选择 CE-MCP 服务器脚本", "Select CE-MCP Server Script"),
|
||
Filter = "Python (*.py)|*.py|All (*.*)|*.*"
|
||
};
|
||
if (dlg.ShowDialog() == true)
|
||
TxtCeMcpPath.Text = dlg.FileName;
|
||
}
|
||
|
||
private void BtnBrowseRules_Click(object sender, RoutedEventArgs e)
|
||
{
|
||
var folder = BrowseFolder(Lang.T("选择扫描规则目录", "Select Scan Rules Directory"));
|
||
if (folder != null) TxtRulesDir.Text = folder;
|
||
}
|
||
|
||
private void BtnBrowseLog_Click(object sender, RoutedEventArgs e)
|
||
{
|
||
var folder = BrowseFolder(Lang.T("选择日志目录", "Select Log Directory"));
|
||
if (folder != null) TxtLogDir.Text = folder;
|
||
}
|
||
|
||
private static string? BrowseFolder(string description)
|
||
{
|
||
var dlg = new OpenFolderDialog { Title = description };
|
||
return dlg.ShowDialog() == true ? dlg.FolderName : null;
|
||
}
|
||
}
|