MosswartMassacre/MosswartMassacre/DecalHarmonyClean.cs
erik 64e690f625 Phase 6: Fix swallowed exceptions and cleanup unused usings
- 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>
2026-02-27 07:59:34 +00:00

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;
}
}
}
}