From 7d02cb9616fb93ee65afd43bd567869a039adab9 Mon Sep 17 00:00:00 2001 From: js6pak Date: Tue, 27 Jul 2021 16:18:11 +0200 Subject: [PATCH] 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); }