Initial commit: Complete open-source Decal rebuild
All 5 phases of the open-source Decal rebuild: Phase 1: 14 decompiled .NET projects (Interop.*, Adapter, FileService, DecalUtil) Phase 2: 10 native DLLs rewritten as C# COM servers with matching GUIDs - DecalDat, DHS, SpellFilter, DecalInput, DecalNet, DecalFilters - Decal.Core, DecalControls, DecalRender, D3DService Phase 3: C++ shims for Inject.DLL (D3D9 hooking) and LauncherHook.DLL Phase 4: DenAgent WinForms tray application Phase 5: WiX installer and build script 25 C# projects building with 0 errors. Native C++ projects require VS 2022 + Windows SDK (x86). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
commit
d1442e3747
1382 changed files with 170725 additions and 0 deletions
408
Managed/Decal.DenAgent/AgentForm.cs
Normal file
408
Managed/Decal.DenAgent/AgentForm.cs
Normal file
|
|
@ -0,0 +1,408 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Windows.Forms;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace Decal.DenAgent
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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 "<Not registered>";
|
||||
|
||||
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 "<No DLL>";
|
||||
|
||||
var vi = FileVersionInfo.GetVersionInfo(dllPath);
|
||||
return vi.FileVersion ?? "<No Version>";
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "<Error>";
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue