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>
346 lines
10 KiB
C#
346 lines
10 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Runtime.InteropServices;
|
|
using Decal.Interop.Core;
|
|
using Microsoft.Win32;
|
|
|
|
namespace Decal.Core
|
|
{
|
|
[ComVisible(true)]
|
|
[Guid("4557D5A1-00DB-48F6-ACB3-4FEF30E2F358")]
|
|
[ClassInterface(ClassInterfaceType.None)]
|
|
[ComSourceInterfaces("Decal.Interop.Core.IDecalEvents\0\0")]
|
|
[ProgId("Decal.DecalCore")]
|
|
public class DecalCoreImpl : IDecalCore
|
|
{
|
|
// COM events
|
|
public event IDecalEvents_InitializeCompleteEventHandler InitializeComplete;
|
|
public event IDecalEvents_TerminateCompleteEventHandler TerminateComplete;
|
|
|
|
// Services and plugins
|
|
private struct ServiceEntry
|
|
{
|
|
public Guid Clsid;
|
|
public IDecalService Service;
|
|
public IDecalRender Renderer;
|
|
}
|
|
|
|
private readonly List<ServiceEntry> _services = new List<ServiceEntry>();
|
|
private readonly List<PluginSite2Impl> _plugins = new List<PluginSite2Impl>();
|
|
private readonly ACHooksImpl _hooks = new ACHooksImpl();
|
|
private bool _servicesRunning;
|
|
private bool _pluginsRunning;
|
|
|
|
// Graphics
|
|
private object _d3dDevice;
|
|
private int _hwnd;
|
|
private int _screenWidth = 800;
|
|
private int _screenHeight = 600;
|
|
|
|
internal ACHooksImpl ACHooksInstance => _hooks;
|
|
|
|
// IDecalCore implementation
|
|
public void InitGraphics(object pD3DDevice)
|
|
{
|
|
_d3dDevice = pD3DDevice;
|
|
|
|
// Notify render services
|
|
foreach (var svc in _services)
|
|
{
|
|
svc.Renderer?.ChangeDirectX();
|
|
}
|
|
}
|
|
|
|
public object GetD3DDevice(ref Guid riid) => _d3dDevice;
|
|
|
|
public int HWND
|
|
{
|
|
get => _hwnd;
|
|
set
|
|
{
|
|
_hwnd = value;
|
|
foreach (var svc in _services)
|
|
svc.Renderer?.ChangeHWND();
|
|
}
|
|
}
|
|
|
|
public bool Focus => true;
|
|
|
|
public void SendWM(int HWND, short uMsg, int wParam, int lParam, ref bool pbEat)
|
|
{
|
|
pbEat = false;
|
|
}
|
|
|
|
public void GetScreenSize(out int pWidth, out int pHeight)
|
|
{
|
|
pWidth = _screenWidth;
|
|
pHeight = _screenHeight;
|
|
}
|
|
|
|
public string MapPath(string pPath)
|
|
{
|
|
if (string.IsNullOrEmpty(pPath)) return pPath;
|
|
|
|
string result = pPath;
|
|
int pos = 0;
|
|
|
|
while (pos < result.Length)
|
|
{
|
|
int start = result.IndexOf('%', pos);
|
|
if (start < 0) break;
|
|
|
|
int end = result.IndexOf('%', start + 1);
|
|
if (end < 0) break;
|
|
|
|
string token = result.Substring(start + 1, end - start - 1);
|
|
string resolved = ResolveToken(token);
|
|
result = result.Substring(0, start) + resolved + result.Substring(end + 1);
|
|
pos = start + resolved.Length;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private string ResolveToken(string token)
|
|
{
|
|
string lower = token.ToLowerInvariant();
|
|
if (lower == "ac" || lower.StartsWith("ac:"))
|
|
{
|
|
try
|
|
{
|
|
using (var key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Turbine\Asheron's Call"))
|
|
{
|
|
return key?.GetValue("Path") as string ?? "";
|
|
}
|
|
}
|
|
catch { return ""; }
|
|
}
|
|
|
|
if (lower == "decal" || lower.StartsWith("decal:"))
|
|
{
|
|
try
|
|
{
|
|
using (var key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Decal\Agent"))
|
|
{
|
|
return key?.GetValue("AgentPath") as string ?? "";
|
|
}
|
|
}
|
|
catch { return ""; }
|
|
}
|
|
|
|
return "%" + token + "%";
|
|
}
|
|
|
|
public void StartPlugins()
|
|
{
|
|
if (_pluginsRunning) return;
|
|
if (!_servicesRunning) StartServices();
|
|
|
|
// Notify services: before plugins
|
|
foreach (var svc in _services)
|
|
svc.Service.BeforePlugins();
|
|
|
|
// Enumerate and load plugins from registry
|
|
var pluginEnum = new DecalEnumImpl();
|
|
pluginEnum.Initialize("Plugins");
|
|
|
|
try
|
|
{
|
|
pluginEnum.Next(); // Move to first
|
|
while (true)
|
|
{
|
|
if (pluginEnum.Enabled)
|
|
{
|
|
try
|
|
{
|
|
var riid = typeof(IPlugin2).GUID;
|
|
var pluginObj = pluginEnum.CreateInstance(ref riid);
|
|
var plugin = pluginObj as IPlugin2;
|
|
if (plugin != null)
|
|
{
|
|
var site = new PluginSite2Impl();
|
|
site.Setup(this, plugin, pluginEnum.ComClass);
|
|
_plugins.Add(site);
|
|
|
|
plugin.Initialize((PluginSite2)(object)site);
|
|
}
|
|
}
|
|
catch { } // Bad plugins are skipped
|
|
}
|
|
pluginEnum.Next();
|
|
}
|
|
}
|
|
catch { } // Next() throws when past end
|
|
|
|
_pluginsRunning = true;
|
|
|
|
// Notify services: after plugins
|
|
foreach (var svc in _services)
|
|
svc.Service.AfterPlugins();
|
|
|
|
InitializeComplete?.Invoke(eDecalComponentType.ePlugin);
|
|
}
|
|
|
|
public void StopPlugins()
|
|
{
|
|
if (!_pluginsRunning) return;
|
|
|
|
// Unload all plugins (copy list since Unload modifies it)
|
|
var sites = new List<PluginSite2Impl>(_plugins);
|
|
foreach (var site in sites)
|
|
{
|
|
try { site.Unload(); } catch { }
|
|
}
|
|
_plugins.Clear();
|
|
|
|
_pluginsRunning = false;
|
|
|
|
// Notify services: after plugins stopped
|
|
foreach (var svc in _services)
|
|
{
|
|
try { svc.Service.AfterPlugins(); } catch { }
|
|
}
|
|
|
|
TerminateComplete?.Invoke(eDecalComponentType.ePlugin);
|
|
}
|
|
|
|
public object Object => null;
|
|
|
|
public void StartServices()
|
|
{
|
|
if (_servicesRunning) return;
|
|
|
|
// Initialize ACHooks first
|
|
_hooks.SetDecal(this);
|
|
|
|
// Enumerate and load services from registry
|
|
var serviceEnum = new DecalEnumImpl();
|
|
serviceEnum.Initialize("Services");
|
|
|
|
try
|
|
{
|
|
serviceEnum.Next();
|
|
while (true)
|
|
{
|
|
if (serviceEnum.Enabled)
|
|
{
|
|
try
|
|
{
|
|
var riid = typeof(IDecalService).GUID;
|
|
var svcObj = serviceEnum.CreateInstance(ref riid);
|
|
var service = svcObj as IDecalService;
|
|
if (service != null)
|
|
{
|
|
service.Initialize((DecalCore)(object)this);
|
|
|
|
// Check for render capability
|
|
IDecalRender renderer = svcObj as IDecalRender;
|
|
|
|
_services.Add(new ServiceEntry
|
|
{
|
|
Clsid = serviceEnum.ComClass,
|
|
Service = service,
|
|
Renderer = renderer
|
|
});
|
|
}
|
|
}
|
|
catch { } // Service init failure - skip
|
|
}
|
|
serviceEnum.Next();
|
|
}
|
|
}
|
|
catch { } // Next() throws when past end
|
|
|
|
_servicesRunning = true;
|
|
InitializeComplete?.Invoke(eDecalComponentType.eService);
|
|
}
|
|
|
|
public DecalEnum Configuration
|
|
{
|
|
get
|
|
{
|
|
var e = new DecalEnumImpl();
|
|
e.Initialize("Plugins");
|
|
return (DecalEnum)(object)e;
|
|
}
|
|
}
|
|
|
|
public void StopServices()
|
|
{
|
|
if (!_servicesRunning) return;
|
|
if (_pluginsRunning) StopPlugins();
|
|
|
|
// Stop services in reverse order (LIFO)
|
|
for (int i = _services.Count - 1; i >= 0; i--)
|
|
{
|
|
try { _services[i].Service.Terminate(); } catch { }
|
|
}
|
|
_services.Clear();
|
|
|
|
_servicesRunning = false;
|
|
TerminateComplete?.Invoke(eDecalComponentType.eService);
|
|
}
|
|
|
|
public object Plugin => null;
|
|
public object Service => null;
|
|
|
|
public void Render2D()
|
|
{
|
|
foreach (var svc in _services)
|
|
svc.Renderer?.Render2D();
|
|
}
|
|
|
|
public void Render3D()
|
|
{
|
|
_hooks.FireRenderPreUI();
|
|
foreach (var svc in _services)
|
|
svc.Renderer?.Render3D();
|
|
}
|
|
|
|
public void PreReset()
|
|
{
|
|
foreach (var svc in _services)
|
|
svc.Renderer?.PreReset();
|
|
}
|
|
|
|
public void PostReset()
|
|
{
|
|
foreach (var svc in _services)
|
|
svc.Renderer?.PostReset();
|
|
}
|
|
|
|
public ACHooks Hooks => (ACHooks)(object)_hooks;
|
|
|
|
public void StartFilters()
|
|
{
|
|
// Filters are started as part of services
|
|
}
|
|
|
|
public void StopFilters()
|
|
{
|
|
// Filters are stopped as part of services
|
|
}
|
|
|
|
public bool KillBitCheckByGUID(string bstrGuid, string bstrVersion) => false;
|
|
public bool KillBitCheckByProgID(string bstProgID, string bstrVersion) => false;
|
|
|
|
public bool PluginsRunning => _pluginsRunning;
|
|
|
|
// Internal methods for PluginSite2
|
|
internal void RemovePlugin(PluginSite2Impl site) => _plugins.Remove(site);
|
|
|
|
internal object GetService(Guid clsid)
|
|
{
|
|
foreach (var svc in _services)
|
|
if (svc.Clsid == clsid) return svc.Service;
|
|
return null;
|
|
}
|
|
|
|
internal object GetPlugin(Guid clsid)
|
|
{
|
|
foreach (var site in _plugins)
|
|
if (site.PluginClsid == clsid) return site.Plugin;
|
|
return null;
|
|
}
|
|
}
|
|
}
|