From 84dec875b9555019b0b9cc4136c35709605c4627 Mon Sep 17 00:00:00 2001 From: "NGnius (Graham)" Date: Mon, 8 Mar 2021 11:23:47 -0500 Subject: [PATCH] Add network debugging tools --- CLre/API/Tools/AccessToolsWarnings.cs | 55 +++++++++++++ CLre/API/Tools/NetClientListener.cs | 47 +++++++++++ CLre/API/Tools/NetClientSender.cs | 114 ++++++++++++++++++++++++++ CLre/API/Utility/Reflection.cs | 4 + CLre/CLre.cs | 21 ++++- CLre/CLre.csproj | 5 +- 6 files changed, 240 insertions(+), 6 deletions(-) create mode 100644 CLre/API/Tools/AccessToolsWarnings.cs create mode 100644 CLre/API/Tools/NetClientListener.cs create mode 100644 CLre/API/Tools/NetClientSender.cs diff --git a/CLre/API/Tools/AccessToolsWarnings.cs b/CLre/API/Tools/AccessToolsWarnings.cs new file mode 100644 index 0000000..b57e002 --- /dev/null +++ b/CLre/API/Tools/AccessToolsWarnings.cs @@ -0,0 +1,55 @@ +using System; +using System.Diagnostics; +using System.Reflection; +using HarmonyLib; + +#if DEBUG +namespace CLre.API.Tools +{ + public static class AccessToolsWarnings + { + internal static bool isEnabled = false; + + public static void Enable() + { + isEnabled = true; + } + + public static void Disable() + { + isEnabled = false; + } + } + + [HarmonyPatch(typeof(AccessTools), "TypeByName")] + class AccessTools_TypeByName_Patch + { + [HarmonyPostfix] + public static void AfterMethodCall(Type __result, string name) + { + if (!AccessToolsWarnings.isEnabled) return; + if (__result == null) + { + var method = (new StackTrace()).GetFrame(2).GetMethod(); + Utility.Logging.LogWarning($"[{method.DeclaringType.FullName}.{method.Name}] AccessTools.TypeByName(\"{name}\") returned null result"); + } + } + } + + [HarmonyPatch(typeof(AccessTools), "Method", + new Type[] {typeof(string), typeof(Type[]), typeof(Type[])})] + class AccessTools_Method_Patch + { + [HarmonyPostfix] + public static void AfterMethodCall(MethodInfo __result, string typeColonMethodname) + { + if (!AccessToolsWarnings.isEnabled) return; + if (__result == null) + { + var method = (new StackTrace()).GetFrame(2).GetMethod(); + Utility.Logging.LogWarning($"[{method.DeclaringType.FullName}.{method.Name}] AccessTools.Method(\"{typeColonMethodname}\") returned null result"); + } + } + } +} +#endif \ No newline at end of file diff --git a/CLre/API/Tools/NetClientListener.cs b/CLre/API/Tools/NetClientListener.cs new file mode 100644 index 0000000..cd3feca --- /dev/null +++ b/CLre/API/Tools/NetClientListener.cs @@ -0,0 +1,47 @@ +using System.Reflection; +using GameNetworkLayer.Shared; +using HarmonyLib; + +namespace CLre.API.Tools +{ + public class NetClientListener + { + internal static bool isEnabled = false; + + public static void Enable() + { + isEnabled = true; + } + + public static void Disable() + { + isEnabled = false; + } + } + + [HarmonyPatch] + class NetMessageClientListener_HandleAllMessages_Patch + { + [HarmonyPrefix] + public static void BeforeMethodCall(object ____deserializer, int playerId, object value) + { + if (!NetClientListener.isEnabled) return; + // TODO optimize this to not use Traverse + Traverse result = Traverse.Create(____deserializer).Method("Deserialize", value); + NetworkDispatcherCode code = result.Field("dispatcherCode").Value; + byte[] data = result.Field("bytes").Value; + if (data == null) + { + Utility.Logging.LogWarning("Network message data was deserialized as null"); + return; + } + Utility.Logging.Log($"Received network message for player {playerId} (code: {code.ToString()}, len: {data.Length})"); + } + + [HarmonyTargetMethod] + public static MethodBase Target() + { + return AccessTools.Method("GameNetworkLayer.Client.NetMessageClientListener:HandleAllMessages"); + } + } +} \ No newline at end of file diff --git a/CLre/API/Tools/NetClientSender.cs b/CLre/API/Tools/NetClientSender.cs new file mode 100644 index 0000000..7b03a44 --- /dev/null +++ b/CLre/API/Tools/NetClientSender.cs @@ -0,0 +1,114 @@ +using System; +using System.Reflection; +using System.Text; +using GameNetworkLayer.Shared; +using HarmonyLib; +using NetworkFramework.Shared; + +namespace CLre.API.Tools +{ + public class NetClientSender + { + private struct DummyNetDataStruct : ISerializedNetData + { + public byte[] Serialize() + { + return new byte[0]; + } + + public void Deserialize(byte[] data) + { + } + } + + private static readonly MethodInfo _genericSendMessage = AccessTools.Method("GameNetworkLayer.Client.NetMessageClientSender:SendMessage"); + + private static readonly MethodInfo _genericGetSendMessageMethod = + AccessTools.Method(typeof(NetClientSender), "GetSendMessageMethod",parameters: new Type[0]);/* + /*((Func) GetSendMessageMethod).Method + .GetBaseDefinition() + .GetGenericMethodDefinition();*/ + + private static readonly MethodInfo _genericLog = + AccessTools.Method(typeof(NetClientSender), "Log");/* + ((Action) Log).Method + .GetBaseDefinition() + .GetGenericMethodDefinition();*/ + + private static readonly MethodInfo _genericGetLogMethod = + AccessTools.Method(typeof(NetClientSender), "GetLogMethod", new Type[0]);/* + ((Func) GetLogMethod).Method + .GetBaseDefinition() + .GetGenericMethodDefinition();*/ + + public static MethodInfo GetSendMessageMethod(Type t) + { + return (MethodInfo) _genericGetSendMessageMethod.MakeGenericMethod(t) + .Invoke(null, new object[0]); + } + + public static MethodInfo GetSendMessageMethod() where T : struct, ISerializedNetData + { + return _genericSendMessage.MakeGenericMethod(typeof(T)); + } + + public static MethodInfo DebugSendMessage(Harmony instance = null, MethodInfo before = null, MethodInfo after = null, MethodInfo transpiler = null, MethodInfo finalizer = null) where T : struct, ISerializedNetData + { + return DebugSendMessage(typeof(T), instance, before, after, transpiler, finalizer); + } + + public static MethodInfo DebugSendMessage(Type generic, Harmony instance = null, MethodInfo before = null, MethodInfo after = null, MethodInfo transpiler = null, MethodInfo finalizer = null) + { + return DebugSendMessage( + generic, instance, + before == null ? null : new HarmonyMethod(before), + after == null ? null : new HarmonyMethod(after), + transpiler == null ? null : new HarmonyMethod(transpiler), + finalizer == null ? null : new HarmonyMethod(finalizer)); + } + + public static MethodInfo DebugSendMessage(Harmony instance = null, HarmonyMethod before = null, HarmonyMethod after = null, HarmonyMethod transpiler = null, HarmonyMethod finalizer = null) where T : struct, ISerializedNetData + { + return DebugSendMessage(typeof(T), instance, before, after, transpiler, finalizer); + } + + public static MethodInfo DebugSendMessage(Type generic, Harmony instance = null, HarmonyMethod before = null, HarmonyMethod after = null, HarmonyMethod transpiler = null, HarmonyMethod finalizer = null) + { + if (instance == null) instance = CLre.harmonyInstance; + MethodInfo target = GetSendMessageMethod(generic); + return instance.Patch(target, + before, + after, + transpiler, + finalizer); + } + + public static MethodInfo GetLogMethod(Type t) + { + return (MethodInfo) _genericGetLogMethod.MakeGenericMethod(t) + .Invoke(null, new object[0]); + } + + public static MethodInfo GetLogMethod() where T : struct, ISerializedNetData + { + return _genericLog.MakeGenericMethod(typeof(T)); + } + + private static void Log(NetworkDispatcherCode code, ref T data) where T : struct, ISerializedNetData + { + //Utility.Logging.Log($"Sending ISerializedNetData {data.GetType().FullName} (code: {code.ToString()})"); + Traverse d = Traverse.Create(data); + StringBuilder sb = new StringBuilder($"Sending ISerializedNetData {data.GetType().FullName} (code: {code.ToString()})"); + foreach (string fieldName in d.Fields()) + { + Traverse field = d.Field(fieldName); + sb.Append("\n"); + sb.Append("\""); + sb.Append(fieldName.Substring(fieldName.IndexOf('<')+1, fieldName.LastIndexOf('>')-1)); + sb.Append("\": "); + sb.Append(field.GetValue()); + } + Utility.Logging.Log(sb.ToString()); + } + } +} \ No newline at end of file diff --git a/CLre/API/Utility/Reflection.cs b/CLre/API/Utility/Reflection.cs index edcc6af..37b1a5a 100644 --- a/CLre/API/Utility/Reflection.cs +++ b/CLre/API/Utility/Reflection.cs @@ -1,6 +1,8 @@ using System; using System.Reflection; +using GameNetworkLayer.Shared; using HarmonyLib; +using NetworkFramework.Shared; using Svelto.DataStructures; using Svelto.ECS; @@ -25,6 +27,8 @@ namespace CLre.API.Utility public delegate T[] QueryEntitiesV1(ExclusiveGroup.ExclusiveGroupStruct group, out int count) where T : IEntityStruct; + public delegate object[] SendMessage(NetworkDispatcherCode dispatcherCode, ref T value) where T : struct, ISerializedNetData; + // useful reflection functions public static TFuncProto BuildDelegate(MethodInfo method) where TFuncProto : Delegate { diff --git a/CLre/CLre.cs b/CLre/CLre.cs index 86e80e3..1e4a10e 100644 --- a/CLre/CLre.cs +++ b/CLre/CLre.cs @@ -4,6 +4,8 @@ using System.Linq; using System.Reflection; using System.Text; using CLre.API.Characters; +using CLre.API.Tools; +using GameNetworkLayer.Shared; using HarmonyLib; using Svelto.ECS; using UnityEngine; @@ -16,7 +18,7 @@ namespace CLre public override string Version { get; } = Assembly.GetExecutingAssembly().GetName().Version.ToString(); - private Harmony harmonyInstance = null; + internal static Harmony harmonyInstance = null; // called when Cardlife shuts down public override void OnApplicationQuit() { @@ -29,6 +31,9 @@ namespace CLre #if DEBUG FileLog.Reset(); Harmony.DEBUG = true; + // enable CLre debug functionality + AccessToolsWarnings.Enable(); + NetClientListener.Enable(); Stopwatch startup = Stopwatch.StartNew(); #endif // init all Harmony patches in project @@ -46,10 +51,22 @@ namespace CLre API.Utility.Logging.MetaLog($"{Name} init complete."); #if DEBUG + // configure CLre debug functionality + Type netData = AccessTools.TypeByName("Game.Handhelds.DrawingStateMessage"); + NetClientSender.DebugSendMessage(netData, harmonyInstance, + NetClientSender.GetLogMethod(netData)); + API.Utility.Logging.MetaLog("Patched SendMessage"); + + netData = AccessTools.TypeByName("Shared.Inventory.HandheldEquipmentRequest"); + NetClientSender.DebugSendMessage(netData, harmonyInstance, + NetClientSender.GetLogMethod(netData)); + API.Utility.Logging.MetaLog("Patched SendMessage"); + + // API debug and testing API.App.Client.InitComplete += (_, __) => { startup.Stop(); - API.Utility.Logging.MetaLog($"Startup took {startup.ElapsedMilliseconds}ms"); + API.Utility.Logging.Log($"Startup took {startup.ElapsedMilliseconds}ms"); API.Utility.Logging.Log( $"EAC has detected code mods? {EasyAntiCheat.Client.Hydra.Runtime.Integrity.Violated}" + (EasyAntiCheat.Client.Hydra.Runtime.Integrity.Violated diff --git a/CLre/CLre.csproj b/CLre/CLre.csproj index 68c98a3..ff2e0f8 100644 --- a/CLre/CLre.csproj +++ b/CLre/CLre.csproj @@ -11,7 +11,7 @@ - + @@ -281,9 +281,6 @@ ..\..\cl\Cardlife_Data\Managed\IllusionPlugin.dll - - -