From ce0da4ebe8ca9e30f9b529ea25ee847955e232a7 Mon Sep 17 00:00:00 2001 From: js6pak Date: Tue, 19 Jan 2021 17:04:33 +0100 Subject: [PATCH] Rework handshake --- Reactor.Example/ExampleRpc.cs | 2 +- Reactor/CustomRpc.cs | 59 +++++++++- Reactor/Net/Mod.cs | 21 ++++ Reactor/Net/ModList.cs | 34 ++++++ Reactor/Net/ModdedHandshakeC2S.cs | 20 ++++ Reactor/Net/ModdedHandshakeS2C.cs | 14 +++ Reactor/Patches/ModdedHandshake.cs | 80 -------------- Reactor/Patches/ModdedHandshakePatches.cs | 127 ++++++++++++++++++++++ Reactor/ReactorPlugin.cs | 7 ++ Reactor/ReactorPluginSideAttribute.cs | 2 +- 10 files changed, 278 insertions(+), 88 deletions(-) create mode 100644 Reactor/Net/Mod.cs create mode 100644 Reactor/Net/ModList.cs create mode 100644 Reactor/Net/ModdedHandshakeC2S.cs create mode 100644 Reactor/Net/ModdedHandshakeS2C.cs delete mode 100644 Reactor/Patches/ModdedHandshake.cs create mode 100644 Reactor/Patches/ModdedHandshakePatches.cs diff --git a/Reactor.Example/ExampleRpc.cs b/Reactor.Example/ExampleRpc.cs index 5ea298f..027d8f9 100644 --- a/Reactor.Example/ExampleRpc.cs +++ b/Reactor.Example/ExampleRpc.cs @@ -11,7 +11,7 @@ namespace Reactor.Example public readonly struct Data { - public string Message { get; } + public readonly string Message; public Data(string message) { diff --git a/Reactor/CustomRpc.cs b/Reactor/CustomRpc.cs index 4b6a542..20c6f43 100644 --- a/Reactor/CustomRpc.cs +++ b/Reactor/CustomRpc.cs @@ -1,11 +1,14 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Reflection; +using System.Text; using BepInEx; using BepInEx.IL2CPP; using HarmonyLib; using Hazel; +using Reactor.Net; namespace Reactor { @@ -38,7 +41,7 @@ namespace Reactor public abstract void UnsafeHandle(InnerNetObject innerNetObject, object data); } - public abstract class CustomRpc : UnsafeCustomRpc where TPlugin : BasePlugin where TInnerNetObject : InnerNetObject where TData : struct + public abstract class CustomRpc : UnsafeCustomRpc where TPlugin : BasePlugin where TInnerNetObject : InnerNetObject { protected CustomRpc(TPlugin plugin) : base(plugin) { @@ -80,6 +83,9 @@ namespace Reactor _map.SelectMany(pair => pair.Value, (pair, value) => new { pair.Key, Value = value }) .ToLookup(pair => pair.Key, pair => pair.Value); + public Dictionary PluginIdMap { get; private set; } + public Dictionary PluginIdMapReversed { get; private set; } + public CustomRpcManager() { foreach (var type in HandleRpcPatch.InnerNetObjectTypes) @@ -88,6 +94,24 @@ namespace Reactor } } + public void ReloadPluginIdMap() + { + PluginIdMap = new Dictionary(); + PluginIdMapReversed = new Dictionary(); + + var i = -1; + + foreach (var mod in ModList.GetCurrent()) + { + if (mod.Side == PluginSide.Both) + { + PluginIdMap[mod.Id] = i; + PluginIdMapReversed[i] = mod.Id; + i--; + } + } + } + public UnsafeCustomRpc Register(UnsafeCustomRpc customRpc) { customRpc.Id = List.Count(x => x.UnsafePlugin == customRpc.UnsafePlugin); @@ -124,8 +148,16 @@ namespace Reactor true => AmongUsClient.Instance.StartRpcImmediately(netObject.NetId, CallId, SendOption.Reliable, -1) }; - writer.Write(customRpc.PluginId); - writer.Write(customRpc.Id!.Value); + if (PluginIdMap.TryGetValue(customRpc.PluginId, out var pluginId)) + { + writer.WritePacked(pluginId); + } + else + { + writer.Write(customRpc.PluginId); + } + + writer.WritePacked(customRpc.Id!.Value); customRpc.UnsafeWrite(writer, data); if (immediately) @@ -153,14 +185,29 @@ namespace Reactor return InnerNetObjectTypes.Select(x => x.GetMethod(nameof(InnerNetObject.HandleRpc), AccessTools.all)); } + private static string ReadString(MessageReader reader, int len) + { + if (reader.BytesRemaining < len) throw new InvalidDataException($"Read length is longer than message length: {len} of {reader.BytesRemaining}"); + + var output = Encoding.UTF8.GetString(reader.Buffer, reader.readHead, len); + reader.Position += len; + return output; + } + public static bool Prefix(InnerNetObject __instance, [HarmonyArgument(0)] byte callId, [HarmonyArgument(1)] MessageReader reader) { if (callId == CallId) { - var customRpcs = PluginSingleton.Instance.CustomRpcManager.Map[__instance.GetType()]; + var manager = PluginSingleton.Instance.CustomRpcManager; + var customRpcs = manager.Map[__instance.GetType()]; - var pluginId = reader.ReadString(); - var id = reader.ReadInt32(); + var lengthOrShortId = reader.ReadPackedInt32(); + + var pluginId = lengthOrShortId < 0 + ? manager.PluginIdMapReversed[lengthOrShortId] + : ReadString(reader, lengthOrShortId); + + var id = reader.ReadPackedInt32(); var customRpc = customRpcs.Single(x => x.PluginId == pluginId && x.Id == id); diff --git a/Reactor/Net/Mod.cs b/Reactor/Net/Mod.cs new file mode 100644 index 0000000..f6870dd --- /dev/null +++ b/Reactor/Net/Mod.cs @@ -0,0 +1,21 @@ +namespace Reactor.Net +{ + public readonly struct Mod + { + public readonly string Id; + public readonly string Version; + public readonly PluginSide Side; + + public Mod(string id, string version, PluginSide side) + { + Id = id; + Version = version; + Side = side; + } + + public override string ToString() + { + return $"{Id} ({Version})"; + } + } +} diff --git a/Reactor/Net/ModList.cs b/Reactor/Net/ModList.cs new file mode 100644 index 0000000..ebcaafd --- /dev/null +++ b/Reactor/Net/ModList.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using BepInEx.IL2CPP; +using Hazel; + +namespace Reactor.Net +{ + public static class ModList + { + public static void Serialize(MessageWriter writer, ISet mods) + { + writer.WritePacked(mods.Count); + + foreach (var mod in mods) + { + writer.Write(mod.Id); + writer.Write(mod.Version); + writer.Write((byte) mod.Side); + } + } + + public static ISet GetCurrent() + { + return IL2CPPChainloader.Instance.Plugins + .Select(plugin => new Mod( + plugin.Key, + plugin.Value.Metadata.Version.ToString(), + plugin.Value.Instance.GetType().GetCustomAttribute()?.Side ?? PluginSide.Both + )) + .ToHashSet(); + } + } +} diff --git a/Reactor/Net/ModdedHandshakeC2S.cs b/Reactor/Net/ModdedHandshakeC2S.cs new file mode 100644 index 0000000..d071ed3 --- /dev/null +++ b/Reactor/Net/ModdedHandshakeC2S.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using Hazel; +using UnhollowerBaseLib; + +namespace Reactor.Net +{ + public static class ModdedHandshakeC2S + { + public static void Serialize(MessageWriter writer, Il2CppStructArray clientVersion, string name, ISet mods = null) + { + writer.Write(clientVersion); + writer.Write(name); + + if (mods != null) + { + ModList.Serialize(writer, mods); + } + } + } +} diff --git a/Reactor/Net/ModdedHandshakeS2C.cs b/Reactor/Net/ModdedHandshakeS2C.cs new file mode 100644 index 0000000..63b9d9b --- /dev/null +++ b/Reactor/Net/ModdedHandshakeS2C.cs @@ -0,0 +1,14 @@ +using Hazel; + +namespace Reactor.Net +{ + public static class ModdedHandshakeS2C + { + public static void Serialize(MessageWriter writer, string serverBrand) + { + writer.StartMessage(byte.MaxValue); + writer.Write(serverBrand); + writer.EndMessage(); + } + } +} diff --git a/Reactor/Patches/ModdedHandshake.cs b/Reactor/Patches/ModdedHandshake.cs deleted file mode 100644 index 47034f8..0000000 --- a/Reactor/Patches/ModdedHandshake.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System.Linq; -using System.Reflection; -using BepInEx.IL2CPP; -using HarmonyLib; -using Hazel; -using UnhollowerBaseLib; - -namespace Reactor.Patches -{ - internal static class ModdedHandshake - { - // TODO add back when vent issue is fixed - // [HarmonyPatch(typeof(InnerNetServer), nameof(InnerNetServer.OnServerConnect))] - // public static class FreeplayPatch - // { - // public static void Prefix([HarmonyArgument(0)] NewConnectionEventArgs evt) - // { - // var handshakeData = evt.HandshakeData; - // - // var protocolVersion = handshakeData.ReadInt32(); - // if (protocolVersion != -1) - // { - // // reset position if vanilla - // handshakeData._position -= sizeof(int); - // handshakeData.readHead -= sizeof(int); - // } - // } - // } - - // workaround for ^ - [HarmonyPatch(typeof(InnerNetServer), nameof(InnerNetServer.IsCompatibleVersion))] - public static class FreeplayPatch - { - public static bool Prefix([HarmonyArgument(0)] int version, ref bool __result) - { - if (version == -1) - { - __result = true; - return false; - } - - return true; - } - } - - [HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.GetConnectionData))] - public static class HandshakePatch - { - public static bool Prefix(out Il2CppStructArray __result) - { - var handshake = MessageWriter.Get(SendOption.Reliable); - - var plugins = IL2CPPChainloader.Instance.Plugins; - - if (plugins.Values - .Select(pluginInfo => pluginInfo.Instance.GetType().GetCustomAttribute()) - .Any(attribute => attribute == null || attribute.Side == PluginSide.Both)) - { - handshake.Write(-1); - } - - handshake.Write(Constants.GetBroadcastVersion()); - handshake.Write(SaveManager.PlayerName); - - handshake.WritePacked(plugins.Count); - - foreach (var (pluginId, plugin) in plugins) - { - handshake.Write(pluginId); - handshake.Write(plugin.Metadata.Version.ToString()); - } - - __result = handshake.ToByteArray(false); - handshake.Recycle(); - - return false; - } - } - } -} diff --git a/Reactor/Patches/ModdedHandshakePatches.cs b/Reactor/Patches/ModdedHandshakePatches.cs new file mode 100644 index 0000000..1429b36 --- /dev/null +++ b/Reactor/Patches/ModdedHandshakePatches.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using HarmonyLib; +using Hazel; +using Hazel.Udp; +using Reactor.Extensions; +using Reactor.Net; +using UnhollowerBaseLib; +using UnityEngine; + +namespace Reactor.Patches +{ + internal static class ModdedHandshakePatches + { + // [HarmonyPatch(typeof(InnerNetServer), nameof(InnerNetServer.OnServerConnect))] + [HarmonyPatch] + public static class ServerPatch + { + public static IEnumerable TargetMethods() + { + return typeof(InnerNetServer).GetMethods(typeof(void), typeof(NewConnectionEventArgs)); + } + + private static readonly string ServerBrand = $"Among Us {Application.version}"; + + public static void Postfix([HarmonyArgument(0)] NewConnectionEventArgs evt) + { + var writer = MessageWriter.Get(SendOption.Reliable); + ModdedHandshakeS2C.Serialize(writer, ServerBrand); + evt.Connection.Send(writer); + writer.Recycle(); + } + } + + // [HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.HandleMessage))] + [HarmonyPatch] + public static class HandleMessagePatch + { + public static IEnumerable TargetMethods() + { + return typeof(InnerNetClient).GetMethods(typeof(void), typeof(MessageReader), typeof(SendOption)); + } + + public static bool Prefix(InnerNetClient __instance, [HarmonyArgument(0)] MessageReader reader, [HarmonyArgument(1)] SendOption sendOption) + { + if (reader.Tag == byte.MaxValue) + { + var serverBrand = reader.ReadString(); + + __instance.Field_23.State = ConnectionState.Connected; + + PluginSingleton.Instance.Log.LogDebug($"Connected to modded server ({serverBrand})"); + + return false; + } + + return true; + } + } + + [HarmonyPatch(typeof(UdpConnection), nameof(UdpConnection.HandleSend))] + public static class SendHelloPatch + { + public static void Prefix(UdpConnection __instance, byte sendOption, ref Il2CppSystem.Action ackCallback) + { + if (sendOption != 8 || AmongUsClient.Instance == null || AmongUsClient.Instance.Field_23 == null || !AmongUsClient.Instance.Field_23.Equals(__instance)) + { + return; + } + + if (!PluginSingleton.Instance.ModdedHandshake.Value) + { + return; + } + + ackCallback = (Action) (() => + { + PluginSingleton.Instance.Log.LogDebug("Hello was acked, waiting for modded handshake response"); + + Coroutines.Start(Coroutine()); + + __instance.add_Disconnected((Action) ((_, _) => + { + Coroutines.Stop(Coroutine()); + })); + }); + } + } + + private static IEnumerator Coroutine() + { + yield return new WaitForSeconds(3); + + var client = AmongUsClient.Instance; + + if (client != null && client.Field_23 != null && client.Field_23.State == ConnectionState.Connecting) + { + client.LastDisconnectReason = DisconnectReasons.Custom; + client.LastCustomDisconnect = "Server didn't respond to modded handshake"; + client.HandleDisconnect(client.LastDisconnectReason, client.LastCustomDisconnect); + } + } + + [HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.GetConnectionData))] + public static class HandshakePatch + { + public static bool Prefix(out Il2CppStructArray __result) + { + var handshake = MessageWriter.Get(SendOption.Reliable); + + ModdedHandshakeC2S.Serialize( + handshake, + Constants.GetBroadcastVersion(), + SaveManager.PlayerName, + ModList.GetCurrent() + ); + + __result = handshake.ToByteArray(false); + handshake.Recycle(); + + return false; + } + } + } +} diff --git a/Reactor/ReactorPlugin.cs b/Reactor/ReactorPlugin.cs index 4bbc862..1e59ef7 100644 --- a/Reactor/ReactorPlugin.cs +++ b/Reactor/ReactorPlugin.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using BepInEx; +using BepInEx.Configuration; using BepInEx.IL2CPP; using HarmonyLib; using Reactor.Extensions; @@ -21,12 +22,16 @@ namespace Reactor public Harmony Harmony { get; } = new Harmony(Id); public CustomRpcManager CustomRpcManager { get; } = new CustomRpcManager(); + public ConfigEntry ModdedHandshake; + private RegionInfoWatcher RegionInfoWatcher { get; } = new RegionInfoWatcher(); public override void Load() { RegisterInIl2CppAttribute.Register(); + ModdedHandshake = Config.Bind("Features", "Modded handshake", true); + var gameObject = new GameObject(nameof(ReactorPlugin)).DontDestroy(); gameObject.AddComponent().Plugin = this; @@ -54,6 +59,8 @@ namespace Reactor public void Start() { + Plugin.CustomRpcManager.ReloadPluginIdMap(); + Camera.onPostRender = Camera.onPostRender == null ? new Action(OnPostRenderM) : Il2CppSystem.Delegate.Combine(Camera.onPostRender, Il2CppSystem.Delegate.CreateDelegate(GetIl2CppType(), GetIl2CppType().GetMethod(nameof(OnPostRenderM), Il2CppSystem.Reflection.BindingFlags.Static | Il2CppSystem.Reflection.BindingFlags.Public))).Cast(); diff --git a/Reactor/ReactorPluginSideAttribute.cs b/Reactor/ReactorPluginSideAttribute.cs index 0a23ce4..af32b5f 100644 --- a/Reactor/ReactorPluginSideAttribute.cs +++ b/Reactor/ReactorPluginSideAttribute.cs @@ -24,7 +24,7 @@ namespace Reactor /// /// Plugin side used in modded handshake /// - public enum PluginSide + public enum PluginSide : byte { /// /// Required by both sides, reject connection if missing on the other side