mirror of
https://github.com/NuclearPowered/Reactor.git
synced 2026-10-19 15:51:54 +00:00
Protocol v3
This commit is contained in:
25 files changed
+755
-307
No files matched your search
@@ -14,7 +14,6 @@ namespace Reactor.Debugger;
|
||||
[BepInAutoPlugin("gg.reactor.debugger")]
|
||||
[BepInProcess("Among Us.exe")]
|
||||
[BepInDependency(ReactorPlugin.Id)]
|
||||
[ReactorPluginSide(PluginSide.ClientOnly)]
|
||||
public partial class DebuggerPlugin : BasePlugin
|
||||
{
|
||||
public Harmony Harmony { get; } = new(Id);
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace Reactor.Example;
|
||||
[BepInAutoPlugin("gg.reactor.Example")]
|
||||
[BepInProcess("Among Us.exe")]
|
||||
[BepInDependency(ReactorPlugin.Id)]
|
||||
[ReactorModFlags(ModFlags.RequireOnAllClients)]
|
||||
public partial class ExamplePlugin : BasePlugin
|
||||
{
|
||||
public override void Load()
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
|
||||
namespace Reactor;
|
||||
|
||||
[Flags]
|
||||
public enum ModFlags : ushort
|
||||
{
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Requires all clients in a lobby to have the mod.
|
||||
/// </summary>
|
||||
RequireOnAllClients = 1 << 0,
|
||||
|
||||
/// <summary>
|
||||
/// Requires the server to have a plugin that handles the mod.
|
||||
/// </summary>
|
||||
RequireOnServer = 1 << 1,
|
||||
|
||||
/// <summary>
|
||||
/// Requires the host of the lobby to have the mod.
|
||||
/// </summary>
|
||||
RequireOnHost = 1 << 2,
|
||||
}
|
||||
@@ -1,39 +1,78 @@
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Reactor.Networking;
|
||||
|
||||
public class Mod
|
||||
public struct Mod
|
||||
{
|
||||
public Mod(uint netId, string id, string version, PluginSide side)
|
||||
public Mod(string id, string version, ModFlags flags, string? name = null)
|
||||
{
|
||||
NetId = netId;
|
||||
Id = id;
|
||||
Version = version;
|
||||
Side = side;
|
||||
Flags = flags;
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public uint NetId { get; }
|
||||
public string Id { get; }
|
||||
public string Version { get; }
|
||||
public PluginSide Side { get; }
|
||||
public ModFlags Flags { get; }
|
||||
public string? Name { get; }
|
||||
|
||||
protected bool Equals(Mod other)
|
||||
public bool IsRequiredOnAllClients => (Flags & ModFlags.RequireOnAllClients) != 0;
|
||||
|
||||
public 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);
|
||||
return obj is Mod other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(Id, Version);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Name ?? Id} ({Version})";
|
||||
}
|
||||
|
||||
internal static bool Validate(IReadOnlyCollection<Mod> clientMods, IReadOnlyCollection<Mod> hostMods, [NotNullWhen(false)] out string? reason)
|
||||
{
|
||||
var clientMissing = hostMods.Where(mod => mod.IsRequiredOnAllClients && !clientMods.Contains(mod)).ToArray();
|
||||
var hostMissing = clientMods.Where(mod => mod.IsRequiredOnAllClients && !hostMods.Contains(mod)).ToArray();
|
||||
|
||||
if (clientMissing.Any() || hostMissing.Any())
|
||||
{
|
||||
var message = new StringBuilder();
|
||||
|
||||
if (clientMissing.Any())
|
||||
{
|
||||
message.Append("You are missing: ");
|
||||
message.AppendJoin(", ", clientMissing.Select(x => x.ToString()));
|
||||
message.AppendLine();
|
||||
}
|
||||
|
||||
if (hostMissing.Any())
|
||||
{
|
||||
message.Append("Host is missing: ");
|
||||
message.AppendJoin(", ", hostMissing.Select(x => x.ToString()));
|
||||
message.AppendLine();
|
||||
}
|
||||
|
||||
reason = message.ToString();
|
||||
return false;
|
||||
}
|
||||
|
||||
reason = null;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Reactor.Networking;
|
||||
|
||||
public enum ReactorGameDataFlag : byte
|
||||
{
|
||||
KickWithReason,
|
||||
}
|
||||
@@ -3,6 +3,4 @@ namespace Reactor.Networking;
|
||||
public enum ReactorMessageFlags : byte
|
||||
{
|
||||
Handshake,
|
||||
ModDeclaration,
|
||||
PluginDeclaration,
|
||||
}
|
||||
@@ -1,17 +1,22 @@
|
||||
namespace Reactor.Networking;
|
||||
|
||||
/// <summary>
|
||||
/// Version of the Reactor.Net protocol format.
|
||||
/// Version of the Reactor.Networking protocol format.
|
||||
/// </summary>
|
||||
public enum ReactorProtocolVersion : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Initial version.
|
||||
/// First public Reactor Protocol version.
|
||||
/// </summary>
|
||||
Initial = 1,
|
||||
V2 = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Version introducing vanilla server support, syncer concept and registries.
|
||||
/// </summary>
|
||||
V3 = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Latest version.
|
||||
/// </summary>
|
||||
Latest = Initial,
|
||||
Latest = V3,
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
namespace Reactor;
|
||||
|
||||
/// <summary>
|
||||
/// Plugin side used in modded handshake
|
||||
/// </summary>
|
||||
public enum PluginSide : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Required by both sides, reject connection if missing on the other side
|
||||
/// </summary>
|
||||
Both,
|
||||
|
||||
/// <summary>
|
||||
/// Required only by client
|
||||
/// </summary>
|
||||
ClientOnly,
|
||||
}
|
||||
@@ -4,6 +4,7 @@ using System.Linq;
|
||||
using System.Reflection;
|
||||
using HarmonyLib;
|
||||
using Hazel;
|
||||
using Il2CppInterop.Generator.Extensions;
|
||||
using InnerNet;
|
||||
|
||||
namespace Reactor.Networking;
|
||||
@@ -20,32 +21,17 @@ public class CustomRpcManager
|
||||
public const byte CallId = byte.MaxValue;
|
||||
|
||||
private readonly List<UnsafeCustomRpc> _list = new();
|
||||
private readonly Dictionary<Type, List<UnsafeCustomRpc>> _map = new();
|
||||
private readonly Dictionary<Type, Dictionary<Mod, Dictionary<uint, UnsafeCustomRpc>>> _map = new();
|
||||
|
||||
public IReadOnlyList<UnsafeCustomRpc> List => _list.AsReadOnly();
|
||||
|
||||
public ILookup<Type, UnsafeCustomRpc> Map =>
|
||||
_map.SelectMany(pair => pair.Value, (pair, value) => new { pair.Key, Value = value })
|
||||
.ToLookup(pair => pair.Key, pair => pair.Value);
|
||||
|
||||
public CustomRpcManager()
|
||||
{
|
||||
foreach (var type in HandleRpcPatch.InnerNetObjectTypes)
|
||||
{
|
||||
_map[type] = new List<UnsafeCustomRpc>();
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
_list.Add(customRpc);
|
||||
_map[customRpc.InnerNetObjectType].Add(customRpc);
|
||||
_map.GetOrCreate(customRpc.InnerNetObjectType, _ => new Dictionary<Mod, Dictionary<uint, UnsafeCustomRpc>>())
|
||||
.GetOrCreate(customRpc.Mod, _ => new Dictionary<uint, UnsafeCustomRpc>())
|
||||
.Add(customRpc.Id, customRpc);
|
||||
|
||||
if (customRpc.IsSingleton)
|
||||
{
|
||||
@@ -67,24 +53,28 @@ public class CustomRpcManager
|
||||
|
||||
public static bool Prefix(InnerNetObject __instance, [HarmonyArgument(0)] byte callId, [HarmonyArgument(1)] MessageReader reader)
|
||||
{
|
||||
if (callId == CallId)
|
||||
{
|
||||
var manager = PluginSingleton<ReactorPlugin>.Instance.CustomRpcManager;
|
||||
var customRpcs = manager.Map[__instance.GetType()];
|
||||
return !HandleRpc(__instance, callId, reader);
|
||||
}
|
||||
}
|
||||
|
||||
var pluginNetId = reader.ReadPackedUInt32();
|
||||
var id = reader.ReadPackedUInt32();
|
||||
public static bool HandleRpc(InnerNetObject innerNetObject, byte callId, MessageReader reader)
|
||||
{
|
||||
if (callId == CallId)
|
||||
{
|
||||
var manager = PluginSingleton<ReactorPlugin>.Instance.CustomRpcManager;
|
||||
var customRpcs = manager._map[innerNetObject.GetType()];
|
||||
|
||||
var pluginId = ModList.GetByNetId(pluginNetId).Id;
|
||||
var customRpc = customRpcs.Single(x => x.PluginId == pluginId && x.Id == id);
|
||||
var mod = reader.ReadMod();
|
||||
var id = reader.ReadPackedUInt32();
|
||||
|
||||
customRpc.UnsafeHandle(__instance, customRpc.UnsafeRead(reader.ReadMessage()));
|
||||
var customRpc = customRpcs[mod][id];
|
||||
|
||||
return false;
|
||||
}
|
||||
customRpc.UnsafeHandle(innerNetObject, customRpc.UnsafeRead(reader.ReadMessage()));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
using Hazel;
|
||||
using InnerNet;
|
||||
|
||||
namespace Reactor.Networking;
|
||||
|
||||
public static class Extensions
|
||||
{
|
||||
public static void KickWithReason(this InnerNetClient innerNetClient, int targetClientId, string reason)
|
||||
{
|
||||
var writer = MessageWriter.Get(SendOption.Reliable);
|
||||
writer.StartMessage(Tags.GameDataTo);
|
||||
writer.Write(innerNetClient.GameId);
|
||||
writer.WritePacked(targetClientId);
|
||||
{
|
||||
writer.StartMessage(byte.MaxValue);
|
||||
writer.Write((byte) ReactorGameDataFlag.KickWithReason);
|
||||
writer.Write(reason);
|
||||
writer.EndMessage();
|
||||
}
|
||||
writer.EndMessage();
|
||||
innerNetClient.SendOrDisconnect(writer);
|
||||
writer.Recycle();
|
||||
}
|
||||
|
||||
public static void DisconnectWithReason(this InnerNetClient innerNetClient, string reason)
|
||||
{
|
||||
innerNetClient.LastCustomDisconnect = reason;
|
||||
innerNetClient.HandleDisconnect(innerNetClient.LastDisconnectReason = DisconnectReasons.Custom, reason);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,43 +1,121 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using BepInEx.Unity.IL2CPP;
|
||||
using Hazel;
|
||||
using Reactor.Networking.Patches;
|
||||
|
||||
namespace Reactor.Networking;
|
||||
|
||||
public static class ModList
|
||||
{
|
||||
private static Dictionary<string, Mod>? _mapById;
|
||||
private static Dictionary<uint, Mod>? _mapByNetId;
|
||||
public static IReadOnlyCollection<Mod> Current { get; private set; } = null!;
|
||||
|
||||
public static ISet<Mod>? Current { get; private set; }
|
||||
private static readonly Dictionary<string, Mod> _mapById = new();
|
||||
private static readonly Dictionary<Type, Mod> _mapByPluginType = new();
|
||||
|
||||
public static Mod GetById(string id)
|
||||
{
|
||||
return _mapById![id];
|
||||
return _mapById[id];
|
||||
}
|
||||
|
||||
public static Mod GetByNetId(uint netId)
|
||||
internal static Mod GetByPluginType(Type pluginType)
|
||||
{
|
||||
return _mapByNetId![netId];
|
||||
return _mapByPluginType[pluginType];
|
||||
}
|
||||
|
||||
internal static ISet<Mod> Update()
|
||||
private static readonly Dictionary<uint, Mod> _mapByNetId = new();
|
||||
private static readonly Dictionary<Mod, uint> _netIdMap = new();
|
||||
|
||||
internal static Mod GetByNetId(uint netId)
|
||||
{
|
||||
var i = (uint) 0;
|
||||
return _mapByNetId[netId];
|
||||
}
|
||||
|
||||
internal static uint GetNetId(this Mod mod)
|
||||
{
|
||||
if (!mod.IsRequiredOnAllClients) throw new ArgumentException("Cannot get a net id for a mod without RequireOnAllClients flag", nameof(mod));
|
||||
return _netIdMap[mod];
|
||||
}
|
||||
|
||||
public static Mod ReadMod(this MessageReader reader)
|
||||
{
|
||||
var netId = reader.ReadPackedUInt32();
|
||||
if (netId > 0)
|
||||
{
|
||||
return GetByNetId(netId);
|
||||
}
|
||||
|
||||
var id = reader.ReadString();
|
||||
return GetById(id);
|
||||
}
|
||||
|
||||
public static void Write(this MessageWriter writer, Mod mod)
|
||||
{
|
||||
if (mod.IsRequiredOnAllClients)
|
||||
{
|
||||
writer.WritePacked(mod.GetNetId());
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WritePacked(0);
|
||||
writer.Write(mod.Id);
|
||||
}
|
||||
}
|
||||
|
||||
private static void Refresh()
|
||||
{
|
||||
if (ReactorConnection.Instance != null) throw new InvalidOperationException("Can't refresh the mod list during a connection");
|
||||
|
||||
Current = IL2CPPChainloader.Instance.Plugins.Values
|
||||
.OrderByDescending(x => x.Metadata.GUID == ReactorPlugin.Id)
|
||||
.Select(plugin => new Mod(
|
||||
i++,
|
||||
plugin.Metadata.GUID,
|
||||
plugin.Metadata.Version.Clean(),
|
||||
ReactorPluginSideAttribute.GetPluginSide(plugin.Instance.GetType())
|
||||
))
|
||||
.Select(pluginInfo => GetById(pluginInfo.Metadata.GUID))
|
||||
.OrderByDescending(x => x.Id == ReactorPlugin.Id)
|
||||
.ThenBy(x => x.Id, StringComparer.Ordinal)
|
||||
.ToHashSet();
|
||||
|
||||
_mapById = Current.ToDictionary(mod => mod.Id, mod => mod);
|
||||
_mapByNetId = Current.ToDictionary(mod => mod.NetId, mod => mod);
|
||||
_mapByNetId.Clear();
|
||||
_netIdMap.Clear();
|
||||
|
||||
return Current;
|
||||
uint netId = 1;
|
||||
foreach (var mod in Current)
|
||||
{
|
||||
if (!mod.IsRequiredOnAllClients) continue;
|
||||
|
||||
_mapByNetId[netId] = mod;
|
||||
_netIdMap[mod] = netId;
|
||||
netId++;
|
||||
}
|
||||
|
||||
var debug = new StringBuilder("Mod list:");
|
||||
foreach (var mod in Current)
|
||||
{
|
||||
debug.AppendLine();
|
||||
debug.Append($" - {mod.Id} version: {mod.Version}, flags: {mod.Flags}");
|
||||
if (mod.IsRequiredOnAllClients) debug.Append($", netId: {mod.GetNetId()}");
|
||||
}
|
||||
|
||||
Debug(debug.ToString());
|
||||
}
|
||||
|
||||
internal static void Initialize()
|
||||
{
|
||||
IL2CPPChainloader.Instance.PluginLoad += (pluginInfo, _, plugin) =>
|
||||
{
|
||||
var pluginType = plugin.GetType();
|
||||
|
||||
var mod = new Mod(
|
||||
pluginInfo.Metadata.GUID,
|
||||
pluginInfo.Metadata.Version.Clean(),
|
||||
ReactorModFlagsAttribute.GetModFlags(pluginType),
|
||||
pluginInfo.Metadata.Name
|
||||
);
|
||||
|
||||
_mapById[mod.Id] = mod;
|
||||
_mapByPluginType[pluginType] = mod;
|
||||
};
|
||||
|
||||
IL2CPPChainloader.Instance.Finished += Refresh;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,35 @@
|
||||
using System.Collections.Generic;
|
||||
using Hazel;
|
||||
|
||||
namespace Reactor.Networking;
|
||||
|
||||
public static class ModdedHandshakeC2S
|
||||
{
|
||||
public static void Serialize(MessageWriter writer, int modCount)
|
||||
public static void Serialize(MessageWriter writer, IReadOnlyCollection<Mod> mods)
|
||||
{
|
||||
writer.Write((byte) ReactorProtocolVersion.Initial);
|
||||
writer.WritePacked(modCount);
|
||||
writer.WritePacked(mods.Count);
|
||||
foreach (var mod in mods)
|
||||
{
|
||||
writer.Write(mod.Id);
|
||||
writer.Write(mod.Version);
|
||||
writer.Write((ushort) mod.Flags);
|
||||
if (mod.IsRequiredOnAllClients) writer.Write(mod.Name);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Deserialize(MessageReader reader, out Mod[] mods)
|
||||
{
|
||||
var modCount = reader.ReadPackedInt32();
|
||||
mods = new Mod[modCount];
|
||||
|
||||
for (var i = 0; i < modCount; i++)
|
||||
{
|
||||
var id = reader.ReadString();
|
||||
var version = reader.ReadString();
|
||||
var flags = (ModFlags) reader.ReadUInt16();
|
||||
var name = (flags & ModFlags.RequireOnAllClients) != 0 ? reader.ReadString() : null;
|
||||
|
||||
mods[i] = new Mod(id, version, flags, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,241 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using HarmonyLib;
|
||||
using Hazel;
|
||||
using Hazel.Udp;
|
||||
using Il2CppInterop.Runtime;
|
||||
using Il2CppInterop.Runtime.InteropTypes.Arrays;
|
||||
using InnerNet;
|
||||
using Reactor.Extensions;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Reactor.Networking.Patches;
|
||||
|
||||
internal static class ClientPatches
|
||||
{
|
||||
[HarmonyPatch(typeof(InnerNetClient._HandleGameDataInner_d__38), nameof(InnerNetClient._HandleGameDataInner_d__38.MoveNext))]
|
||||
public static class HandleGameDataInnerPatch
|
||||
{
|
||||
public static bool Prefix(InnerNetClient._HandleGameDataInner_d__38 __instance, ref bool __result)
|
||||
{
|
||||
var innerNetClient = __instance.__4__this;
|
||||
var reader = __instance.reader;
|
||||
|
||||
if (__instance.__1__state != 0) return true;
|
||||
|
||||
if (reader.Tag == byte.MaxValue)
|
||||
{
|
||||
var flag = (ReactorGameDataFlag) reader.ReadByte();
|
||||
switch (flag)
|
||||
{
|
||||
case ReactorGameDataFlag.KickWithReason:
|
||||
{
|
||||
var reason = reader.ReadString();
|
||||
Debug("Received KickWithReason: " + reason);
|
||||
innerNetClient.DisconnectWithReason(reason);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
reader.Recycle();
|
||||
|
||||
__result = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ReactorConnection.Instance!.Syncer == Syncer.Host && reader.Tag == InnerNetClient.SceneChangeFlag)
|
||||
{
|
||||
var clientId = reader.ReadPackedInt32();
|
||||
var clientData = innerNetClient.FindClientById(clientId);
|
||||
var sceneName = reader.ReadString();
|
||||
|
||||
if (clientData != null && !string.IsNullOrWhiteSpace(sceneName))
|
||||
{
|
||||
ReactorClientData? reactorClientData = null;
|
||||
|
||||
// PATCH - Read injected ReactorHandshakeC2S
|
||||
{
|
||||
if (reader.BytesRemaining >= ReactorHeader.Size && ReactorHeader.Read(reader))
|
||||
{
|
||||
ModdedHandshakeC2S.Deserialize(reader, out var mods);
|
||||
|
||||
reactorClientData = new ReactorClientData(clientData, mods);
|
||||
ReactorClientData.Set(clientId, reactorClientData);
|
||||
|
||||
Debug("Received reactor handshake for " + clientData.PlayerName);
|
||||
}
|
||||
|
||||
if (innerNetClient.AmHost)
|
||||
{
|
||||
if (reactorClientData == null)
|
||||
{
|
||||
Warning("Kicking " + clientData.PlayerName + " for not having Reactor installed");
|
||||
PlayerControl.LocalPlayer.RpcSendChat(clientData.PlayerName + " tried joining without Reactor installed");
|
||||
innerNetClient.KickPlayer(clientData.Id, false);
|
||||
|
||||
__result = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Mod.Validate(reactorClientData.Mods, ModList.Current, out var reason))
|
||||
{
|
||||
innerNetClient.KickWithReason(clientData.Id, reason);
|
||||
__result = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
innerNetClient.StartCoroutine(innerNetClient.CoOnPlayerChangedScene(clientData, sceneName));
|
||||
}
|
||||
else
|
||||
{
|
||||
UnityEngine.Debug.Log($"Couldn't find client {clientId} to change scene to {sceneName}");
|
||||
reader.Recycle();
|
||||
}
|
||||
|
||||
__result = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(InnerNetClient._CoSendSceneChange_d__29), nameof(InnerNetClient._CoSendSceneChange_d__29.MoveNext))]
|
||||
public static class CoSendSceneChangePatch
|
||||
{
|
||||
public static bool Prefix(InnerNetClient._CoSendSceneChange_d__29 __instance, ref bool __result)
|
||||
{
|
||||
if (ReactorConnection.Instance!.Syncer != Syncer.Host) return true;
|
||||
|
||||
var innerNetClient = __instance.__4__this;
|
||||
|
||||
if (__instance.__1__state == 2)
|
||||
{
|
||||
if (!innerNetClient.AmHost && innerNetClient.connection.State == ConnectionState.Connected)
|
||||
{
|
||||
var writer = MessageWriter.Get(SendOption.Reliable);
|
||||
writer.StartMessage(Tags.GameData);
|
||||
writer.Write(innerNetClient.GameId);
|
||||
writer.StartMessage(InnerNetClient.SceneChangeFlag);
|
||||
writer.WritePacked(innerNetClient.ClientId);
|
||||
writer.Write(__instance.sceneName);
|
||||
|
||||
// PATCH - Inject ReactorHandshakeC2S
|
||||
Debug("Injecting ReactorHandshakeC2S to CoSendSceneChange");
|
||||
ReactorHeader.Write(writer);
|
||||
ModdedHandshakeC2S.Serialize(writer, ModList.Current);
|
||||
//
|
||||
|
||||
writer.EndMessage();
|
||||
writer.EndMessage();
|
||||
innerNetClient.SendOrDisconnect(writer);
|
||||
writer.Recycle();
|
||||
}
|
||||
|
||||
__instance.__1__state = -1;
|
||||
__result = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(InnerNetClient._CoHandleSpawn_d__39), nameof(InnerNetClient._CoHandleSpawn_d__39.MoveNext))]
|
||||
public static class CoHandleSpawnPatch
|
||||
{
|
||||
public static void Postfix(InnerNetClient._CoHandleSpawn_d__39 __instance, bool __result)
|
||||
{
|
||||
if (ReactorConnection.Instance!.Syncer != Syncer.Host) return;
|
||||
|
||||
if (!__result && !AmongUsClient.Instance.AmHost && __instance._ownerId_5__2 == AmongUsClient.Instance.ClientId)
|
||||
{
|
||||
var reader = __instance.reader;
|
||||
if (reader.BytesRemaining >= ReactorHeader.Size && ReactorHeader.Read(reader))
|
||||
{
|
||||
ModdedHandshakeS2C.Deserialize(reader, out var serverName, out var serverVersion, out _);
|
||||
Debug($"Host is modded ({serverName} {serverVersion})");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug("Host is not modded");
|
||||
if (!Mod.Validate(ModList.Current, Array.Empty<Mod>(), out var reason))
|
||||
{
|
||||
AmongUsClient.Instance.DisconnectWithReason(reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.WriteSpawnMessage))]
|
||||
public static class WriteSpawnMessagePatch
|
||||
{
|
||||
public static bool Prefix(InnerNetClient __instance, InnerNetObject netObjParent, int ownerId, SpawnFlags flags, MessageWriter msg)
|
||||
{
|
||||
if (ReactorConnection.Instance!.Syncer != Syncer.Host) return true;
|
||||
|
||||
msg.StartMessage(4);
|
||||
msg.WritePacked(netObjParent.SpawnId);
|
||||
msg.WritePacked(ownerId);
|
||||
msg.Write((byte) flags);
|
||||
InnerNetObject[] componentsInChildren = netObjParent.GetComponentsInChildren<InnerNetObject>();
|
||||
msg.WritePacked(componentsInChildren.Length);
|
||||
foreach (InnerNetObject innerNetObject in componentsInChildren)
|
||||
{
|
||||
innerNetObject.OwnerId = ownerId;
|
||||
innerNetObject.SpawnFlags = flags;
|
||||
if (innerNetObject.NetId == 0)
|
||||
{
|
||||
innerNetObject.NetId = __instance.NetIdCnt++;
|
||||
__instance.allObjects.Add(innerNetObject);
|
||||
__instance.allObjectsFast.Add(innerNetObject.NetId, innerNetObject);
|
||||
}
|
||||
|
||||
msg.WritePacked(innerNetObject.NetId);
|
||||
msg.StartMessage(1);
|
||||
innerNetObject.Serialize(msg, initialState: true);
|
||||
msg.EndMessage();
|
||||
}
|
||||
|
||||
// PATCH - Inject ReactorHandshakeS2C
|
||||
if (__instance.ClientId != ownerId && IL2CPP.il2cpp_object_get_class(netObjParent.Pointer) == Il2CppClassPointerStore<PlayerControl>.NativeClassPtr)
|
||||
{
|
||||
Debug("Injecting ReactorHandshakeS2C to WriteSpawnMessage");
|
||||
ReactorHeader.Write(msg);
|
||||
ModdedHandshakeS2C.Serialize(msg, "Among Us", Application.version, 0); // TODO
|
||||
}
|
||||
//
|
||||
|
||||
msg.EndMessage();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.HandleMessage))]
|
||||
public static class HandleMessagePatch
|
||||
{
|
||||
public static bool Prefix(InnerNetClient __instance, [HarmonyArgument(0)] MessageReader reader)
|
||||
{
|
||||
if (__instance.GameMode == GameModes.FreePlay) return true;
|
||||
|
||||
var isFirst = false;
|
||||
|
||||
if (ReactorConnection.Instance!.Syncer == null)
|
||||
{
|
||||
var parentBuffer = reader.Parent.Buffer;
|
||||
var sendOption = (SendOption) parentBuffer[0];
|
||||
if (sendOption == SendOption.Reliable)
|
||||
{
|
||||
var id = (ushort) ((parentBuffer[1] << 8) + parentBuffer[2]);
|
||||
isFirst = id == 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (reader.Tag == byte.MaxValue)
|
||||
{
|
||||
var flag = (ReactorMessageFlags) reader.ReadByte();
|
||||
@@ -28,129 +246,34 @@ internal static class ClientPatches
|
||||
{
|
||||
ModdedHandshakeS2C.Deserialize(reader, out var serverName, out var serverVersion, out var pluginCount);
|
||||
|
||||
Logger<ReactorPlugin>.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()
|
||||
if (isFirst)
|
||||
{
|
||||
expected++;
|
||||
|
||||
__instance.connection.Send(writer, () =>
|
||||
{
|
||||
got++;
|
||||
|
||||
if (got >= expected)
|
||||
{
|
||||
Logger<ReactorPlugin>.Debug("Received all acks");
|
||||
__instance.connection.State = ConnectionState.Connected;
|
||||
}
|
||||
});
|
||||
writer.Recycle();
|
||||
Info($"Connected to a modded server ({serverName} {serverVersion}, {pluginCount} plugins)");
|
||||
ReactorConnection.Instance.Syncer = Syncer.Server;
|
||||
}
|
||||
|
||||
foreach (var mod in mods)
|
||||
else
|
||||
{
|
||||
ModDeclaration.Serialize(writer, mod);
|
||||
|
||||
if (writer.Length >= 500)
|
||||
{
|
||||
writer.CancelMessage();
|
||||
|
||||
Send();
|
||||
|
||||
writer = MessageWriter.Get(SendOption.Reliable);
|
||||
ModDeclaration.Serialize(writer, mod);
|
||||
}
|
||||
|
||||
writer.EndMessage();
|
||||
Warning("Modded handshake came in late");
|
||||
__instance.DisconnectWithReason("Reactor handshake was sent out of order, please try connecting again.");
|
||||
}
|
||||
|
||||
Send();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case ReactorMessageFlags.PluginDeclaration:
|
||||
{
|
||||
// TODO
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isFirst)
|
||||
{
|
||||
ReactorConnection.Instance.Syncer = Syncer.Host;
|
||||
Warning("Server is not modded, falling back to HAAS");
|
||||
}
|
||||
|
||||
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<ReactorPlugin>.Debug("Hello was acked, waiting for modded handshake response");
|
||||
|
||||
var coroutine = Coroutines.Start(Coroutine(__instance));
|
||||
|
||||
__instance.Disconnected = Il2CppSystem.Delegate.Combine(
|
||||
(Il2CppSystem.EventHandler<DisconnectedEventArgs>) (Action<Il2CppSystem.Object, DisconnectedEventArgs>) ((_, _) =>
|
||||
{
|
||||
Coroutines.Stop(coroutine);
|
||||
}),
|
||||
__instance.Disconnected
|
||||
).Cast<Il2CppSystem.EventHandler<DisconnectedEventArgs>>();
|
||||
});
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
var reactorPlugin = PluginSingleton<ReactorPlugin>.Instance;
|
||||
|
||||
if (reactorPlugin.AllowVanillaServers!.Value)
|
||||
{
|
||||
if (reactorPlugin.CustomRpcManager.List.Any())
|
||||
{
|
||||
Logger<ReactorPlugin>.Warning("Config option AllowVanillaServers was set to true, but there are custom rpcs registered!");
|
||||
}
|
||||
else
|
||||
{
|
||||
connection.State = ConnectionState.Connected;
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
|
||||
client.LastDisconnectReason = DisconnectReasons.Custom;
|
||||
client.LastCustomDisconnect = "Server didn't respond to modded handshake";
|
||||
client.HandleDisconnect(client.LastDisconnectReason, client.LastCustomDisconnect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.GetConnectionData))]
|
||||
public static class HandshakePatch
|
||||
{
|
||||
@@ -165,15 +288,15 @@ internal static class ClientPatches
|
||||
|
||||
public static void Postfix(ref Il2CppStructArray<byte> __result)
|
||||
{
|
||||
var mods = ModList.Update();
|
||||
|
||||
var handshake = new MessageWriter(1000);
|
||||
|
||||
handshake.Write(__result);
|
||||
|
||||
ReactorHeader.Write(handshake);
|
||||
|
||||
ModdedHandshakeC2S.Serialize(
|
||||
handshake,
|
||||
mods.Count
|
||||
ModList.Current
|
||||
);
|
||||
|
||||
__result = handshake.ToByteArray(true);
|
||||
|
||||
@@ -2,12 +2,13 @@ using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using HarmonyLib;
|
||||
using Il2CppInterop.Runtime.InteropTypes.Arrays;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Networking;
|
||||
using UnityEngine.UI;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace Reactor.Networking.Patches;
|
||||
|
||||
[HarmonyPatch]
|
||||
internal static class HttpPatches
|
||||
{
|
||||
public const int Version = 1;
|
||||
@@ -17,53 +18,119 @@ internal static class HttpPatches
|
||||
var stringBuilder = new StringBuilder();
|
||||
|
||||
stringBuilder.Append(Version);
|
||||
stringBuilder.Append(";");
|
||||
stringBuilder.Append(';');
|
||||
|
||||
var mods = ModList.Update().Where(m => m.Side == PluginSide.Both).OrderBy(m => m.Id, StringComparer.Ordinal).ToArray();
|
||||
var mods = ModList.Current.Where(m => m.IsRequiredOnAllClients).ToArray();
|
||||
|
||||
stringBuilder.Append(mods.Length);
|
||||
|
||||
foreach (var mod in mods)
|
||||
{
|
||||
stringBuilder.Append(";");
|
||||
stringBuilder.Append(';');
|
||||
stringBuilder.Append(mod.Id);
|
||||
stringBuilder.Append("=");
|
||||
stringBuilder.Append('=');
|
||||
stringBuilder.Append(mod.Version);
|
||||
}
|
||||
|
||||
return stringBuilder.ToString();
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(UnityWebRequest), nameof(UnityWebRequest.Get), typeof(string))]
|
||||
[HarmonyPatch(typeof(UnityWebRequest), nameof(UnityWebRequest.Post), typeof(string), typeof(string))]
|
||||
[HarmonyPatch(typeof(UnityWebRequest), nameof(UnityWebRequest.Put), typeof(string), typeof(Il2CppStructArray<byte>))]
|
||||
[HarmonyPostfix]
|
||||
public static void UnityWebRequestPatch(UnityWebRequest __result)
|
||||
internal static bool IsCurrentRegionModded()
|
||||
{
|
||||
var path = __result.uri.AbsolutePath;
|
||||
var currentRegion = ServerManager.Instance.CurrentRegion;
|
||||
|
||||
if (path == "/api/games")
|
||||
if (currentRegion.TryCast<StaticHttpRegionInfo>() is { } httpRegionInfo)
|
||||
{
|
||||
__result.SetRequestHeader("Client-Mods", BuildHeader());
|
||||
var lastConnection = SendWebRequestPatch.LastConnection;
|
||||
return lastConnection.HasValue && lastConnection.Value.RegionInfo.Equals(httpRegionInfo) && lastConnection.Value.IsModded;
|
||||
}
|
||||
|
||||
return ReactorConnection.Instance is { Syncer: Syncer.Server };
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(UnityWebRequest), nameof(UnityWebRequest.SendWebRequest))]
|
||||
private static class SendWebRequestPatch
|
||||
{
|
||||
internal static (StaticHttpRegionInfo RegionInfo, bool IsModded)? LastConnection { get; private set; }
|
||||
|
||||
public static void Prefix(UnityWebRequest __instance)
|
||||
{
|
||||
var path = __instance.uri.AbsolutePath;
|
||||
if (path == "/api/games")
|
||||
{
|
||||
Debug($"{__instance.method} {path}");
|
||||
__instance.SetRequestHeader("Client-Mods", BuildHeader());
|
||||
}
|
||||
}
|
||||
|
||||
public static void Postfix(UnityWebRequest __instance, UnityWebRequestAsyncOperation __result)
|
||||
{
|
||||
var path = __instance.uri.AbsolutePath;
|
||||
if (path == "/api/games")
|
||||
{
|
||||
__result.add_completed((Action<AsyncOperation>) (_ =>
|
||||
{
|
||||
if (!HttpUtils.IsSuccess(__instance.responseCode)) return;
|
||||
|
||||
var responseHeader = __instance.GetResponseHeader("Client-Mods-Processed");
|
||||
|
||||
if (responseHeader != null)
|
||||
{
|
||||
Debug("Connected to a modded HTTP matchmaking server");
|
||||
}
|
||||
else
|
||||
{
|
||||
Warning("Connected to a vanilla HTTP matchmaking server");
|
||||
}
|
||||
|
||||
if (__instance.GetMethod() == UnityWebRequest.UnityWebRequestMethod.Get)
|
||||
{
|
||||
if (responseHeader == null)
|
||||
{
|
||||
DisconnectPopup.Instance.ShowCustom("This region doesn't support modded handshake.\nThe lobbies shown may not be compatible with your current mods.\nFor more info see https://reactor.gg/handshake");
|
||||
}
|
||||
}
|
||||
|
||||
LastConnection = (ServerManager.Instance.CurrentRegion.Cast<StaticHttpRegionInfo>(), responseHeader != null);
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(HttpMatchmakerManager._CoRequestGameList_d__7), nameof(HttpMatchmakerManager._CoRequestGameList_d__7.MoveNext))]
|
||||
public static class CoRequestGameListPatch
|
||||
[HarmonyPatch(typeof(GameStartManager), nameof(GameStartManager.Start))]
|
||||
private static class GameStartManagerPatch
|
||||
{
|
||||
public static void Prefix(HttpMatchmakerManager._CoRequestGameList_d__7 __instance)
|
||||
public static void Postfix(GameStartManager __instance)
|
||||
{
|
||||
if (__instance.__1__state == 2)
|
||||
if (ModList.Current.Any(m => m.IsRequiredOnAllClients) && !IsCurrentRegionModded())
|
||||
{
|
||||
if (HttpUtils.IsSuccess(__instance._request_5__3.responseCode))
|
||||
Warning("Vanilla region, locking public toggle");
|
||||
|
||||
__instance.MakePublicButtonBehaviour.enabled = false;
|
||||
var actionMapGlyphDisplay = __instance.MakePublicButton.GetComponentInChildren<ActionMapGlyphDisplay>(includeInactive: true);
|
||||
if (actionMapGlyphDisplay)
|
||||
{
|
||||
var responseHeader = __instance._request_5__3.GetResponseHeader("Client-Mods-Processed");
|
||||
if (responseHeader == null)
|
||||
{
|
||||
AmongUsClient.Instance.LastDisconnectReason = DisconnectReasons.Custom;
|
||||
AmongUsClient.Instance.LastCustomDisconnect = "This region doesn't support modded handshake.\nThe lobbies shown may not be compatible with your current mods.\nFor more info see https://reactor.gg/handshake";
|
||||
DisconnectPopup.Instance.Show();
|
||||
}
|
||||
actionMapGlyphDisplay.gameObject.SetActive(value: false);
|
||||
}
|
||||
|
||||
__instance.MakePublicButton.color = new Color(1, 1, 1, 0.5f);
|
||||
__instance.MakePublicButton.sprite = __instance.PrivateGameImage;
|
||||
|
||||
var onClick = __instance.MakePublicButton.GetComponent<PassiveButton>().OnClick = new Button.ButtonClickedEvent();
|
||||
onClick.AddListener((Action) (() =>
|
||||
{
|
||||
var popup = Object.Instantiate(DiscordManager.Instance.discordPopup, Camera.main!.transform);
|
||||
var background = popup.transform.Find("Background").GetComponent<SpriteRenderer>();
|
||||
var size = background.size;
|
||||
size.x *= 2.5f;
|
||||
background.size = size;
|
||||
popup.TextAreaTMP.fontSizeMin = 2;
|
||||
popup.Show("You can't make public lobbies on regions that don't support modded handshake.\nFor more info see https://reactor.gg/handshake");
|
||||
}));
|
||||
|
||||
if (AmongUsClient.Instance.AmHost && AmongUsClient.Instance.IsGamePublic)
|
||||
{
|
||||
AmongUsClient.Instance.ChangeGamePublic(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
using System.Collections.Generic;
|
||||
using HarmonyLib;
|
||||
using InnerNet;
|
||||
|
||||
namespace Reactor.Networking.Patches;
|
||||
|
||||
internal class ReactorClientData
|
||||
{
|
||||
public ClientData ClientData { get; }
|
||||
|
||||
public Mod[] Mods { get; }
|
||||
|
||||
public ReactorClientData(ClientData clientData, Mod[] mods)
|
||||
{
|
||||
ClientData = clientData;
|
||||
Mods = mods;
|
||||
}
|
||||
|
||||
private static readonly Dictionary<int, ReactorClientData> _map = new();
|
||||
|
||||
public static ReactorClientData Get(int clientId)
|
||||
{
|
||||
return _map[clientId];
|
||||
}
|
||||
|
||||
internal static void Set(int clientId, ReactorClientData data)
|
||||
{
|
||||
_map[clientId] = data;
|
||||
}
|
||||
|
||||
[HarmonyPatch]
|
||||
private static class ClearPatches
|
||||
{
|
||||
[HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.DisconnectInternal))]
|
||||
[HarmonyPostfix]
|
||||
public static void DisconnectInternalPostfix()
|
||||
{
|
||||
_map.Clear();
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(EndGameResult), nameof(EndGameResult.Create))]
|
||||
[HarmonyPostfix]
|
||||
public static void EndGamePostfix()
|
||||
{
|
||||
_map.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using HarmonyLib;
|
||||
using InnerNet;
|
||||
|
||||
namespace Reactor.Networking.Patches;
|
||||
|
||||
public class ReactorConnection
|
||||
{
|
||||
public Syncer? Syncer { get; internal set; }
|
||||
|
||||
public static ReactorConnection? Instance { get; private set; }
|
||||
|
||||
[HarmonyPatch]
|
||||
private static class Patches
|
||||
{
|
||||
[HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.CoConnect), typeof(string))]
|
||||
[HarmonyPrefix]
|
||||
public static void CoConnect()
|
||||
{
|
||||
Debug("New ReactorConnection created");
|
||||
Instance = new ReactorConnection();
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.DisconnectInternal))]
|
||||
[HarmonyPostfix]
|
||||
public static void DisconnectInternalPostfix()
|
||||
{
|
||||
Debug("ReactorConnection disconnected");
|
||||
Instance = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum Syncer
|
||||
{
|
||||
Server,
|
||||
Host,
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
using HarmonyLib;
|
||||
using Hazel;
|
||||
using InnerNet;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Reactor.Networking.Patches;
|
||||
|
||||
internal static class ServerPatches
|
||||
{
|
||||
[HarmonyPatch(typeof(InnerNetServer), nameof(InnerNetServer.OnServerConnect))]
|
||||
public static class HandshakeResponsePatch
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using Hazel;
|
||||
|
||||
namespace Reactor.Networking;
|
||||
|
||||
public static class ReactorHeader
|
||||
{
|
||||
private const ulong MAGIC = 0x72656163746f72; // "reactor" in ascii, 7 bytes
|
||||
|
||||
public static int Size => sizeof(ulong);
|
||||
|
||||
public static void Read(MessageReader reader, out ulong magic, out byte version)
|
||||
{
|
||||
var value = reader.ReadUInt64();
|
||||
magic = value >> 8;
|
||||
version = (byte) (value & 0xFF);
|
||||
}
|
||||
|
||||
public static bool Read(MessageReader reader)
|
||||
{
|
||||
Read(reader, out var magic, out var version);
|
||||
return magic == MAGIC && version == (int) ReactorProtocolVersion.Latest;
|
||||
}
|
||||
|
||||
public static void Write(MessageWriter writer)
|
||||
{
|
||||
var version = (byte) ReactorProtocolVersion.Latest;
|
||||
var value = (MAGIC << 8) | version;
|
||||
writer.Write(value);
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ public abstract class UnsafeCustomRpc
|
||||
|
||||
public uint Id { get; }
|
||||
public BasePlugin UnsafePlugin { get; }
|
||||
public string PluginId { get; }
|
||||
public Mod Mod { get; }
|
||||
|
||||
public abstract Type InnerNetObjectType { get; }
|
||||
|
||||
@@ -27,7 +27,7 @@ public abstract class UnsafeCustomRpc
|
||||
protected UnsafeCustomRpc(BasePlugin plugin, uint id)
|
||||
{
|
||||
UnsafePlugin = plugin;
|
||||
PluginId = MetadataHelper.GetMetadata(plugin).GUID;
|
||||
Mod = ModList.GetByPluginType(plugin.GetType());
|
||||
Id = id;
|
||||
}
|
||||
|
||||
@@ -55,8 +55,7 @@ public abstract class UnsafeCustomRpc
|
||||
true => AmongUsClient.Instance.StartRpcImmediately(innerNetObject.NetId, CustomRpcManager.CallId, SendOption, targetClientId),
|
||||
};
|
||||
|
||||
var pluginNetId = ModList.GetById(PluginId).NetId;
|
||||
writer.WritePacked(pluginNetId);
|
||||
writer.Write(Mod);
|
||||
writer.WritePacked(Id);
|
||||
|
||||
writer.StartMessage(0);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using BepInEx.Unity.IL2CPP;
|
||||
@@ -8,7 +9,16 @@ public static class PluginSingleton<T> where T : BasePlugin
|
||||
{
|
||||
private static T? _instance;
|
||||
|
||||
public static T Instance => _instance ??= IL2CPPChainloader.Instance.Plugins.Values.Select(x => x.Instance).OfType<T>().Single();
|
||||
public static T Instance
|
||||
{
|
||||
get => _instance ??= IL2CPPChainloader.Instance.Plugins.Values.Select(x => x.Instance).OfType<T>().Single();
|
||||
set
|
||||
{
|
||||
if (_instance == value) return;
|
||||
if (_instance != null) throw new InvalidOperationException($"Instance for {typeof(T)} is already set");
|
||||
_instance = value;
|
||||
}
|
||||
}
|
||||
|
||||
internal static void Initialize()
|
||||
{
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<DebugType>embedded</DebugType>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
|
||||
<VersionPrefix>2.0.0</VersionPrefix>
|
||||
<VersionSuffix>dev</VersionSuffix>
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Reactor;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class ReactorModFlagsAttribute : Attribute
|
||||
{
|
||||
public ModFlags Flags { get; }
|
||||
|
||||
public ReactorModFlagsAttribute(ModFlags flags)
|
||||
{
|
||||
Flags = flags;
|
||||
}
|
||||
|
||||
public static ModFlags GetModFlags(Type type)
|
||||
{
|
||||
var attribute = type.GetCustomAttribute<ReactorModFlagsAttribute>();
|
||||
if (attribute != null)
|
||||
{
|
||||
return attribute.Flags;
|
||||
}
|
||||
|
||||
var metadataAttribute = type.Assembly.GetCustomAttributes<AssemblyMetadataAttribute>().SingleOrDefault(x => x.Key == "Reactor.ModFlags");
|
||||
if (metadataAttribute != null && metadataAttribute.Value != null)
|
||||
{
|
||||
return Enum.Parse<ModFlags>(metadataAttribute.Value);
|
||||
}
|
||||
|
||||
return ModFlags.None;
|
||||
}
|
||||
}
|
||||
@@ -30,8 +30,10 @@ public partial class ReactorPlugin : BasePlugin
|
||||
|
||||
public ReactorPlugin()
|
||||
{
|
||||
PluginSingleton<ReactorPlugin>.Instance = this;
|
||||
PluginSingleton<BasePlugin>.Initialize();
|
||||
RegisterInIl2CppAttribute.Initialize();
|
||||
ModList.Initialize();
|
||||
RegisterCustomRpcAttribute.Initialize();
|
||||
MessageConverterAttribute.Initialize();
|
||||
MethodRpcAttribute.Initialize();
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Reactor;
|
||||
|
||||
/// <summary>
|
||||
/// This attribute specifies plugin's side
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class ReactorPluginSideAttribute : Attribute
|
||||
{
|
||||
public PluginSide Side { get; }
|
||||
|
||||
public ReactorPluginSideAttribute(PluginSide side)
|
||||
{
|
||||
Side = side;
|
||||
}
|
||||
|
||||
public static PluginSide GetPluginSide(Type type)
|
||||
{
|
||||
var attribute = type.GetCustomAttribute<ReactorPluginSideAttribute>();
|
||||
if (attribute != null)
|
||||
{
|
||||
return attribute.Side;
|
||||
}
|
||||
|
||||
var metadataAttribute = type.Assembly.GetCustomAttributes<AssemblyMetadataAttribute>().SingleOrDefault(x => x.Key == "Reactor.PluginSide");
|
||||
if (metadataAttribute != null)
|
||||
{
|
||||
return Enum.Parse<PluginSide>(metadataAttribute.Value, true);
|
||||
}
|
||||
|
||||
return PluginSide.Both;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user