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>
170 lines
5.2 KiB
C#
170 lines
5.2 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Runtime.InteropServices;
|
|
using Decal.Interop.Core;
|
|
using Decal.Interop.Input;
|
|
|
|
namespace Decal.DecalInput
|
|
{
|
|
[ComVisible(true)]
|
|
[Guid("B33307BA-706D-474A-80B9-70BB8D13EF3E")]
|
|
[ClassInterface(ClassInterfaceType.None)]
|
|
[ProgId("DecalInput.InputService")]
|
|
public class InputServiceImpl : IInputService, IDecalService
|
|
{
|
|
internal static InputServiceImpl Instance { get; private set; }
|
|
|
|
private IDecalCore _decalCore;
|
|
private readonly List<HotkeyImpl> _hotkeys = new List<HotkeyImpl>();
|
|
private readonly List<TimerImpl> _timers = new List<TimerImpl>();
|
|
private readonly List<WinMsgHookImpl> _msgHooks = new List<WinMsgHookImpl>();
|
|
private readonly Dictionary<string, int> _commands = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
|
private WndMsgImpl _message;
|
|
|
|
internal InputBufferImpl ActiveBuffer { get; set; }
|
|
|
|
public DecalCore Decal => (DecalCore)_decalCore;
|
|
|
|
// Parameterized property - KeyByName takes a string parameter via IDispatch
|
|
public int KeyByName => 0;
|
|
|
|
internal int GetKeyByName(string name)
|
|
{
|
|
return KeyNames.VKFromName(name);
|
|
}
|
|
|
|
// IDecalService
|
|
public void Initialize(DecalCore pDecal)
|
|
{
|
|
_decalCore = (IDecalCore)pDecal;
|
|
Instance = this;
|
|
_message = new WndMsgImpl();
|
|
}
|
|
|
|
public void BeforePlugins()
|
|
{
|
|
// In native code: subclasses game window and loads keymap
|
|
// Window hooking requires being in-process, handled by Inject.DLL
|
|
}
|
|
|
|
public void AfterPlugins()
|
|
{
|
|
_commands.Clear();
|
|
// Native code restores original window procedure here
|
|
}
|
|
|
|
public void Terminate()
|
|
{
|
|
_hotkeys.Clear();
|
|
_timers.Clear();
|
|
_msgHooks.Clear();
|
|
_commands.Clear();
|
|
_message = null;
|
|
ActiveBuffer = null;
|
|
|
|
if (Instance == this)
|
|
Instance = null;
|
|
}
|
|
|
|
// Registration methods for child objects
|
|
internal void RegisterHotkey(HotkeyImpl hotkey)
|
|
{
|
|
if (!_hotkeys.Contains(hotkey))
|
|
_hotkeys.Add(hotkey);
|
|
}
|
|
|
|
internal void UnregisterHotkey(HotkeyImpl hotkey)
|
|
{
|
|
_hotkeys.Remove(hotkey);
|
|
}
|
|
|
|
internal void RegisterTimer(TimerImpl timer)
|
|
{
|
|
if (!_timers.Contains(timer))
|
|
_timers.Add(timer);
|
|
}
|
|
|
|
internal void UnregisterTimer(TimerImpl timer)
|
|
{
|
|
_timers.Remove(timer);
|
|
}
|
|
|
|
internal void RegisterMsgHook(WinMsgHookImpl hook)
|
|
{
|
|
if (!_msgHooks.Contains(hook))
|
|
_msgHooks.Add(hook);
|
|
}
|
|
|
|
internal void UnregisterMsgHook(WinMsgHookImpl hook)
|
|
{
|
|
_msgHooks.Remove(hook);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called from the window hook procedure for each window message.
|
|
/// Returns true if the message should be eaten (suppressed).
|
|
/// </summary>
|
|
internal bool ProcessWindowMessage(int hwnd, int msg, int wParam, int lParam)
|
|
{
|
|
_message.Set(hwnd, msg, wParam, lParam);
|
|
|
|
// Process message hooks
|
|
foreach (var hook in _msgHooks)
|
|
{
|
|
hook.FireMessage(hook, _message);
|
|
if (_message.Eat)
|
|
return true;
|
|
}
|
|
|
|
// Process hotkeys on WM_KEYUP (0x0101), only if no active buffer
|
|
if (msg == 0x0101 && ActiveBuffer == null)
|
|
{
|
|
foreach (var hotkey in _hotkeys)
|
|
{
|
|
if (hotkey.Enabled && hotkey.Key.Length > 0)
|
|
{
|
|
// Match virtual key
|
|
int vk = wParam;
|
|
string keyName = hotkey.Key;
|
|
int hotkeyVk;
|
|
|
|
if (keyName.StartsWith("{") && keyName.EndsWith("}"))
|
|
{
|
|
string name = keyName.Substring(1, keyName.Length - 2);
|
|
hotkeyVk = KeyNames.VKFromName(name);
|
|
}
|
|
else
|
|
{
|
|
hotkeyVk = char.ToUpper(keyName[0]);
|
|
}
|
|
|
|
if (vk == hotkeyVk)
|
|
{
|
|
hotkey.FireHotkey();
|
|
break; // Only one hotkey per key press
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called from the render loop to process timers and input buffer delays.
|
|
/// </summary>
|
|
internal void ProcessTimers()
|
|
{
|
|
uint now = GetTickCount();
|
|
// Copy list to avoid modification during iteration
|
|
var timersCopy = new List<TimerImpl>(_timers);
|
|
foreach (var timer in timersCopy)
|
|
{
|
|
timer.CheckTimeout(now);
|
|
}
|
|
}
|
|
|
|
[DllImport("kernel32.dll")]
|
|
private static extern uint GetTickCount();
|
|
}
|
|
}
|