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