diff --git a/AmongUs.props b/AmongUs.props index 932a068..3908f13 100644 --- a/AmongUs.props +++ b/AmongUs.props @@ -1,6 +1,6 @@ - 2021.3.5s + 2021.3.31.3s $(DefineConstants);STEAM @@ -10,11 +10,11 @@ - NuclearPowered/Mappings:0.2.0 + NuclearPowered/Mappings:0.3.0 - + diff --git a/Reactor.Debugger/DebuggerPlugin.cs b/Reactor.Debugger/DebuggerPlugin.cs index 9a659d6..36dc0df 100644 --- a/Reactor.Debugger/DebuggerPlugin.cs +++ b/Reactor.Debugger/DebuggerPlugin.cs @@ -3,6 +3,7 @@ using System.Linq; using BepInEx; using BepInEx.IL2CPP; using HarmonyLib; +using InnerNet; using Reactor.Extensions; using UnhollowerBaseLib; using UnhollowerBaseLib.Attributes; diff --git a/Reactor.Example/CustomRpcs.cs b/Reactor.Example/CustomRpcs.cs new file mode 100644 index 0000000..5568438 --- /dev/null +++ b/Reactor.Example/CustomRpcs.cs @@ -0,0 +1,7 @@ +namespace Reactor.Example +{ + public enum CustomRpcCalls : uint + { + Example + } +} diff --git a/Reactor.Example/ExamplePlugin.cs b/Reactor.Example/ExamplePlugin.cs index f64d39e..c729d2c 100644 --- a/Reactor.Example/ExamplePlugin.cs +++ b/Reactor.Example/ExamplePlugin.cs @@ -1,7 +1,9 @@ using System; using BepInEx; using BepInEx.IL2CPP; +using InnerNet; using Reactor.Extensions; +using Reactor.Networking; using UnhollowerBaseLib.Attributes; using UnityEngine; diff --git a/Reactor.Example/ExampleRpc.cs b/Reactor.Example/ExampleRpc.cs index f95bd45..f6f5899 100644 --- a/Reactor.Example/ExampleRpc.cs +++ b/Reactor.Example/ExampleRpc.cs @@ -1,11 +1,12 @@ using Hazel; +using Reactor.Networking; namespace Reactor.Example { - [RegisterCustomRpc] + [RegisterCustomRpc((uint) CustomRpcCalls.Example)] public class ExampleRpc : PlayerCustomRpc { - public ExampleRpc(ExamplePlugin plugin) : base(plugin) + public ExampleRpc(ExamplePlugin plugin, uint id) : base(plugin, id) { } diff --git a/Reactor.Shared/Networking/Mod.cs b/Reactor.Shared/Networking/Mod.cs new file mode 100644 index 0000000..4f58f79 --- /dev/null +++ b/Reactor.Shared/Networking/Mod.cs @@ -0,0 +1,40 @@ +#nullable enable + +using System; + +namespace Reactor.Networking +{ + public class Mod + { + public Mod(uint netId, string id, string version, PluginSide side) + { + NetId = netId; + Id = id; + Version = version; + Side = side; + } + + public uint NetId { get; } + public string Id { get; } + public string Version { get; } + public PluginSide Side { get; } + + protected bool Equals(Mod other) + { + return Id == other.Id && Version == other.Version; + } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != GetType()) return false; + return Equals((Mod) obj); + } + + public override int GetHashCode() + { + return HashCode.Combine(Id, Version); + } + } +} diff --git a/Reactor.Shared/Networking/ReactorMessageFlags.cs b/Reactor.Shared/Networking/ReactorMessageFlags.cs new file mode 100644 index 0000000..4e7462a --- /dev/null +++ b/Reactor.Shared/Networking/ReactorMessageFlags.cs @@ -0,0 +1,9 @@ +namespace Reactor.Networking +{ + public enum ReactorMessageFlags : byte + { + Handshake, + ModDeclaration, + PluginDeclaration + } +} diff --git a/Reactor.Shared/Networking/ReactorProtocolVersion.cs b/Reactor.Shared/Networking/ReactorProtocolVersion.cs new file mode 100644 index 0000000..dde00fe --- /dev/null +++ b/Reactor.Shared/Networking/ReactorProtocolVersion.cs @@ -0,0 +1,18 @@ +namespace Reactor.Networking +{ + /// + /// Version of the Reactor.Net protocol format. + /// + public enum ReactorProtocolVersion : byte + { + /// + /// Initial version. + /// + Initial = 1, + + /// + /// Latest version. + /// + Latest = Initial, + } +} diff --git a/Reactor.Shared/PluginSide.cs b/Reactor.Shared/PluginSide.cs new file mode 100644 index 0000000..f895e30 --- /dev/null +++ b/Reactor.Shared/PluginSide.cs @@ -0,0 +1,18 @@ +namespace Reactor +{ + /// + /// Plugin side used in modded handshake + /// + public enum PluginSide : byte + { + /// + /// Required by both sides, reject connection if missing on the other side + /// + Both, + + /// + /// Required only by client + /// + ClientOnly + } +} diff --git a/Reactor.Shared/Reactor.Shared.projitems b/Reactor.Shared/Reactor.Shared.projitems new file mode 100644 index 0000000..9c4059e --- /dev/null +++ b/Reactor.Shared/Reactor.Shared.projitems @@ -0,0 +1,5 @@ + + + + + diff --git a/Reactor.Shared/Reactor.Shared.shproj b/Reactor.Shared/Reactor.Shared.shproj new file mode 100644 index 0000000..43dae88 --- /dev/null +++ b/Reactor.Shared/Reactor.Shared.shproj @@ -0,0 +1,3 @@ + + + diff --git a/Reactor.sln b/Reactor.sln index 3cada7a..9dcf9fc 100644 --- a/Reactor.sln +++ b/Reactor.sln @@ -9,6 +9,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{E2F4DAFA EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Reactor.Example", "Reactor.Example\Reactor.Example.csproj", "{26A83466-26FE-4383-A35C-B8629902F909}" EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Reactor.Shared", "Reactor.Shared\Reactor.Shared.shproj", "{8C1EEFE9-EF8A-47C4-810E-6D9BA1E7218B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/Reactor/Coroutines.cs b/Reactor/Coroutines.cs index a4cfbee..a2af356 100644 --- a/Reactor/Coroutines.cs +++ b/Reactor/Coroutines.cs @@ -343,7 +343,7 @@ namespace Reactor } catch (Exception e) { - PluginSingleton.Instance.Log.LogError(e.ToString()); + Logger.Error(e.ToString()); Stop(FindOriginalCoroutine(enumerator)); // We want the entire coroutine hierarchy to stop when an error happen } @@ -366,7 +366,7 @@ namespace Reactor if (nextAsEnumerator != null) // il2cpp IEnumerator also handles CustomYieldInstruction next = new Il2CppEnumeratorWrapper(nextAsEnumerator); else - PluginSingleton.Instance.Log.LogWarning($"Unknown coroutine yield object of type {il2CppObjectBase} for coroutine {enumerator}"); + Logger.Warning($"Unknown coroutine yield object of type {il2CppObjectBase} for coroutine {enumerator}"); break; } diff --git a/Reactor/Extensions/Extensions.cs b/Reactor/Extensions/Extensions.cs index 124dc80..492a375 100644 --- a/Reactor/Extensions/Extensions.cs +++ b/Reactor/Extensions/Extensions.cs @@ -51,7 +51,7 @@ namespace Reactor.Extensions /// https://docs.unity3d.com/ScriptReference/ColorUtility.ToHtmlStringRGBA.html public static string ToHtmlStringRGBA(this Color32 color) { - return $"{color.r:X2}{color.g:X2}{color.b:X2}{color.a:X2}"; + return ColorUtility.ToHtmlStringRGBA(color); } /// diff --git a/Reactor/Extensions/MessageExtensions.cs b/Reactor/Extensions/ExtraMessageExtensions.cs similarity index 55% rename from Reactor/Extensions/MessageExtensions.cs rename to Reactor/Extensions/ExtraMessageExtensions.cs index 163ac41..73c587c 100644 --- a/Reactor/Extensions/MessageExtensions.cs +++ b/Reactor/Extensions/ExtraMessageExtensions.cs @@ -1,9 +1,11 @@ +using System; using Hazel; +using Hazel.Udp; using UnityEngine; namespace Reactor.Extensions { - public static class MessageExtensions + public static class ExtraMessageExtensions { private const float MIN = -50f; private const float MAX = 50f; @@ -30,15 +32,19 @@ namespace Reactor.Extensions return new Vector2(Mathf.Lerp(MIN, MAX, x), Mathf.Lerp(MIN, MAX, y)); } - // public static void Write(MessageWriter writer, InnerNetObject value) - // { - // global::MessageExtensions.WriteNetObject(writer, value); - // } + public static void Send(this UdpConnection connection, MessageWriter msg, Action ackCallback) + { + if (msg.SendOption != SendOption.Reliable) + throw new InvalidOperationException("Message SendOption has to be Reliable."); - // TODO OxygenFilter crashes, Mono.Cecil hates generic arguments, I have no idea why - // public static T ReadNetObject(MessageReader reader) where T : InnerNetObject - // { - // return global::MessageExtensions.ReadNetObject(reader); - // } + var buffer = new byte[msg.Length]; + Buffer.BlockCopy(msg.Buffer, 0, buffer, 0, msg.Length); + + connection.ResetKeepAliveTimer(); + + connection.AttachReliableID(buffer, 1, ackCallback); + connection.WriteBytesToConnection(buffer, buffer.Length); + connection.Statistics.LogReliableSend(buffer.Length - 3, buffer.Length); + } } } diff --git a/Reactor/Logger.cs b/Reactor/Logger.cs new file mode 100644 index 0000000..8b6a9a8 --- /dev/null +++ b/Reactor/Logger.cs @@ -0,0 +1,28 @@ +using BepInEx.IL2CPP; +using BepInEx.Logging; + +namespace Reactor +{ + public static class Logger where T : BasePlugin + { + public static ManualLogSource Instance => PluginSingleton.Instance.Log; + + /// + public static void Fatal(object data) => Instance.LogFatal(data); + + /// + public static void Error(object data) => Instance.LogError(data); + + /// + public static void Warning(object data) => Instance.LogWarning(data); + + /// + public static void Message(object data) => Instance.LogMessage(data); + + /// + public static void Info(object data) => Instance.LogInfo(data); + + /// + public static void Debug(object data) => Instance.LogDebug(data); + } +} diff --git a/Reactor/Net/Mod.cs b/Reactor/Net/Mod.cs deleted file mode 100644 index f6870dd..0000000 --- a/Reactor/Net/Mod.cs +++ /dev/null @@ -1,21 +0,0 @@ -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 deleted file mode 100644 index ebcaafd..0000000 --- a/Reactor/Net/ModList.cs +++ /dev/null @@ -1,34 +0,0 @@ -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 deleted file mode 100644 index d071ed3..0000000 --- a/Reactor/Net/ModdedHandshakeC2S.cs +++ /dev/null @@ -1,20 +0,0 @@ -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 deleted file mode 100644 index 63b9d9b..0000000 --- a/Reactor/Net/ModdedHandshakeS2C.cs +++ /dev/null @@ -1,14 +0,0 @@ -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/CustomRpc.cs b/Reactor/Networking/CustomRpc.cs similarity index 76% rename from Reactor/CustomRpc.cs rename to Reactor/Networking/CustomRpc.cs index 13fc10f..2bcf351 100644 --- a/Reactor/CustomRpc.cs +++ b/Reactor/Networking/CustomRpc.cs @@ -1,17 +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 InnerNet; -using Reactor.Net; -namespace Reactor +namespace Reactor.Networking { public enum RpcLocalHandling { @@ -22,9 +19,9 @@ namespace Reactor public abstract class UnsafeCustomRpc { - public int? Id { get; internal set; } - internal CustomRpcManager Manager { get; set; } + + public uint Id { get; } public BasePlugin UnsafePlugin { get; } public string PluginId { get; } @@ -33,10 +30,11 @@ namespace Reactor public virtual SendOption SendOption { get; } = SendOption.Reliable; public abstract RpcLocalHandling LocalHandling { get; } - protected UnsafeCustomRpc(BasePlugin plugin) + protected UnsafeCustomRpc(BasePlugin plugin, uint id) { UnsafePlugin = plugin; PluginId = MetadataHelper.GetMetadata(plugin).GUID; + Id = id; } public abstract void UnsafeWrite(MessageWriter writer, object data); @@ -47,7 +45,7 @@ namespace Reactor { if (netObject == null) throw new ArgumentNullException(nameof(netObject)); - if (Id == null) + if (Manager == null) { throw new InvalidOperationException("Can't send unregistered CustomRpc"); } @@ -63,17 +61,13 @@ namespace Reactor true => AmongUsClient.Instance.StartRpcImmediately(netObject.NetId, CustomRpcManager.CallId, SendOption, targetClientId) }; - if (Manager.PluginIdMap.TryGetValue(PluginId, out var pluginId)) - { - writer.WritePacked(pluginId); - } - else - { - writer.Write(PluginId); - } + var pluginNetId = ModList.Current.Single(x => x.Id == PluginId).NetId; + writer.WritePacked(pluginNetId); + writer.WritePacked(Id); - writer.WritePacked(Id!.Value); + writer.StartMessage(0); UnsafeWrite(writer, data); + writer.EndMessage(); if (immediately) { @@ -93,7 +87,7 @@ namespace Reactor public abstract class CustomRpc : UnsafeCustomRpc where TPlugin : BasePlugin where TInnerNetObject : InnerNetObject { - protected CustomRpc(TPlugin plugin) : base(plugin) + protected CustomRpc(TPlugin plugin, uint id) : base(plugin, id) { } @@ -132,7 +126,7 @@ namespace Reactor public abstract class PlayerCustomRpc : CustomRpc where TPlugin : BasePlugin { - protected PlayerCustomRpc(TPlugin plugin) : base(plugin) + protected PlayerCustomRpc(TPlugin plugin, uint id) : base(plugin, id) { } @@ -160,9 +154,6 @@ 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) @@ -171,28 +162,14 @@ namespace Reactor } } - public void ReloadPluginIdMap() - { - PluginIdMap = new Dictionary(); - PluginIdMapReversed = new Dictionary(); - - var i = -1; - - foreach (var mod in ModList.GetCurrent().OrderBy(x => x.Id)) - { - if (mod.Side == PluginSide.Both) - { - PluginIdMap[mod.Id] = i; - PluginIdMapReversed[i] = mod.Id; - i--; - } - } - } - public UnsafeCustomRpc Register(UnsafeCustomRpc customRpc) { + if (_list.Any(x => x.UnsafePlugin == customRpc.UnsafePlugin && x.Id == customRpc.Id)) + { + throw new ArgumentException("Rpc with that id was already registered"); + } + customRpc.Manager = this; - customRpc.Id = List.Count(x => x.UnsafePlugin == customRpc.UnsafePlugin); _list.Add(customRpc); _map[customRpc.InnerNetObjectType].Add(customRpc); @@ -211,15 +188,6 @@ 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) @@ -227,17 +195,13 @@ namespace Reactor var manager = PluginSingleton.Instance.CustomRpcManager; var customRpcs = manager.Map[__instance.GetType()]; - var lengthOrShortId = reader.ReadPackedInt32(); - - var pluginId = lengthOrShortId < 0 - ? manager.PluginIdMapReversed[lengthOrShortId] - : ReadString(reader, lengthOrShortId); - - var id = reader.ReadPackedInt32(); + var pluginNetId = reader.ReadPackedUInt32(); + var id = reader.ReadPackedUInt32(); + var pluginId = ModList.Current.Single(x => x.NetId == pluginNetId).Id; var customRpc = customRpcs.Single(x => x.PluginId == pluginId && x.Id == id); - customRpc.UnsafeHandle(__instance, customRpc.UnsafeRead(reader)); + customRpc.UnsafeHandle(__instance, customRpc.UnsafeRead(reader.ReadMessage())); return false; } diff --git a/Reactor/Networking/ModDeclaration.cs b/Reactor/Networking/ModDeclaration.cs new file mode 100644 index 0000000..51e2acd --- /dev/null +++ b/Reactor/Networking/ModDeclaration.cs @@ -0,0 +1,17 @@ +using Hazel; + +namespace Reactor.Networking +{ + public static class ModDeclaration + { + public static void Serialize(MessageWriter writer, Mod mod) + { + writer.StartMessage(byte.MaxValue); + writer.Write((byte) ReactorMessageFlags.ModDeclaration); + writer.WritePacked(mod.NetId); + writer.Write(mod.Id); + writer.Write(mod.Version); + writer.Write((byte) mod.Side); + } + } +} diff --git a/Reactor/Networking/ModList.cs b/Reactor/Networking/ModList.cs new file mode 100644 index 0000000..f8f9f44 --- /dev/null +++ b/Reactor/Networking/ModList.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using BepInEx.IL2CPP; + +namespace Reactor.Networking +{ + public static class ModList + { + public static ISet Current { get; private set; } + + internal static void Update() + { + var i = (uint) 0; + + Current = IL2CPPChainloader.Instance.Plugins.Values + .OrderByDescending(x => x.Metadata.GUID == ReactorPlugin.Id) + .Select(plugin => new Mod( + i++, + plugin.Metadata.GUID, + plugin.Metadata.Version.Clean(), + plugin.Instance.GetType().GetCustomAttribute()?.Side ?? PluginSide.Both + )) + .ToHashSet(); + } + } +} diff --git a/Reactor/Networking/ModdedHandshakeC2S.cs b/Reactor/Networking/ModdedHandshakeC2S.cs new file mode 100644 index 0000000..3b4fb79 --- /dev/null +++ b/Reactor/Networking/ModdedHandshakeC2S.cs @@ -0,0 +1,13 @@ +using Hazel; + +namespace Reactor.Networking +{ + public static class ModdedHandshakeC2S + { + public static void Serialize(MessageWriter writer, int modCount) + { + writer.Write((byte) ReactorProtocolVersion.Initial); + writer.WritePacked(modCount); + } + } +} diff --git a/Reactor/Networking/ModdedHandshakeS2C.cs b/Reactor/Networking/ModdedHandshakeS2C.cs new file mode 100644 index 0000000..58f89c2 --- /dev/null +++ b/Reactor/Networking/ModdedHandshakeS2C.cs @@ -0,0 +1,24 @@ +using Hazel; + +namespace Reactor.Networking +{ + public static class ModdedHandshakeS2C + { + public static void Serialize(MessageWriter writer, string serverName, string serverVersion, int pluginCount) + { + writer.StartMessage(byte.MaxValue); + writer.Write((byte) ReactorMessageFlags.Handshake); + writer.Write(serverName); + writer.Write(serverVersion); + writer.WritePacked(pluginCount); + writer.EndMessage(); + } + + public static void Deserialize(MessageReader reader, out string serverName, out string serverVersion, out int pluginCount) + { + serverName = reader.ReadString(); + serverVersion = reader.ReadString(); + pluginCount = reader.ReadPackedInt32(); + } + } +} diff --git a/Reactor/Networking/Patches/ClientPatches.cs b/Reactor/Networking/Patches/ClientPatches.cs new file mode 100644 index 0000000..1bfce88 --- /dev/null +++ b/Reactor/Networking/Patches/ClientPatches.cs @@ -0,0 +1,156 @@ +using System; +using System.Collections; +using HarmonyLib; +using Hazel; +using Hazel.Udp; +using InnerNet; +using Reactor.Extensions; +using UnhollowerBaseLib; +using UnityEngine; + +namespace Reactor.Networking.Patches +{ + internal static class ClientPatches + { + [HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.HandleMessage))] + public static class HandleMessagePatch + { + public static bool Prefix(InnerNetClient __instance, [HarmonyArgument(0)] MessageReader reader) + { + if (reader.Tag == byte.MaxValue) + { + var flag = (ReactorMessageFlags) reader.ReadByte(); + + switch (flag) + { + case ReactorMessageFlags.Handshake: + { + ModdedHandshakeS2C.Deserialize(reader, out var serverName, out var serverVersion, out var pluginCount); + + Logger.Info($"Connected to a modded server ({serverName} {serverVersion}, {pluginCount} plugins), sending mod declarations"); + + var mods = ModList.Current; + + var writer = MessageWriter.Get(SendOption.Reliable); + + var expected = 0; + var got = 0; + + void Send() + { + expected++; + + __instance.connection.Send(writer, () => + { + got++; + + if (got >= expected) + { + Logger.Debug("Received all acks"); + __instance.connection.State = ConnectionState.Connected; + } + }); + writer.Recycle(); + } + + foreach (var mod in mods) + { + ModDeclaration.Serialize(writer, mod); + + if (writer.Length >= 500) + { + writer.CancelMessage(); + + Send(); + + writer = MessageWriter.Get(SendOption.Reliable); + ModDeclaration.Serialize(writer, mod); + } + + writer.EndMessage(); + } + + Send(); + + break; + } + + case ReactorMessageFlags.PluginDeclaration: + { + // TODO + break; + } + + default: + throw new ArgumentOutOfRangeException(); + } + + 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.connection == null || !AmongUsClient.Instance.connection.Equals(__instance)) + { + return; + } + + ackCallback = (Action) (() => + { + if (__instance.State == ConnectionState.Connected) + return; + + Logger.Debug("Hello was acked, waiting for modded handshake response"); + + var coroutine = Coroutines.Start(Coroutine(__instance)); + + __instance.Disconnected = (Action) ((_, _) => + { + Coroutines.Stop(coroutine); + }); + }); + } + + private static IEnumerator Coroutine(UdpConnection connection) + { + yield return new WaitForSeconds(3); + + var client = AmongUsClient.Instance; + if (client != null && client.connection != null && client.connection.Equals(connection) && client.connection.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 void Postfix(ref Il2CppStructArray __result) + { + ModList.Update(); + + var handshake = MessageWriter.Get(SendOption.Reliable); + + handshake.Write(__result); + + ModdedHandshakeC2S.Serialize( + handshake, + ModList.Current.Count + ); + + __result = handshake.ToByteArray(false); + handshake.Recycle(); + } + } + } +} diff --git a/Reactor/Networking/Patches/ServerPatches.cs b/Reactor/Networking/Patches/ServerPatches.cs new file mode 100644 index 0000000..e52533c --- /dev/null +++ b/Reactor/Networking/Patches/ServerPatches.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using System.Reflection; +using HarmonyLib; +using Hazel; +using InnerNet; +using Reactor.Extensions; +using UnityEngine; + +namespace Reactor.Networking.Patches +{ + internal static class ServerPatches + { + // [HarmonyPatch(typeof(InnerNetServer), nameof(InnerNetServer.OnServerConnect))] + [HarmonyPatch] + public static class HandshakeResponsePatch + { + public static IEnumerable TargetMethods() + { + return typeof(InnerNetServer).GetMethods(typeof(void), typeof(NewConnectionEventArgs)); + } + + public static void Postfix([HarmonyArgument(0)] NewConnectionEventArgs evt) + { + var writer = MessageWriter.Get(SendOption.Reliable); + ModdedHandshakeS2C.Serialize(writer, "Among Us", Application.version, 0); + evt.Connection.Send(writer); + writer.Recycle(); + } + } + } +} diff --git a/Reactor/Patches/KillButtonPatches.cs b/Reactor/Patches/KillButtonPatches.cs deleted file mode 100644 index adc6f65..0000000 --- a/Reactor/Patches/KillButtonPatches.cs +++ /dev/null @@ -1,33 +0,0 @@ -using HarmonyLib; - -namespace Reactor.Patches -{ - /// - /// Fixes kill cooldown resetting for a few frames if anyone on the map got the kill - /// Also fixes invisible kill button if KillCooldown is set to 0 - /// - internal class KillButtonPatches - { - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.SetKillTimer))] - [HarmonyPriority(Priority.First)] - public static class SetKillTimerPatch - { - public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] float time) - { - __instance.killTimer = time; - - // Patched because of this - game doesn't check if you're the killer - if (__instance.AmOwner) - { - HudManager.Instance.KillButton.SetCoolDown( - time, - // if MaxTimer is 0, kill button disappears - PlayerControl.GameOptions.KillCooldown <= 0 ? 1 : PlayerControl.GameOptions.KillCooldown - ); - } - - return false; - } - } - } -} diff --git a/Reactor/Patches/ModdedHandshakePatches.cs b/Reactor/Patches/ModdedHandshakePatches.cs deleted file mode 100644 index 099add6..0000000 --- a/Reactor/Patches/ModdedHandshakePatches.cs +++ /dev/null @@ -1,131 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Reflection; -using HarmonyLib; -using Hazel; -using Hazel.Udp; -using InnerNet; -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.connection.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.connection == null || !AmongUsClient.Instance.connection.Equals(__instance)) - { - return; - } - - if (!PluginSingleton.Instance.ModdedHandshake.Value) - { - return; - } - - ackCallback = (Action) (() => - { - if (__instance.State == ConnectionState.Connected) - return; - - 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.connection != null && client.connection.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.GetBroadcastVersionBytes(), - SaveManager.PlayerName, - ModList.GetCurrent() - ); - - __result = handshake.ToByteArray(false); - handshake.Recycle(); - - return false; - } - } - } -} diff --git a/Reactor/Patches/ReactorVersionShower.cs b/Reactor/Patches/ReactorVersionShower.cs index ea345fa..3843700 100644 --- a/Reactor/Patches/ReactorVersionShower.cs +++ b/Reactor/Patches/ReactorVersionShower.cs @@ -2,6 +2,7 @@ using System; using System.Reflection; using BepInEx; using BepInEx.IL2CPP; +using HarmonyLib; using Reactor.Extensions; using UnityEngine; using UnityEngine.SceneManagement; @@ -53,5 +54,15 @@ namespace Reactor.Patches Text.Text += "\nMods: " + IL2CPPChainloader.Instance.Plugins.Count; TextUpdated?.Invoke(Text); } + + [HarmonyPatch(typeof(FreeWeekendShower), nameof(FreeWeekendShower.Start))] + private static class FreeWeekendShowerPatch + { + public static bool Prefix(FreeWeekendShower __instance) + { + __instance.Output.Destroy(); + return false; + } + } } } diff --git a/Reactor/Patches/UiFocusPatch.cs b/Reactor/Patches/UiFocusPatch.cs deleted file mode 100644 index eae3a22..0000000 --- a/Reactor/Patches/UiFocusPatch.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Collections.Generic; -using System.Reflection; -using HarmonyLib; -using UnityEngine; -using Reactor.Extensions; - -namespace Reactor.Patches -{ - internal static class UiFocusPatch - { - // [HarmonyPatch(typeof(PassiveButtonManager), nameof(PassiveButtonManager.HandleMouseOver))] - [HarmonyPatch] - public static class HandleFocusPatch - { - public static IEnumerable TargetMethods() - { - return typeof(PassiveButtonManager).GetMethods(typeof(void), typeof(PassiveUiElement), typeof(Collider2D)); - } - - public static bool Prefix() - { - return Application.isFocused; - } - } - } -} diff --git a/Reactor/Reactor.csproj b/Reactor/Reactor.csproj index b898204..14a4cff 100644 --- a/Reactor/Reactor.csproj +++ b/Reactor/Reactor.csproj @@ -3,7 +3,7 @@ netstandard2.1 latest - 0.3.0 + 0.4.0 dev Core mod and API for Among Us NuclearPowered @@ -14,4 +14,6 @@ + + diff --git a/Reactor/ReactorPlugin.cs b/Reactor/ReactorPlugin.cs index 5c47099..0ecf764 100644 --- a/Reactor/ReactorPlugin.cs +++ b/Reactor/ReactorPlugin.cs @@ -1,10 +1,10 @@ using System; using System.Linq; using BepInEx; -using BepInEx.Configuration; using BepInEx.IL2CPP; using HarmonyLib; using Reactor.Extensions; +using Reactor.Networking; using Reactor.Patches; using Reactor.Unstrip; using UnhollowerBaseLib.Attributes; @@ -14,7 +14,6 @@ namespace Reactor { [BepInPlugin(Id)] [BepInProcess("Among Us.exe")] - [ReactorPluginSide(PluginSide.ClientOnly)] public class ReactorPlugin : BasePlugin { public const string Id = "gg.reactor.api"; @@ -22,8 +21,6 @@ namespace Reactor public Harmony Harmony { get; } = new Harmony(Id); public CustomRpcManager CustomRpcManager { get; } = new CustomRpcManager(); - public ConfigEntry ModdedHandshake; - private GameObject _gameObject; private RegionInfoWatcher RegionInfoWatcher { get; } = new RegionInfoWatcher(); @@ -31,8 +28,6 @@ namespace Reactor { RegisterInIl2CppAttribute.Register(); - ModdedHandshake = Config.Bind("Features", "Modded handshake", true); - _gameObject = new GameObject(nameof(ReactorPlugin)).DontDestroy(); _gameObject.AddComponent().Plugin = this; _gameObject.AddComponent(); @@ -61,11 +56,6 @@ namespace Reactor { } - private void Start() - { - Plugin.CustomRpcManager.ReloadPluginIdMap(); - } - private void Update() { if (Plugin.RegionInfoWatcher.Reload) diff --git a/Reactor/ReactorPluginSideAttribute.cs b/Reactor/ReactorPluginSideAttribute.cs index af32b5f..9ec7e2c 100644 --- a/Reactor/ReactorPluginSideAttribute.cs +++ b/Reactor/ReactorPluginSideAttribute.cs @@ -12,33 +12,7 @@ namespace Reactor public ReactorPluginSideAttribute(PluginSide side) { - if (side == PluginSide.ServerOnly) - { - throw new ArgumentException("BepInEx plugin can't be server only!"); - } - Side = side; } } - - /// - /// Plugin side used in modded handshake - /// - public enum PluginSide : byte - { - /// - /// Required by both sides, reject connection if missing on the other side - /// - Both, - - /// - /// Required only by client - /// - ClientOnly, - - /// - /// Required only by server - /// - ServerOnly - } } diff --git a/Reactor/RegisterCustomRpcAttribute.cs b/Reactor/RegisterCustomRpcAttribute.cs index cec430e..09f144a 100644 --- a/Reactor/RegisterCustomRpcAttribute.cs +++ b/Reactor/RegisterCustomRpcAttribute.cs @@ -2,6 +2,7 @@ using System; using System.Reflection; using BepInEx.IL2CPP; using HarmonyLib; +using Reactor.Networking; namespace Reactor { @@ -11,6 +12,13 @@ namespace Reactor [AttributeUsage(AttributeTargets.Class)] public class RegisterCustomRpcAttribute : Attribute { + public uint Id { get; } + + public RegisterCustomRpcAttribute(uint id) + { + Id = id; + } + public static void Register(BasePlugin plugin) { Register(Assembly.GetCallingAssembly(), plugin); @@ -29,7 +37,7 @@ namespace Reactor throw new InvalidOperationException($"Type {type.FullDescription()} has {nameof(RegisterCustomRpcAttribute)} but doesn't extend {nameof(UnsafeCustomRpc)}."); } - var customRpc = (UnsafeCustomRpc) Activator.CreateInstance(type, plugin); + var customRpc = (UnsafeCustomRpc) Activator.CreateInstance(type, plugin, attribute.Id); PluginSingleton.Instance.CustomRpcManager.Register(customRpc); } } diff --git a/Reactor/RegisterInIl2CppAttribute.cs b/Reactor/RegisterInIl2CppAttribute.cs index 3b046a9..43842ed 100644 --- a/Reactor/RegisterInIl2CppAttribute.cs +++ b/Reactor/RegisterInIl2CppAttribute.cs @@ -64,7 +64,7 @@ namespace Reactor } catch (Exception e) { - PluginSingleton.Instance.Log.LogWarning($"Failed to register {type.FullDescription()}: {e}"); + Logger.Warning($"Failed to register {type.FullDescription()}: {e}"); } }