mirror of
https://github.com/NuclearPowered/Reactor.git
synced 2026-10-19 15:51:54 +00:00
IL gen refactor and serialization changes
This commit is contained in:
15 files changed
+505
-534
No files matched your search
@@ -6,6 +6,7 @@ using Reactor.Networking;
|
||||
using Reactor.Networking.MethodRpc;
|
||||
using UnhollowerBaseLib.Attributes;
|
||||
using UnityEngine;
|
||||
using Random = UnityEngine.Random;
|
||||
|
||||
namespace Reactor.Example
|
||||
{
|
||||
@@ -34,7 +35,9 @@ namespace Reactor.Example
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (Input.GetKeyDown(KeyCode.F3) && AmongUsClient.Instance && PlayerControl.LocalPlayer)
|
||||
if (AmongUsClient.Instance && PlayerControl.LocalPlayer)
|
||||
{
|
||||
if (Input.GetKeyDown(KeyCode.F3))
|
||||
{
|
||||
var name = PlayerControl.LocalPlayer.Data.PlayerName;
|
||||
Rpc<ExampleRpc>.Instance.Send(new ExampleRpc.Data($"Send: from {name}"));
|
||||
@@ -45,17 +48,18 @@ namespace Reactor.Example
|
||||
}
|
||||
}
|
||||
|
||||
if (Input.GetKeyDown(KeyCode.F4) && AmongUsClient.Instance && PlayerControl.LocalPlayer)
|
||||
if (Input.GetKeyDown(KeyCode.F4))
|
||||
{
|
||||
RpcSay(PlayerControl.LocalPlayer, "Hello from method rpc");
|
||||
RpcSay(PlayerControl.LocalPlayer, "Hello from method rpc", Random.value, PlayerControl.LocalPlayer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[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<ExamplePlugin>.Info($"{sender.Data.PlayerName} said: {text}");
|
||||
Logger<ExamplePlugin>.Info($"{player.Data.PlayerName} text: {text} number: {number} testPlayer: {testPlayer.NetId}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<DebugType>embedded</DebugType>
|
||||
|
||||
<Version>0.1.0</Version>
|
||||
<Description>Example mod using Reactor</Description>
|
||||
|
||||
@@ -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<MethodBase, CustomMethodRpc> allMethodRPCsFast = new();
|
||||
|
||||
private Func<object[], object> Invoker;
|
||||
public Type[] Parameters { get; }
|
||||
public Dictionary<Type, FieldInfo[]> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
|
||||
/// <summary>
|
||||
/// Hooks the <paramref name="method"/> rpc with a dynamic method that sends it
|
||||
/// </summary>
|
||||
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<MethodInfo, MethodRpc> 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<HandleDelegate>();
|
||||
}
|
||||
|
||||
return GenerateHandler((DynamicMethod) detour.GenerateTrampoline());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 { get; }
|
||||
public SendOption Option { get; set; } = SendOption.Reliable;
|
||||
public RpcLocalHandling LocalHandling { get; set; } = RpcLocalHandling.Before;
|
||||
|
||||
public uint id;
|
||||
public SendOption option;
|
||||
public RpcLocalHandling localHandling;
|
||||
|
||||
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<ReactorPlugin>.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<MethodRpcAttribute>();
|
||||
if (attribute == null)
|
||||
{
|
||||
Logger<ReactorPlugin>.Warning("Cannot register non static custom method rpc");
|
||||
continue;
|
||||
}
|
||||
|
||||
var attribute = method.GetCustomAttribute<MethodRpcAttribute>();
|
||||
|
||||
var customRpc = new CustomMethodRpc(plugin, method, attribute.id, attribute.option,
|
||||
attribute.localHandling);
|
||||
PluginSingleton<ReactorPlugin>.Instance.Harmony.Patch(method, new HarmonyMethod(_rpcPrefixGenerator));
|
||||
try
|
||||
{
|
||||
var customRpc = new MethodRpc(plugin, method, attribute.Id, attribute.Option, attribute.LocalHandling);
|
||||
PluginSingleton<ReactorPlugin>.Instance.CustomRpcManager.Register(customRpc);
|
||||
}
|
||||
catch (Exception)
|
||||
catch (Exception e)
|
||||
{
|
||||
// ignored
|
||||
Logger<ReactorPlugin>.Warning($"Failed to register {method.FullDescription()}: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal static void Initialize()
|
||||
|
||||
@@ -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<Type, UnsafeReaderWriter> _readerWriters = new();
|
||||
|
||||
public IReadOnlyDictionary<Type, UnsafeReaderWriter> All => _readerWriters;
|
||||
|
||||
public void Register(UnsafeReaderWriter readerWriter)
|
||||
{
|
||||
Logger<ReactorPlugin>.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<T> : 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);
|
||||
}
|
||||
}
|
||||
@@ -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<ReaderWriterAttribute>();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<ReactorPlugin>.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<object[], object> GenerateCaller(MethodInfo method)
|
||||
{
|
||||
var methodPrm = method.GetParameters();
|
||||
|
||||
var args = Expression.Parameter(typeof(object[]), "Args");
|
||||
|
||||
List<Expression> 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<Func<object[], object>>(invoke, args);
|
||||
Logger<ReactorPlugin>.Debug($"Generated caller for {method.Name} -> {caller}");
|
||||
return caller.Compile();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<ReactorPlugin>.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<ReactorPlugin>.Warning($"Tried serializing unsupported type {o.GetType()}");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static object[] Deserialize(MessageReader reader,CustomMethodRpc methodRpc)
|
||||
{
|
||||
Logger<ReactorPlugin>.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<ReactorPlugin>.Debug($"Deserialized {fields[n].FieldType} from {t}");
|
||||
}
|
||||
t.GetType().GetConstructors().ToList().ForEach(x=>Logger<ReactorPlugin>.Info($"Gw {x.GetParameters().Length}"));
|
||||
|
||||
args[i] = Activator.CreateInstance(t,structArgs);
|
||||
}
|
||||
else
|
||||
{
|
||||
var value = reader.Read(t);
|
||||
Logger<ReactorPlugin>.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<ReactorPlugin>.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<Type, Func<object[], object>> _findObjectByNetIdInvokers = new();
|
||||
static Func<object[], object> 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<T>(uint id) where T : InnerNetObject
|
||||
{
|
||||
return AmongUsClient.Instance.FindObjectByNetId<T>(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
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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<InnerNetObject>
|
||||
{
|
||||
private static readonly MethodInfo _readNetObject = AccessTools.Method(typeof(MessageExtensions), nameof(MessageExtensions.ReadNetObject));
|
||||
private static readonly Dictionary<Type, Func<MessageReader, InnerNetObject>> _readNetObjectMap = new Dictionary<Type, Func<MessageReader, InnerNetObject>>();
|
||||
|
||||
public static InnerNetObject ReadNetObject(MessageReader reader, Type t)
|
||||
{
|
||||
if (_readNetObjectMap.TryGetValue(t, out var value))
|
||||
{
|
||||
return value(reader);
|
||||
}
|
||||
|
||||
var @delegate = _readNetObject.MakeGenericMethod(t).CreateDelegate<Func<MessageReader, InnerNetObject>>();
|
||||
_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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<T> : 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);
|
||||
}
|
||||
}
|
||||
@@ -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<MessageConverterAttribute>();
|
||||
|
||||
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<ReactorPlugin>.Warning($"Failed to register {type.FullDescription()}: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void Initialize()
|
||||
{
|
||||
ChainloaderHooks.PluginLoad += plugin => Register(plugin.GetType().Assembly);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<UnsafeMessageConverter> MessageConverters { get; } = new List<UnsafeMessageConverter>();
|
||||
|
||||
private static Dictionary<Type, UnsafeMessageConverter> MessageConverterMap { get; } = new Dictionary<Type, UnsafeMessageConverter>();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<BasePlugin>.Initialize();
|
||||
RegisterInIl2CppAttribute.Initialize();
|
||||
RegisterCustomRpcAttribute.Initialize();
|
||||
MessageConverterAttribute.Initialize();
|
||||
MethodRpcAttribute.Initialize();
|
||||
ReaderWriterAttribute.Initialize();
|
||||
|
||||
ChainloaderHooks.OnPluginLoad(this);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user