From eb7fce3350739a4e228867232924f71e3546f019 Mon Sep 17 00:00:00 2001 From: Zirno Date: Mon, 5 Jul 2021 21:44:01 +0200 Subject: [PATCH 1/5] method Rpc --- .../Networking/MethodRpc/CustomMethodRpc.cs | 60 ++++++ .../MethodRpc/MethodRpcAttribute.cs | 93 +++++++++ .../MethodRpc/RpcSerializationHandels.cs | 73 +++++++ Reactor/Networking/MethodRpc/RpcSerializer.cs | 179 ++++++++++++++++++ Reactor/ReactorPlugin.cs | 2 + 5 files changed, 407 insertions(+) create mode 100644 Reactor/Networking/MethodRpc/CustomMethodRpc.cs create mode 100644 Reactor/Networking/MethodRpc/MethodRpcAttribute.cs create mode 100644 Reactor/Networking/MethodRpc/RpcSerializationHandels.cs create mode 100644 Reactor/Networking/MethodRpc/RpcSerializer.cs diff --git a/Reactor/Networking/MethodRpc/CustomMethodRpc.cs b/Reactor/Networking/MethodRpc/CustomMethodRpc.cs new file mode 100644 index 0000000..c95e3fe --- /dev/null +++ b/Reactor/Networking/MethodRpc/CustomMethodRpc.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using BepInEx.IL2CPP; +using Hazel; +using InnerNet; + +namespace Reactor.Networking.MethodRpc +{ + public class CustomMethodRpc : UnsafeCustomRpc + { + public static readonly List AllCustomMethodRpcs = new(); + + internal static bool SkipNextSend; + + public CustomMethodRpc(BasePlugin plugin, MethodInfo method, uint id, SendOption option, RpcLocalHandling localHandling) : base(plugin, id) + { + Method = method; + LocalHandling = localHandling; + SendOption = option; + + AllCustomMethodRpcs.Add(this); + } + + public MethodInfo Method { get; } + public override Type InnerNetObjectType { get; } = typeof(PlayerControl); + public override RpcLocalHandling LocalHandling { get; } + public override SendOption SendOption { get; } + + public override void UnsafeWrite(MessageWriter writer, object data) + { + var args = (object[]) data; + RpcSerializer.SendMassage(writer,args); + } + + public override object UnsafeRead(MessageReader reader) + { + var args = RpcSerializer.Deserialize(reader,Method.GetParameters().Select(x => x.ParameterType).ToArray()); + return args; + } + + public override void UnsafeHandle(InnerNetObject innerNetObject, object data) + { + SkipNextSend = true; + + var result = Method.Invoke(null,(object[]) data); + if (Method.ReturnParameter.ParameterType == typeof(IEnumerator)) + { + Coroutines.Start((IEnumerator) result); + } + } + + public void Send(object[] args) + { + UnsafeSend(PlayerControl.LocalPlayer,args); + } + } +} diff --git a/Reactor/Networking/MethodRpc/MethodRpcAttribute.cs b/Reactor/Networking/MethodRpc/MethodRpcAttribute.cs new file mode 100644 index 0000000..17ed355 --- /dev/null +++ b/Reactor/Networking/MethodRpc/MethodRpcAttribute.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using BepInEx.IL2CPP; +using HarmonyLib; +using Hazel; + +namespace Reactor.Networking.MethodRpc +{ + [AttributeUsage(AttributeTargets.Method)] + public class MethodRpcAttribute : Attribute + { + public byte id; + public SendOption option; + public RpcLocalHandling localHandling; + + public MethodRpcAttribute( byte id, SendOption option = SendOption.Reliable, RpcLocalHandling localHandling = RpcLocalHandling.Before) + { + this.id = id; + this.option = option; + this.localHandling = localHandling; + } + + public static void Register(Assembly assembly, BasePlugin plugin) + { + Logger.Info($"Registering Method Rpc for {assembly.GetName()}"); + Dictionary serializeMethods = new(); + + MethodInfo serializeMethod; + int index = 0; + + while ((serializeMethod = RpcSerializationHandels.GetHandleFor(index)) != null) + { + serializeMethods.Add(index, serializeMethod); + Logger.Debug($"Registered Rpc Serialization Handle for {index} args"); + index++; + } + + var rpcMethods = assembly.GetTypes() + .SelectMany(t => t.GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public)) + .Where(m => m.GetCustomAttributes(typeof(MethodRpcAttribute), false).Length > 0) + .ToArray(); + + + foreach (var method in rpcMethods) + { + if (!method.IsStatic) + { + Logger.Warning("Cannot register non static custom method rpc"); + continue; + } + + var seializer = serializeMethods[method.GetParameters().Length]; + if (seializer == null) + { + Logger.Error($"Unsupported Args count, max is {RpcSerializationHandels.GetMaxArgs()}"); + continue; + } + + var attribute = method.GetCustomAttribute(); + if (attribute.localHandling == RpcLocalHandling.Before) + { + PluginSingleton.Instance.Harmony.Patch(method, new HarmonyMethod(seializer)); + } + else if (attribute.localHandling == RpcLocalHandling.After) + { + PluginSingleton.Instance.Harmony.Patch(method, postfix: new HarmonyMethod(seializer)); + } + var customRpc = new CustomMethodRpc(plugin, method, attribute.id, attribute.option, attribute.localHandling); + try + { + PluginSingleton.Instance.CustomRpcManager.Register(customRpc); + } + catch (Exception) + { + // ignored + } + } + } + + internal static void Initialize() + { + ChainloaderHooks.PluginLoad += plugin => Register(plugin.GetType().Assembly, plugin); + } + + [MethodRpc(123)] + static void Hello() + { + + } + } +} diff --git a/Reactor/Networking/MethodRpc/RpcSerializationHandels.cs b/Reactor/Networking/MethodRpc/RpcSerializationHandels.cs new file mode 100644 index 0000000..a47b695 --- /dev/null +++ b/Reactor/Networking/MethodRpc/RpcSerializationHandels.cs @@ -0,0 +1,73 @@ +using System.Linq; +using System.Reflection; +using HarmonyLib; +using Reactor.Networking; +using Reactor.Networking.MethodRpc; + +namespace Reactor.Networking +{ + internal class RpcSerializationHandels + { + + public static MethodInfo GetHandleFor(int count) + { + return typeof(RpcSerializationHandels).GetRuntimeMethod(nameof(Serialize), + Enumerable.Repeat(typeof(MethodBase),1).Concat(Enumerable.Repeat(typeof(object), count)).ToArray()); + } + + public static int GetMaxArgs() + { + return typeof(RpcSerializationHandels).GetMethods().OrderBy(x => x.GetParameters().Length).Last().GetParameters().Length; + } + + public static bool RpcPrefix(MethodBase originalMethod, params object[] list) + { + if (CustomMethodRpc.SkipNextSend) + { + CustomMethodRpc.SkipNextSend = false; + return true; + } + + CustomMethodRpc methodRpc = + CustomMethodRpc.AllCustomMethodRpcs.FirstOrDefault(x => x.Method == originalMethod); + typeof(CustomMethodRpc).GetMethod(nameof(CustomMethodRpc.Send))?.Invoke(methodRpc,new object[]{list}); + return false; + } + + public static bool Serialize(MethodBase __originalMethod) + { + return RpcPrefix(__originalMethod); + } + + public static bool Serialize(MethodBase __originalMethod, [HarmonyArgument(0)] object arg0) + { + return RpcPrefix(__originalMethod,arg0); + } + + public static bool Serialize(MethodBase __originalMethod, [HarmonyArgument(0)] object arg0,[HarmonyArgument(1)] object arg1) + { + return RpcPrefix(__originalMethod,new object[]{arg0,arg1}); + } + public static bool Serialize(MethodBase __originalMethod, [HarmonyArgument(0)] object arg0 , [HarmonyArgument(1)] object arg1, [HarmonyArgument(2)] object arg2) + { + return RpcPrefix(__originalMethod,arg0,arg1,arg2); + } + + public static bool Serialize(MethodBase __originalMethod, [HarmonyArgument(0)] object arg0 , [HarmonyArgument(1)] object arg1, [HarmonyArgument(2)] object arg2,[HarmonyArgument(3)] object arg3) + { + return RpcPrefix(__originalMethod,arg0,arg1,arg2,arg3); + } + + public static bool Serialize(MethodBase __originalMethod, [HarmonyArgument(0)] object arg0 , [HarmonyArgument(1)] object arg1, [HarmonyArgument(2)] object arg2,[HarmonyArgument(3)] object arg3,[HarmonyArgument(4)] object arg4) + { + return RpcPrefix(__originalMethod,arg0,arg1,arg2,arg3,arg4); + } + + public static bool Serialize(MethodBase __originalMethod, [HarmonyArgument(0)] object arg0 , [HarmonyArgument(1)] object arg1, [HarmonyArgument(2)] object arg2,[HarmonyArgument(3)] object arg3,[HarmonyArgument(4)] object arg4,[HarmonyArgument(5)] object arg5) + { + return RpcPrefix(__originalMethod,arg0,arg1,arg2,arg3,arg4,arg5); + } + + + } +} diff --git a/Reactor/Networking/MethodRpc/RpcSerializer.cs b/Reactor/Networking/MethodRpc/RpcSerializer.cs new file mode 100644 index 0000000..feabced --- /dev/null +++ b/Reactor/Networking/MethodRpc/RpcSerializer.cs @@ -0,0 +1,179 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using Hazel; +using InnerNet; +using Reactor; +using Reactor.Extensions; +using Reactor.Networking.MethodRpc; +using UnityEngine; + +namespace Reactor.Networking +{ + public static class RpcSerializer + { + public static void SendMassage(MessageWriter writer, params object[] list) + { + Logger.Debug($"Serializing {list.Length} object"); + + foreach (var o in list) + { + if (o.GetType().IsValueType && !o.GetType().IsPrimitive) + { + var structFeilds = o.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance); + foreach (var feild in structFeilds) + { + writer.Write(feild.GetValue(o)); + } + } + else + { + writer.Write(o); + } + } + } + + public static void Write(this MessageWriter writer, object o) + { + switch (o) + { + case int i: + writer.WritePacked(i); + Logger.Debug($"Serialized int"); + break; + case uint i: + writer.WritePacked(i); + Logger.Debug($"Serialized uint"); + break; + case byte i: + writer.Write(i); + Logger.Debug($"Serialized byte"); + break; + case float i: + writer.Write(i); + Logger.Debug($"Serialized float"); + break; + case sbyte i: + writer.Write(i); + Logger.Debug($"Serialized sbyte"); + break; + case ushort i: + writer.Write(i); + Logger.Debug($"Serialized ushort"); + break; + case bool i: + writer.Write(i); + Logger.Debug($"Serialized bool"); + break; + case Vector2 i: + writer.Write(i); + Logger.Debug($"Serialized Vector2"); + break; + case string i: + writer.Write(i); + Logger.Debug($"Serialized string"); + break; + default: + if (o.GetType().IsSubclassOf(typeof(InnerNetObject))) + { + Logger.Debug($"Serialized {nameof(InnerNetObject)}"); + writer.WritePacked(((InnerNetObject)o).NetId); + } + else + { + Logger.Warning($"Tried serializing unsupported type {nameof(o)}"); + } + + break; + } + } + + + public static object[] Deserialize(MessageReader reader, Type[] list) + { + Logger.Debug($"Deserializing {list.Length} object"); + + List args = new(); + + foreach (var t in list) + { + if (t.IsValueType && !t.IsPrimitive) + { + List structArgs = new(); + var feilds = t.GetFields(BindingFlags.Public | BindingFlags.Instance); + foreach (var feild in feilds) + { + structArgs.Add(reader.Read(feild.GetType())); + } + + args.Add(Activator.CreateInstance(t, structArgs)); + } + else + { + args.Add(reader.Read(t)); + } + } + + return args.ToArray(); + } + + + public static object Read(this MessageReader reader, Type t) + { + if (t == typeof(int) ) + { + return reader.ReadPackedInt32(); + } + + if (t == typeof(uint)) + { + return (reader.ReadPackedUInt32()); + } + + if (t == typeof(byte)) + { + return (reader.ReadByte()); + } + + if (t == typeof(float)) + { + return (reader.ReadSingle()); + } + + if (t == typeof(sbyte)) + { + return (reader.ReadSByte()); + } + + if (t == typeof(ushort)) + { + return (reader.ReadUInt16()); + } + + if (t == typeof(bool)) + { + return (reader.ReadBoolean()); + } + + if (t == typeof(Vector2)) + { + return (reader.ReadVector2()); + } + + if (t == typeof(string)) + { + return (reader.ReadString()); + } + + if (t.IsSubclassOf(typeof(InnerNetObject))) + { + return typeof(AmongUsClient).GetMethod(nameof(AmongUsClient.FindObjectByNetId)).MakeGenericMethod(t) + .Invoke(AmongUsClient.Instance, new object[]{reader.ReadPackedUInt32()}); + } + + Logger.Warning($"Tried deserializing unsupported type {t.BaseType.Name}"); + return null; + + } + } +} diff --git a/Reactor/ReactorPlugin.cs b/Reactor/ReactorPlugin.cs index 80be6ee..f70fc0a 100644 --- a/Reactor/ReactorPlugin.cs +++ b/Reactor/ReactorPlugin.cs @@ -6,6 +6,7 @@ using BepInEx.IL2CPP; using HarmonyLib; using Reactor.Extensions; using Reactor.Networking; +using Reactor.Networking.MethodRpc; using Reactor.Patches; using Reactor.Unstrip; using UnhollowerBaseLib.Attributes; @@ -37,6 +38,7 @@ namespace Reactor PluginSingleton.Initialize(); RegisterInIl2CppAttribute.Initialize(); RegisterCustomRpcAttribute.Initialize(); + MethodRpcAttribute.Initialize(); ChainloaderHooks.OnPluginLoad(this); } From 63fdde22c6325b67aec94fc4c908127b229e2bda Mon Sep 17 00:00:00 2001 From: Zirno Date: Mon, 5 Jul 2021 21:58:19 +0200 Subject: [PATCH 2/5] Add an Example --- Reactor.Example/CustomRpcs.cs | 3 ++- Reactor.Example/ExamplePlugin.cs | 12 ++++++++++++ Reactor/Networking/MethodRpc/MethodRpcAttribute.cs | 4 ++-- .../Networking/MethodRpc/RpcSerializationHandels.cs | 10 +++++++--- Reactor/Networking/MethodRpc/RpcSerializer.cs | 2 -- 5 files changed, 23 insertions(+), 8 deletions(-) diff --git a/Reactor.Example/CustomRpcs.cs b/Reactor.Example/CustomRpcs.cs index 5568438..975da54 100644 --- a/Reactor.Example/CustomRpcs.cs +++ b/Reactor.Example/CustomRpcs.cs @@ -2,6 +2,7 @@ namespace Reactor.Example { public enum CustomRpcCalls : uint { - Example + Example, + MethodRpcExample } } diff --git a/Reactor.Example/ExamplePlugin.cs b/Reactor.Example/ExamplePlugin.cs index 2ffa382..fe3a0ae 100644 --- a/Reactor.Example/ExamplePlugin.cs +++ b/Reactor.Example/ExamplePlugin.cs @@ -3,6 +3,7 @@ using BepInEx; using BepInEx.IL2CPP; using Reactor.Extensions; using Reactor.Networking; +using Reactor.Networking.MethodRpc; using UnhollowerBaseLib.Attributes; using UnityEngine; @@ -43,7 +44,18 @@ namespace Reactor.Example Rpc.Instance.SendTo(AmongUsClient.Instance.HostId, new ExampleRpc.Data($"SendTo: from {name} to host")); } } + + if (Input.GetKeyDown(KeyCode.F4) && AmongUsClient.Instance && PlayerControl.LocalPlayer) + { + RpcSay(PlayerControl.LocalPlayer, "Hello from method rpc"); + } } } + + [MethodRpc((uint) CustomRpcCalls.MethodRpcExample)] + public static void RpcSay(PlayerControl sender, string text) + { + Logger.Info($"{sender.Data.PlayerName} said: {text}"); + } } } diff --git a/Reactor/Networking/MethodRpc/MethodRpcAttribute.cs b/Reactor/Networking/MethodRpc/MethodRpcAttribute.cs index 17ed355..9b9f191 100644 --- a/Reactor/Networking/MethodRpc/MethodRpcAttribute.cs +++ b/Reactor/Networking/MethodRpc/MethodRpcAttribute.cs @@ -11,11 +11,11 @@ namespace Reactor.Networking.MethodRpc [AttributeUsage(AttributeTargets.Method)] public class MethodRpcAttribute : Attribute { - public byte id; + public uint id; public SendOption option; public RpcLocalHandling localHandling; - public MethodRpcAttribute( byte id, SendOption option = SendOption.Reliable, RpcLocalHandling localHandling = RpcLocalHandling.Before) + public MethodRpcAttribute( uint id, SendOption option = SendOption.Reliable, RpcLocalHandling localHandling = RpcLocalHandling.Before) { this.id = id; this.option = option; diff --git a/Reactor/Networking/MethodRpc/RpcSerializationHandels.cs b/Reactor/Networking/MethodRpc/RpcSerializationHandels.cs index a47b695..ba88146 100644 --- a/Reactor/Networking/MethodRpc/RpcSerializationHandels.cs +++ b/Reactor/Networking/MethodRpc/RpcSerializationHandels.cs @@ -28,9 +28,13 @@ namespace Reactor.Networking return true; } - CustomMethodRpc methodRpc = - CustomMethodRpc.AllCustomMethodRpcs.FirstOrDefault(x => x.Method == originalMethod); - typeof(CustomMethodRpc).GetMethod(nameof(CustomMethodRpc.Send))?.Invoke(methodRpc,new object[]{list}); + UnsafeCustomRpc methodRpc = + PluginSingleton.Instance.CustomRpcManager.List.FirstOrDefault(x => x is CustomMethodRpc && (x as CustomMethodRpc).Method == originalMethod); + if (methodRpc != null) + { + typeof(CustomMethodRpc).GetMethod(nameof(CustomMethodRpc.Send))?.Invoke((CustomMethodRpc)methodRpc, new object[] {list}); + } + return false; } diff --git a/Reactor/Networking/MethodRpc/RpcSerializer.cs b/Reactor/Networking/MethodRpc/RpcSerializer.cs index feabced..62b98f4 100644 --- a/Reactor/Networking/MethodRpc/RpcSerializer.cs +++ b/Reactor/Networking/MethodRpc/RpcSerializer.cs @@ -3,9 +3,7 @@ using System.Collections.Generic; using System.Reflection; using Hazel; using InnerNet; -using Reactor; using Reactor.Extensions; -using Reactor.Networking.MethodRpc; using UnityEngine; namespace Reactor.Networking From c6d2ff686b03a6f6f8d87d8c16e5be7ba697e45e Mon Sep 17 00:00:00 2001 From: Zirno Date: Thu, 8 Jul 2021 23:11:03 +0200 Subject: [PATCH 3/5] Some Optimizations --- .../Networking/MethodRpc/CustomMethodRpc.cs | 52 ++++++-- .../MethodRpc/MethodRpcAttribute.cs | 51 ++------ .../Networking/MethodRpc/RpcPrefixHandle.cs | 122 ++++++++++++++++++ .../MethodRpc/RpcSerializationHandels.cs | 77 ----------- Reactor/Networking/MethodRpc/RpcSerializer.cs | 55 +++++--- 5 files changed, 215 insertions(+), 142 deletions(-) create mode 100644 Reactor/Networking/MethodRpc/RpcPrefixHandle.cs delete mode 100644 Reactor/Networking/MethodRpc/RpcSerializationHandels.cs diff --git a/Reactor/Networking/MethodRpc/CustomMethodRpc.cs b/Reactor/Networking/MethodRpc/CustomMethodRpc.cs index c95e3fe..12d8dd2 100644 --- a/Reactor/Networking/MethodRpc/CustomMethodRpc.cs +++ b/Reactor/Networking/MethodRpc/CustomMethodRpc.cs @@ -11,42 +11,68 @@ namespace Reactor.Networking.MethodRpc { public class CustomMethodRpc : UnsafeCustomRpc { - public static readonly List AllCustomMethodRpcs = new(); - internal static bool SkipNextSend; - - public CustomMethodRpc(BasePlugin plugin, MethodInfo method, uint id, SendOption option, RpcLocalHandling localHandling) : base(plugin, id) + internal static Dictionary allMethodRPCsFast = new(); + + private Func Invoker; + public Type[] Parameters { get; } + public Dictionary StructBook { get; } = new(); + + public CustomMethodRpc(BasePlugin plugin, MethodInfo method, uint id, SendOption option, + RpcLocalHandling localHandling) : base(plugin, id) { Method = method; LocalHandling = localHandling; SendOption = option; - - AllCustomMethodRpcs.Add(this); + + var methodParameters = method.GetParameters(); + + List allArgs = new List(); + foreach (var arg in methodParameters) + { + var argType = arg.ParameterType; + if (argType.IsValueType && !argType.IsPrimitive && !StructBook.ContainsKey(arg.ParameterType)) + { + var feilds = argType.GetFields(BindingFlags.Instance | BindingFlags.Public) + .ToArray(); + StructBook.Add(argType, feilds); + } + + allArgs.Add(argType); + + } + + Parameters = allArgs.ToArray(); + + Invoker = RpcPrefixHandle.GenerateCaller(Method); + + allMethodRPCsFast.Add(Method, this); + } public MethodInfo Method { get; } public override Type InnerNetObjectType { get; } = typeof(PlayerControl); - public override RpcLocalHandling LocalHandling { get; } + public override RpcLocalHandling LocalHandling { get; } = RpcLocalHandling.None; public override SendOption SendOption { get; } public override void UnsafeWrite(MessageWriter writer, object data) { var args = (object[]) data; - RpcSerializer.SendMassage(writer,args); + RpcSerializer.Serialize(writer, this, args); } public override object UnsafeRead(MessageReader reader) { - var args = RpcSerializer.Deserialize(reader,Method.GetParameters().Select(x => x.ParameterType).ToArray()); + var args = RpcSerializer.Deserialize(reader, this); return args; } public override void UnsafeHandle(InnerNetObject innerNetObject, object data) { SkipNextSend = true; - - var result = Method.Invoke(null,(object[]) data); - if (Method.ReturnParameter.ParameterType == typeof(IEnumerator)) + + var result = Invoker((object[]) data); + if (result != null) { Coroutines.Start((IEnumerator) result); } @@ -54,7 +80,7 @@ namespace Reactor.Networking.MethodRpc public void Send(object[] args) { - UnsafeSend(PlayerControl.LocalPlayer,args); + UnsafeSend(PlayerControl.LocalPlayer, args); } } } diff --git a/Reactor/Networking/MethodRpc/MethodRpcAttribute.cs b/Reactor/Networking/MethodRpc/MethodRpcAttribute.cs index 9b9f191..063ab10 100644 --- a/Reactor/Networking/MethodRpc/MethodRpcAttribute.cs +++ b/Reactor/Networking/MethodRpc/MethodRpcAttribute.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Reflection; using BepInEx.IL2CPP; @@ -11,6 +10,10 @@ namespace Reactor.Networking.MethodRpc [AttributeUsage(AttributeTargets.Method)] public class MethodRpcAttribute : Attribute { + private static readonly MethodInfo _rpcPrefixGenerator = typeof(RpcPrefixHandle).GetMethod( + nameof(RpcPrefixHandle.GeneratePrefix), + BindingFlags.Static | BindingFlags.Public); + public uint id; public SendOption option; public RpcLocalHandling localHandling; @@ -21,53 +24,31 @@ namespace Reactor.Networking.MethodRpc this.option = option; this.localHandling = localHandling; } - + public static void Register(Assembly assembly, BasePlugin plugin) { Logger.Info($"Registering Method Rpc for {assembly.GetName()}"); - Dictionary serializeMethods = new(); - MethodInfo serializeMethod; - int index = 0; - - while ((serializeMethod = RpcSerializationHandels.GetHandleFor(index)) != null) - { - serializeMethods.Add(index, serializeMethod); - Logger.Debug($"Registered Rpc Serialization Handle for {index} args"); - index++; - } - var rpcMethods = assembly.GetTypes() .SelectMany(t => t.GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public)) .Where(m => m.GetCustomAttributes(typeof(MethodRpcAttribute), false).Length > 0) .ToArray(); - - + + foreach (var method in rpcMethods) { + if (!method.IsStatic) { Logger.Warning("Cannot register non static custom method rpc"); continue; } - var seializer = serializeMethods[method.GetParameters().Length]; - if (seializer == null) - { - Logger.Error($"Unsupported Args count, max is {RpcSerializationHandels.GetMaxArgs()}"); - continue; - } - var attribute = method.GetCustomAttribute(); - if (attribute.localHandling == RpcLocalHandling.Before) - { - PluginSingleton.Instance.Harmony.Patch(method, new HarmonyMethod(seializer)); - } - else if (attribute.localHandling == RpcLocalHandling.After) - { - PluginSingleton.Instance.Harmony.Patch(method, postfix: new HarmonyMethod(seializer)); - } - var customRpc = new CustomMethodRpc(plugin, method, attribute.id, attribute.option, attribute.localHandling); + + var customRpc = new CustomMethodRpc(plugin, method, attribute.id, attribute.option, + attribute.localHandling); + PluginSingleton.Instance.Harmony.Patch(method, new HarmonyMethod(_rpcPrefixGenerator)); try { PluginSingleton.Instance.CustomRpcManager.Register(customRpc); @@ -78,16 +59,12 @@ namespace Reactor.Networking.MethodRpc } } } + + internal static void Initialize() { ChainloaderHooks.PluginLoad += plugin => Register(plugin.GetType().Assembly, plugin); } - - [MethodRpc(123)] - static void Hello() - { - - } } } diff --git a/Reactor/Networking/MethodRpc/RpcPrefixHandle.cs b/Reactor/Networking/MethodRpc/RpcPrefixHandle.cs new file mode 100644 index 0000000..718886e --- /dev/null +++ b/Reactor/Networking/MethodRpc/RpcPrefixHandle.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Reflection.Emit; + +namespace Reactor.Networking.MethodRpc +{ + internal class RpcPrefixHandle + { + private static readonly MethodInfo _rpcPrefix = typeof(RpcPrefixHandle).GetMethod(nameof(RpcPrefix)); + private static readonly MethodInfo _emptyArray = typeof(Array).GetMethod(nameof(Array.Empty))?.MakeGenericMethod(typeof(object)); + + public static bool RpcPrefix(MethodBase originalMethod, params object[] list) + { + if (CustomMethodRpc.SkipNextSend) + { + CustomMethodRpc.SkipNextSend = false; + return true; + } + + var methodRpc = CustomMethodRpc.allMethodRPCsFast[originalMethod]; + methodRpc?.Send(list); + + return false; + } + + public static DynamicMethod GeneratePrefix(MethodBase __originalMethod) + { + Logger.Debug($"Generating Prefix for {__originalMethod.Name}"); + + var originalPrm = __originalMethod.GetParameters(); + + var prmType = originalPrm.Select(x => x.ParameterType).ToList(); + prmType.Insert(0, typeof(MethodBase)); + + DynamicMethod m = new DynamicMethod($"{__originalMethod.Name}Prefix", MethodAttributes.Static, + CallingConventions.Standard, typeof(bool), prmType.ToArray(), typeof(RpcPrefixHandle), false); + + m.DefineParameter(1, ParameterAttributes.None, "__originalMethod"); + for (var i = 2; i < originalPrm.Length + 2; i++) //0: Return Parameter 1:MethodBase Parameter + { + m.DefineParameter(i, ParameterAttributes.None, originalPrm[i - 2].Name); + } + + #region il_code + + var il = m.GetILGenerator(); + + il.Emit(OpCodes.Ldarg_0); + if (prmType.Count == 1) + { + il.Emit(OpCodes.Call,_emptyArray); + } + else + { + il.Emit(OpCodes.Ldc_I4, prmType.Count - 1); + il.Emit(OpCodes.Newarr, typeof(object)); + } + + for (var i = 1; i < prmType.Count; i++) AppendArg(i, prmType[i]); + + void AppendArg(int index, Type type) + { + il.Emit(OpCodes.Dup); + il.Emit(OpCodes.Ldc_I4, index - 1); + il.Emit(OpCodes.Ldarg_S, index); + + if (type.IsValueType) + { + il.Emit(OpCodes.Box, type); + } + + il.Emit(OpCodes.Stelem_Ref); + } + + il.Emit(OpCodes.Call, _rpcPrefix); + il.Emit(OpCodes.Ret); + + #endregion + + return m; + } + + + public static Func GenerateCaller(MethodInfo method) + { + var methodPrm = method.GetParameters(); + + var args = Expression.Parameter(typeof(object[]), "Args"); + + List elements = new(); + for (int i = 0; i < methodPrm.Length; i++) + { + var element = Expression.ArrayAccess(args, Expression.Constant(i, typeof(int))); + var convert = Expression.Convert(element, methodPrm[i].ParameterType); + elements.Add(convert); + } + + + Expression invoke = Expression.Call(method, elements); + + var returnTarget = Expression.Label(typeof(object)); + + if (method.ReturnParameter.ParameterType == typeof(void)) + { + invoke = Expression.Block(invoke, + Expression.Label(returnTarget, Expression.Default(typeof(object))) + ); + } + else + { + invoke = Expression.Label(returnTarget, invoke); + } + + var caller = Expression.Lambda>(invoke, args); + Logger.Debug($"Generated caller for {method.Name} -> {caller}"); + return caller.Compile(); + } + } +} diff --git a/Reactor/Networking/MethodRpc/RpcSerializationHandels.cs b/Reactor/Networking/MethodRpc/RpcSerializationHandels.cs deleted file mode 100644 index ba88146..0000000 --- a/Reactor/Networking/MethodRpc/RpcSerializationHandels.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System.Linq; -using System.Reflection; -using HarmonyLib; -using Reactor.Networking; -using Reactor.Networking.MethodRpc; - -namespace Reactor.Networking -{ - internal class RpcSerializationHandels - { - - public static MethodInfo GetHandleFor(int count) - { - return typeof(RpcSerializationHandels).GetRuntimeMethod(nameof(Serialize), - Enumerable.Repeat(typeof(MethodBase),1).Concat(Enumerable.Repeat(typeof(object), count)).ToArray()); - } - - public static int GetMaxArgs() - { - return typeof(RpcSerializationHandels).GetMethods().OrderBy(x => x.GetParameters().Length).Last().GetParameters().Length; - } - - public static bool RpcPrefix(MethodBase originalMethod, params object[] list) - { - if (CustomMethodRpc.SkipNextSend) - { - CustomMethodRpc.SkipNextSend = false; - return true; - } - - UnsafeCustomRpc methodRpc = - PluginSingleton.Instance.CustomRpcManager.List.FirstOrDefault(x => x is CustomMethodRpc && (x as CustomMethodRpc).Method == originalMethod); - if (methodRpc != null) - { - typeof(CustomMethodRpc).GetMethod(nameof(CustomMethodRpc.Send))?.Invoke((CustomMethodRpc)methodRpc, new object[] {list}); - } - - return false; - } - - public static bool Serialize(MethodBase __originalMethod) - { - return RpcPrefix(__originalMethod); - } - - public static bool Serialize(MethodBase __originalMethod, [HarmonyArgument(0)] object arg0) - { - return RpcPrefix(__originalMethod,arg0); - } - - public static bool Serialize(MethodBase __originalMethod, [HarmonyArgument(0)] object arg0,[HarmonyArgument(1)] object arg1) - { - return RpcPrefix(__originalMethod,new object[]{arg0,arg1}); - } - public static bool Serialize(MethodBase __originalMethod, [HarmonyArgument(0)] object arg0 , [HarmonyArgument(1)] object arg1, [HarmonyArgument(2)] object arg2) - { - return RpcPrefix(__originalMethod,arg0,arg1,arg2); - } - - public static bool Serialize(MethodBase __originalMethod, [HarmonyArgument(0)] object arg0 , [HarmonyArgument(1)] object arg1, [HarmonyArgument(2)] object arg2,[HarmonyArgument(3)] object arg3) - { - return RpcPrefix(__originalMethod,arg0,arg1,arg2,arg3); - } - - public static bool Serialize(MethodBase __originalMethod, [HarmonyArgument(0)] object arg0 , [HarmonyArgument(1)] object arg1, [HarmonyArgument(2)] object arg2,[HarmonyArgument(3)] object arg3,[HarmonyArgument(4)] object arg4) - { - return RpcPrefix(__originalMethod,arg0,arg1,arg2,arg3,arg4); - } - - public static bool Serialize(MethodBase __originalMethod, [HarmonyArgument(0)] object arg0 , [HarmonyArgument(1)] object arg1, [HarmonyArgument(2)] object arg2,[HarmonyArgument(3)] object arg3,[HarmonyArgument(4)] object arg4,[HarmonyArgument(5)] object arg5) - { - return RpcPrefix(__originalMethod,arg0,arg1,arg2,arg3,arg4,arg5); - } - - - } -} diff --git a/Reactor/Networking/MethodRpc/RpcSerializer.cs b/Reactor/Networking/MethodRpc/RpcSerializer.cs index 62b98f4..bcf1c30 100644 --- a/Reactor/Networking/MethodRpc/RpcSerializer.cs +++ b/Reactor/Networking/MethodRpc/RpcSerializer.cs @@ -6,19 +6,40 @@ using InnerNet; using Reactor.Extensions; using UnityEngine; -namespace Reactor.Networking +namespace Reactor.Networking.MethodRpc { public static class RpcSerializer { - public static void SendMassage(MessageWriter writer, params object[] list) - { - Logger.Debug($"Serializing {list.Length} object"); + #region GetFindObjectByNetIdInvoker + private static readonly MethodInfo _findObjectByNetId = + typeof(RpcSerializer).GetMethod(nameof(FindObjectByNetId), BindingFlags.Static | BindingFlags.NonPublic); + + private static Dictionary> _findObjectByNetIdInvokers = new(); + static Func GetFindObjectByNetIdInvoker(Type type) + { + if (_findObjectByNetIdInvokers.ContainsKey(type)) return _findObjectByNetIdInvokers[type]; + + var invoker = RpcPrefixHandle.GenerateCaller(_findObjectByNetId.MakeGenericMethod(type)); + _findObjectByNetIdInvokers.Add(type, invoker); + return invoker; + } + + private static InnerNetObject FindObjectByNetId(uint id) where T : InnerNetObject + { + return AmongUsClient.Instance.FindObjectByNetId(id); + } + + #endregion + + + public static void Serialize(MessageWriter writer,CustomMethodRpc methodRpc, params object[] list) + { foreach (var o in list) { if (o.GetType().IsValueType && !o.GetType().IsPrimitive) { - var structFeilds = o.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance); + var structFeilds = methodRpc.StructBook[o.GetType()]; foreach (var feild in structFeilds) { writer.Write(feild.GetValue(o)); @@ -87,28 +108,33 @@ namespace Reactor.Networking } - public static object[] Deserialize(MessageReader reader, Type[] list) + public static object[] Deserialize(MessageReader reader,CustomMethodRpc methodRpc) { - Logger.Debug($"Deserializing {list.Length} object"); + Logger.Debug($"Deserializing {methodRpc.Parameters.Length} object"); List args = new(); - foreach (var t in list) + foreach (var t in methodRpc.Parameters) { if (t.IsValueType && !t.IsPrimitive) { List structArgs = new(); - var feilds = t.GetFields(BindingFlags.Public | BindingFlags.Instance); + var feilds = methodRpc.StructBook[t]; foreach (var feild in feilds) { - structArgs.Add(reader.Read(feild.GetType())); + var value = reader.Read(feild.FieldType); + Logger.Debug($"Read {value} of {feild.FieldType.Name} for {methodRpc.Method.Name}"); + structArgs.Add(value); } - - args.Add(Activator.CreateInstance(t, structArgs)); + + args.Add(Activator.CreateInstance(t,args: structArgs.ToArray())); //Todo: Improve somehow } else { - args.Add(reader.Read(t)); + var value = reader.Read(t); + Logger.Debug($"Read {value} of {t.Name} for {methodRpc.Method.Name}"); + + args.Add(value); } } @@ -165,8 +191,7 @@ namespace Reactor.Networking if (t.IsSubclassOf(typeof(InnerNetObject))) { - return typeof(AmongUsClient).GetMethod(nameof(AmongUsClient.FindObjectByNetId)).MakeGenericMethod(t) - .Invoke(AmongUsClient.Instance, new object[]{reader.ReadPackedUInt32()}); + return GetFindObjectByNetIdInvoker(t)(new object[]{reader.ReadPackedUInt32()}); } Logger.Warning($"Tried deserializing unsupported type {t.BaseType.Name}"); From 41413eea20da5657f9a1f8edc6d89677b38afbb8 Mon Sep 17 00:00:00 2001 From: Zirno Date: Fri, 9 Jul 2021 19:23:31 +0200 Subject: [PATCH 4/5] Add ReaderWriter for custom types --- .../Networking/MethodRpc/CustomMethodRpc.cs | 19 ++- .../MethodRpc/MethodRpcAttribute.cs | 7 +- Reactor/Networking/MethodRpc/ReaderWriter.cs | 45 ++++++++ .../MethodRpc/ReaderWriterAttribute.cs | 36 ++++++ .../Networking/MethodRpc/RpcPrefixHandle.cs | 6 +- Reactor/Networking/MethodRpc/RpcSerializer.cs | 108 +++++++++--------- Reactor/ReactorPlugin.cs | 1 + 7 files changed, 152 insertions(+), 70 deletions(-) create mode 100644 Reactor/Networking/MethodRpc/ReaderWriter.cs create mode 100644 Reactor/Networking/MethodRpc/ReaderWriterAttribute.cs diff --git a/Reactor/Networking/MethodRpc/CustomMethodRpc.cs b/Reactor/Networking/MethodRpc/CustomMethodRpc.cs index 12d8dd2..1140634 100644 --- a/Reactor/Networking/MethodRpc/CustomMethodRpc.cs +++ b/Reactor/Networking/MethodRpc/CustomMethodRpc.cs @@ -27,32 +27,29 @@ namespace Reactor.Networking.MethodRpc var methodParameters = method.GetParameters(); - List allArgs = new List(); - foreach (var arg in methodParameters) + Parameters = new Type[methodParameters.Length]; + for (var i = 0; i < methodParameters.Length; i++) { + var arg = methodParameters[i]; var argType = arg.ParameterType; if (argType.IsValueType && !argType.IsPrimitive && !StructBook.ContainsKey(arg.ParameterType)) { - var feilds = argType.GetFields(BindingFlags.Instance | BindingFlags.Public) + var fields = argType.GetFields(BindingFlags.Instance | BindingFlags.Public) .ToArray(); - StructBook.Add(argType, feilds); + StructBook.Add(argType, fields); } - allArgs.Add(argType); - + Parameters[i] = argType; } - Parameters = allArgs.ToArray(); - Invoker = RpcPrefixHandle.GenerateCaller(Method); allMethodRPCsFast.Add(Method, this); - } public MethodInfo Method { get; } public override Type InnerNetObjectType { get; } = typeof(PlayerControl); - public override RpcLocalHandling LocalHandling { get; } = RpcLocalHandling.None; + public override RpcLocalHandling LocalHandling { get; } public override SendOption SendOption { get; } public override void UnsafeWrite(MessageWriter writer, object data) @@ -76,6 +73,8 @@ namespace Reactor.Networking.MethodRpc { Coroutines.Start((IEnumerator) result); } + + SkipNextSend = false; } public void Send(object[] args) diff --git a/Reactor/Networking/MethodRpc/MethodRpcAttribute.cs b/Reactor/Networking/MethodRpc/MethodRpcAttribute.cs index 063ab10..78081b3 100644 --- a/Reactor/Networking/MethodRpc/MethodRpcAttribute.cs +++ b/Reactor/Networking/MethodRpc/MethodRpcAttribute.cs @@ -37,7 +37,7 @@ namespace Reactor.Networking.MethodRpc foreach (var method in rpcMethods) { - + if (!method.IsStatic) { Logger.Warning("Cannot register non static custom method rpc"); @@ -45,7 +45,7 @@ namespace Reactor.Networking.MethodRpc } var attribute = method.GetCustomAttribute(); - + var customRpc = new CustomMethodRpc(plugin, method, attribute.id, attribute.option, attribute.localHandling); PluginSingleton.Instance.Harmony.Patch(method, new HarmonyMethod(_rpcPrefixGenerator)); @@ -58,9 +58,8 @@ namespace Reactor.Networking.MethodRpc // ignored } } - } - + } internal static void Initialize() { diff --git a/Reactor/Networking/MethodRpc/ReaderWriter.cs b/Reactor/Networking/MethodRpc/ReaderWriter.cs new file mode 100644 index 0000000..9b60115 --- /dev/null +++ b/Reactor/Networking/MethodRpc/ReaderWriter.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using Hazel; + +namespace Reactor.Networking.MethodRpc +{ + + internal class ReaderWriterManager + { + public static ReaderWriterManager Instance { get; } = new(); + + private readonly Dictionary _readerWriters = new(); + + public IReadOnlyDictionary All => _readerWriters; + + public void Register(UnsafeReaderWriter readerWriter) + { + Logger.Instance.LogInfo($"Registered ReaderWriter for {readerWriter.Type}"); + if(_readerWriters.ContainsKey(readerWriter.Type)) return; + + _readerWriters.Add(readerWriter.Type, readerWriter); + } + } + + public abstract class UnsafeReaderWriter + { + public abstract Type Type { get; } + public abstract void UnsafeWrite(MessageWriter writer, object dateTime); + + public abstract object UnsafeRead(MessageReader reader); + } + + public abstract class ReaderWriter : UnsafeReaderWriter + { + public abstract void Write(MessageWriter writer, T o); + + public abstract T Read(MessageReader reader); + + public override Type Type { get; } = typeof(T); + + public override void UnsafeWrite(MessageWriter writer, object o) => Write(writer, (T) o); + + public override object UnsafeRead(MessageReader reader) => Read(reader); + } +} diff --git a/Reactor/Networking/MethodRpc/ReaderWriterAttribute.cs b/Reactor/Networking/MethodRpc/ReaderWriterAttribute.cs new file mode 100644 index 0000000..3c62ad1 --- /dev/null +++ b/Reactor/Networking/MethodRpc/ReaderWriterAttribute.cs @@ -0,0 +1,36 @@ +using System; +using System.Reflection; +using BepInEx.IL2CPP; +using HarmonyLib; + +namespace Reactor.Networking.MethodRpc +{ + [AttributeUsage(AttributeTargets.Class)] + public class ReaderWriterAttribute : System.Attribute + { + + public static void Register(Assembly assembly, BasePlugin plugin) + { + foreach (var type in assembly.GetTypes()) + { + var attribute = type.GetCustomAttribute(); + + if (attribute != null) + { + if (!type.IsSubclassOf(typeof(UnsafeReaderWriter))) + { + throw new InvalidOperationException($"Type {type.FullDescription()} has {nameof(ReaderWriterAttribute)} but doesn't extend {nameof(UnsafeReaderWriter)}."); + } + + var customRpc = (UnsafeReaderWriter) Activator.CreateInstance(type); + ReaderWriterManager.Instance.Register(customRpc); + } + } + } + + internal static void Initialize() + { + ChainloaderHooks.PluginLoad += plugin => Register(plugin.GetType().Assembly, plugin); + } + } +} diff --git a/Reactor/Networking/MethodRpc/RpcPrefixHandle.cs b/Reactor/Networking/MethodRpc/RpcPrefixHandle.cs index 718886e..1c959a3 100644 --- a/Reactor/Networking/MethodRpc/RpcPrefixHandle.cs +++ b/Reactor/Networking/MethodRpc/RpcPrefixHandle.cs @@ -14,11 +14,7 @@ namespace Reactor.Networking.MethodRpc public static bool RpcPrefix(MethodBase originalMethod, params object[] list) { - if (CustomMethodRpc.SkipNextSend) - { - CustomMethodRpc.SkipNextSend = false; - return true; - } + if (CustomMethodRpc.SkipNextSend) return true; var methodRpc = CustomMethodRpc.allMethodRPCsFast[originalMethod]; methodRpc?.Send(list); diff --git a/Reactor/Networking/MethodRpc/RpcSerializer.cs b/Reactor/Networking/MethodRpc/RpcSerializer.cs index bcf1c30..0c7d3b5 100644 --- a/Reactor/Networking/MethodRpc/RpcSerializer.cs +++ b/Reactor/Networking/MethodRpc/RpcSerializer.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Reflection; using Hazel; using InnerNet; @@ -10,29 +11,6 @@ namespace Reactor.Networking.MethodRpc { public static class RpcSerializer { - #region GetFindObjectByNetIdInvoker - - private static readonly MethodInfo _findObjectByNetId = - typeof(RpcSerializer).GetMethod(nameof(FindObjectByNetId), BindingFlags.Static | BindingFlags.NonPublic); - - private static Dictionary> _findObjectByNetIdInvokers = new(); - static Func GetFindObjectByNetIdInvoker(Type type) - { - if (_findObjectByNetIdInvokers.ContainsKey(type)) return _findObjectByNetIdInvokers[type]; - - var invoker = RpcPrefixHandle.GenerateCaller(_findObjectByNetId.MakeGenericMethod(type)); - _findObjectByNetIdInvokers.Add(type, invoker); - return invoker; - } - - private static InnerNetObject FindObjectByNetId(uint id) where T : InnerNetObject - { - return AmongUsClient.Instance.FindObjectByNetId(id); - } - - #endregion - - public static void Serialize(MessageWriter writer,CustomMethodRpc methodRpc, params object[] list) { foreach (var o in list) @@ -49,6 +27,7 @@ namespace Reactor.Networking.MethodRpc { writer.Write(o); } + Logger.Debug($"Serialized {o.GetType()}"); } } @@ -58,51 +37,44 @@ namespace Reactor.Networking.MethodRpc { case int i: writer.WritePacked(i); - Logger.Debug($"Serialized int"); break; case uint i: writer.WritePacked(i); - Logger.Debug($"Serialized uint"); break; case byte i: writer.Write(i); - Logger.Debug($"Serialized byte"); break; case float i: writer.Write(i); - Logger.Debug($"Serialized float"); break; case sbyte i: writer.Write(i); - Logger.Debug($"Serialized sbyte"); break; case ushort i: writer.Write(i); - Logger.Debug($"Serialized ushort"); break; case bool i: writer.Write(i); - Logger.Debug($"Serialized bool"); break; case Vector2 i: writer.Write(i); - Logger.Debug($"Serialized Vector2"); break; case string i: writer.Write(i); - Logger.Debug($"Serialized string"); break; default: if (o.GetType().IsSubclassOf(typeof(InnerNetObject))) { - Logger.Debug($"Serialized {nameof(InnerNetObject)}"); - writer.WritePacked(((InnerNetObject)o).NetId); + writer.WriteNetObject((InnerNetObject) o); + } + else if (ReaderWriterManager.Instance.All.ContainsKey(o.GetType())) + { + ReaderWriterManager.Instance.All[o.GetType()].UnsafeWrite(writer, o); } else { - Logger.Warning($"Tried serializing unsupported type {nameof(o)}"); + Logger.Warning($"Tried serializing unsupported type {o.GetType()}"); } - break; } } @@ -112,33 +84,34 @@ namespace Reactor.Networking.MethodRpc { Logger.Debug($"Deserializing {methodRpc.Parameters.Length} object"); - List args = new(); + object[] args = new object[methodRpc.Parameters.Length]; - foreach (var t in methodRpc.Parameters) + for (int i = 0; i < args.Length; i++) { + Type t = methodRpc.Parameters[i]; if (t.IsValueType && !t.IsPrimitive) { - List structArgs = new(); - var feilds = methodRpc.StructBook[t]; - foreach (var feild in feilds) - { - var value = reader.Read(feild.FieldType); - Logger.Debug($"Read {value} of {feild.FieldType.Name} for {methodRpc.Method.Name}"); - structArgs.Add(value); - } + var fields = methodRpc.StructBook[t]; - args.Add(Activator.CreateInstance(t,args: structArgs.ToArray())); //Todo: Improve somehow + object[] structArgs = new object[fields.Length]; + for (int n = 0; n < structArgs.Length; n++) + { + structArgs[n] = reader.Read(fields[n].FieldType); + Logger.Debug($"Deserialized {fields[n].FieldType} from {t}"); + } + t.GetType().GetConstructors().ToList().ForEach(x=>Logger.Info($"Gw {x.GetParameters().Length}")); + + args[i] = Activator.CreateInstance(t,structArgs); } else { var value = reader.Read(t); Logger.Debug($"Read {value} of {t.Name} for {methodRpc.Method.Name}"); - - args.Add(value); + args[i] = value; } } - return args.ToArray(); + return args; } @@ -191,12 +164,45 @@ namespace Reactor.Networking.MethodRpc if (t.IsSubclassOf(typeof(InnerNetObject))) { - return GetFindObjectByNetIdInvoker(t)(new object[]{reader.ReadPackedUInt32()}); + return reader.ReadNetObject(t); + } + + if (ReaderWriterManager.Instance.All.ContainsKey(t.BaseType!)) + { + return ReaderWriterManager.Instance.All[t.BaseType].UnsafeRead(reader); } Logger.Warning($"Tried deserializing unsupported type {t.BaseType.Name}"); return null; } + + #region InnerNetObject + + private static readonly MethodInfo _findObjectByNetId = + typeof(RpcSerializer).GetMethod(nameof(FindObjectByNetId), BindingFlags.Static | BindingFlags.NonPublic); + + private static Dictionary> _findObjectByNetIdInvokers = new(); + static Func GetFindObjectByNetIdInvoker(Type type) + { + if (_findObjectByNetIdInvokers.ContainsKey(type)) return _findObjectByNetIdInvokers[type]; + + var invoker = RpcPrefixHandle.GenerateCaller(_findObjectByNetId.MakeGenericMethod(type)); + _findObjectByNetIdInvokers.Add(type, invoker); + return invoker; + } + + private static InnerNetObject FindObjectByNetId(uint id) where T : InnerNetObject + { + return AmongUsClient.Instance.FindObjectByNetId(id); + } + + public static void WriteNetObject(this MessageWriter writer, InnerNetObject o) => writer.WritePacked(o.NetId); + + public static InnerNetObject ReadNetObject(this MessageReader reader, Type t) => + (InnerNetObject) GetFindObjectByNetIdInvoker(t)(new object[] {reader.ReadPackedUInt32()}); + + #endregion + } } diff --git a/Reactor/ReactorPlugin.cs b/Reactor/ReactorPlugin.cs index f70fc0a..eb83a9e 100644 --- a/Reactor/ReactorPlugin.cs +++ b/Reactor/ReactorPlugin.cs @@ -39,6 +39,7 @@ namespace Reactor RegisterInIl2CppAttribute.Initialize(); RegisterCustomRpcAttribute.Initialize(); MethodRpcAttribute.Initialize(); + ReaderWriterAttribute.Initialize(); ChainloaderHooks.OnPluginLoad(this); } From 7d02cb9616fb93ee65afd43bd567869a039adab9 Mon Sep 17 00:00:00 2001 From: js6pak Date: Tue, 27 Jul 2021 16:18:11 +0200 Subject: [PATCH 5/5] IL gen refactor and serialization changes --- Reactor.Example/ExamplePlugin.cs | 30 +-- Reactor.Example/Reactor.Example.csproj | 1 + .../Networking/MethodRpc/CustomMethodRpc.cs | 85 ------- Reactor/Networking/MethodRpc/MethodRpc.cs | 209 ++++++++++++++++++ .../MethodRpc/MethodRpcAttribute.cs | 43 ++-- Reactor/Networking/MethodRpc/ReaderWriter.cs | 45 ---- .../MethodRpc/ReaderWriterAttribute.cs | 36 --- .../Networking/MethodRpc/RpcPrefixHandle.cs | 118 ---------- Reactor/Networking/MethodRpc/RpcSerializer.cs | 208 ----------------- .../Serialization/InnerNetObjectConverter.cs | 39 ++++ .../Serialization/MessageConverter.cs | 29 +++ .../MessageConverterAttribute.cs | 41 ++++ .../Serialization/MessageSerializer.cs | 156 +++++++++++++ Reactor/Networking/UnsafeCustomRpc.cs | 12 +- Reactor/ReactorPlugin.cs | 3 +- 15 files changed, 513 insertions(+), 542 deletions(-) delete mode 100644 Reactor/Networking/MethodRpc/CustomMethodRpc.cs create mode 100644 Reactor/Networking/MethodRpc/MethodRpc.cs delete mode 100644 Reactor/Networking/MethodRpc/ReaderWriter.cs delete mode 100644 Reactor/Networking/MethodRpc/ReaderWriterAttribute.cs delete mode 100644 Reactor/Networking/MethodRpc/RpcPrefixHandle.cs delete mode 100644 Reactor/Networking/MethodRpc/RpcSerializer.cs create mode 100644 Reactor/Networking/Serialization/InnerNetObjectConverter.cs create mode 100644 Reactor/Networking/Serialization/MessageConverter.cs create mode 100644 Reactor/Networking/Serialization/MessageConverterAttribute.cs create mode 100644 Reactor/Networking/Serialization/MessageSerializer.cs diff --git a/Reactor.Example/ExamplePlugin.cs b/Reactor.Example/ExamplePlugin.cs index fe3a0ae..6b4a945 100644 --- a/Reactor.Example/ExamplePlugin.cs +++ b/Reactor.Example/ExamplePlugin.cs @@ -6,6 +6,7 @@ using Reactor.Networking; using Reactor.Networking.MethodRpc; using UnhollowerBaseLib.Attributes; using UnityEngine; +using Random = UnityEngine.Random; namespace Reactor.Example { @@ -34,28 +35,31 @@ namespace Reactor.Example private void Update() { - if (Input.GetKeyDown(KeyCode.F3) && AmongUsClient.Instance && PlayerControl.LocalPlayer) + if (AmongUsClient.Instance && PlayerControl.LocalPlayer) { - var name = PlayerControl.LocalPlayer.Data.PlayerName; - Rpc.Instance.Send(new ExampleRpc.Data($"Send: from {name}")); - - if (!AmongUsClient.Instance.AmHost) + if (Input.GetKeyDown(KeyCode.F3)) { - Rpc.Instance.SendTo(AmongUsClient.Instance.HostId, new ExampleRpc.Data($"SendTo: from {name} to host")); + var name = PlayerControl.LocalPlayer.Data.PlayerName; + Rpc.Instance.Send(new ExampleRpc.Data($"Send: from {name}")); + + if (!AmongUsClient.Instance.AmHost) + { + Rpc.Instance.SendTo(AmongUsClient.Instance.HostId, new ExampleRpc.Data($"SendTo: from {name} to host")); + } + } + + if (Input.GetKeyDown(KeyCode.F4)) + { + RpcSay(PlayerControl.LocalPlayer, "Hello from method rpc", Random.value, PlayerControl.LocalPlayer); } - } - - if (Input.GetKeyDown(KeyCode.F4) && AmongUsClient.Instance && PlayerControl.LocalPlayer) - { - RpcSay(PlayerControl.LocalPlayer, "Hello from method rpc"); } } } [MethodRpc((uint) CustomRpcCalls.MethodRpcExample)] - public static void RpcSay(PlayerControl sender, string text) + public static void RpcSay(PlayerControl player, string text, float number, PlayerControl testPlayer) { - Logger.Info($"{sender.Data.PlayerName} said: {text}"); + Logger.Info($"{player.Data.PlayerName} text: {text} number: {number} testPlayer: {testPlayer.NetId}"); } } } diff --git a/Reactor.Example/Reactor.Example.csproj b/Reactor.Example/Reactor.Example.csproj index ddd980b..ad82b9c 100644 --- a/Reactor.Example/Reactor.Example.csproj +++ b/Reactor.Example/Reactor.Example.csproj @@ -2,6 +2,7 @@ netstandard2.1 latest + embedded 0.1.0 Example mod using Reactor diff --git a/Reactor/Networking/MethodRpc/CustomMethodRpc.cs b/Reactor/Networking/MethodRpc/CustomMethodRpc.cs deleted file mode 100644 index 1140634..0000000 --- a/Reactor/Networking/MethodRpc/CustomMethodRpc.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using BepInEx.IL2CPP; -using Hazel; -using InnerNet; - -namespace Reactor.Networking.MethodRpc -{ - public class CustomMethodRpc : UnsafeCustomRpc - { - internal static bool SkipNextSend; - internal static Dictionary allMethodRPCsFast = new(); - - private Func Invoker; - public Type[] Parameters { get; } - public Dictionary StructBook { get; } = new(); - - public CustomMethodRpc(BasePlugin plugin, MethodInfo method, uint id, SendOption option, - RpcLocalHandling localHandling) : base(plugin, id) - { - Method = method; - LocalHandling = localHandling; - SendOption = option; - - var methodParameters = method.GetParameters(); - - Parameters = new Type[methodParameters.Length]; - for (var i = 0; i < methodParameters.Length; i++) - { - var arg = methodParameters[i]; - var argType = arg.ParameterType; - if (argType.IsValueType && !argType.IsPrimitive && !StructBook.ContainsKey(arg.ParameterType)) - { - var fields = argType.GetFields(BindingFlags.Instance | BindingFlags.Public) - .ToArray(); - StructBook.Add(argType, fields); - } - - Parameters[i] = argType; - } - - Invoker = RpcPrefixHandle.GenerateCaller(Method); - - allMethodRPCsFast.Add(Method, this); - } - - public MethodInfo Method { get; } - public override Type InnerNetObjectType { get; } = typeof(PlayerControl); - public override RpcLocalHandling LocalHandling { get; } - public override SendOption SendOption { get; } - - public override void UnsafeWrite(MessageWriter writer, object data) - { - var args = (object[]) data; - RpcSerializer.Serialize(writer, this, args); - } - - public override object UnsafeRead(MessageReader reader) - { - var args = RpcSerializer.Deserialize(reader, this); - return args; - } - - public override void UnsafeHandle(InnerNetObject innerNetObject, object data) - { - SkipNextSend = true; - - var result = Invoker((object[]) data); - if (result != null) - { - Coroutines.Start((IEnumerator) result); - } - - SkipNextSend = false; - } - - public void Send(object[] args) - { - UnsafeSend(PlayerControl.LocalPlayer, args); - } - } -} diff --git a/Reactor/Networking/MethodRpc/MethodRpc.cs b/Reactor/Networking/MethodRpc/MethodRpc.cs new file mode 100644 index 0000000..7206395 --- /dev/null +++ b/Reactor/Networking/MethodRpc/MethodRpc.cs @@ -0,0 +1,209 @@ +using System; +using System.Collections; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using System.Runtime.InteropServices; +using BepInEx.IL2CPP; +using HarmonyLib; +using Hazel; +using InnerNet; +using MonoMod.RuntimeDetour; +using MonoMod.Utils; +using Reactor.Networking.Serialization; + +namespace Reactor.Networking.MethodRpc +{ + public class MethodRpc : UnsafeCustomRpc + { + public delegate object HandleDelegate(InnerNetObject innerNetObject, object[] args); + + public MethodRpc(BasePlugin plugin, MethodInfo method, uint id, SendOption option, RpcLocalHandling localHandling) : base(plugin, id) + { + Method = method; + LocalHandling = localHandling; + SendOption = option; + + if (!method.IsStatic) + { + throw new NotImplementedException("Instance method rpc support is postponed until unhollower v0.5.0"); + } + + var parameters = method.GetParameters(); + + if (method.IsStatic && parameters.Length == 0) + { + throw new ArgumentException("Static method rpc requires at least one argument", nameof(method)); + } + + var innerNetObjectType = parameters.First().ParameterType; + + if (!typeof(InnerNetObject).IsAssignableFrom(innerNetObjectType)) + { + throw new ArgumentException("First argument of a static method rpc has to be an InnerNetObject", nameof(method)); + } + + InnerNetObjectType = innerNetObjectType; + + Handle = Hook(method, parameters); + } + + public MethodInfo Method { get; } + public HandleDelegate Handle { get; } + + public override Type InnerNetObjectType { get; } + public override RpcLocalHandling LocalHandling { get; } + public override SendOption SendOption { get; } + + public override void UnsafeWrite(MessageWriter writer, object data) + { + var args = (object[]) data; + MessageSerializer.Serialize(writer, args); + } + + public override object UnsafeRead(MessageReader reader) + { + var args = MessageSerializer.Deserialize(reader, this); + return args; + } + + public override void UnsafeHandle(InnerNetObject innerNetObject, object data) + { + var args = (object[]) data; + var result = Handle(innerNetObject, args); + + if (result is IEnumerator enumerator) + { + Coroutines.Start(enumerator); + } + } + + public void Send(InnerNetObject innerNetObject, object[] args) + { + UnsafeSend(innerNetObject, args); + } + + private static readonly MethodInfo _sendMethod = AccessTools.Method(typeof(MethodRpc), nameof(Send)); + + /// + /// Hooks the rpc with a dynamic method that sends it + /// + private HandleDelegate Hook(MethodInfo method, ParameterInfo[] parameters) + { + var detour = new Detour(method, GenerateSender()); + + // Used as target when hooking, sends the method rpc + DynamicMethod GenerateSender() + { + var dynamicMethod = new DynamicMethod($"Sender<{method.GetID(simple: true)}>", method.ReturnType, parameters.Select(x => x.ParameterType).ToArray()); + + foreach (var parameter in parameters) + { + dynamicMethod.DefineParameter(parameter.Position, ParameterAttributes.None, parameter.Name); + } + + var il = dynamicMethod.GetILGenerator(); + + // :yeefuckinhaw: + // Black magic from stackoverflow, they also said that you shouldn't do that ;) + // Resort to Dictionary if this turns out to be unreliable + { + var handle = GCHandle.Alloc(this); + var ptr = GCHandle.ToIntPtr(handle); + + if (IntPtr.Size == 4) + { + il.Emit(OpCodes.Ldc_I4, ptr.ToInt32()); + } + else + { + il.Emit(OpCodes.Ldc_I8, ptr.ToInt64()); + } + + il.Emit(OpCodes.Conv_I); + + // TODO Figure out why this always throws NullReferenceException on mono + // il.Emit(OpCodes.Ldobj, typeof(MethodRpc)); + + // Workaround for ^, speed seems to be the same + il.Emit(OpCodes.Call, AccessTools.Method(typeof(GCHandle), nameof(GCHandle.FromIntPtr))); + var temp = il.DeclareLocal(typeof(GCHandle)); + il.Emit(OpCodes.Stloc, temp); + il.Emit(OpCodes.Ldloca, temp); + il.Emit(OpCodes.Call, AccessTools.PropertyGetter(typeof(GCHandle), nameof(GCHandle.Target))); + } + + il.Emit(OpCodes.Ldarg_0); + + il.Emit(OpCodes.Ldc_I4, parameters.Length - 1); + il.Emit(OpCodes.Newarr, typeof(object)); + + for (var i = 1; i < parameters.Length; i++) + { + var parameter = parameters[i]; + + il.Emit(OpCodes.Dup); + il.Emit(OpCodes.Ldc_I4, i - 1); + il.Emit(OpCodes.Ldarg, i); + + var parameterType = parameter.ParameterType; + if (parameterType.IsValueType) + { + il.Emit(OpCodes.Box, parameterType); + } + + il.Emit(OpCodes.Stelem_Ref); + } + + il.Emit(OpCodes.Call, _sendMethod); + + if (method.ReturnType != typeof(void)) + { + il.Emit(OpCodes.Ldnull); + } + + il.Emit(OpCodes.Ret); + + return dynamicMethod; + } + + // Proxy translating object array to trampoline method args, used by MethodRpc to invoke original handling + HandleDelegate GenerateHandler(DynamicMethod trampoline) + { + var dynamicMethod = new DynamicMethod($"Handler<{method.GetID(simple: true)}>", typeof(object), new[] { typeof(InnerNetObject), typeof(object[]) }); + dynamicMethod.DefineParameter(0, ParameterAttributes.None, "innerNetObject"); + dynamicMethod.DefineParameter(1, ParameterAttributes.None, "args"); + + var il = dynamicMethod.GetILGenerator(); + + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Castclass, InnerNetObjectType); + + for (var i = 1; i < parameters.Length; i++) + { + var parameter = parameters[i]; + + il.Emit(OpCodes.Ldarg_1); + il.Emit(OpCodes.Ldc_I4, i - 1); + il.Emit(OpCodes.Ldelem_Ref); + + var parameterType = parameter.ParameterType; + il.Emit(parameterType.IsValueType ? OpCodes.Unbox_Any : OpCodes.Castclass, parameterType); + } + + il.Emit(OpCodes.Call, trampoline); + + if (method.ReturnType == typeof(void)) + { + il.Emit(OpCodes.Ldnull); + } + + il.Emit(OpCodes.Ret); + + return dynamicMethod.CreateDelegate(); + } + + return GenerateHandler((DynamicMethod) detour.GenerateTrampoline()); + } + } +} diff --git a/Reactor/Networking/MethodRpc/MethodRpcAttribute.cs b/Reactor/Networking/MethodRpc/MethodRpcAttribute.cs index 78081b3..e9118d9 100644 --- a/Reactor/Networking/MethodRpc/MethodRpcAttribute.cs +++ b/Reactor/Networking/MethodRpc/MethodRpcAttribute.cs @@ -10,55 +10,38 @@ namespace Reactor.Networking.MethodRpc [AttributeUsage(AttributeTargets.Method)] public class MethodRpcAttribute : Attribute { - private static readonly MethodInfo _rpcPrefixGenerator = typeof(RpcPrefixHandle).GetMethod( - nameof(RpcPrefixHandle.GeneratePrefix), - BindingFlags.Static | BindingFlags.Public); - - public uint id; - public SendOption option; - public RpcLocalHandling localHandling; + public uint Id { get; } + public SendOption Option { get; set; } = SendOption.Reliable; + public RpcLocalHandling LocalHandling { get; set; } = RpcLocalHandling.Before; - public MethodRpcAttribute( uint id, SendOption option = SendOption.Reliable, RpcLocalHandling localHandling = RpcLocalHandling.Before) + public MethodRpcAttribute(uint id) { - this.id = id; - this.option = option; - this.localHandling = localHandling; + Id = id; } public static void Register(Assembly assembly, BasePlugin plugin) { - Logger.Info($"Registering Method Rpc for {assembly.GetName()}"); + var methods = assembly.GetTypes() + .SelectMany(t => t.GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public)); - var rpcMethods = assembly.GetTypes() - .SelectMany(t => t.GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public)) - .Where(m => m.GetCustomAttributes(typeof(MethodRpcAttribute), false).Length > 0) - .ToArray(); - - - foreach (var method in rpcMethods) + foreach (var method in methods) { - - if (!method.IsStatic) + var attribute = method.GetCustomAttribute(); + if (attribute == null) { - Logger.Warning("Cannot register non static custom method rpc"); continue; } - var attribute = method.GetCustomAttribute(); - - var customRpc = new CustomMethodRpc(plugin, method, attribute.id, attribute.option, - attribute.localHandling); - PluginSingleton.Instance.Harmony.Patch(method, new HarmonyMethod(_rpcPrefixGenerator)); try { + var customRpc = new MethodRpc(plugin, method, attribute.Id, attribute.Option, attribute.LocalHandling); PluginSingleton.Instance.CustomRpcManager.Register(customRpc); } - catch (Exception) + catch (Exception e) { - // ignored + Logger.Warning($"Failed to register {method.FullDescription()}: {e}"); } } - } internal static void Initialize() diff --git a/Reactor/Networking/MethodRpc/ReaderWriter.cs b/Reactor/Networking/MethodRpc/ReaderWriter.cs deleted file mode 100644 index 9b60115..0000000 --- a/Reactor/Networking/MethodRpc/ReaderWriter.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.Collections.Generic; -using Hazel; - -namespace Reactor.Networking.MethodRpc -{ - - internal class ReaderWriterManager - { - public static ReaderWriterManager Instance { get; } = new(); - - private readonly Dictionary _readerWriters = new(); - - public IReadOnlyDictionary All => _readerWriters; - - public void Register(UnsafeReaderWriter readerWriter) - { - Logger.Instance.LogInfo($"Registered ReaderWriter for {readerWriter.Type}"); - if(_readerWriters.ContainsKey(readerWriter.Type)) return; - - _readerWriters.Add(readerWriter.Type, readerWriter); - } - } - - public abstract class UnsafeReaderWriter - { - public abstract Type Type { get; } - public abstract void UnsafeWrite(MessageWriter writer, object dateTime); - - public abstract object UnsafeRead(MessageReader reader); - } - - public abstract class ReaderWriter : UnsafeReaderWriter - { - public abstract void Write(MessageWriter writer, T o); - - public abstract T Read(MessageReader reader); - - public override Type Type { get; } = typeof(T); - - public override void UnsafeWrite(MessageWriter writer, object o) => Write(writer, (T) o); - - public override object UnsafeRead(MessageReader reader) => Read(reader); - } -} diff --git a/Reactor/Networking/MethodRpc/ReaderWriterAttribute.cs b/Reactor/Networking/MethodRpc/ReaderWriterAttribute.cs deleted file mode 100644 index 3c62ad1..0000000 --- a/Reactor/Networking/MethodRpc/ReaderWriterAttribute.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Reflection; -using BepInEx.IL2CPP; -using HarmonyLib; - -namespace Reactor.Networking.MethodRpc -{ - [AttributeUsage(AttributeTargets.Class)] - public class ReaderWriterAttribute : System.Attribute - { - - public static void Register(Assembly assembly, BasePlugin plugin) - { - foreach (var type in assembly.GetTypes()) - { - var attribute = type.GetCustomAttribute(); - - if (attribute != null) - { - if (!type.IsSubclassOf(typeof(UnsafeReaderWriter))) - { - throw new InvalidOperationException($"Type {type.FullDescription()} has {nameof(ReaderWriterAttribute)} but doesn't extend {nameof(UnsafeReaderWriter)}."); - } - - var customRpc = (UnsafeReaderWriter) Activator.CreateInstance(type); - ReaderWriterManager.Instance.Register(customRpc); - } - } - } - - internal static void Initialize() - { - ChainloaderHooks.PluginLoad += plugin => Register(plugin.GetType().Assembly, plugin); - } - } -} diff --git a/Reactor/Networking/MethodRpc/RpcPrefixHandle.cs b/Reactor/Networking/MethodRpc/RpcPrefixHandle.cs deleted file mode 100644 index 1c959a3..0000000 --- a/Reactor/Networking/MethodRpc/RpcPrefixHandle.cs +++ /dev/null @@ -1,118 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Reflection; -using System.Reflection.Emit; - -namespace Reactor.Networking.MethodRpc -{ - internal class RpcPrefixHandle - { - private static readonly MethodInfo _rpcPrefix = typeof(RpcPrefixHandle).GetMethod(nameof(RpcPrefix)); - private static readonly MethodInfo _emptyArray = typeof(Array).GetMethod(nameof(Array.Empty))?.MakeGenericMethod(typeof(object)); - - public static bool RpcPrefix(MethodBase originalMethod, params object[] list) - { - if (CustomMethodRpc.SkipNextSend) return true; - - var methodRpc = CustomMethodRpc.allMethodRPCsFast[originalMethod]; - methodRpc?.Send(list); - - return false; - } - - public static DynamicMethod GeneratePrefix(MethodBase __originalMethod) - { - Logger.Debug($"Generating Prefix for {__originalMethod.Name}"); - - var originalPrm = __originalMethod.GetParameters(); - - var prmType = originalPrm.Select(x => x.ParameterType).ToList(); - prmType.Insert(0, typeof(MethodBase)); - - DynamicMethod m = new DynamicMethod($"{__originalMethod.Name}Prefix", MethodAttributes.Static, - CallingConventions.Standard, typeof(bool), prmType.ToArray(), typeof(RpcPrefixHandle), false); - - m.DefineParameter(1, ParameterAttributes.None, "__originalMethod"); - for (var i = 2; i < originalPrm.Length + 2; i++) //0: Return Parameter 1:MethodBase Parameter - { - m.DefineParameter(i, ParameterAttributes.None, originalPrm[i - 2].Name); - } - - #region il_code - - var il = m.GetILGenerator(); - - il.Emit(OpCodes.Ldarg_0); - if (prmType.Count == 1) - { - il.Emit(OpCodes.Call,_emptyArray); - } - else - { - il.Emit(OpCodes.Ldc_I4, prmType.Count - 1); - il.Emit(OpCodes.Newarr, typeof(object)); - } - - for (var i = 1; i < prmType.Count; i++) AppendArg(i, prmType[i]); - - void AppendArg(int index, Type type) - { - il.Emit(OpCodes.Dup); - il.Emit(OpCodes.Ldc_I4, index - 1); - il.Emit(OpCodes.Ldarg_S, index); - - if (type.IsValueType) - { - il.Emit(OpCodes.Box, type); - } - - il.Emit(OpCodes.Stelem_Ref); - } - - il.Emit(OpCodes.Call, _rpcPrefix); - il.Emit(OpCodes.Ret); - - #endregion - - return m; - } - - - public static Func GenerateCaller(MethodInfo method) - { - var methodPrm = method.GetParameters(); - - var args = Expression.Parameter(typeof(object[]), "Args"); - - List elements = new(); - for (int i = 0; i < methodPrm.Length; i++) - { - var element = Expression.ArrayAccess(args, Expression.Constant(i, typeof(int))); - var convert = Expression.Convert(element, methodPrm[i].ParameterType); - elements.Add(convert); - } - - - Expression invoke = Expression.Call(method, elements); - - var returnTarget = Expression.Label(typeof(object)); - - if (method.ReturnParameter.ParameterType == typeof(void)) - { - invoke = Expression.Block(invoke, - Expression.Label(returnTarget, Expression.Default(typeof(object))) - ); - } - else - { - invoke = Expression.Label(returnTarget, invoke); - } - - var caller = Expression.Lambda>(invoke, args); - Logger.Debug($"Generated caller for {method.Name} -> {caller}"); - return caller.Compile(); - } - } -} diff --git a/Reactor/Networking/MethodRpc/RpcSerializer.cs b/Reactor/Networking/MethodRpc/RpcSerializer.cs deleted file mode 100644 index 0c7d3b5..0000000 --- a/Reactor/Networking/MethodRpc/RpcSerializer.cs +++ /dev/null @@ -1,208 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using Hazel; -using InnerNet; -using Reactor.Extensions; -using UnityEngine; - -namespace Reactor.Networking.MethodRpc -{ - public static class RpcSerializer - { - public static void Serialize(MessageWriter writer,CustomMethodRpc methodRpc, params object[] list) - { - foreach (var o in list) - { - if (o.GetType().IsValueType && !o.GetType().IsPrimitive) - { - var structFeilds = methodRpc.StructBook[o.GetType()]; - foreach (var feild in structFeilds) - { - writer.Write(feild.GetValue(o)); - } - } - else - { - writer.Write(o); - } - Logger.Debug($"Serialized {o.GetType()}"); - } - } - - public static void Write(this MessageWriter writer, object o) - { - switch (o) - { - case int i: - writer.WritePacked(i); - break; - case uint i: - writer.WritePacked(i); - break; - case byte i: - writer.Write(i); - break; - case float i: - writer.Write(i); - break; - case sbyte i: - writer.Write(i); - break; - case ushort i: - writer.Write(i); - break; - case bool i: - writer.Write(i); - break; - case Vector2 i: - writer.Write(i); - break; - case string i: - writer.Write(i); - break; - default: - if (o.GetType().IsSubclassOf(typeof(InnerNetObject))) - { - writer.WriteNetObject((InnerNetObject) o); - } - else if (ReaderWriterManager.Instance.All.ContainsKey(o.GetType())) - { - ReaderWriterManager.Instance.All[o.GetType()].UnsafeWrite(writer, o); - } - else - { - Logger.Warning($"Tried serializing unsupported type {o.GetType()}"); - } - break; - } - } - - - public static object[] Deserialize(MessageReader reader,CustomMethodRpc methodRpc) - { - Logger.Debug($"Deserializing {methodRpc.Parameters.Length} object"); - - object[] args = new object[methodRpc.Parameters.Length]; - - for (int i = 0; i < args.Length; i++) - { - Type t = methodRpc.Parameters[i]; - if (t.IsValueType && !t.IsPrimitive) - { - var fields = methodRpc.StructBook[t]; - - object[] structArgs = new object[fields.Length]; - for (int n = 0; n < structArgs.Length; n++) - { - structArgs[n] = reader.Read(fields[n].FieldType); - Logger.Debug($"Deserialized {fields[n].FieldType} from {t}"); - } - t.GetType().GetConstructors().ToList().ForEach(x=>Logger.Info($"Gw {x.GetParameters().Length}")); - - args[i] = Activator.CreateInstance(t,structArgs); - } - else - { - var value = reader.Read(t); - Logger.Debug($"Read {value} of {t.Name} for {methodRpc.Method.Name}"); - args[i] = value; - } - } - - return args; - } - - - public static object Read(this MessageReader reader, Type t) - { - if (t == typeof(int) ) - { - return reader.ReadPackedInt32(); - } - - if (t == typeof(uint)) - { - return (reader.ReadPackedUInt32()); - } - - if (t == typeof(byte)) - { - return (reader.ReadByte()); - } - - if (t == typeof(float)) - { - return (reader.ReadSingle()); - } - - if (t == typeof(sbyte)) - { - return (reader.ReadSByte()); - } - - if (t == typeof(ushort)) - { - return (reader.ReadUInt16()); - } - - if (t == typeof(bool)) - { - return (reader.ReadBoolean()); - } - - if (t == typeof(Vector2)) - { - return (reader.ReadVector2()); - } - - if (t == typeof(string)) - { - return (reader.ReadString()); - } - - if (t.IsSubclassOf(typeof(InnerNetObject))) - { - return reader.ReadNetObject(t); - } - - if (ReaderWriterManager.Instance.All.ContainsKey(t.BaseType!)) - { - return ReaderWriterManager.Instance.All[t.BaseType].UnsafeRead(reader); - } - - Logger.Warning($"Tried deserializing unsupported type {t.BaseType.Name}"); - return null; - - } - - #region InnerNetObject - - private static readonly MethodInfo _findObjectByNetId = - typeof(RpcSerializer).GetMethod(nameof(FindObjectByNetId), BindingFlags.Static | BindingFlags.NonPublic); - - private static Dictionary> _findObjectByNetIdInvokers = new(); - static Func GetFindObjectByNetIdInvoker(Type type) - { - if (_findObjectByNetIdInvokers.ContainsKey(type)) return _findObjectByNetIdInvokers[type]; - - var invoker = RpcPrefixHandle.GenerateCaller(_findObjectByNetId.MakeGenericMethod(type)); - _findObjectByNetIdInvokers.Add(type, invoker); - return invoker; - } - - private static InnerNetObject FindObjectByNetId(uint id) where T : InnerNetObject - { - return AmongUsClient.Instance.FindObjectByNetId(id); - } - - public static void WriteNetObject(this MessageWriter writer, InnerNetObject o) => writer.WritePacked(o.NetId); - - public static InnerNetObject ReadNetObject(this MessageReader reader, Type t) => - (InnerNetObject) GetFindObjectByNetIdInvoker(t)(new object[] {reader.ReadPackedUInt32()}); - - #endregion - - } -} diff --git a/Reactor/Networking/Serialization/InnerNetObjectConverter.cs b/Reactor/Networking/Serialization/InnerNetObjectConverter.cs new file mode 100644 index 0000000..3effd6f --- /dev/null +++ b/Reactor/Networking/Serialization/InnerNetObjectConverter.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using HarmonyLib; +using Hazel; +using InnerNet; +using MonoMod.Utils; + +namespace Reactor.Networking.Serialization +{ + [MessageConverter] + public class InnerNetObjectConverter : MessageConverter + { + private static readonly MethodInfo _readNetObject = AccessTools.Method(typeof(MessageExtensions), nameof(MessageExtensions.ReadNetObject)); + private static readonly Dictionary> _readNetObjectMap = new Dictionary>(); + + public static InnerNetObject ReadNetObject(MessageReader reader, Type t) + { + if (_readNetObjectMap.TryGetValue(t, out var value)) + { + return value(reader); + } + + var @delegate = _readNetObject.MakeGenericMethod(t).CreateDelegate>(); + _readNetObjectMap[t] = @delegate; + return @delegate(reader); + } + + public override void Write(MessageWriter writer, InnerNetObject value) + { + MessageExtensions.WriteNetObject(writer, value); + } + + public override InnerNetObject Read(MessageReader reader, Type objectType) + { + return ReadNetObject(reader, objectType); + } + } +} diff --git a/Reactor/Networking/Serialization/MessageConverter.cs b/Reactor/Networking/Serialization/MessageConverter.cs new file mode 100644 index 0000000..5e7465c --- /dev/null +++ b/Reactor/Networking/Serialization/MessageConverter.cs @@ -0,0 +1,29 @@ +using System; +using Hazel; + +namespace Reactor.Networking.Serialization +{ + public abstract class UnsafeMessageConverter + { + public abstract void UnsafeWrite(MessageWriter writer, object value); + public abstract object UnsafeRead(MessageReader reader, Type objectType); + + public abstract bool CanConvert(Type objectType); + } + + public abstract class MessageConverter : UnsafeMessageConverter + { + public abstract void Write(MessageWriter writer, T value); + + public abstract T Read(MessageReader reader, Type objectType); + + public override bool CanConvert(Type objectType) + { + return typeof(T).IsAssignableFrom(objectType); + } + + public override void UnsafeWrite(MessageWriter writer, object value) => Write(writer, (T) value); + + public override object UnsafeRead(MessageReader reader, Type objectType) => Read(reader, objectType); + } +} diff --git a/Reactor/Networking/Serialization/MessageConverterAttribute.cs b/Reactor/Networking/Serialization/MessageConverterAttribute.cs new file mode 100644 index 0000000..aa743e8 --- /dev/null +++ b/Reactor/Networking/Serialization/MessageConverterAttribute.cs @@ -0,0 +1,41 @@ +using System; +using System.Reflection; +using HarmonyLib; + +namespace Reactor.Networking.Serialization +{ + [AttributeUsage(AttributeTargets.Class)] + public class MessageConverterAttribute : Attribute + { + public static void Register(Assembly assembly) + { + foreach (var type in assembly.GetTypes()) + { + var attribute = type.GetCustomAttribute(); + + if (attribute != null) + { + if (!type.IsSubclassOf(typeof(UnsafeMessageConverter))) + { + throw new InvalidOperationException($"Type {type.FullDescription()} has {nameof(MessageConverterAttribute)} but doesn't extend {nameof(UnsafeMessageConverter)}."); + } + + try + { + var messageConverter = (UnsafeMessageConverter) Activator.CreateInstance(type); + MessageSerializer.Register(messageConverter); + } + catch (Exception e) + { + Logger.Warning($"Failed to register {type.FullDescription()}: {e}"); + } + } + } + } + + internal static void Initialize() + { + ChainloaderHooks.PluginLoad += plugin => Register(plugin.GetType().Assembly); + } + } +} diff --git a/Reactor/Networking/Serialization/MessageSerializer.cs b/Reactor/Networking/Serialization/MessageSerializer.cs new file mode 100644 index 0000000..5f00f04 --- /dev/null +++ b/Reactor/Networking/Serialization/MessageSerializer.cs @@ -0,0 +1,156 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Hazel; +using Reactor.Extensions; +using UnityEngine; + +namespace Reactor.Networking.Serialization +{ + public static class MessageSerializer + { + private static List MessageConverters { get; } = new List(); + + private static Dictionary MessageConverterMap { get; } = new Dictionary(); + + public static void Register(UnsafeMessageConverter messageConverter) + { + MessageConverters.Add(messageConverter); + MessageConverterMap.Clear(); + } + + public static UnsafeMessageConverter FindConverter(Type type) + { + if (MessageConverterMap.TryGetValue(type, out var value)) + { + return value; + } + + var converter = MessageConverters.SingleOrDefault(x => x.CanConvert(type)); + MessageConverterMap.Add(type, converter); + + return converter; + } + + public static void Serialize(MessageWriter writer, object[] args) + { + foreach (var arg in args) + { + writer.Serialize(arg); + } + } + + public static void Serialize(this MessageWriter writer, object o) + { + switch (o) + { + case int i: + writer.WritePacked(i); + break; + case uint i: + writer.WritePacked(i); + break; + case byte i: + writer.Write(i); + break; + case float i: + writer.Write(i); + break; + case sbyte i: + writer.Write(i); + break; + case ushort i: + writer.Write(i); + break; + case bool i: + writer.Write(i); + break; + case Vector2 i: + writer.Write(i); + break; + case string i: + writer.Write(i); + break; + default: + var converter = FindConverter(o.GetType()); + if (converter != null) + { + converter.UnsafeWrite(writer, o); + break; + } + + throw new NotSupportedException("Couldn't serialize " + o.GetType()); + } + } + + public static object[] Deserialize(MessageReader reader, MethodRpc.MethodRpc methodRpc) + { + var parameters = methodRpc.Method.GetParameters(); + var args = new object[parameters.Length - 1]; + + for (var i = 1; i < parameters.Length; i++) + { + var parameter = parameters[i]; + args[i - 1] = reader.Deserialize(parameter.ParameterType); + } + + return args; + } + + public static object Deserialize(this MessageReader reader, Type t) + { + if (t == typeof(int)) + { + return reader.ReadPackedInt32(); + } + + if (t == typeof(uint)) + { + return reader.ReadPackedUInt32(); + } + + if (t == typeof(byte)) + { + return reader.ReadByte(); + } + + if (t == typeof(float)) + { + return reader.ReadSingle(); + } + + if (t == typeof(sbyte)) + { + return reader.ReadSByte(); + } + + if (t == typeof(ushort)) + { + return reader.ReadUInt16(); + } + + if (t == typeof(bool)) + { + return reader.ReadBoolean(); + } + + if (t == typeof(Vector2)) + { + return reader.ReadVector2(); + } + + if (t == typeof(string)) + { + return reader.ReadString(); + } + + var converter = FindConverter(t); + if (converter != null) + { + return converter.UnsafeRead(reader, t); + } + + throw new NotSupportedException("Couldn't deserialize " + t); + } + } +} diff --git a/Reactor/Networking/UnsafeCustomRpc.cs b/Reactor/Networking/UnsafeCustomRpc.cs index 6a9f806..405a982 100644 --- a/Reactor/Networking/UnsafeCustomRpc.cs +++ b/Reactor/Networking/UnsafeCustomRpc.cs @@ -30,9 +30,9 @@ namespace Reactor.Networking public abstract object UnsafeRead(MessageReader reader); public abstract void UnsafeHandle(InnerNetObject innerNetObject, object data); - public void UnsafeSend(InnerNetObject netObject, object data, bool immediately = false, int targetClientId = -1) + public void UnsafeSend(InnerNetObject innerNetObject, object data, bool immediately = false, int targetClientId = -1) { - if (netObject == null) throw new ArgumentNullException(nameof(netObject)); + if (innerNetObject == null) throw new ArgumentNullException(nameof(innerNetObject)); if (Manager == null) { @@ -41,13 +41,13 @@ namespace Reactor.Networking if (LocalHandling == RpcLocalHandling.Before) { - UnsafeHandle(netObject, data); + UnsafeHandle(innerNetObject, data); } var writer = immediately switch { - false => AmongUsClient.Instance.StartRpc(netObject.NetId, CustomRpcManager.CallId, SendOption), - true => AmongUsClient.Instance.StartRpcImmediately(netObject.NetId, CustomRpcManager.CallId, SendOption, targetClientId) + false => AmongUsClient.Instance.StartRpc(innerNetObject.NetId, CustomRpcManager.CallId, SendOption), + true => AmongUsClient.Instance.StartRpcImmediately(innerNetObject.NetId, CustomRpcManager.CallId, SendOption, targetClientId) }; var pluginNetId = ModList.GetById(PluginId).NetId; @@ -69,7 +69,7 @@ namespace Reactor.Networking if (LocalHandling == RpcLocalHandling.After) { - UnsafeHandle(netObject, data); + UnsafeHandle(innerNetObject, data); } } } diff --git a/Reactor/ReactorPlugin.cs b/Reactor/ReactorPlugin.cs index eb83a9e..814b04a 100644 --- a/Reactor/ReactorPlugin.cs +++ b/Reactor/ReactorPlugin.cs @@ -7,6 +7,7 @@ using HarmonyLib; using Reactor.Extensions; using Reactor.Networking; using Reactor.Networking.MethodRpc; +using Reactor.Networking.Serialization; using Reactor.Patches; using Reactor.Unstrip; using UnhollowerBaseLib.Attributes; @@ -38,8 +39,8 @@ namespace Reactor PluginSingleton.Initialize(); RegisterInIl2CppAttribute.Initialize(); RegisterCustomRpcAttribute.Initialize(); + MessageConverterAttribute.Initialize(); MethodRpcAttribute.Initialize(); - ReaderWriterAttribute.Initialize(); ChainloaderHooks.OnPluginLoad(this); }