- Add debug logging to all empty catch blocks in DecalHarmonyClean.cs setup methods (prefix method catches intentionally stay silent to never break other plugins) - Add error logging to VtankControl.VtSetSetting catch - Add logging to DecalPatchMethods.ProcessInterceptedMessage catch - Remove unused usings from PluginCore.cs (System.Diagnostics, System.Drawing, System.Text, System.Text.RegularExpressions) - Flatten redundant nested try/catch in PatchPluginHost Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
559 lines
No EOL
19 KiB
C#
559 lines
No EOL
19 KiB
C#
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
|
|
{
|
|
/// <summary>
|
|
/// Clean Harmony implementation using UtilityBelt's exact pattern
|
|
/// Uses same namespace convention, same API, same Harmony package (Lib.Harmony 2.2.2)
|
|
/// </summary>
|
|
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<string> debugLog = new Queue<string>();
|
|
private const int MAX_DEBUG_ENTRIES = 50;
|
|
|
|
/// <summary>
|
|
/// Initialize Harmony patches using UtilityBelt's exact pattern
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Apply patches to DECAL's AddChatText methods using UtilityBelt approach
|
|
/// </summary>
|
|
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)
|
|
{
|
|
AddDebugLog($"ApplyDecalPatches failed: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Patch HooksWrapper.AddChatText methods
|
|
/// </summary>
|
|
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)
|
|
{
|
|
AddDebugLog($"PatchHooksWrapper single patch failed ({prefixMethodName}): {ex.Message}");
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
AddDebugLog($"PatchHooksWrapper failed: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Patch Host.Actions.AddChatText methods (what our plugin uses)
|
|
/// </summary>
|
|
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)
|
|
{
|
|
AddDebugLog($"PatchHostActions single patch failed ({prefixMethodName}): {ex.Message}");
|
|
}
|
|
}
|
|
|
|
// PATHWAY 3: Try to patch at PluginHost level
|
|
PatchPluginHost();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
AddDebugLog($"PatchHostActions failed: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Try a different approach - patch the actual chat system or find global instances
|
|
/// </summary>
|
|
private static void PatchPluginHost()
|
|
{
|
|
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)
|
|
{
|
|
AddDebugLog($"PatchPluginHost single patch failed: {ex.Message}");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
AddDebugLog($"PatchPluginHost failed: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Apply a single patch using UtilityBelt's method
|
|
/// </summary>
|
|
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)
|
|
{
|
|
AddDebugLog($"ApplySinglePatch failed ({prefixMethodName}): {ex.Message}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// List available methods for debugging
|
|
/// </summary>
|
|
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));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clean up patches using UtilityBelt's pattern
|
|
/// </summary>
|
|
public static void Cleanup()
|
|
{
|
|
try
|
|
{
|
|
if (harmonyDecal != null && patchesApplied)
|
|
{
|
|
// Use UtilityBelt's cleanup pattern
|
|
harmonyDecal.UnpatchAll(harmonyNamespace + ".patches");
|
|
}
|
|
patchesApplied = false;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
AddDebugLog($"Cleanup failed: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Status checks
|
|
/// </summary>
|
|
public static bool IsActive() => patchesApplied && harmonyDecal != null;
|
|
public static int GetMessagesIntercepted() => messagesIntercepted;
|
|
|
|
/// <summary>
|
|
/// Add debug log entry
|
|
/// </summary>
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get debug log entries
|
|
/// </summary>
|
|
public static string[] GetDebugLog()
|
|
{
|
|
lock (debugLog)
|
|
{
|
|
return debugLog.ToArray();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Patch methods for DECAL interception using UtilityBelt's approach
|
|
/// </summary>
|
|
public static class DecalPatchMethods
|
|
{
|
|
/// <summary>
|
|
/// Prefix method for HooksWrapper.AddChatText(string, int) - intercepts plugin messages via HooksWrapper
|
|
/// </summary>
|
|
public static bool AddChatTextPrefixHooks(string text, int color)
|
|
{
|
|
try
|
|
{
|
|
// Always increment to verify patch is working
|
|
DecalHarmonyClean.messagesIntercepted++;
|
|
|
|
if (PluginCore.AggressiveChatStreamingEnabled)
|
|
{
|
|
}
|
|
|
|
// 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
|
|
{
|
|
// Never let our interception break other plugins
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Prefix method for HooksWrapper.AddChatText(string, int, int) - 3-parameter version
|
|
/// </summary>
|
|
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
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generic prefix method for any other HooksWrapper AddChatText overloads
|
|
/// </summary>
|
|
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
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Process intercepted plugin messages
|
|
/// </summary>
|
|
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
|
|
|
|
// 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)
|
|
{
|
|
DecalHarmonyClean.AddDebugLog($"ProcessInterceptedMessage failed: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Identify which plugin sent the message using UtilityBelt's patterns
|
|
/// </summary>
|
|
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 =====
|
|
|
|
/// <summary>
|
|
/// Prefix method for Host.Actions.AddChatText(string, int, int) - 3-parameter version
|
|
/// </summary>
|
|
public static bool AddChatTextPrefixHost(string text, int color, int target)
|
|
{
|
|
try
|
|
{
|
|
DecalHarmonyClean.messagesIntercepted++;
|
|
|
|
if (PluginCore.AggressiveChatStreamingEnabled)
|
|
{
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(text) && !text.Contains("[Mosswart Massacre]"))
|
|
{
|
|
ProcessInterceptedMessage(text, color, $"Host.Actions-3param(target={target})");
|
|
|
|
// Silent operation - debug output removed
|
|
}
|
|
|
|
return true;
|
|
}
|
|
catch
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Prefix method for Host.Actions.AddChatText(string, int, int, int) - 4-parameter version
|
|
/// </summary>
|
|
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
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generic prefix method for any other Host.Actions.AddChatText overloads
|
|
/// </summary>
|
|
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
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// ===== COREMANAGER.ACTIONS PREFIX METHODS =====
|
|
|
|
/// <summary>
|
|
/// Prefix method for CoreManager.Actions methods - trying different instances
|
|
/// </summary>
|
|
public static bool AddChatTextPrefixCore2(string text, int color)
|
|
{
|
|
try
|
|
{
|
|
DecalHarmonyClean.messagesIntercepted++;
|
|
|
|
if (PluginCore.AggressiveChatStreamingEnabled)
|
|
{
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(text) && !text.Contains("[Mosswart Massacre]"))
|
|
{
|
|
ProcessInterceptedMessage(text, color, "CoreActions-2param");
|
|
|
|
// Silent operation - debug output removed
|
|
}
|
|
|
|
return true;
|
|
}
|
|
catch
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Prefix method for CoreManager.Actions 3-param methods
|
|
/// </summary>
|
|
public static bool AddChatTextPrefixCore3(string text, int color, int target)
|
|
{
|
|
try
|
|
{
|
|
DecalHarmonyClean.messagesIntercepted++;
|
|
|
|
if (PluginCore.AggressiveChatStreamingEnabled)
|
|
{
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(text) && !text.Contains("[Mosswart Massacre]"))
|
|
{
|
|
ProcessInterceptedMessage(text, color, $"CoreActions-3param(target={target})");
|
|
|
|
// Silent operation - debug output removed
|
|
}
|
|
|
|
return true;
|
|
}
|
|
catch
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
} |