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