IL gen refactor and serialization changes

This commit is contained in:
js6pak committed 2021-07-27 16:18:11 +02:00
1 parent 41413eea20
commit 7d02cb9616
15 files changed
+505 -534

No files matched your search

+9 -5
View File
@@ -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}");
}
}
}
+1
View File
@@ -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);
}
}
}
+209
View File
@@ -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);
}
}
}
+6 -6
View File
@@ -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);
}
}
}
+2 -1
View File
@@ -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);
}