diff --git a/.gitignore b/.gitignore
index a8a8685..9fb5b30 100644
--- a/.gitignore
+++ b/.gitignore
@@ -363,3 +363,6 @@ MigrationBackup/
FodyWeavers.xsd
/UI_Creation_Manual.md
/UI_Creation_VirindiViewService_Manual.md
+/MosswartMassacre/Unused
+/MosswartMassacre/Decal.Adapter.csproj
+/MosswartMassacre/Decal.Interop.Core.csproj
diff --git a/MosswartMassacre/DecalHarmonyClean.cs b/MosswartMassacre/DecalHarmonyClean.cs
new file mode 100644
index 0000000..5e75f6a
--- /dev/null
+++ b/MosswartMassacre/DecalHarmonyClean.cs
@@ -0,0 +1,584 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Threading.Tasks;
+using Decal.Adapter;
+using Decal.Adapter.Wrappers;
+using Harmony; // UtilityBelt's Harmony 1.2.0.1 uses old Harmony namespace
+
+namespace MosswartMassacre
+{
+ ///
+ /// Clean Harmony implementation using UtilityBelt's exact pattern
+ /// Uses same namespace convention, same API, same Harmony package (Lib.Harmony 2.2.2)
+ ///
+ public static class DecalHarmonyClean
+ {
+ // Use UtilityBelt's namespace pattern
+ private static readonly string harmonyNamespace = "com.mosswartmassacre.decal";
+ private static HarmonyInstance harmonyDecal;
+ private static bool patchesApplied = false;
+ internal static int messagesIntercepted = 0;
+
+ // Debug logging for troubleshooting
+ private static readonly Queue debugLog = new Queue();
+ private const int MAX_DEBUG_ENTRIES = 50;
+
+ ///
+ /// Initialize Harmony patches using UtilityBelt's exact pattern
+ ///
+ public static bool Initialize()
+ {
+ try
+ {
+ // Use UtilityBelt's exact pattern: lazy initialization check + new Harmony()
+ if (harmonyDecal == null)
+ harmonyDecal = HarmonyInstance.Create(harmonyNamespace + ".patches");
+
+ // Apply patches using UtilityBelt's approach
+ ApplyDecalPatches();
+
+ return patchesApplied;
+ }
+ catch (Exception ex)
+ {
+ // Only log critical initialization failures
+ PluginCore.WriteToChat($"[DECAL] Critical initialization error: {ex.Message}");
+ return false;
+ }
+ }
+
+ ///
+ /// Apply patches to DECAL's AddChatText methods using UtilityBelt approach
+ ///
+ private static void ApplyDecalPatches()
+ {
+ try
+ {
+ // PATHWAY 1: Target HooksWrapper.AddChatText
+ PatchHooksWrapper();
+
+ // PATHWAY 2: Target Host.Actions.AddChatText (what our plugin uses)
+ PatchHostActions();
+ }
+ catch (Exception ex)
+ {
+ // Only log if completely unable to apply any patches
+ System.Diagnostics.Debug.WriteLine($"Patch application failed: {ex.Message}");
+ }
+ }
+
+ ///
+ /// Patch HooksWrapper.AddChatText methods
+ ///
+ private static void PatchHooksWrapper()
+ {
+ try
+ {
+ Type hooksWrapperType = typeof(HooksWrapper);
+
+ var allAddChatTextMethods = hooksWrapperType.GetMethods(BindingFlags.Public | BindingFlags.Instance)
+ .Where(m => m.Name == "AddChatText" || m.Name == "AddChatTextRaw").ToArray();
+
+ foreach (var method in allAddChatTextMethods)
+ {
+ var parameters = method.GetParameters();
+
+ string prefixMethodName = parameters.Length == 2 ? "AddChatTextPrefixHooks" :
+ parameters.Length == 3 ? "AddChatTextPrefixHooks3" :
+ "AddChatTextPrefixHooksGeneric";
+
+ try
+ {
+ ApplySinglePatch(method, prefixMethodName);
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"HooksWrapper patch failed: {ex.Message}");
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"HooksWrapper patch failed: {ex.Message}");
+ }
+ }
+
+ ///
+ /// Patch Host.Actions.AddChatText methods (what our plugin uses)
+ ///
+ private static void PatchHostActions()
+ {
+ try
+ {
+ if (PluginCore.MyHost?.Actions == null)
+ {
+ return;
+ }
+
+ Type actionsType = PluginCore.MyHost.Actions.GetType();
+
+ // Check if Host.Actions is already a HooksWrapper (to avoid double patching)
+ if (actionsType == typeof(HooksWrapper))
+ {
+ // PATHWAY 3: Try to patch at PluginHost level
+ PatchPluginHost();
+ return;
+ }
+
+ var hostAddChatTextMethods = actionsType.GetMethods(BindingFlags.Public | BindingFlags.Instance)
+ .Where(m => m.Name == "AddChatText" || m.Name == "AddChatTextRaw").ToArray();
+
+ foreach (var method in hostAddChatTextMethods)
+ {
+ var parameters = method.GetParameters();
+
+ string prefixMethodName = parameters.Length == 3 ? "AddChatTextPrefixHost" :
+ parameters.Length == 4 ? "AddChatTextPrefixHost4" :
+ "AddChatTextPrefixHostGeneric";
+
+ try
+ {
+ ApplySinglePatch(method, prefixMethodName);
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"Host.Actions patch failed: {ex.Message}");
+ }
+ }
+
+ // PATHWAY 3: Try to patch at PluginHost level
+ PatchPluginHost();
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"Host.Actions patch failed: {ex.Message}");
+ }
+ }
+
+ ///
+ /// Try a different approach - patch the actual chat system or find global instances
+ ///
+ private static void PatchPluginHost()
+ {
+ try
+ {
+ // Try to patch CoreManager.Current.Actions if it's different
+ try
+ {
+ var coreActions = CoreManager.Current?.Actions;
+ if (coreActions != null && coreActions != PluginCore.MyHost?.Actions)
+ {
+ var coreActionsMethods = coreActions.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance)
+ .Where(m => m.Name == "AddChatText" || m.Name == "AddChatTextRaw" || m.Name == "AddStatusText").ToArray();
+
+ foreach (var method in coreActionsMethods)
+ {
+ var parameters = method.GetParameters();
+
+ try
+ {
+ string prefixMethodName = "AddChatTextPrefixCore" + parameters.Length;
+ ApplySinglePatch(method, prefixMethodName);
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"CoreActions patch failed: {ex.Message}");
+ }
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"CoreManager patch failed: {ex.Message}");
+ }
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"PluginHost patch failed: {ex.Message}");
+ }
+ }
+
+ ///
+ /// Apply a single patch using UtilityBelt's method
+ ///
+ private static void ApplySinglePatch(MethodInfo targetMethod, string prefixMethodName)
+ {
+ try
+ {
+ // Get our prefix method
+ var prefixMethod = typeof(DecalPatchMethods).GetMethod(prefixMethodName,
+ BindingFlags.Static | BindingFlags.Public);
+
+ if (prefixMethod != null)
+ {
+ // Use UtilityBelt's exact approach
+ harmonyDecal.Patch(targetMethod, new HarmonyMethod(prefixMethod));
+ patchesApplied = true;
+ }
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"Failed to apply patch {prefixMethodName}: {ex.Message}");
+ }
+ }
+
+ ///
+ /// List available methods for debugging
+ ///
+ private static void ListAvailableMethods(Type type)
+ {
+ var methods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance)
+ .Where(m => m.Name == "AddChatText").ToArray();
+
+ foreach (var method in methods)
+ {
+ var parameters = method.GetParameters();
+ string paramInfo = string.Join(", ", parameters.Select(p => p.ParameterType.Name));
+ System.Diagnostics.Debug.WriteLine($"AddChatText({paramInfo})");
+ }
+ }
+
+ ///
+ /// Clean up patches using UtilityBelt's pattern
+ ///
+ public static void Cleanup()
+ {
+ try
+ {
+ if (harmonyDecal != null && patchesApplied)
+ {
+ // Use UtilityBelt's cleanup pattern
+ harmonyDecal.UnpatchAll(harmonyNamespace + ".patches");
+ }
+ patchesApplied = false;
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"Harmony cleanup error: {ex.Message}");
+ }
+ }
+
+ ///
+ /// Status checks
+ ///
+ public static bool IsActive() => patchesApplied && harmonyDecal != null;
+ public static int GetMessagesIntercepted() => messagesIntercepted;
+
+ ///
+ /// Add debug log entry
+ ///
+ public static void AddDebugLog(string message)
+ {
+ lock (debugLog)
+ {
+ debugLog.Enqueue($"{DateTime.Now:HH:mm:ss.fff} {message}");
+ while (debugLog.Count > MAX_DEBUG_ENTRIES)
+ {
+ debugLog.Dequeue();
+ }
+ }
+ }
+
+ ///
+ /// Get debug log entries
+ ///
+ public static string[] GetDebugLog()
+ {
+ lock (debugLog)
+ {
+ return debugLog.ToArray();
+ }
+ }
+ }
+
+ ///
+ /// Patch methods for DECAL interception using UtilityBelt's approach
+ ///
+ public static class DecalPatchMethods
+ {
+ ///
+ /// Prefix method for HooksWrapper.AddChatText(string, int) - intercepts plugin messages via HooksWrapper
+ ///
+ public static bool AddChatTextPrefixHooks(string text, int color)
+ {
+ try
+ {
+ // Always increment to verify patch is working
+ DecalHarmonyClean.messagesIntercepted++;
+
+ // DEBUG: Log ALL intercepted messages for troubleshooting
+ if (PluginCore.AggressiveChatStreamingEnabled)
+ {
+ DecalHarmonyClean.AddDebugLog($"[DEBUG] HOOKS-RAW #{DecalHarmonyClean.messagesIntercepted}: [{text ?? "NULL"}] (color={color})");
+ }
+
+ // Process ALL messages (including our own) for streaming
+ if (!string.IsNullOrEmpty(text))
+ {
+ // Process the intercepted message
+ ProcessInterceptedMessage(text, color, "HooksWrapper-2param");
+
+ // Silent operation - debug output removed
+ }
+
+ // Always return true to let the original AddChatText continue
+ return true;
+ }
+ catch (Exception ex)
+ {
+ // Never let our interception break other plugins
+ System.Diagnostics.Debug.WriteLine($"Harmony interception error: {ex.Message}");
+ return true;
+ }
+ }
+
+ ///
+ /// Prefix method for HooksWrapper.AddChatText(string, int, int) - 3-parameter version
+ ///
+ public static bool AddChatTextPrefixHooks3(string text, int color, int target)
+ {
+ try
+ {
+ DecalHarmonyClean.messagesIntercepted++;
+
+ if (!string.IsNullOrEmpty(text))
+ {
+ ProcessInterceptedMessage(text, color, $"HooksWrapper-3param(target={target})");
+
+ // Silent operation - debug output removed
+ }
+
+ return true;
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"Harmony interception3 error: {ex.Message}");
+ return true;
+ }
+ }
+
+ ///
+ /// Generic prefix method for any other HooksWrapper AddChatText overloads
+ ///
+ public static bool AddChatTextPrefixHooksGeneric(string text)
+ {
+ try
+ {
+ DecalHarmonyClean.messagesIntercepted++;
+
+ if (!string.IsNullOrEmpty(text))
+ {
+ ProcessInterceptedMessage(text, 0, "HooksWrapper-generic");
+
+ // Silent operation - debug output removed
+ }
+
+ return true;
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"Harmony generic interception error: {ex.Message}");
+ return true;
+ }
+ }
+
+ ///
+ /// Process intercepted plugin messages
+ ///
+ private static void ProcessInterceptedMessage(string text, int color, string source)
+ {
+ try
+ {
+ // Identify source plugin
+ string sourcePlugin = IdentifySourcePlugin(text);
+
+ // Add timestamp
+ var timestamp = DateTime.Now.ToString("HH:mm:ss");
+ var fullMessage = $"{timestamp} [{sourcePlugin}] {text}";
+
+ // Debug logging
+ System.Diagnostics.Debug.WriteLine($"[TARGET] HARMONY CAPTURED ({source}): {fullMessage}");
+
+ // Stream to WebSocket if both debug streaming AND WebSocket are enabled
+ if (PluginCore.AggressiveChatStreamingEnabled && PluginCore.WebSocketEnabled)
+ {
+ Task.Run(() => WebSocket.SendChatTextAsync(color, text));
+ }
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"Message processing error: {ex.Message}");
+ }
+ }
+
+ ///
+ /// Identify which plugin sent the message using UtilityBelt's patterns
+ ///
+ private static string IdentifySourcePlugin(string text)
+ {
+ // Known plugin prefixes
+ if (text.StartsWith("[VTank]")) return "VTank";
+ if (text.StartsWith("[UB]") || text.Contains("UtilityBelt")) return "UtilityBelt";
+ if (text.StartsWith("[VGI]")) return "VGI";
+ if (text.StartsWith("[VI]")) return "VirindiIntegrator";
+ if (text.StartsWith("[GoArrow]")) return "GoArrow";
+ if (text.StartsWith("[Meta]")) return "Meta";
+ if (text.StartsWith("[VTClassic]")) return "VTClassic";
+
+ // Pattern-based detection for messages without prefixes
+ if (text.Contains("Macro started") || text.Contains("Macro stopped")) return "VTank";
+ if (text.Contains("Quest data updated")) return "UtilityBelt";
+ if (text.Contains("Database read complete")) return "VGI";
+ if (text.Contains("Disconnected, retry")) return "VI";
+
+ return "Unknown";
+ }
+
+ // ===== HOST.ACTIONS PREFIX METHODS =====
+
+ ///
+ /// Prefix method for Host.Actions.AddChatText(string, int, int) - 3-parameter version
+ ///
+ public static bool AddChatTextPrefixHost(string text, int color, int target)
+ {
+ try
+ {
+ DecalHarmonyClean.messagesIntercepted++;
+
+ // DEBUG: Log ALL intercepted messages for troubleshooting
+ if (PluginCore.AggressiveChatStreamingEnabled)
+ {
+ DecalHarmonyClean.AddDebugLog($"[DEBUG] HOST-RAW #{DecalHarmonyClean.messagesIntercepted}: [{text ?? "NULL"}] (color={color}, target={target})");
+ }
+
+ if (!string.IsNullOrEmpty(text) && !text.Contains("[Mosswart Massacre]"))
+ {
+ ProcessInterceptedMessage(text, color, $"Host.Actions-3param(target={target})");
+
+ // Silent operation - debug output removed
+ }
+
+ return true;
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"Harmony Host.Actions interception error: {ex.Message}");
+ return true;
+ }
+ }
+
+ ///
+ /// Prefix method for Host.Actions.AddChatText(string, int, int, int) - 4-parameter version
+ ///
+ public static bool AddChatTextPrefixHost4(string text, int color, int target, int window)
+ {
+ try
+ {
+ DecalHarmonyClean.messagesIntercepted++;
+
+ if (!string.IsNullOrEmpty(text) && !text.Contains("[Mosswart Massacre]"))
+ {
+ ProcessInterceptedMessage(text, color, $"Host.Actions-4param(target={target},window={window})");
+
+ // Silent operation - debug output removed
+ }
+
+ return true;
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"Harmony Host.Actions 4param interception error: {ex.Message}");
+ return true;
+ }
+ }
+
+ ///
+ /// Generic prefix method for any other Host.Actions.AddChatText overloads
+ ///
+ public static bool AddChatTextPrefixHostGeneric(string text)
+ {
+ try
+ {
+ DecalHarmonyClean.messagesIntercepted++;
+
+ if (!string.IsNullOrEmpty(text) && !text.Contains("[Mosswart Massacre]"))
+ {
+ ProcessInterceptedMessage(text, 0, "Host.Actions-generic");
+
+ // Silent operation - debug output removed
+ }
+
+ return true;
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"Harmony Host.Actions generic interception error: {ex.Message}");
+ return true;
+ }
+ }
+
+ // ===== COREMANAGER.ACTIONS PREFIX METHODS =====
+
+ ///
+ /// Prefix method for CoreManager.Actions methods - trying different instances
+ ///
+ public static bool AddChatTextPrefixCore2(string text, int color)
+ {
+ try
+ {
+ DecalHarmonyClean.messagesIntercepted++;
+
+ if (PluginCore.AggressiveChatStreamingEnabled)
+ {
+ DecalHarmonyClean.AddDebugLog($"[DEBUG] CORE2-RAW #{DecalHarmonyClean.messagesIntercepted}: [{text ?? "NULL"}] (color={color})");
+ }
+
+ if (!string.IsNullOrEmpty(text) && !text.Contains("[Mosswart Massacre]"))
+ {
+ ProcessInterceptedMessage(text, color, "CoreActions-2param");
+
+ // Silent operation - debug output removed
+ }
+
+ return true;
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"Harmony Core2 interception error: {ex.Message}");
+ return true;
+ }
+ }
+
+ ///
+ /// Prefix method for CoreManager.Actions 3-param methods
+ ///
+ public static bool AddChatTextPrefixCore3(string text, int color, int target)
+ {
+ try
+ {
+ DecalHarmonyClean.messagesIntercepted++;
+
+ if (PluginCore.AggressiveChatStreamingEnabled)
+ {
+ DecalHarmonyClean.AddDebugLog($"[DEBUG] CORE3-RAW #{DecalHarmonyClean.messagesIntercepted}: [{text ?? "NULL"}] (color={color}, target={target})");
+ }
+
+ if (!string.IsNullOrEmpty(text) && !text.Contains("[Mosswart Massacre]"))
+ {
+ ProcessInterceptedMessage(text, color, $"CoreActions-3param(target={target})");
+
+ // Silent operation - debug output removed
+ }
+
+ return true;
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"Harmony Core3 interception error: {ex.Message}");
+ return true;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/MosswartMassacre/ManyHook.cs b/MosswartMassacre/ManyHook.cs
deleted file mode 100644
index 424cd46..0000000
--- a/MosswartMassacre/ManyHook.cs
+++ /dev/null
@@ -1,392 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
-using System.Linq;
-using System.Runtime.InteropServices;
-using Decal.Adapter;
-
-namespace MosswartMassacre
-{
- public class MultiHook
- {
- internal IntPtr Entrypoint;
- internal Delegate Del;
-
- internal List CallLocations = new List();
- internal List Hooks = new List();
-
- public MultiHook(int entrypoint, params int[] callLocations)
- {
- Entrypoint = (IntPtr)entrypoint;
- CallLocations.AddRange(callLocations);
- Hooks.AddRange(callLocations.Select(c => new Hook((int)Entrypoint, c)));
- }
-
- public bool Setup(Delegate del)
- {
- Del = del;
- return !Hooks.Any(h => !h.Setup(del));
- }
-
- public bool Remove()
- {
- return !Hooks.Any(h => !h.Remove());
- }
- }
-
- ///
- /// New improved Hooker
- ///
- public class Hook
- {
- internal IntPtr Entrypoint;
- internal Delegate Del;
- internal int call;
-
- public Hook(int entrypoint, int call_location)
- {
- Entrypoint = (IntPtr)entrypoint;
- call = call_location;
- }
- public bool Setup(Delegate del)
- {
- if (!hookers.Contains(this))
- {
- Del = del;
- if (ReadCall(call) != (int)Entrypoint)
- {
- PluginCore.WriteToChat(
- $"[Hook] Failed to detour 0x{call:X8}. " +
- $"Expected 0x{((int)Entrypoint):X8}, " +
- $"got 0x{ReadCall(call):X8}"
- );
- return false;
- }
- if (!PatchCall(call, Marshal.GetFunctionPointerForDelegate(Del)))
- return false;
-
- hookers.Add(this);
- PluginCore.WriteToChat($"[Hook] Hooked 0x{(int)Entrypoint:X8}");
- return true;
- }
- return false;
- }
- public bool Remove()
- {
- if (hookers.Contains(this))
- {
- hookers.Remove(this);
- if (PatchCall(call, Entrypoint))
- {
- PluginCore.WriteToChat($"[Hook] Un-hooked 0x{(int)Entrypoint:X8}");
- return true;
- }
- }
- return false;
- }
-
- // static half
- internal static List hookers = new List();
-
- [DllImport("kernel32.dll")]
- internal static extern bool VirtualProtectEx(
- IntPtr hProcess, IntPtr lpAddress,
- UIntPtr dwSize, int flNewProtect,
- out int lpflOldProtect
- );
-
- internal static void Write(IntPtr address, int newValue)
- {
- unsafe
- {
- VirtualProtectEx(
- Process.GetCurrentProcess().Handle,
- address, (UIntPtr)4, 0x40, out int old
- );
- *(int*)address = newValue;
- VirtualProtectEx(
- Process.GetCurrentProcess().Handle,
- address, (UIntPtr)4, old, out old
- );
- }
- }
-
- internal static bool PatchCall(int callLocation, IntPtr newPointer)
- {
- unsafe
- {
- byte* p = (byte*)callLocation;
- if ((p[0] & 0xFE) != 0xE8) return false;
- int newOffset = (int)newPointer - (callLocation + 5);
- Write((IntPtr)(callLocation + 1), newOffset);
- return true;
- }
- }
-
- internal static int ReadCall(int callLocation)
- {
- unsafe
- {
- byte* p = (byte*)callLocation;
- if ((p[0] & 0xFE) != 0xE8) return 0;
- int prevOffset = *(int*)(callLocation + 1);
- return prevOffset + (callLocation + 5);
- }
- }
-
- internal static void Cleanup()
- {
- for (int i = hookers.Count - 1; i >= 0; i--)
- hookers[i].Remove();
- }
- }
-
- ///
- /// New improved Hooker (Virtual‐table edition)
- ///
- public class VHook
- {
- internal int Entrypoint;
- internal Delegate Del;
- internal int call;
-
- public VHook(int entrypoint, int vtbl_address)
- {
- Entrypoint = entrypoint;
- call = vtbl_address;
- }
- public bool Setup(Delegate del)
- {
- if (!hookers.Contains(this))
- {
- Del = del;
- if (ReadVCall(call) != Entrypoint) return false;
- if (PatchVCall(call, (int)Marshal.GetFunctionPointerForDelegate(Del)))
- {
- hookers.Add(this);
- PluginCore.WriteToChat($"[VHook] Hooked vtbl slot 0x{call:X8}");
- return true;
- }
- }
- return false;
- }
- public bool Remove()
- {
- if (hookers.Contains(this))
- {
- hookers.Remove(this);
- if (PatchVCall(call, Entrypoint))
- {
- PluginCore.WriteToChat($"[VHook] Un-hooked vtbl slot 0x{call:X8}");
- return true;
- }
- }
- return false;
- }
-
- // static half
- internal static List hookers = new List();
-
- internal static bool PatchVCall(int callLocation, int newPointer)
- {
- unsafe
- {
- Hook.Write((IntPtr)callLocation, newPointer);
- return *(int*)callLocation == newPointer;
- }
- }
- internal unsafe static int ReadVCall(int callLocation) => *(int*)callLocation;
-
- internal static void Cleanup()
- {
- for (int i = hookers.Count - 1; i >= 0; i--)
- hookers[i].Remove();
- }
- }
-
- internal static class ChatHooks
- {
- // For loc_463D60 – UIElement, probably SetText(this, string)
- [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
- private delegate void SetTextDelegate(IntPtr @this, IntPtr strPtr);
-
- // For loc_469440 – Same signature; adjust if you find it’s different in IDA!
- [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
- private delegate void SetTextDelegate2(IntPtr @this, IntPtr strPtr);
-
- private static MultiHook _hook1, _hook2;
- private static SetTextDelegate _orig1;
- private static SetTextDelegate2 _orig2;
-
- // All relevant direct code addresses (calls/jmps) to loc_463D60:
- private static readonly int[] Sites1 = {
- //0x00464A22,
- //0x0046B8FF,
- //0x0047037F,
- //0x00474AA1,
- 0x004D22A3,
- 0x004E5F20,
- 0x004F4663,
- // (skip 0x004BD370, it's a jmp)
-};
-
- // All relevant direct code addresses (calls/jmps) to loc_469440:
- private static readonly int[] Sites2 = {
- //0x00468EFA,
- //0x00469F23,
- //0x0046A4C5,
- //0x004B80EC,
- 0x004CDD49,
- 0x004F4679,
- //0x004F5392,
- //0x004F623B,
- //0x004F6253,
- // (skip 0x004F46E6, it's a jmp)
-};
-
-
- private static bool IsSanePtr(IntPtr ptr)
- {
- long v = ptr.ToInt64();
- // Only allow user-mode addresses (protects from 0, 0x10, or other garbage)
- return v > 0x10000 && v < 0x00007FFFFFFFFFFF;
- }
-
- private static void MyHook1(IntPtr @this, IntPtr strPtr)
- {
- string msg;
- long val = strPtr.ToInt64();
-
- // Always log, even if pointer looks bad!
- if (val == 0 || val == 0x10 || val < 0x10000)
- {
- msg = $"[SetText1] strPtr=0x{val:X} [skipped-invalid]";
- }
- else
- {
- try
- {
- // Try direct pointer to string (UNICODE)
- msg = Marshal.PtrToStringUni(strPtr);
- if (string.IsNullOrEmpty(msg))
- {
- msg = $"[SetText1] strPtr=0x{val:X} [direct:empty]";
- }
- else
- {
- msg = $"[SetText1] strPtr=0x{val:X} \"{msg}\"";
- }
- }
- catch (Exception ex)
- {
- msg = $"[SetText1] strPtr=0x{val:X} [EX:{ex.GetType().Name}]";
- }
- }
-
- // Actually write to log
- LogToFile("SetText1", msg);
-
- // Always call the original
- _orig1(@this, strPtr);
- }
-
-
- // Repeat for MyHook2, just change the tag/log if you want to differentiate
- private static void MyHook2(IntPtr @this, IntPtr strPtr)
- {
- try
- {
- string msg;
- long val = strPtr.ToInt64();
- if (val == 0 || val == 0x10 || val < 0x10000)
- {
- msg = $"[SetText2] strPtr=0x{val:X} [skipped-invalid]";
- }
- else
- {
- try
- {
- msg = Marshal.PtrToStringUni(strPtr);
- if (string.IsNullOrEmpty(msg))
- msg = $"[SetText2] strPtr=0x{val:X} [direct:empty]";
- else
- msg = $"[SetText2] \"{msg}\"";
- }
- catch (Exception ex)
- {
- msg = $"[SetText2] strPtr=0x{val:X} [EX:{ex.GetType().Name}]";
- }
- }
-
- LogToFile("SetText2", msg);
- }
- catch { }
- finally
- {
- _orig2(@this, strPtr);
- }
- }
-
-
-
-
- public static void Init()
- {
- unsafe
- {
- foreach (int addr in Sites2)
- {
- byte* p = (byte*)addr;
- int callTarget = Hook.ReadCall(addr);
- // Print the bytes and resolved call target
- PluginCore.WriteToChat(
- $"[DEBUG] Addr: 0x{addr:X8} Bytes: {p[0]:X2} {p[1]:X2} {p[2]:X2} {p[3]:X2} {p[4]:X2} -> 0x{callTarget:X8}"
- );
- }
- }
- // Patch for loc_463D60 (ENTRY1)
- _hook1 = new MultiHook(0x00463D60, Sites1);
- _orig1 = Marshal.GetDelegateForFunctionPointer(_hook1.Entrypoint);
- if (_hook1.Setup((SetTextDelegate)MyHook1))
- PluginCore.WriteToChat("[ChatHooks] SetText1 hook installed.");
- else
- PluginCore.WriteToChat("[ChatHooks] SetText1 hook FAILED.");
-
- // Patch for loc_469440 (ENTRY2)
- _hook2 = new MultiHook(0x00469440, Sites2);
- _orig2 = Marshal.GetDelegateForFunctionPointer(_hook2.Entrypoint);
- if (_hook2.Setup((SetTextDelegate2)MyHook2))
- PluginCore.WriteToChat("[ChatHooks] SetText2 hook installed.");
- else
- PluginCore.WriteToChat("[ChatHooks] SetText2 hook FAILED.");
- }
-
- public static void Dispose()
- {
- _hook1?.Remove();
- _hook2?.Remove();
- _hook1 = null;
- _hook2 = null;
- Hook.Cleanup();
- VHook.Cleanup();
- PluginCore.WriteToChat("[ChatHooks] All hooks removed.");
- }
- private static void LogToFile(string prefix, string msg)
- {
- try
- {
- string characterName = CoreManager.Current.CharacterFilter.Name;
- string pluginFolder = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
- string characterFolder = Path.Combine(pluginFolder, characterName);
-
- Directory.CreateDirectory(characterFolder);
-
- string logFile = Path.Combine(characterFolder, "hooklog.txt");
- File.AppendAllText(logFile, $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} [{prefix}] {msg}\r\n");
- }
- catch { }
- }
- }
-
-}
diff --git a/MosswartMassacre/MosswartMassacre.csproj b/MosswartMassacre/MosswartMassacre.csproj
index b200028..e4c90ea 100644
--- a/MosswartMassacre/MosswartMassacre.csproj
+++ b/MosswartMassacre/MosswartMassacre.csproj
@@ -34,6 +34,10 @@
4
+
+ C:\Games\Decal Plugins\UtilityBelt\0Harmony.dll
+ False
+
lib\Decal.Adapter.dll
False
@@ -155,7 +159,7 @@
Shared\VCS_Connector.cs
-
+
diff --git a/MosswartMassacre/PluginCore.cs b/MosswartMassacre/PluginCore.cs
index c73f492..92da851 100644
--- a/MosswartMassacre/PluginCore.cs
+++ b/MosswartMassacre/PluginCore.cs
@@ -71,8 +71,9 @@ namespace MosswartMassacre
public static bool HttpServerEnabled { get; set; } = false;
public static string CharTag { get; set; } = "";
public static bool TelemetryEnabled { get; set; } = false;
- public bool WebSocketEnabled { get; set; } = false;
+ public static bool WebSocketEnabled { get; set; } = false;
public bool InventoryLogEnabled { get; set; } = false;
+ public static bool AggressiveChatStreamingEnabled { get; set; } = true;
private MossyInventory _inventoryLogger;
public static NavVisualization navVisualization;
@@ -85,8 +86,14 @@ namespace MosswartMassacre
try
{
MyHost = Host;
+
+ // DIAGNOSTIC: Test basic loading first
+ PluginLoadDiagnostic.TestBasicLoad();
+
+ // TEMPORARILY DISABLED: Harmony initialization
+ // HarmonyCompatibilityEnsurer.EnsureCompatibility();
- WriteToChat("Mosswart Massacre has started!");
+ // Note: Startup messages will appear after character login
// Subscribe to chat message event
CoreManager.Current.ChatBoxMessage += new EventHandler(OnChatText);
CoreManager.Current.ChatBoxMessage += new EventHandler(AllChatText);
@@ -117,7 +124,8 @@ namespace MosswartMassacre
// Initialize navigation visualization system
navVisualization = new NavVisualization();
-
+ // Note: DECAL Harmony patches will be initialized in LoginComplete event
+ // where the chat system is available for error messages
}
catch (Exception ex)
@@ -168,6 +176,9 @@ namespace MosswartMassacre
navVisualization = null;
}
+ // Clean up Harmony patches
+ DecalHarmonyClean.Cleanup();
+
MyHost = null;
}
catch (Exception ex)
@@ -179,6 +190,8 @@ namespace MosswartMassacre
{
CoreManager.Current.CharacterFilter.LoginComplete -= CharacterFilter_LoginComplete;
+ WriteToChat("Mosswart Massacre has started!");
+
PluginSettings.Initialize(); // Safe to call now
// Apply the values
@@ -195,6 +208,19 @@ namespace MosswartMassacre
if (WebSocketEnabled)
WebSocket.Start();
+ // Initialize Harmony patches using UtilityBelt's loaded DLL
+ try
+ {
+ bool success = DecalHarmonyClean.Initialize();
+ if (success)
+ WriteToChat("[OK] Plugin message interception active");
+ else
+ WriteToChat("[FAIL] Could not initialize message interception");
+ }
+ catch (Exception ex)
+ {
+ WriteToChat($"[ERROR] Harmony initialization failed: {ex.Message}");
+ }
}
@@ -236,7 +262,11 @@ namespace MosswartMassacre
try
{
string cleaned = NormalizeChatLine(e.Text);
+
+ // Send to WebSocket
await WebSocket.SendChatTextAsync(e.Color, cleaned);
+
+ // Note: Plugin message analysis is now handled by Harmony patches
}
catch (Exception ex)
{
@@ -578,9 +608,9 @@ namespace MosswartMassacre
WriteToChat("/mm remotecommand - Listen to allegiance !do/!dot enable|disable");
WriteToChat("/mm getmetastate - Gets the current metastate");
WriteToChat("/mm nextwp - Advance VTank to next waypoint");
- break;
- case "debug":
- DispatchChatToBoxWithPluginIntercept("/ub give bajs to Town Crier");
+ WriteToChat("/mm decalstatus - Check Harmony patch status (UtilityBelt version)");
+ WriteToChat("/mm decaldebug - Enable/disable plugin message debug output + WebSocket streaming");
+ WriteToChat("/mm harmonyraw - Show raw intercepted messages (debug output)");
break;
case "report":
TimeSpan elapsed = DateTime.Now - statsStartTime;
@@ -687,6 +717,92 @@ namespace MosswartMassacre
}
break;
+ case "decalstatus":
+ try
+ {
+ WriteToChat("=== Harmony Patch Status (UtilityBelt Pattern) ===");
+ WriteToChat($"Patches Active: {DecalHarmonyClean.IsActive()}");
+ WriteToChat($"Messages Intercepted: {DecalHarmonyClean.GetMessagesIntercepted()}");
+ WriteToChat($"Debug Streaming: {AggressiveChatStreamingEnabled}");
+ WriteToChat($"WebSocket Streaming: {(AggressiveChatStreamingEnabled && WebSocketEnabled ? "ACTIVE" : "INACTIVE")}");
+
+ // Test Harmony availability
+ WriteToChat("=== Harmony Version Status ===");
+ try
+ {
+ var harmonyTest = Harmony.HarmonyInstance.Create("test.version.check");
+ WriteToChat($"[OK] Harmony Available (ID: {harmonyTest.Id})");
+
+ // Check Harmony assembly version
+ var harmonyAssembly = typeof(Harmony.HarmonyInstance).Assembly;
+ WriteToChat($"[OK] Harmony Version: {harmonyAssembly.GetName().Version}");
+ WriteToChat($"[OK] Harmony Location: {harmonyAssembly.Location}");
+ }
+ catch (Exception harmonyEx)
+ {
+ WriteToChat($"[FAIL] Harmony Test Failed: {harmonyEx.Message}");
+ }
+ }
+ catch (Exception ex)
+ {
+ WriteToChat($"Status check error: {ex.Message}");
+ }
+ break;
+
+ case "decaldebug":
+ if (args.Length > 1)
+ {
+ if (args[1].Equals("enable", StringComparison.OrdinalIgnoreCase))
+ {
+ AggressiveChatStreamingEnabled = true;
+ WriteToChat("[OK] DECAL debug streaming ENABLED - will show captured messages + stream via WebSocket");
+ }
+ else if (args[1].Equals("disable", StringComparison.OrdinalIgnoreCase))
+ {
+ AggressiveChatStreamingEnabled = false;
+ WriteToChat("[FAIL] DECAL debug streaming DISABLED - WebSocket streaming also disabled");
+ }
+ else
+ {
+ WriteToChat("Usage: /mm decaldebug ");
+ }
+ }
+ else
+ {
+ WriteToChat("Usage: /mm decaldebug ");
+ }
+ break;
+
+
+ case "harmonyraw":
+ try
+ {
+ WriteToChat("=== Raw Harmony Interception Log ===");
+ var debugEntries = DecalHarmonyClean.GetDebugLog();
+ if (debugEntries.Length == 0)
+ {
+ WriteToChat("No debug entries found. Enable debug streaming first: /mm decaldebug enable");
+ }
+ else
+ {
+ WriteToChat($"Last {debugEntries.Length} intercepted messages:");
+ foreach (var entry in debugEntries.Skip(Math.Max(0, debugEntries.Length - 10)))
+ {
+ WriteToChat($" {entry}");
+ }
+ if (debugEntries.Length > 10)
+ {
+ WriteToChat($"... ({debugEntries.Length - 10} more entries)");
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ WriteToChat($"Debug log error: {ex.Message}");
+ }
+ break;
+
+
default:
WriteToChat($"Unknown /mm command: {subCommand}. Try /mm help");
break;
diff --git a/MosswartMassacre/Properties/AssemblyInfo.cs b/MosswartMassacre/Properties/AssemblyInfo.cs
index b7482f9..b034aba 100644
--- a/MosswartMassacre/Properties/AssemblyInfo.cs
+++ b/MosswartMassacre/Properties/AssemblyInfo.cs
@@ -26,5 +26,5 @@ using System.Runtime.InteropServices;
// Minor Version
// Build Number
// Revision
-[assembly: AssemblyVersion("3.0.1.0")]
-[assembly: AssemblyFileVersion("3.0.1.0")]
\ No newline at end of file
+[assembly: AssemblyVersion("3.0.1.1")]
+[assembly: AssemblyFileVersion("3.0.1.1")]
\ No newline at end of file
diff --git a/MosswartMassacre/Views/VVSTabbedMainView.cs b/MosswartMassacre/Views/VVSTabbedMainView.cs
index 0c13665..a2c9ad8 100644
--- a/MosswartMassacre/Views/VVSTabbedMainView.cs
+++ b/MosswartMassacre/Views/VVSTabbedMainView.cs
@@ -297,6 +297,7 @@ namespace MosswartMassacre.Views
try
{
PluginSettings.Instance.WebSocketEnabled = chkWebSocketEnabled.Checked;
+ PluginCore.WebSocketEnabled = chkWebSocketEnabled.Checked;
if (chkWebSocketEnabled.Checked)
{
WebSocket.Start();
diff --git a/MosswartMassacre/lib/0Harmony.dll b/MosswartMassacre/lib/0Harmony.dll
new file mode 100644
index 0000000..6c0dd94
Binary files /dev/null and b/MosswartMassacre/lib/0Harmony.dll differ