using System; using System.Diagnostics; using System.IO; using System.Windows.Forms; using Microsoft.Win32; namespace Decal.DenAgent { /// /// Main plugin management dialog. Shows all registered Decal plugins/services /// with enable/disable toggle, drag-drop reordering, and version display. /// Replaces the MFC CDenAgentDlg class. /// internal sealed class AgentForm : Form { private readonly ListView _pluginList; private readonly ImageList _imageList; private readonly Button _btnClose; private readonly Button _btnAdd; private readonly Button _btnRemove; private readonly Button _btnRefresh; private readonly Button _btnOptions; private readonly Label _lblMessages; private readonly Label _lblMemLocs; // Plugin group definitions matching the original DenAgent private static readonly PluginGroup[] Groups = { new("Plugins", "Plugins", 0), new("Network Filters", "NetworkFilters", 1), new("File Filters", "FileFilters", 1), new("Services", "Services", 2), new("Surrogates", "Surrogates", 3), new("Input Actions", "InputActions", 4), }; public AgentForm() { Text = "Decal Agent " + GetAgentVersion(); Size = new System.Drawing.Size(420, 360); FormBorderStyle = FormBorderStyle.FixedDialog; MaximizeBox = false; StartPosition = FormStartPosition.CenterScreen; // Image list for enabled/disabled/group icons _imageList = new ImageList { ImageSize = new System.Drawing.Size(16, 16) }; // Index 0 = group (folder), 1 = enabled (check), 2 = disabled (x) _imageList.Images.Add(CreateColorIcon(System.Drawing.Color.SteelBlue)); // 0: group _imageList.Images.Add(CreateColorIcon(System.Drawing.Color.ForestGreen)); // 1: enabled _imageList.Images.Add(CreateColorIcon(System.Drawing.Color.Gray)); // 2: disabled // Plugin list view _pluginList = new ListView { Location = new System.Drawing.Point(10, 35), Size = new System.Drawing.Size(295, 230), View = View.Details, FullRowSelect = true, SmallImageList = _imageList, HeaderStyle = ColumnHeaderStyle.None, AllowDrop = true }; _pluginList.Columns.Add("Component", 210); _pluginList.Columns.Add("Version", 80, HorizontalAlignment.Right); _pluginList.MouseClick += OnPluginListClick; Controls.Add(_pluginList); // Description label var lblDesc = new Label { Text = "Choose the Plugins you'd like to run.", Location = new System.Drawing.Point(10, 10), Size = new System.Drawing.Size(390, 20) }; Controls.Add(lblDesc); // Buttons int btnX = 320; _btnClose = AddButton("Close", btnX, 300, (s, e) => Close()); _btnRefresh = AddButton("Refresh", btnX, 270, (s, e) => LoadPluginList()); _btnAdd = AddButton("Add", btnX, 35, (s, e) => OnAddPlugin()); _btnRemove = AddButton("Remove", btnX, 65, (s, e) => OnRemovePlugin()); _btnOptions = AddButton("Options", btnX, 105, (s, e) => OnShowOptions()); // Status labels _lblMessages = new Label { Text = "Messages: -", Location = new System.Drawing.Point(10, 275), Size = new System.Drawing.Size(300, 16), Font = new System.Drawing.Font("Segoe UI", 8f) }; Controls.Add(_lblMessages); _lblMemLocs = new Label { Text = "Memory Locations: -", Location = new System.Drawing.Point(10, 293), Size = new System.Drawing.Size(300, 16), Font = new System.Drawing.Font("Segoe UI", 8f) }; Controls.Add(_lblMemLocs); AcceptButton = _btnClose; LoadPluginList(); UpdateFileInfo(); } private Button AddButton(string text, int x, int y, EventHandler click) { var btn = new Button { Text = text, Location = new System.Drawing.Point(x, y), Size = new System.Drawing.Size(80, 25) }; btn.Click += click; Controls.Add(btn); return btn; } private void LoadPluginList() { _pluginList.BeginUpdate(); _pluginList.Items.Clear(); foreach (var group in Groups) { // Add group header var groupItem = new ListViewItem(group.DisplayName, 0) { Tag = null // Group headers have no tag }; groupItem.SubItems.Add(""); _pluginList.Items.Add(groupItem); // Enumerate plugins in this group from registry string keyPath = $@"SOFTWARE\Decal\{group.RegistryKey}"; try { using var key = Registry.LocalMachine.OpenSubKey(keyPath); if (key == null) continue; foreach (string clsid in key.GetSubKeyNames()) { using var pluginKey = key.OpenSubKey(clsid); if (pluginKey == null) continue; string name = pluginKey.GetValue("FriendlyName") as string ?? pluginKey.GetValue("") as string ?? clsid; bool enabled = true; object enabledVal = pluginKey.GetValue("Enabled"); if (enabledVal is int ei) enabled = ei != 0; var info = new PluginInfo(clsid, group.RegistryKey, name, enabled); int imageIdx = enabled ? 1 : 2; var item = new ListViewItem(" " + name, imageIdx) { Tag = info, IndentCount = 1 }; // Get version from COM registration string version = GetPluginVersion(clsid); item.SubItems.Add(version); _pluginList.Items.Add(item); } } catch { /* Skip groups we can't read */ } } _pluginList.EndUpdate(); } private void OnPluginListClick(object sender, MouseEventArgs e) { var hitTest = _pluginList.HitTest(e.Location); if (hitTest.Item == null) return; var info = hitTest.Item.Tag as PluginInfo; if (info == null) return; // Group header // Toggle enabled state when clicking the icon area if (e.X < 40) { info.Enabled = !info.Enabled; hitTest.Item.ImageIndex = info.Enabled ? 1 : 2; // Save to registry try { string keyPath = $@"SOFTWARE\Decal\{info.GroupKey}\{info.Clsid}"; using var key = Registry.LocalMachine.OpenSubKey(keyPath, writable: true); key?.SetValue("Enabled", info.Enabled ? 1 : 0, RegistryValueKind.DWord); } catch { /* May need admin rights */ } } } private void OnAddPlugin() { using var dlg = new OpenFileDialog { Title = "Select Plugin DLL", Filter = "DLL files (*.dll)|*.dll|All files (*.*)|*.*", CheckFileExists = true }; if (dlg.ShowDialog(this) != DialogResult.OK) return; // Register the DLL as a COM server and add to Decal registry string dllPath = dlg.FileName; try { var psi = new ProcessStartInfo { FileName = "regsvr32.exe", Arguments = $"/s \"{dllPath}\"", UseShellExecute = true, Verb = "runas" }; Process.Start(psi)?.WaitForExit(); LoadPluginList(); } catch (Exception ex) { MessageBox.Show($"Failed to register plugin: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } } private void OnRemovePlugin() { if (_pluginList.SelectedItems.Count == 0) return; var item = _pluginList.SelectedItems[0]; var info = item.Tag as PluginInfo; if (info == null) return; var result = MessageBox.Show( $"Remove plugin '{info.Name}'?\nThis will unregister it from Decal.", "Remove Plugin", MessageBoxButtons.OKCancel, MessageBoxIcon.Warning); if (result != DialogResult.OK) return; try { // Check for uninstaller string keyPath = $@"SOFTWARE\Decal\{info.GroupKey}\{info.Clsid}"; using var key = Registry.LocalMachine.OpenSubKey(keyPath); string uninstaller = key?.GetValue("Uninstaller") as string; if (!string.IsNullOrEmpty(uninstaller)) { Process.Start(uninstaller); } else { // Just delete the registry entry string parentPath = $@"SOFTWARE\Decal\{info.GroupKey}"; using var parentKey = Registry.LocalMachine.OpenSubKey(parentPath, writable: true); parentKey?.DeleteSubKeyTree(info.Clsid, false); } LoadPluginList(); } catch (Exception ex) { MessageBox.Show($"Failed to remove plugin: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } } private void OnShowOptions() { using var dlg = new OptionsForm(); dlg.ShowDialog(this); } private static string GetPluginVersion(string clsid) { try { string keyPath = $@"CLSID\{clsid}\InprocServer32"; using var key = Registry.ClassesRoot.OpenSubKey(keyPath); if (key == null) return ""; string dllPath = key.GetValue("") as string ?? ""; // .NET assemblies register as mscoree.dll; get the CodeBase instead if (dllPath.EndsWith("mscoree.dll", StringComparison.OrdinalIgnoreCase)) { string codeBase = key.GetValue("CodeBase") as string ?? ""; if (codeBase.StartsWith("file:///", StringComparison.OrdinalIgnoreCase)) dllPath = codeBase.Substring(8).Replace('/', '\\'); } if (!File.Exists(dllPath)) return ""; var vi = FileVersionInfo.GetVersionInfo(dllPath); return vi.FileVersion ?? ""; } catch { return ""; } } private void UpdateFileInfo() { // Check for XML data files used by Decal string agentPath = GetAgentPath(); if (string.IsNullOrEmpty(agentPath)) return; string messagesFile = Path.Combine(agentPath, "messages.xml"); string memlocsFile = Path.Combine(agentPath, "memlocs.xml"); _lblMessages.Text = "Messages: " + (File.Exists(messagesFile) ? File.GetLastWriteTime(messagesFile).ToString("yyyy-MM-dd HH:mm") : "Not found"); _lblMemLocs.Text = "Memory Locations: " + (File.Exists(memlocsFile) ? File.GetLastWriteTime(memlocsFile).ToString("yyyy-MM-dd HH:mm") : "Not found"); } private static string GetAgentPath() { try { using var key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Decal\Agent"); return key?.GetValue("AgentPath") as string ?? ""; } catch { return ""; } } private static string GetAgentVersion() { try { var vi = FileVersionInfo.GetVersionInfo( System.Reflection.Assembly.GetExecutingAssembly().Location); return vi.FileVersion ?? "0.0.0.0"; } catch { return "0.0.0.0"; } } private static System.Drawing.Icon CreateColorIcon(System.Drawing.Color color) { using var bmp = new System.Drawing.Bitmap(16, 16); using var g = System.Drawing.Graphics.FromImage(bmp); g.Clear(System.Drawing.Color.Transparent); using var brush = new System.Drawing.SolidBrush(color); g.FillEllipse(brush, 2, 2, 12, 12); return System.Drawing.Icon.FromHandle(bmp.GetHicon()); } protected override void OnFormClosing(FormClosingEventArgs e) { if (e.CloseReason == CloseReason.UserClosing) { e.Cancel = true; Hide(); } else { base.OnFormClosing(e); } } private class PluginGroup { public string DisplayName { get; } public string RegistryKey { get; } public int IconIndex { get; } public PluginGroup(string displayName, string registryKey, int iconIndex) { DisplayName = displayName; RegistryKey = registryKey; IconIndex = iconIndex; } } private class PluginInfo { public string Clsid { get; } public string GroupKey { get; } public string Name { get; } public bool Enabled { get; set; } public PluginInfo(string clsid, string groupKey, string name, bool enabled) { Clsid = clsid; GroupKey = groupKey; Name = name; Enabled = enabled; } } } }