Now with Plugin chat intercept. version 3.0.1.1
This commit is contained in:
parent
591da42d36
commit
96b85ed226
8 changed files with 717 additions and 401 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -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
|
||||
|
|
|
|||
584
MosswartMassacre/DecalHarmonyClean.cs
Normal file
584
MosswartMassacre/DecalHarmonyClean.cs
Normal file
|
|
@ -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
|
||||
{
|
||||
/// <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)
|
||||
{
|
||||
// Only log if completely unable to apply any patches
|
||||
System.Diagnostics.Debug.WriteLine($"Patch application 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)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"HooksWrapper patch failed: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"HooksWrapper patch 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)
|
||||
{
|
||||
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}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try a different approach - patch the actual chat system or find global instances
|
||||
/// </summary>
|
||||
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}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Failed to apply patch {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));
|
||||
System.Diagnostics.Debug.WriteLine($"AddChatText({paramInfo})");
|
||||
}
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Harmony cleanup error: {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++;
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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 (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Harmony interception3 error: {ex.Message}");
|
||||
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 (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Harmony generic interception error: {ex.Message}");
|
||||
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
|
||||
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}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <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++;
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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 (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Harmony Host.Actions 4param interception error: {ex.Message}");
|
||||
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 (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Harmony Host.Actions generic interception error: {ex.Message}");
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<int> CallLocations = new List<int>();
|
||||
internal List<Hook> Hooks = new List<Hook>();
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// New improved Hooker
|
||||
/// </summary>
|
||||
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<Hook> hookers = new List<Hook>();
|
||||
|
||||
[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();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// New improved Hooker (Virtual‐table edition)
|
||||
/// </summary>
|
||||
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<VHook> hookers = new List<VHook>();
|
||||
|
||||
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<SetTextDelegate>(_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<SetTextDelegate2>(_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 { }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -34,6 +34,10 @@
|
|||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="0Harmony">
|
||||
<HintPath>C:\Games\Decal Plugins\UtilityBelt\0Harmony.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="Decal.Adapter">
|
||||
<HintPath>lib\Decal.Adapter.dll</HintPath>
|
||||
<EmbedInteropTypes>False</EmbedInteropTypes>
|
||||
|
|
@ -155,7 +159,7 @@
|
|||
<Link>Shared\VCS_Connector.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="ClientTelemetry.cs" />
|
||||
<Compile Include="ManyHook.cs" />
|
||||
<Compile Include="DecalHarmonyClean.cs" />
|
||||
<Compile Include="MossyInventory.cs" />
|
||||
<Compile Include="NavRoute.cs" />
|
||||
<Compile Include="NavVisualization.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<ChatTextInterceptEventArgs>(OnChatText);
|
||||
CoreManager.Current.ChatBoxMessage += new EventHandler<ChatTextInterceptEventArgs>(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 <enable|disable>");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteToChat("Usage: /mm decaldebug <enable|disable>");
|
||||
}
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -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")]
|
||||
[assembly: AssemblyVersion("3.0.1.1")]
|
||||
[assembly: AssemblyFileVersion("3.0.1.1")]
|
||||
|
|
@ -297,6 +297,7 @@ namespace MosswartMassacre.Views
|
|||
try
|
||||
{
|
||||
PluginSettings.Instance.WebSocketEnabled = chkWebSocketEnabled.Checked;
|
||||
PluginCore.WebSocketEnabled = chkWebSocketEnabled.Checked;
|
||||
if (chkWebSocketEnabled.Checked)
|
||||
{
|
||||
WebSocket.Start();
|
||||
|
|
|
|||
BIN
MosswartMassacre/lib/0Harmony.dll
Normal file
BIN
MosswartMassacre/lib/0Harmony.dll
Normal file
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue