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:
erik 2026-02-08 18:27:56 +01:00
commit d1442e3747
1382 changed files with 170725 additions and 0 deletions

View 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;
}
}
}
}