mirror of
https://github.com/NuclearPowered/Reactor.git
synced 2026-10-19 15:51:54 +00:00
Reformat code
This commit is contained in:
52 files changed
+2126
-2180
No files matched your search
@@ -9,110 +9,109 @@ using UnhollowerBaseLib;
|
||||
using UnhollowerBaseLib.Attributes;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Reactor.Debugger
|
||||
namespace Reactor.Debugger;
|
||||
|
||||
[BepInAutoPlugin("gg.reactor.debugger")]
|
||||
[BepInProcess("Among Us.exe")]
|
||||
[BepInDependency(ReactorPlugin.Id)]
|
||||
[ReactorPluginSide(PluginSide.ClientOnly)]
|
||||
public partial class DebuggerPlugin : BasePlugin
|
||||
{
|
||||
[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);
|
||||
public DebuggerComponent Component { get; private set; }
|
||||
|
||||
public override void Load()
|
||||
{
|
||||
public Harmony Harmony { get; } = new Harmony(Id);
|
||||
public DebuggerComponent Component { get; private set; }
|
||||
Component = this.AddComponent<DebuggerComponent>();
|
||||
|
||||
public override void Load()
|
||||
GameOptionsData.MaxImpostors = GameOptionsData.RecommendedImpostors = Enumerable.Repeat((int) byte.MaxValue, byte.MaxValue).ToArray();
|
||||
GameOptionsData.MinPlayers = Enumerable.Repeat(1, 4).ToArray();
|
||||
|
||||
Harmony.PatchAll();
|
||||
}
|
||||
|
||||
[RegisterInIl2Cpp]
|
||||
public class DebuggerComponent : MonoBehaviour
|
||||
{
|
||||
[HideFromIl2Cpp]
|
||||
public bool DisableGameEnd { get; set; }
|
||||
|
||||
[HideFromIl2Cpp]
|
||||
public DragWindow TestWindow { get; }
|
||||
|
||||
public DebuggerComponent(IntPtr ptr) : base(ptr)
|
||||
{
|
||||
Component = this.AddComponent<DebuggerComponent>();
|
||||
TestWindow = new DragWindow(new Rect(20, 20, 0, 0), "Debugger", () =>
|
||||
{
|
||||
GUILayout.Label("Name: " + SaveManager.PlayerName);
|
||||
DisableGameEnd = GUILayout.Toggle(DisableGameEnd, "Disable game end");
|
||||
|
||||
GameOptionsData.MaxImpostors = GameOptionsData.RecommendedImpostors = Enumerable.Repeat((int) byte.MaxValue, byte.MaxValue).ToArray();
|
||||
GameOptionsData.MinPlayers = Enumerable.Repeat(1, 4).ToArray();
|
||||
if (ShipStatus.Instance && AmongUsClient.Instance.AmHost)
|
||||
{
|
||||
if (GUILayout.Button("Force game end"))
|
||||
{
|
||||
ShipStatus.Instance.enabled = false;
|
||||
ShipStatus.RpcEndGame(GameOverReason.ImpostorDisconnect, false);
|
||||
}
|
||||
|
||||
Harmony.PatchAll();
|
||||
if (GUILayout.Button("Call a meeting"))
|
||||
{
|
||||
PlayerControl.LocalPlayer.CmdReportDeadBody(null);
|
||||
}
|
||||
}
|
||||
|
||||
if (TutorialManager.InstanceExists && PlayerControl.LocalPlayer)
|
||||
{
|
||||
var data = PlayerControl.LocalPlayer.Data;
|
||||
|
||||
var newIsImpostor = GUILayout.Toggle(data.Role.IsImpostor, "Is Impostor");
|
||||
if (data.Role.IsImpostor != newIsImpostor)
|
||||
{
|
||||
PlayerControl.LocalPlayer.RpcSetRole(newIsImpostor ? RoleTypes.Impostor : RoleTypes.Crewmate);
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Spawn a dummy"))
|
||||
{
|
||||
var playerControl = Instantiate(TutorialManager.Instance.PlayerPrefab);
|
||||
var i = playerControl.PlayerId = (byte) GameData.Instance.GetAvailableId();
|
||||
GameData.Instance.AddPlayer(playerControl);
|
||||
AmongUsClient.Instance.Spawn(playerControl, -2, SpawnFlags.None);
|
||||
playerControl.transform.position = PlayerControl.LocalPlayer.transform.position;
|
||||
playerControl.GetComponent<DummyBehaviour>().enabled = true;
|
||||
playerControl.NetTransform.enabled = false;
|
||||
playerControl.SetName($"{TranslationController.Instance.GetString(StringNames.Dummy, Array.Empty<Il2CppSystem.Object>())} {i}");
|
||||
var color = (byte) (i % Palette.PlayerColors.Length);
|
||||
playerControl.SetColor(color);
|
||||
playerControl.SetHat(HatManager.Instance.allHats[i % HatManager.Instance.allHats.Count].ProdId, playerControl.Data.DefaultOutfit.ColorId);
|
||||
playerControl.SetPet(HatManager.Instance.allPets[i % HatManager.Instance.allPets.Count].ProdId);
|
||||
playerControl.SetSkin(HatManager.Instance.allSkins[i % HatManager.Instance.allSkins.Count].ProdId, color);
|
||||
GameData.Instance.RpcSetTasks(playerControl.PlayerId, new Il2CppStructArray<byte>(0));
|
||||
}
|
||||
}
|
||||
|
||||
if (PlayerControl.LocalPlayer)
|
||||
{
|
||||
var position = PlayerControl.LocalPlayer.transform.position;
|
||||
GUILayout.Label($"x: {position.x}");
|
||||
GUILayout.Label($"y: {position.y}");
|
||||
}
|
||||
})
|
||||
{
|
||||
Enabled = false,
|
||||
};
|
||||
}
|
||||
|
||||
[RegisterInIl2Cpp]
|
||||
public class DebuggerComponent : MonoBehaviour
|
||||
private void Update()
|
||||
{
|
||||
[HideFromIl2Cpp]
|
||||
public bool DisableGameEnd { get; set; }
|
||||
|
||||
[HideFromIl2Cpp]
|
||||
public DragWindow TestWindow { get; }
|
||||
|
||||
public DebuggerComponent(IntPtr ptr) : base(ptr)
|
||||
if (Input.GetKeyDown(KeyCode.F1))
|
||||
{
|
||||
TestWindow = new DragWindow(new Rect(20, 20, 0, 0), "Debugger", () =>
|
||||
{
|
||||
GUILayout.Label("Name: " + SaveManager.PlayerName);
|
||||
DisableGameEnd = GUILayout.Toggle(DisableGameEnd, "Disable game end");
|
||||
|
||||
if (ShipStatus.Instance && AmongUsClient.Instance.AmHost)
|
||||
{
|
||||
if (GUILayout.Button("Force game end"))
|
||||
{
|
||||
ShipStatus.Instance.enabled = false;
|
||||
ShipStatus.RpcEndGame(GameOverReason.ImpostorDisconnect, false);
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Call a meeting"))
|
||||
{
|
||||
PlayerControl.LocalPlayer.CmdReportDeadBody(null);
|
||||
}
|
||||
}
|
||||
|
||||
if (TutorialManager.InstanceExists && PlayerControl.LocalPlayer)
|
||||
{
|
||||
var data = PlayerControl.LocalPlayer.Data;
|
||||
|
||||
var newIsImpostor = GUILayout.Toggle(data.Role.IsImpostor, "Is Impostor");
|
||||
if (data.Role.IsImpostor != newIsImpostor)
|
||||
{
|
||||
PlayerControl.LocalPlayer.RpcSetRole(newIsImpostor ? RoleTypes.Impostor : RoleTypes.Crewmate);
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Spawn a dummy"))
|
||||
{
|
||||
var playerControl = Instantiate(TutorialManager.Instance.PlayerPrefab);
|
||||
var i = playerControl.PlayerId = (byte) GameData.Instance.GetAvailableId();
|
||||
GameData.Instance.AddPlayer(playerControl);
|
||||
AmongUsClient.Instance.Spawn(playerControl, -2, SpawnFlags.None);
|
||||
playerControl.transform.position = PlayerControl.LocalPlayer.transform.position;
|
||||
playerControl.GetComponent<DummyBehaviour>().enabled = true;
|
||||
playerControl.NetTransform.enabled = false;
|
||||
playerControl.SetName($"{TranslationController.Instance.GetString(StringNames.Dummy, Array.Empty<Il2CppSystem.Object>())} {i}");
|
||||
var color = (byte) (i % Palette.PlayerColors.Length);
|
||||
playerControl.SetColor(color);
|
||||
playerControl.SetHat(HatManager.Instance.allHats[i % HatManager.Instance.allHats.Count].ProdId, playerControl.Data.DefaultOutfit.ColorId);
|
||||
playerControl.SetPet(HatManager.Instance.allPets[i % HatManager.Instance.allPets.Count].ProdId);
|
||||
playerControl.SetSkin(HatManager.Instance.allSkins[i % HatManager.Instance.allSkins.Count].ProdId, color);
|
||||
GameData.Instance.RpcSetTasks(playerControl.PlayerId, new Il2CppStructArray<byte>(0));
|
||||
}
|
||||
}
|
||||
|
||||
if (PlayerControl.LocalPlayer)
|
||||
{
|
||||
var position = PlayerControl.LocalPlayer.transform.position;
|
||||
GUILayout.Label($"x: {position.x}");
|
||||
GUILayout.Label($"y: {position.y}");
|
||||
}
|
||||
})
|
||||
{
|
||||
Enabled = false
|
||||
};
|
||||
TestWindow.Enabled = !TestWindow.Enabled;
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (Input.GetKeyDown(KeyCode.F1))
|
||||
{
|
||||
TestWindow.Enabled = !TestWindow.Enabled;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
TestWindow.OnGUI();
|
||||
}
|
||||
private void OnGUI()
|
||||
{
|
||||
TestWindow.OnGUI();
|
||||
}
|
||||
}
|
||||
}
|
||||
+43
-44
@@ -3,68 +3,67 @@ using HarmonyLib;
|
||||
using UnhollowerBaseLib;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Reactor.Debugger
|
||||
namespace Reactor.Debugger;
|
||||
|
||||
internal static class Patches
|
||||
{
|
||||
internal static class Patches
|
||||
[HarmonyPatch(typeof(GameStartManager), nameof(GameStartManager.Update))]
|
||||
public static class UpdatePatch
|
||||
{
|
||||
[HarmonyPatch(typeof(GameStartManager), nameof(GameStartManager.Update))]
|
||||
public static class UpdatePatch
|
||||
public static void Prefix(GameStartManager __instance)
|
||||
{
|
||||
public static void Prefix(GameStartManager __instance)
|
||||
{
|
||||
__instance.MinPlayers = 1;
|
||||
}
|
||||
__instance.MinPlayers = 1;
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(GameStartManager), nameof(GameStartManager.Update))]
|
||||
public static class CountdownPatch
|
||||
[HarmonyPatch(typeof(GameStartManager), nameof(GameStartManager.Update))]
|
||||
public static class CountdownPatch
|
||||
{
|
||||
public static void Prefix(GameStartManager __instance)
|
||||
{
|
||||
public static void Prefix(GameStartManager __instance)
|
||||
{
|
||||
__instance.countDownTimer = 0;
|
||||
}
|
||||
__instance.countDownTimer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(StatsManager), nameof(StatsManager.AmBanned), MethodType.Getter)]
|
||||
public static class AmBannedPatch
|
||||
[HarmonyPatch(typeof(StatsManager), nameof(StatsManager.AmBanned), MethodType.Getter)]
|
||||
public static class AmBannedPatch
|
||||
{
|
||||
public static void Postfix(out bool __result)
|
||||
{
|
||||
public static void Postfix(out bool __result)
|
||||
{
|
||||
__result = false;
|
||||
}
|
||||
__result = false;
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.CheckEndCriteria))]
|
||||
[HarmonyPriority(Priority.First)]
|
||||
public static class CheckEndCriteriaPatch
|
||||
[HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.CheckEndCriteria))]
|
||||
[HarmonyPriority(Priority.First)]
|
||||
public static class CheckEndCriteriaPatch
|
||||
{
|
||||
public static bool Prefix()
|
||||
{
|
||||
public static bool Prefix()
|
||||
{
|
||||
return !PluginSingleton<DebuggerPlugin>.Instance.Component.DisableGameEnd;
|
||||
}
|
||||
return !PluginSingleton<DebuggerPlugin>.Instance.Component.DisableGameEnd;
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(GameSettingMenu), nameof(GameSettingMenu.Start))]
|
||||
public static class GameSettingMenuPatch
|
||||
[HarmonyPatch(typeof(GameSettingMenu), nameof(GameSettingMenu.Start))]
|
||||
public static class GameSettingMenuPatch
|
||||
{
|
||||
public static void Prefix(GameSettingMenu __instance)
|
||||
{
|
||||
public static void Prefix(GameSettingMenu __instance)
|
||||
{
|
||||
// Unlocks map/impostor amount changing in online (for testing on your custom servers)
|
||||
__instance.HideForOnline = new Il2CppReferenceArray<Transform>(0);
|
||||
}
|
||||
// Unlocks map/impostor amount changing in online (for testing on your custom servers)
|
||||
__instance.HideForOnline = new Il2CppReferenceArray<Transform>(0);
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(GameOptionsMenu), nameof(GameOptionsMenu.Start))]
|
||||
[HarmonyPriority(Priority.First)]
|
||||
public static class GameOptionsMenuPatch
|
||||
[HarmonyPatch(typeof(GameOptionsMenu), nameof(GameOptionsMenu.Start))]
|
||||
[HarmonyPriority(Priority.First)]
|
||||
public static class GameOptionsMenuPatch
|
||||
{
|
||||
public static void Postfix(GameOptionsMenu __instance)
|
||||
{
|
||||
public static void Postfix(GameOptionsMenu __instance)
|
||||
{
|
||||
__instance.Children
|
||||
.Single(o => o.Title == StringNames.GameNumImpostors)
|
||||
.Cast<NumberOption>()
|
||||
.ValidRange = new FloatRange(0, byte.MaxValue);
|
||||
}
|
||||
__instance.Children
|
||||
.Single(o => o.Title == StringNames.GameNumImpostors)
|
||||
.Cast<NumberOption>()
|
||||
.ValidRange = new FloatRange(0, byte.MaxValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
namespace Reactor.Example
|
||||
namespace Reactor.Example;
|
||||
|
||||
public enum CustomRpcCalls : uint
|
||||
{
|
||||
public enum CustomRpcCalls : uint
|
||||
{
|
||||
Example,
|
||||
MethodRpcExample
|
||||
}
|
||||
Example,
|
||||
MethodRpcExample,
|
||||
}
|
||||
@@ -8,52 +8,51 @@ using UnhollowerBaseLib.Attributes;
|
||||
using UnityEngine;
|
||||
using Random = UnityEngine.Random;
|
||||
|
||||
namespace Reactor.Example
|
||||
namespace Reactor.Example;
|
||||
|
||||
[BepInAutoPlugin("gg.reactor.Example")]
|
||||
[BepInProcess("Among Us.exe")]
|
||||
[BepInDependency(ReactorPlugin.Id)]
|
||||
public partial class ExamplePlugin : BasePlugin
|
||||
{
|
||||
[BepInAutoPlugin("gg.reactor.Example")]
|
||||
[BepInProcess("Among Us.exe")]
|
||||
[BepInDependency(ReactorPlugin.Id)]
|
||||
public partial class ExamplePlugin : BasePlugin
|
||||
public override void Load()
|
||||
{
|
||||
public override void Load()
|
||||
this.AddComponent<ExampleComponent>();
|
||||
}
|
||||
|
||||
[RegisterInIl2Cpp]
|
||||
public class ExampleComponent : MonoBehaviour
|
||||
{
|
||||
public ExampleComponent(IntPtr ptr) : base(ptr)
|
||||
{
|
||||
this.AddComponent<ExampleComponent>();
|
||||
}
|
||||
|
||||
[RegisterInIl2Cpp]
|
||||
public class ExampleComponent : MonoBehaviour
|
||||
private void Update()
|
||||
{
|
||||
public ExampleComponent(IntPtr ptr) : base(ptr)
|
||||
if (AmongUsClient.Instance && PlayerControl.LocalPlayer)
|
||||
{
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (AmongUsClient.Instance && PlayerControl.LocalPlayer)
|
||||
if (Input.GetKeyDown(KeyCode.F3))
|
||||
{
|
||||
if (Input.GetKeyDown(KeyCode.F3))
|
||||
{
|
||||
var name = PlayerControl.LocalPlayer.Data.PlayerName;
|
||||
Rpc<ExampleRpc>.Instance.Send(new ExampleRpc.Data($"Send: from {name}"));
|
||||
var name = PlayerControl.LocalPlayer.Data.PlayerName;
|
||||
Rpc<ExampleRpc>.Instance.Send(new ExampleRpc.Data($"Send: from {name}"));
|
||||
|
||||
if (!AmongUsClient.Instance.AmHost)
|
||||
{
|
||||
Rpc<ExampleRpc>.Instance.SendTo(AmongUsClient.Instance.HostId, new ExampleRpc.Data($"SendTo: from {name} to host"));
|
||||
}
|
||||
}
|
||||
|
||||
if (Input.GetKeyDown(KeyCode.F4))
|
||||
if (!AmongUsClient.Instance.AmHost)
|
||||
{
|
||||
RpcSay(PlayerControl.LocalPlayer, "Hello from method rpc", Random.value, PlayerControl.LocalPlayer);
|
||||
Rpc<ExampleRpc>.Instance.SendTo(AmongUsClient.Instance.HostId, new ExampleRpc.Data($"SendTo: from {name} to host"));
|
||||
}
|
||||
}
|
||||
|
||||
if (Input.GetKeyDown(KeyCode.F4))
|
||||
{
|
||||
RpcSay(PlayerControl.LocalPlayer, "Hello from method rpc", Random.value, PlayerControl.LocalPlayer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[MethodRpc((uint) CustomRpcCalls.MethodRpcExample)]
|
||||
public static void RpcSay(PlayerControl player, string text, float number, PlayerControl testPlayer)
|
||||
{
|
||||
Logger<ExamplePlugin>.Info($"{player.Data.PlayerName} text: {text} number: {number} testPlayer: {testPlayer.NetId}");
|
||||
}
|
||||
[MethodRpc((uint) CustomRpcCalls.MethodRpcExample)]
|
||||
public static void RpcSay(PlayerControl player, string text, float number, PlayerControl testPlayer)
|
||||
{
|
||||
Logger<ExamplePlugin>.Info($"{player.Data.PlayerName} text: {text} number: {number} testPlayer: {testPlayer.NetId}");
|
||||
}
|
||||
}
|
||||
@@ -1,40 +1,39 @@
|
||||
using Hazel;
|
||||
using Reactor.Networking;
|
||||
|
||||
namespace Reactor.Example
|
||||
namespace Reactor.Example;
|
||||
|
||||
[RegisterCustomRpc((uint) CustomRpcCalls.Example)]
|
||||
public class ExampleRpc : PlayerCustomRpc<ExamplePlugin, ExampleRpc.Data>
|
||||
{
|
||||
[RegisterCustomRpc((uint) CustomRpcCalls.Example)]
|
||||
public class ExampleRpc : PlayerCustomRpc<ExamplePlugin, ExampleRpc.Data>
|
||||
public ExampleRpc(ExamplePlugin plugin, uint id) : base(plugin, id)
|
||||
{
|
||||
public ExampleRpc(ExamplePlugin plugin, uint id) : base(plugin, id)
|
||||
}
|
||||
|
||||
public readonly struct Data
|
||||
{
|
||||
public readonly string Message;
|
||||
|
||||
public Data(string message)
|
||||
{
|
||||
}
|
||||
|
||||
public readonly struct Data
|
||||
{
|
||||
public readonly string Message;
|
||||
|
||||
public Data(string message)
|
||||
{
|
||||
Message = message;
|
||||
}
|
||||
}
|
||||
|
||||
public override RpcLocalHandling LocalHandling => RpcLocalHandling.None;
|
||||
|
||||
public override void Write(MessageWriter writer, Data data)
|
||||
{
|
||||
writer.Write(data.Message);
|
||||
}
|
||||
|
||||
public override Data Read(MessageReader reader)
|
||||
{
|
||||
return new Data(reader.ReadString());
|
||||
}
|
||||
|
||||
public override void Handle(PlayerControl innerNetObject, Data data)
|
||||
{
|
||||
Plugin.Log.LogWarning($"Handle: {innerNetObject.Data.PlayerName} sent \"{data.Message}\"");
|
||||
Message = message;
|
||||
}
|
||||
}
|
||||
|
||||
public override RpcLocalHandling LocalHandling => RpcLocalHandling.None;
|
||||
|
||||
public override void Write(MessageWriter writer, Data data)
|
||||
{
|
||||
writer.Write(data.Message);
|
||||
}
|
||||
|
||||
public override Data Read(MessageReader reader)
|
||||
{
|
||||
return new Data(reader.ReadString());
|
||||
}
|
||||
|
||||
public override void Handle(PlayerControl innerNetObject, Data data)
|
||||
{
|
||||
Plugin.Log.LogWarning($"Handle: {innerNetObject.Data.PlayerName} sent \"{data.Message}\"");
|
||||
}
|
||||
}
|
||||
@@ -2,39 +2,38 @@
|
||||
|
||||
using System;
|
||||
|
||||
namespace Reactor.Networking
|
||||
namespace Reactor.Networking;
|
||||
|
||||
public class Mod
|
||||
{
|
||||
public class Mod
|
||||
public Mod(uint netId, string id, string version, PluginSide side)
|
||||
{
|
||||
public Mod(uint netId, string id, string version, PluginSide side)
|
||||
{
|
||||
NetId = netId;
|
||||
Id = id;
|
||||
Version = version;
|
||||
Side = 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; }
|
||||
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;
|
||||
}
|
||||
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 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);
|
||||
}
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(Id, Version);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
namespace Reactor.Networking
|
||||
namespace Reactor.Networking;
|
||||
|
||||
public enum ReactorMessageFlags : byte
|
||||
{
|
||||
public enum ReactorMessageFlags : byte
|
||||
{
|
||||
Handshake,
|
||||
ModDeclaration,
|
||||
PluginDeclaration
|
||||
}
|
||||
Handshake,
|
||||
ModDeclaration,
|
||||
PluginDeclaration,
|
||||
}
|
||||
@@ -1,18 +1,17 @@
|
||||
namespace Reactor.Networking
|
||||
namespace Reactor.Networking;
|
||||
|
||||
/// <summary>
|
||||
/// Version of the Reactor.Net protocol format.
|
||||
/// </summary>
|
||||
public enum ReactorProtocolVersion : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Version of the Reactor.Net protocol format.
|
||||
/// Initial version.
|
||||
/// </summary>
|
||||
public enum ReactorProtocolVersion : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Initial version.
|
||||
/// </summary>
|
||||
Initial = 1,
|
||||
Initial = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Latest version.
|
||||
/// </summary>
|
||||
Latest = Initial,
|
||||
}
|
||||
/// <summary>
|
||||
/// Latest version.
|
||||
/// </summary>
|
||||
Latest = Initial,
|
||||
}
|
||||
@@ -1,18 +1,17 @@
|
||||
namespace Reactor
|
||||
namespace Reactor;
|
||||
|
||||
/// <summary>
|
||||
/// Plugin side used in modded handshake
|
||||
/// </summary>
|
||||
public enum PluginSide : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Plugin side used in modded handshake
|
||||
/// Required by both sides, reject connection if missing on the other side
|
||||
/// </summary>
|
||||
public enum PluginSide : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Required by both sides, reject connection if missing on the other side
|
||||
/// </summary>
|
||||
Both,
|
||||
Both,
|
||||
|
||||
/// <summary>
|
||||
/// Required only by client
|
||||
/// </summary>
|
||||
ClientOnly
|
||||
}
|
||||
/// <summary>
|
||||
/// Required only by client
|
||||
/// </summary>
|
||||
ClientOnly,
|
||||
}
|
||||
+21
-22
@@ -3,32 +3,31 @@ using System.Reflection.Emit;
|
||||
using BepInEx.IL2CPP;
|
||||
using HarmonyLib;
|
||||
|
||||
namespace Reactor
|
||||
namespace Reactor;
|
||||
|
||||
public static class ChainloaderHooks
|
||||
{
|
||||
public static class ChainloaderHooks
|
||||
public delegate void PluginLoadHandler(BasePlugin plugin);
|
||||
|
||||
public static event PluginLoadHandler? PluginLoad;
|
||||
|
||||
internal static void OnPluginLoad(BasePlugin plugin)
|
||||
{
|
||||
public delegate void PluginLoadHandler(BasePlugin plugin);
|
||||
PluginLoad?.Invoke(plugin);
|
||||
}
|
||||
|
||||
public static event PluginLoadHandler? PluginLoad;
|
||||
|
||||
internal static void OnPluginLoad(BasePlugin plugin)
|
||||
[HarmonyPatch(typeof(IL2CPPChainloader), nameof(IL2CPPChainloader.LoadPlugin))]
|
||||
private static class LoadPluginPatch
|
||||
{
|
||||
public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
|
||||
{
|
||||
PluginLoad?.Invoke(plugin);
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(IL2CPPChainloader), nameof(IL2CPPChainloader.LoadPlugin))]
|
||||
private static class LoadPluginPatch
|
||||
{
|
||||
public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
|
||||
{
|
||||
return new CodeMatcher(instructions)
|
||||
.MatchForward(false, new CodeMatch(OpCodes.Callvirt, AccessTools.Method(typeof(BasePlugin), nameof(BasePlugin.Load))))
|
||||
.Insert(
|
||||
new CodeInstruction(OpCodes.Dup),
|
||||
new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(ChainloaderHooks), nameof(OnPluginLoad)))
|
||||
)
|
||||
.InstructionEnumeration();
|
||||
}
|
||||
return new CodeMatcher(instructions)
|
||||
.MatchForward(false, new CodeMatch(OpCodes.Callvirt, AccessTools.Method(typeof(BasePlugin), nameof(BasePlugin.Load))))
|
||||
.Insert(
|
||||
new CodeInstruction(OpCodes.Dup),
|
||||
new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(ChainloaderHooks), nameof(OnPluginLoad)))
|
||||
)
|
||||
.InstructionEnumeration();
|
||||
}
|
||||
}
|
||||
}
|
||||
+33
-34
@@ -5,49 +5,48 @@ using System.Runtime.CompilerServices;
|
||||
using BepInEx.IL2CPP.Utils;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Reactor
|
||||
namespace Reactor;
|
||||
|
||||
public static class Coroutines
|
||||
{
|
||||
public static class Coroutines
|
||||
[RegisterInIl2Cpp]
|
||||
internal class Component : MonoBehaviour
|
||||
{
|
||||
[RegisterInIl2Cpp]
|
||||
internal class Component : MonoBehaviour
|
||||
internal static Component? Instance { get; set; }
|
||||
|
||||
public Component(IntPtr ptr) : base(ptr)
|
||||
{
|
||||
internal static Component? Instance { get; set; }
|
||||
|
||||
public Component(IntPtr ptr) : base(ptr)
|
||||
{
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
Instance = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly ConditionalWeakTable<IEnumerator, Coroutine> _ourCoroutineStore = new();
|
||||
|
||||
[return: NotNullIfNotNull("routine")]
|
||||
public static IEnumerator? Start(IEnumerator? routine)
|
||||
private void Awake()
|
||||
{
|
||||
if (routine != null)
|
||||
{
|
||||
_ourCoroutineStore.AddOrUpdate(routine, Component.Instance!.StartCoroutine(routine));
|
||||
}
|
||||
|
||||
return routine;
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
public static void Stop(IEnumerator? enumerator)
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (enumerator != null && _ourCoroutineStore.TryGetValue(enumerator, out var routine))
|
||||
{
|
||||
Component.Instance!.StopCoroutine(routine);
|
||||
}
|
||||
Instance = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly ConditionalWeakTable<IEnumerator, Coroutine> _ourCoroutineStore = new();
|
||||
|
||||
[return: NotNullIfNotNull("routine")]
|
||||
public static IEnumerator? Start(IEnumerator? routine)
|
||||
{
|
||||
if (routine != null)
|
||||
{
|
||||
_ourCoroutineStore.AddOrUpdate(routine, Component.Instance!.StartCoroutine(routine));
|
||||
}
|
||||
|
||||
return routine;
|
||||
}
|
||||
|
||||
public static void Stop(IEnumerator? enumerator)
|
||||
{
|
||||
if (enumerator != null && _ourCoroutineStore.TryGetValue(enumerator, out var routine))
|
||||
{
|
||||
Component.Instance!.StopCoroutine(routine);
|
||||
}
|
||||
}
|
||||
}
|
||||
+36
-37
@@ -3,52 +3,51 @@ using System.Linq;
|
||||
using HarmonyLib;
|
||||
using UnhollowerBaseLib;
|
||||
|
||||
namespace Reactor
|
||||
namespace Reactor;
|
||||
|
||||
public class CustomStringName
|
||||
{
|
||||
public class CustomStringName
|
||||
private static int _lastId = -1;
|
||||
|
||||
private static readonly List<CustomStringName> _list = new();
|
||||
|
||||
public static IReadOnlyList<CustomStringName> List => _list.AsReadOnly();
|
||||
|
||||
public static CustomStringName Register(string value)
|
||||
{
|
||||
private static int _lastId = -1;
|
||||
var customStringName = new CustomStringName(_lastId--, value);
|
||||
_list.Add(customStringName);
|
||||
|
||||
private static readonly List<CustomStringName> _list = new List<CustomStringName>();
|
||||
return customStringName;
|
||||
}
|
||||
|
||||
public static IReadOnlyList<CustomStringName> List => _list.AsReadOnly();
|
||||
public int Id { get; }
|
||||
|
||||
public static CustomStringName Register(string value)
|
||||
public string Value { get; }
|
||||
|
||||
private CustomStringName(int id, string value)
|
||||
{
|
||||
Id = id;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public static implicit operator StringNames(CustomStringName name) => (StringNames) name.Id;
|
||||
public static explicit operator CustomStringName?(StringNames name) => List.SingleOrDefault(x => x.Id == (int) name);
|
||||
|
||||
[HarmonyPatch(typeof(TranslationController), nameof(TranslationController.GetString), typeof(StringNames), typeof(Il2CppReferenceArray<Il2CppSystem.Object>))]
|
||||
private static class GetStringPatch
|
||||
{
|
||||
public static bool Prefix(StringNames id, Il2CppReferenceArray<Il2CppSystem.Object> parts, ref string __result)
|
||||
{
|
||||
var customStringName = new CustomStringName(_lastId--, value);
|
||||
_list.Add(customStringName);
|
||||
var customStringName = (CustomStringName?) id;
|
||||
|
||||
return customStringName;
|
||||
}
|
||||
|
||||
public int Id { get; }
|
||||
|
||||
public string Value { get; }
|
||||
|
||||
private CustomStringName(int id, string value)
|
||||
{
|
||||
Id = id;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public static implicit operator StringNames(CustomStringName name) => (StringNames) name.Id;
|
||||
public static explicit operator CustomStringName?(StringNames name) => List.SingleOrDefault(x => x.Id == (int) name);
|
||||
|
||||
[HarmonyPatch(typeof(TranslationController), nameof(TranslationController.GetString), typeof(StringNames), typeof(Il2CppReferenceArray<Il2CppSystem.Object>))]
|
||||
private static class GetStringPatch
|
||||
{
|
||||
public static bool Prefix(StringNames id, Il2CppReferenceArray<Il2CppSystem.Object> parts, ref string __result)
|
||||
if (customStringName != null)
|
||||
{
|
||||
var customStringName = (CustomStringName?) id;
|
||||
|
||||
if (customStringName != null)
|
||||
{
|
||||
__result = string.Format(customStringName.Value, parts);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
__result = string.Format(customStringName.Value, parts);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,113 +11,112 @@ using UnityEngine.Networking;
|
||||
using UnityEngine.SceneManagement;
|
||||
using Il2CppType = UnhollowerRuntimeLib.Il2CppType;
|
||||
|
||||
namespace Reactor.Extensions
|
||||
namespace Reactor.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// General utilities
|
||||
/// </summary>
|
||||
public static class Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// General utilities
|
||||
/// Adds a delegate to get one notification when a scene has loaded.
|
||||
/// </summary>
|
||||
public static class Extensions
|
||||
public static void once_sceneLoaded(Action<Scene, LoadSceneMode> value)
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a delegate to get one notification when a scene has loaded.
|
||||
/// </summary>
|
||||
public static void once_sceneLoaded(Action<Scene, LoadSceneMode> value)
|
||||
UnityAction<Scene, LoadSceneMode>? unityAction = null;
|
||||
|
||||
unityAction = (Action<Scene, LoadSceneMode>) ((scene, loadMode) =>
|
||||
{
|
||||
UnityAction<Scene, LoadSceneMode>? unityAction = null;
|
||||
SceneManager.remove_sceneLoaded(unityAction);
|
||||
value.Invoke(scene, loadMode);
|
||||
});
|
||||
|
||||
unityAction = (Action<Scene, LoadSceneMode>) ((scene, loadMode) =>
|
||||
{
|
||||
SceneManager.remove_sceneLoaded(unityAction);
|
||||
value.Invoke(scene, loadMode);
|
||||
});
|
||||
SceneManager.add_sceneLoaded(unityAction);
|
||||
}
|
||||
|
||||
SceneManager.add_sceneLoaded(unityAction);
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns random <typeparamref name="T"/> from <paramref name="input"/>
|
||||
/// </summary>
|
||||
public static T? Random<T>(this IEnumerable<T> input)
|
||||
{
|
||||
var list = input as IList<T> ?? input.ToList();
|
||||
return list.Count == 0 ? default : list[UnityEngine.Random.Range(0, list.Count)];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns random <typeparamref name="T"/> from <paramref name="input"/>
|
||||
/// </summary>
|
||||
public static T? Random<T>(this IEnumerable<T> input)
|
||||
/// <summary>
|
||||
/// Returns the color as a hexadecimal string in the format "RRGGBBAA".
|
||||
/// </summary>
|
||||
/// <param name="color">The color to be converted.</param>
|
||||
/// <returns>Hexadecimal string representing the color.</returns>
|
||||
/// <remarks>https://docs.unity3d.com/ScriptReference/ColorUtility.ToHtmlStringRGBA.html</remarks>
|
||||
public static string ToHtmlStringRGBA(this Color32 color)
|
||||
{
|
||||
return $"{color.r:X2}{color.g:X2}{color.b:X2}{color.a:X2}";
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="ToHtmlStringRGBA(UnityEngine.Color32)"/>
|
||||
public static string ToHtmlStringRGBA(this Color color)
|
||||
{
|
||||
return ((Color32) color).ToHtmlStringRGBA();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fully read <paramref name="input"/> stream, can be used as workaround for il2cpp streams.
|
||||
/// </summary>
|
||||
public static byte[] ReadFully(this Stream input)
|
||||
{
|
||||
using (var ms = new MemoryStream())
|
||||
{
|
||||
var list = input as IList<T> ?? input.ToList();
|
||||
return list.Count == 0 ? default : list[UnityEngine.Random.Range(0, list.Count)];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the color as a hexadecimal string in the format "RRGGBBAA".
|
||||
/// </summary>
|
||||
/// <param name="color">The color to be converted.</param>
|
||||
/// <returns>Hexadecimal string representing the color.</returns>
|
||||
/// <remarks>https://docs.unity3d.com/ScriptReference/ColorUtility.ToHtmlStringRGBA.html</remarks>
|
||||
public static string ToHtmlStringRGBA(this Color32 color)
|
||||
{
|
||||
return $"{color.r:X2}{color.g:X2}{color.b:X2}{color.a:X2}";
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="ToHtmlStringRGBA(UnityEngine.Color32)"/>
|
||||
public static string ToHtmlStringRGBA(this Color color)
|
||||
{
|
||||
return ((Color32) color).ToHtmlStringRGBA();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fully read <paramref name="input"/> stream, can be used as workaround for il2cpp streams.
|
||||
/// </summary>
|
||||
public static byte[] ReadFully(this Stream input)
|
||||
{
|
||||
using (var ms = new MemoryStream())
|
||||
{
|
||||
input.CopyTo(ms);
|
||||
return ms.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends <see cref="UnityWebRequest"/> and return task that finishes on <paramref name="request"/> completion.
|
||||
/// </summary>
|
||||
public static Task SendAsync(this UnityWebRequest request)
|
||||
{
|
||||
var task = new TaskCompletionSource<object?>();
|
||||
|
||||
request.Send().m_completeCallback = (Action<AsyncOperation>) (_ =>
|
||||
{
|
||||
task.SetResult(null);
|
||||
});
|
||||
|
||||
return task.Task;
|
||||
}
|
||||
|
||||
private static readonly int _outline = Shader.PropertyToID("_Outline");
|
||||
private static readonly int _outlineColor = Shader.PropertyToID("_OutlineColor");
|
||||
private static readonly int _addColor = Shader.PropertyToID("_AddColor");
|
||||
|
||||
/// <summary>
|
||||
/// Sets color outline for renderers using default Among Us shader
|
||||
/// </summary>
|
||||
public static void SetOutline(this Renderer renderer, Color? color)
|
||||
{
|
||||
renderer.material.SetFloat(_outline, color.HasValue ? 1 : 0);
|
||||
|
||||
if (color.HasValue)
|
||||
{
|
||||
renderer.material.SetColor(_outlineColor, color.Value);
|
||||
renderer.material.SetColor(_addColor, color.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<MethodBase> GetMethods(this Type type, BindingFlags bindingAttr, Type returnType, params Type[] parameterTypes)
|
||||
{
|
||||
return type.GetMethods(bindingAttr).Where(x => x.ReturnType == returnType && x.GetParameters().Select(x => x.ParameterType).SequenceEqual(parameterTypes));
|
||||
}
|
||||
|
||||
public static IEnumerable<MethodBase> GetMethods(this Type type, Type returnType, params Type[] parameterTypes)
|
||||
{
|
||||
return type.GetMethods(AccessTools.all, returnType, parameterTypes);
|
||||
}
|
||||
|
||||
public static T? LoadAsset<T>(this AssetBundle assetBundle, string name) where T : UnityEngine.Object
|
||||
{
|
||||
return assetBundle.LoadAsset(name, Il2CppType.Of<T>())?.Cast<T>();
|
||||
input.CopyTo(ms);
|
||||
return ms.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends <see cref="UnityWebRequest"/> and return task that finishes on <paramref name="request"/> completion.
|
||||
/// </summary>
|
||||
public static Task SendAsync(this UnityWebRequest request)
|
||||
{
|
||||
var task = new TaskCompletionSource<object?>();
|
||||
|
||||
request.Send().m_completeCallback = (Action<AsyncOperation>) (_ =>
|
||||
{
|
||||
task.SetResult(null);
|
||||
});
|
||||
|
||||
return task.Task;
|
||||
}
|
||||
|
||||
private static readonly int _outline = Shader.PropertyToID("_Outline");
|
||||
private static readonly int _outlineColor = Shader.PropertyToID("_OutlineColor");
|
||||
private static readonly int _addColor = Shader.PropertyToID("_AddColor");
|
||||
|
||||
/// <summary>
|
||||
/// Sets color outline for renderers using default Among Us shader
|
||||
/// </summary>
|
||||
public static void SetOutline(this Renderer renderer, Color? color)
|
||||
{
|
||||
renderer.material.SetFloat(_outline, color.HasValue ? 1 : 0);
|
||||
|
||||
if (color.HasValue)
|
||||
{
|
||||
renderer.material.SetColor(_outlineColor, color.Value);
|
||||
renderer.material.SetColor(_addColor, color.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<MethodBase> GetMethods(this Type type, BindingFlags bindingAttr, Type returnType, params Type[] parameterTypes)
|
||||
{
|
||||
return type.GetMethods(bindingAttr).Where(x => x.ReturnType == returnType && x.GetParameters().Select(x => x.ParameterType).SequenceEqual(parameterTypes));
|
||||
}
|
||||
|
||||
public static IEnumerable<MethodBase> GetMethods(this Type type, Type returnType, params Type[] parameterTypes)
|
||||
{
|
||||
return type.GetMethods(AccessTools.all, returnType, parameterTypes);
|
||||
}
|
||||
|
||||
public static T? LoadAsset<T>(this AssetBundle assetBundle, string name) where T : UnityEngine.Object
|
||||
{
|
||||
return assetBundle.LoadAsset(name, Il2CppType.Of<T>())?.Cast<T>();
|
||||
}
|
||||
}
|
||||
@@ -3,47 +3,46 @@ using Hazel;
|
||||
using Hazel.Udp;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Reactor.Extensions
|
||||
namespace Reactor.Extensions;
|
||||
|
||||
public static class ExtraMessageExtensions
|
||||
{
|
||||
public static class ExtraMessageExtensions
|
||||
private const float MIN = -50f;
|
||||
private const float MAX = 50f;
|
||||
|
||||
private static float ReverseLerp(float t)
|
||||
{
|
||||
private const float MIN = -50f;
|
||||
private const float MAX = 50f;
|
||||
return Mathf.Clamp((t - MIN) / (MAX - MIN), 0f, 1f);
|
||||
}
|
||||
|
||||
private static float ReverseLerp(float t)
|
||||
{
|
||||
return Mathf.Clamp((t - MIN) / (MAX - MIN), 0f, 1f);
|
||||
}
|
||||
public static void Write(this MessageWriter writer, Vector2 value)
|
||||
{
|
||||
var x = (ushort) (ReverseLerp(value.x) * ushort.MaxValue);
|
||||
var y = (ushort) (ReverseLerp(value.y) * ushort.MaxValue);
|
||||
|
||||
public static void Write(this MessageWriter writer, Vector2 value)
|
||||
{
|
||||
var x = (ushort) (ReverseLerp(value.x) * ushort.MaxValue);
|
||||
var y = (ushort) (ReverseLerp(value.y) * ushort.MaxValue);
|
||||
writer.Write(x);
|
||||
writer.Write(y);
|
||||
}
|
||||
|
||||
writer.Write(x);
|
||||
writer.Write(y);
|
||||
}
|
||||
public static Vector2 ReadVector2(this MessageReader reader)
|
||||
{
|
||||
var x = reader.ReadUInt16() / (float) ushort.MaxValue;
|
||||
var y = reader.ReadUInt16() / (float) ushort.MaxValue;
|
||||
|
||||
public static Vector2 ReadVector2(this MessageReader reader)
|
||||
{
|
||||
var x = reader.ReadUInt16() / (float) ushort.MaxValue;
|
||||
var y = reader.ReadUInt16() / (float) ushort.MaxValue;
|
||||
return new Vector2(Mathf.Lerp(MIN, MAX, x), Mathf.Lerp(MIN, MAX, y));
|
||||
}
|
||||
|
||||
return new Vector2(Mathf.Lerp(MIN, MAX, x), Mathf.Lerp(MIN, MAX, y));
|
||||
}
|
||||
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.");
|
||||
|
||||
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.");
|
||||
var buffer = new byte[msg.Length];
|
||||
Buffer.BlockCopy(msg.Buffer, 0, buffer, 0, msg.Length);
|
||||
|
||||
var buffer = new byte[msg.Length];
|
||||
Buffer.BlockCopy(msg.Buffer, 0, buffer, 0, msg.Length);
|
||||
connection.ResetKeepAliveTimer();
|
||||
|
||||
connection.ResetKeepAliveTimer();
|
||||
|
||||
connection.AttachReliableID(buffer, 1, ackCallback);
|
||||
connection.WriteBytesToConnection(buffer, buffer.Length);
|
||||
}
|
||||
connection.AttachReliableID(buffer, 1, ackCallback);
|
||||
connection.WriteBytesToConnection(buffer, buffer.Length);
|
||||
}
|
||||
}
|
||||
+120
-121
@@ -4,146 +4,145 @@ using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Reactor.Extensions
|
||||
namespace Reactor.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// GUI utilities
|
||||
/// </summary>
|
||||
public static class GUIExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// GUI utilities
|
||||
/// Shortcut for empty texture
|
||||
/// </summary>
|
||||
public static class GUIExtensions
|
||||
public static Texture2D CreateEmptyTexture(int width = 0, int height = 0)
|
||||
{
|
||||
/// <summary>
|
||||
/// Shortcut for empty texture
|
||||
/// </summary>
|
||||
public static Texture2D CreateEmptyTexture(int width = 0, int height = 0)
|
||||
{
|
||||
return new Texture2D(width, height, TextureFormat.RGBA32, Texture.GenerateAllMips, false, IntPtr.Zero);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clamp Rect to screen size
|
||||
/// </summary>
|
||||
public static Rect ClampScreen(this Rect rect)
|
||||
{
|
||||
rect.x = Mathf.Clamp(rect.x, 0, Screen.width - rect.width);
|
||||
rect.y = Mathf.Clamp(rect.y, 0, Screen.height - rect.height);
|
||||
|
||||
return rect;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reset Rect size
|
||||
/// </summary>
|
||||
public static Rect ResetSize(this Rect rect)
|
||||
{
|
||||
rect.width = rect.height = 0;
|
||||
|
||||
return rect;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create <see cref="Sprite"/> from <paramref name="tex"/>
|
||||
/// </summary>
|
||||
public static Sprite CreateSprite(this Texture2D tex)
|
||||
{
|
||||
return Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height), new Vector2(0.5f, 0.5f), 100f);
|
||||
}
|
||||
|
||||
public static DefaultControls.Resources StandardResources { get; internal set; } = null!;
|
||||
|
||||
public static GameObject CreateCanvas()
|
||||
{
|
||||
var gameObject = new GameObject("Canvas");
|
||||
var canvas = gameObject.AddComponent<Canvas>();
|
||||
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
|
||||
|
||||
var canvasScaler = gameObject.AddComponent<CanvasScaler>();
|
||||
|
||||
canvasScaler.screenMatchMode = CanvasScaler.ScreenMatchMode.Expand;
|
||||
canvasScaler.referencePixelsPerUnit = 100f;
|
||||
|
||||
gameObject.AddComponent<GraphicRaycaster>();
|
||||
|
||||
return gameObject;
|
||||
}
|
||||
|
||||
public static GameObject CreateEventSystem()
|
||||
{
|
||||
var gameObject = new GameObject("EventSystem");
|
||||
gameObject.AddComponent<EventSystem>();
|
||||
gameObject.AddComponent<StandaloneInputModule>();
|
||||
gameObject.AddComponent<BaseInput>();
|
||||
|
||||
return gameObject;
|
||||
}
|
||||
|
||||
public static void SetSize(this RectTransform rectTransform, float width, float height)
|
||||
{
|
||||
rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, width);
|
||||
rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, height);
|
||||
}
|
||||
return new Texture2D(width, height, TextureFormat.RGBA32, Texture.GenerateAllMips, false, IntPtr.Zero);
|
||||
}
|
||||
|
||||
public class Window
|
||||
/// <summary>
|
||||
/// Clamp Rect to screen size
|
||||
/// </summary>
|
||||
public static Rect ClampScreen(this Rect rect)
|
||||
{
|
||||
private static int _lastWindowId;
|
||||
rect.x = Mathf.Clamp(rect.x, 0, Screen.width - rect.width);
|
||||
rect.y = Mathf.Clamp(rect.y, 0, Screen.height - rect.height);
|
||||
|
||||
public static int NextWindowId()
|
||||
return rect;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reset Rect size
|
||||
/// </summary>
|
||||
public static Rect ResetSize(this Rect rect)
|
||||
{
|
||||
rect.width = rect.height = 0;
|
||||
|
||||
return rect;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create <see cref="Sprite"/> from <paramref name="tex"/>
|
||||
/// </summary>
|
||||
public static Sprite CreateSprite(this Texture2D tex)
|
||||
{
|
||||
return Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height), new Vector2(0.5f, 0.5f), 100f);
|
||||
}
|
||||
|
||||
public static DefaultControls.Resources StandardResources { get; internal set; } = null!;
|
||||
|
||||
public static GameObject CreateCanvas()
|
||||
{
|
||||
var gameObject = new GameObject("Canvas");
|
||||
var canvas = gameObject.AddComponent<Canvas>();
|
||||
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
|
||||
|
||||
var canvasScaler = gameObject.AddComponent<CanvasScaler>();
|
||||
|
||||
canvasScaler.screenMatchMode = CanvasScaler.ScreenMatchMode.Expand;
|
||||
canvasScaler.referencePixelsPerUnit = 100f;
|
||||
|
||||
gameObject.AddComponent<GraphicRaycaster>();
|
||||
|
||||
return gameObject;
|
||||
}
|
||||
|
||||
public static GameObject CreateEventSystem()
|
||||
{
|
||||
var gameObject = new GameObject("EventSystem");
|
||||
gameObject.AddComponent<EventSystem>();
|
||||
gameObject.AddComponent<StandaloneInputModule>();
|
||||
gameObject.AddComponent<BaseInput>();
|
||||
|
||||
return gameObject;
|
||||
}
|
||||
|
||||
public static void SetSize(this RectTransform rectTransform, float width, float height)
|
||||
{
|
||||
rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, width);
|
||||
rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, height);
|
||||
}
|
||||
}
|
||||
|
||||
public class Window
|
||||
{
|
||||
private static int _lastWindowId;
|
||||
|
||||
public static int NextWindowId()
|
||||
{
|
||||
return _lastWindowId++;
|
||||
}
|
||||
|
||||
public int Id { get; set; } = NextWindowId();
|
||||
|
||||
public bool Enabled { get; set; } = true;
|
||||
public Rect Rect { get; set; }
|
||||
public Action<int> Func { get; set; }
|
||||
public string Title { get; set; }
|
||||
|
||||
public Window(Rect rect, string title, Action<int> func)
|
||||
{
|
||||
Rect = rect;
|
||||
Title = title;
|
||||
Func = func;
|
||||
}
|
||||
|
||||
public Window(Rect rect, string title, Action func) : this(rect, title, id => func())
|
||||
{
|
||||
}
|
||||
|
||||
public void OnGUI()
|
||||
{
|
||||
if (Enabled)
|
||||
{
|
||||
return _lastWindowId++;
|
||||
}
|
||||
|
||||
public int Id { get; set; } = NextWindowId();
|
||||
|
||||
public bool Enabled { get; set; } = true;
|
||||
public Rect Rect { get; set; }
|
||||
public Action<int> Func { get; set; }
|
||||
public string Title { get; set; }
|
||||
|
||||
public Window(Rect rect, string title, Action<int> func)
|
||||
{
|
||||
Rect = rect;
|
||||
Title = title;
|
||||
Func = func;
|
||||
}
|
||||
|
||||
public Window(Rect rect, string title, Action func) : this(rect, title, id => func())
|
||||
{
|
||||
}
|
||||
|
||||
public void OnGUI()
|
||||
{
|
||||
if (Enabled)
|
||||
if (Event.current.type == EventType.Layout)
|
||||
{
|
||||
if (Event.current.type == EventType.Layout)
|
||||
{
|
||||
Rect = Rect.ResetSize();
|
||||
}
|
||||
Rect = Rect.ResetSize();
|
||||
}
|
||||
|
||||
Rect = GUILayout.Window(Id, Rect, Func, Title);
|
||||
Rect = GUILayout.Window(Id, Rect, Func, Title);
|
||||
|
||||
if ((Input.GetMouseButtonDown(0) || Input.GetMouseButtonDown(1)) && Rect.Contains(new Vector2(Input.mousePosition.x, Screen.height - Input.mousePosition.y)))
|
||||
{
|
||||
Input.ResetInputAxes();
|
||||
}
|
||||
if ((Input.GetMouseButtonDown(0) || Input.GetMouseButtonDown(1)) && Rect.Contains(new Vector2(Input.mousePosition.x, Screen.height - Input.mousePosition.y)))
|
||||
{
|
||||
Input.ResetInputAxes();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class DragWindow : Window
|
||||
public class DragWindow : Window
|
||||
{
|
||||
public DragWindow(Rect rect, string title, Action<int> func) : base(rect, title, func)
|
||||
{
|
||||
public DragWindow(Rect rect, string title, Action<int> func) : base(rect, title, func)
|
||||
Func = id =>
|
||||
{
|
||||
Func = id =>
|
||||
{
|
||||
func(id);
|
||||
func(id);
|
||||
|
||||
GUI.DragWindow(new Rect(0, 0, 10000, 20));
|
||||
Rect = Rect.ClampScreen();
|
||||
};
|
||||
}
|
||||
GUI.DragWindow(new Rect(0, 0, 10000, 20));
|
||||
Rect = Rect.ClampScreen();
|
||||
};
|
||||
}
|
||||
|
||||
public DragWindow(Rect rect, string title, Action func) : this(rect, title, id => func())
|
||||
{
|
||||
}
|
||||
public DragWindow(Rect rect, string title, Action func) : this(rect, title, id => func())
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -5,89 +5,88 @@ using UnhollowerBaseLib;
|
||||
using UnhollowerBaseLib.Attributes;
|
||||
using UnhollowerRuntimeLib;
|
||||
|
||||
namespace Reactor.Extensions
|
||||
namespace Reactor.Extensions;
|
||||
|
||||
[RegisterInIl2Cpp]
|
||||
public class StreamWrapper : Il2CppSystem.IO.Stream
|
||||
{
|
||||
[RegisterInIl2Cpp]
|
||||
public class StreamWrapper : Il2CppSystem.IO.Stream
|
||||
{
|
||||
private readonly Stream _stream;
|
||||
private readonly Stream _stream;
|
||||
|
||||
#pragma warning disable 8618
|
||||
public StreamWrapper(IntPtr ptr) : base(ptr) { }
|
||||
public StreamWrapper(IntPtr ptr) : base(ptr) { }
|
||||
#pragma warning restore 8618
|
||||
|
||||
public StreamWrapper(Stream stream) : base(ClassInjector.DerivedConstructorPointer<StreamWrapper>())
|
||||
{
|
||||
ClassInjector.DerivedConstructorBody(this);
|
||||
_stream = stream;
|
||||
}
|
||||
|
||||
[HideFromIl2Cpp]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static unsafe Span<byte> GetSpan(Il2CppStructArray<byte> buffer, int offset, int count)
|
||||
{
|
||||
var rawBuffer = (byte*) buffer.Pointer + 4 * IntPtr.Size;
|
||||
return new Span<byte>(rawBuffer + offset, count);
|
||||
}
|
||||
|
||||
public override int Read(Il2CppStructArray<byte> buffer, int offset, int count)
|
||||
{
|
||||
return _stream.Read(GetSpan(buffer, offset, count));
|
||||
}
|
||||
|
||||
public override void Write(Il2CppStructArray<byte> buffer, int offset, int count)
|
||||
{
|
||||
_stream.Write(GetSpan(buffer, offset, count));
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
_stream.Flush();
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
_stream.Close();
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
_stream.Dispose();
|
||||
}
|
||||
|
||||
public override long Seek(long offset, Il2CppSystem.IO.SeekOrigin origin)
|
||||
{
|
||||
return _stream.Seek(offset, origin switch
|
||||
{
|
||||
Il2CppSystem.IO.SeekOrigin.Begin => SeekOrigin.Begin,
|
||||
Il2CppSystem.IO.SeekOrigin.Current => SeekOrigin.Current,
|
||||
Il2CppSystem.IO.SeekOrigin.End => SeekOrigin.End,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(origin), origin, null)
|
||||
});
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
_stream.SetLength(value);
|
||||
}
|
||||
|
||||
public override bool CanRead => _stream.CanRead;
|
||||
public override bool CanSeek => _stream.CanSeek;
|
||||
public override bool CanWrite => _stream.CanWrite;
|
||||
public override long Length => _stream.Length;
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get => _stream.Position;
|
||||
set => _stream.Position = value;
|
||||
}
|
||||
public StreamWrapper(Stream stream) : base(ClassInjector.DerivedConstructorPointer<StreamWrapper>())
|
||||
{
|
||||
ClassInjector.DerivedConstructorBody(this);
|
||||
_stream = stream;
|
||||
}
|
||||
|
||||
public static class StreamExtensions
|
||||
[HideFromIl2Cpp]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static unsafe Span<byte> GetSpan(Il2CppStructArray<byte> buffer, int offset, int count)
|
||||
{
|
||||
/// <summary>
|
||||
/// Wraps a System Stream into an Il2Cpp Stream
|
||||
/// </summary>
|
||||
public static StreamWrapper AsIl2Cpp(this Stream stream) => new(stream);
|
||||
var rawBuffer = (byte*) buffer.Pointer + 4 * IntPtr.Size;
|
||||
return new Span<byte>(rawBuffer + offset, count);
|
||||
}
|
||||
|
||||
public override int Read(Il2CppStructArray<byte> buffer, int offset, int count)
|
||||
{
|
||||
return _stream.Read(GetSpan(buffer, offset, count));
|
||||
}
|
||||
|
||||
public override void Write(Il2CppStructArray<byte> buffer, int offset, int count)
|
||||
{
|
||||
_stream.Write(GetSpan(buffer, offset, count));
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
_stream.Flush();
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
_stream.Close();
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
_stream.Dispose();
|
||||
}
|
||||
|
||||
public override long Seek(long offset, Il2CppSystem.IO.SeekOrigin origin)
|
||||
{
|
||||
return _stream.Seek(offset, origin switch
|
||||
{
|
||||
Il2CppSystem.IO.SeekOrigin.Begin => SeekOrigin.Begin,
|
||||
Il2CppSystem.IO.SeekOrigin.Current => SeekOrigin.Current,
|
||||
Il2CppSystem.IO.SeekOrigin.End => SeekOrigin.End,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(origin), origin, null),
|
||||
});
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
_stream.SetLength(value);
|
||||
}
|
||||
|
||||
public override bool CanRead => _stream.CanRead;
|
||||
public override bool CanSeek => _stream.CanSeek;
|
||||
public override bool CanWrite => _stream.CanWrite;
|
||||
public override long Length => _stream.Length;
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get => _stream.Position;
|
||||
set => _stream.Position = value;
|
||||
}
|
||||
}
|
||||
|
||||
public static class StreamExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Wraps a System Stream into an Il2Cpp Stream
|
||||
/// </summary>
|
||||
public static StreamWrapper AsIl2Cpp(this Stream stream) => new(stream);
|
||||
}
|
||||
@@ -1,43 +1,42 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Reactor.Extensions
|
||||
namespace Reactor.Extensions;
|
||||
|
||||
public static class UnityObjectExtensions
|
||||
{
|
||||
public static class UnityObjectExtensions
|
||||
/// <summary>
|
||||
/// Stops <paramref name="obj"/> from being destroyed
|
||||
/// </summary>
|
||||
/// <param name="obj">Object to stop from being destroyed</param>
|
||||
/// <returns>Passed <paramref name="obj"/></returns>
|
||||
public static T DontDestroy<T>(this T obj) where T : Object
|
||||
{
|
||||
/// <summary>
|
||||
/// Stops <paramref name="obj"/> from being destroyed
|
||||
/// </summary>
|
||||
/// <param name="obj">Object to stop from being destroyed</param>
|
||||
/// <returns>Passed <paramref name="obj"/></returns>
|
||||
public static T DontDestroy<T>(this T obj) where T : Object
|
||||
{
|
||||
obj.hideFlags |= HideFlags.HideAndDontSave;
|
||||
obj.hideFlags |= HideFlags.HideAndDontSave;
|
||||
|
||||
return obj.DontDestroyOnLoad();
|
||||
}
|
||||
return obj.DontDestroyOnLoad();
|
||||
}
|
||||
|
||||
public static T DontUnload<T>(this T obj) where T : Object
|
||||
{
|
||||
obj.hideFlags |= HideFlags.DontUnloadUnusedAsset;
|
||||
public static T DontUnload<T>(this T obj) where T : Object
|
||||
{
|
||||
obj.hideFlags |= HideFlags.DontUnloadUnusedAsset;
|
||||
|
||||
return obj;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
public static T DontDestroyOnLoad<T>(this T obj) where T : Object
|
||||
{
|
||||
Object.DontDestroyOnLoad(obj);
|
||||
public static T DontDestroyOnLoad<T>(this T obj) where T : Object
|
||||
{
|
||||
Object.DontDestroyOnLoad(obj);
|
||||
|
||||
return obj;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
public static void Destroy(this Object obj)
|
||||
{
|
||||
Object.Destroy(obj);
|
||||
}
|
||||
public static void Destroy(this Object obj)
|
||||
{
|
||||
Object.Destroy(obj);
|
||||
}
|
||||
|
||||
public static void DestroyImmediate(this Object obj)
|
||||
{
|
||||
Object.DestroyImmediate(obj);
|
||||
}
|
||||
public static void DestroyImmediate(this Object obj)
|
||||
{
|
||||
Object.DestroyImmediate(obj);
|
||||
}
|
||||
}
|
||||
@@ -1,38 +1,37 @@
|
||||
using System.Collections.Generic;
|
||||
using Il2CppSystem.Runtime.CompilerServices;
|
||||
|
||||
namespace Reactor
|
||||
namespace Reactor;
|
||||
|
||||
public sealed class Il2CppEqualityComparer<T> : IEqualityComparer<T> where T : Il2CppSystem.Object
|
||||
{
|
||||
public sealed class Il2CppEqualityComparer<T> : IEqualityComparer<T> where T : Il2CppSystem.Object
|
||||
private static Il2CppEqualityComparer<T>? _instance;
|
||||
|
||||
public static Il2CppEqualityComparer<T> Instance
|
||||
{
|
||||
private static Il2CppEqualityComparer<T>? _instance;
|
||||
|
||||
public static Il2CppEqualityComparer<T> Instance
|
||||
get
|
||||
{
|
||||
get
|
||||
{
|
||||
_instance ??= new Il2CppEqualityComparer<T>();
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
private Il2CppEqualityComparer()
|
||||
{
|
||||
}
|
||||
|
||||
public int GetHashCode(T value)
|
||||
{
|
||||
return RuntimeHelpers.GetHashCode(value);
|
||||
}
|
||||
|
||||
public bool Equals(T? left, T? right)
|
||||
{
|
||||
if (left == null || right == null)
|
||||
{
|
||||
return left == null && right == null;
|
||||
}
|
||||
|
||||
return left.Equals(right);
|
||||
_instance ??= new Il2CppEqualityComparer<T>();
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
private Il2CppEqualityComparer()
|
||||
{
|
||||
}
|
||||
|
||||
public int GetHashCode(T value)
|
||||
{
|
||||
return RuntimeHelpers.GetHashCode(value);
|
||||
}
|
||||
|
||||
public bool Equals(T? left, T? right)
|
||||
{
|
||||
if (left == null || right == null)
|
||||
{
|
||||
return left == null && right == null;
|
||||
}
|
||||
|
||||
return left.Equals(right);
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,8 @@
|
||||
|
||||
// ReSharper disable CheckNamespace
|
||||
|
||||
namespace System.Runtime.CompilerServices
|
||||
namespace System.Runtime.CompilerServices;
|
||||
|
||||
public static class IsExternalInit
|
||||
{
|
||||
public static class IsExternalInit
|
||||
{
|
||||
}
|
||||
}
|
||||
+16
-17
@@ -1,28 +1,27 @@
|
||||
using BepInEx.IL2CPP;
|
||||
using BepInEx.Logging;
|
||||
|
||||
namespace Reactor
|
||||
namespace Reactor;
|
||||
|
||||
public static class Logger<T> where T : BasePlugin
|
||||
{
|
||||
public static class Logger<T> where T : BasePlugin
|
||||
{
|
||||
public static ManualLogSource Instance => PluginSingleton<T>.Instance.Log;
|
||||
public static ManualLogSource Instance => PluginSingleton<T>.Instance.Log;
|
||||
|
||||
/// <inheritdoc cref="ManualLogSource.LogFatal"/>
|
||||
public static void Fatal(object data) => Instance.LogFatal(data);
|
||||
/// <inheritdoc cref="ManualLogSource.LogFatal"/>
|
||||
public static void Fatal(object data) => Instance.LogFatal(data);
|
||||
|
||||
/// <inheritdoc cref="ManualLogSource.LogError"/>
|
||||
public static void Error(object data) => Instance.LogError(data);
|
||||
/// <inheritdoc cref="ManualLogSource.LogError"/>
|
||||
public static void Error(object data) => Instance.LogError(data);
|
||||
|
||||
/// <inheritdoc cref="ManualLogSource.LogWarning"/>
|
||||
public static void Warning(object data) => Instance.LogWarning(data);
|
||||
/// <inheritdoc cref="ManualLogSource.LogWarning"/>
|
||||
public static void Warning(object data) => Instance.LogWarning(data);
|
||||
|
||||
/// <inheritdoc cref="ManualLogSource.LogMessage"/>
|
||||
public static void Message(object data) => Instance.LogMessage(data);
|
||||
/// <inheritdoc cref="ManualLogSource.LogMessage"/>
|
||||
public static void Message(object data) => Instance.LogMessage(data);
|
||||
|
||||
/// <inheritdoc cref="ManualLogSource.LogInfo"/>
|
||||
public static void Info(object data) => Instance.LogInfo(data);
|
||||
/// <inheritdoc cref="ManualLogSource.LogInfo"/>
|
||||
public static void Info(object data) => Instance.LogInfo(data);
|
||||
|
||||
/// <inheritdoc cref="ManualLogSource.LogDebug"/>
|
||||
public static void Debug(object data) => Instance.LogDebug(data);
|
||||
}
|
||||
/// <inheritdoc cref="ManualLogSource.LogDebug"/>
|
||||
public static void Debug(object data) => Instance.LogDebug(data);
|
||||
}
|
||||
@@ -3,80 +3,79 @@ using BepInEx.IL2CPP;
|
||||
using Hazel;
|
||||
using InnerNet;
|
||||
|
||||
namespace Reactor.Networking
|
||||
namespace Reactor.Networking;
|
||||
|
||||
public abstract class CustomRpc<TPlugin, TInnerNetObject, TData> : UnsafeCustomRpc where TPlugin : BasePlugin where TInnerNetObject : InnerNetObject
|
||||
{
|
||||
public abstract class CustomRpc<TPlugin, TInnerNetObject, TData> : UnsafeCustomRpc where TPlugin : BasePlugin where TInnerNetObject : InnerNetObject
|
||||
protected CustomRpc(TPlugin plugin, uint id) : base(plugin, id)
|
||||
{
|
||||
protected CustomRpc(TPlugin plugin, uint id) : base(plugin, id)
|
||||
{
|
||||
}
|
||||
|
||||
public TPlugin Plugin => (TPlugin) UnsafePlugin;
|
||||
public override Type InnerNetObjectType => typeof(TInnerNetObject);
|
||||
|
||||
public abstract void Write(MessageWriter writer, TData? data);
|
||||
public abstract TData Read(MessageReader reader);
|
||||
public abstract void Handle(TInnerNetObject innerNetObject, TData? data);
|
||||
|
||||
public override void UnsafeWrite(MessageWriter writer, object? data)
|
||||
{
|
||||
Write(writer, (TData?) data);
|
||||
}
|
||||
|
||||
public override object? UnsafeRead(MessageReader reader)
|
||||
{
|
||||
return Read(reader);
|
||||
}
|
||||
|
||||
public override void UnsafeHandle(InnerNetObject innerNetObject, object? data)
|
||||
{
|
||||
Handle((TInnerNetObject) innerNetObject, (TData?) data);
|
||||
}
|
||||
|
||||
public void Send(InnerNetObject netObject, TData data, bool immediately = false)
|
||||
{
|
||||
UnsafeSend(netObject, data, immediately);
|
||||
}
|
||||
|
||||
public void SendTo(InnerNetObject netObject, int targetId, TData data)
|
||||
{
|
||||
UnsafeSend(netObject, data, true, targetId);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class CustomRpc<TPlugin, TInnerNetObject> : UnsafeCustomRpc where TPlugin : BasePlugin where TInnerNetObject : InnerNetObject
|
||||
public TPlugin Plugin => (TPlugin) UnsafePlugin;
|
||||
public override Type InnerNetObjectType => typeof(TInnerNetObject);
|
||||
|
||||
public abstract void Write(MessageWriter writer, TData? data);
|
||||
public abstract TData Read(MessageReader reader);
|
||||
public abstract void Handle(TInnerNetObject innerNetObject, TData? data);
|
||||
|
||||
public override void UnsafeWrite(MessageWriter writer, object? data)
|
||||
{
|
||||
protected CustomRpc(TPlugin plugin, uint id) : base(plugin, id)
|
||||
{
|
||||
}
|
||||
Write(writer, (TData?) data);
|
||||
}
|
||||
|
||||
public TPlugin Plugin => (TPlugin) UnsafePlugin;
|
||||
public override Type InnerNetObjectType => typeof(TInnerNetObject);
|
||||
public override object? UnsafeRead(MessageReader reader)
|
||||
{
|
||||
return Read(reader);
|
||||
}
|
||||
|
||||
public abstract void Handle(TInnerNetObject innerNetObject);
|
||||
public override void UnsafeHandle(InnerNetObject innerNetObject, object? data)
|
||||
{
|
||||
Handle((TInnerNetObject) innerNetObject, (TData?) data);
|
||||
}
|
||||
|
||||
public override void UnsafeWrite(MessageWriter writer, object? data)
|
||||
{
|
||||
}
|
||||
public void Send(InnerNetObject netObject, TData data, bool immediately = false)
|
||||
{
|
||||
UnsafeSend(netObject, data, immediately);
|
||||
}
|
||||
|
||||
public override object? UnsafeRead(MessageReader reader)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public override void UnsafeHandle(InnerNetObject innerNetObject, object? data)
|
||||
{
|
||||
Handle((TInnerNetObject) innerNetObject);
|
||||
}
|
||||
|
||||
public void Send(InnerNetObject netObject, bool immediately = false)
|
||||
{
|
||||
UnsafeSend(netObject, immediately);
|
||||
}
|
||||
|
||||
public void SendTo(InnerNetObject netObject, int targetId)
|
||||
{
|
||||
UnsafeSend(netObject, null, true, targetId);
|
||||
}
|
||||
public void SendTo(InnerNetObject netObject, int targetId, TData data)
|
||||
{
|
||||
UnsafeSend(netObject, data, true, targetId);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class CustomRpc<TPlugin, TInnerNetObject> : UnsafeCustomRpc where TPlugin : BasePlugin where TInnerNetObject : InnerNetObject
|
||||
{
|
||||
protected CustomRpc(TPlugin plugin, uint id) : base(plugin, id)
|
||||
{
|
||||
}
|
||||
|
||||
public TPlugin Plugin => (TPlugin) UnsafePlugin;
|
||||
public override Type InnerNetObjectType => typeof(TInnerNetObject);
|
||||
|
||||
public abstract void Handle(TInnerNetObject innerNetObject);
|
||||
|
||||
public override void UnsafeWrite(MessageWriter writer, object? data)
|
||||
{
|
||||
}
|
||||
|
||||
public override object? UnsafeRead(MessageReader reader)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public override void UnsafeHandle(InnerNetObject innerNetObject, object? data)
|
||||
{
|
||||
Handle((TInnerNetObject) innerNetObject);
|
||||
}
|
||||
|
||||
public void Send(InnerNetObject netObject, bool immediately = false)
|
||||
{
|
||||
UnsafeSend(netObject, immediately);
|
||||
}
|
||||
|
||||
public void SendTo(InnerNetObject netObject, int targetId)
|
||||
{
|
||||
UnsafeSend(netObject, null, true, targetId);
|
||||
}
|
||||
}
|
||||
@@ -6,113 +6,112 @@ using HarmonyLib;
|
||||
using Hazel;
|
||||
using InnerNet;
|
||||
|
||||
namespace Reactor.Networking
|
||||
namespace Reactor.Networking;
|
||||
|
||||
public enum RpcLocalHandling
|
||||
{
|
||||
public enum RpcLocalHandling
|
||||
None,
|
||||
Before,
|
||||
After,
|
||||
}
|
||||
|
||||
public class CustomRpcManager
|
||||
{
|
||||
public const byte CallId = byte.MaxValue;
|
||||
|
||||
private readonly List<UnsafeCustomRpc> _list = new();
|
||||
private readonly Dictionary<Type, List<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()
|
||||
{
|
||||
None,
|
||||
Before,
|
||||
After
|
||||
}
|
||||
|
||||
public class CustomRpcManager
|
||||
{
|
||||
public const byte CallId = byte.MaxValue;
|
||||
|
||||
private readonly List<UnsafeCustomRpc> _list = new List<UnsafeCustomRpc>();
|
||||
private readonly Dictionary<Type, List<UnsafeCustomRpc>> _map = new Dictionary<Type, List<UnsafeCustomRpc>>();
|
||||
|
||||
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)
|
||||
{
|
||||
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);
|
||||
|
||||
if (customRpc.IsSingleton)
|
||||
{
|
||||
typeof(Rpc<>).MakeGenericType(customRpc.GetType()).GetProperty("Instance")!.SetValue(null, customRpc);
|
||||
}
|
||||
|
||||
return customRpc;
|
||||
}
|
||||
|
||||
[HarmonyPatch]
|
||||
private static class HandleRpcPatch
|
||||
{
|
||||
internal static List<Type> InnerNetObjectTypes { get; } = typeof(InnerNetObject).Assembly.GetTypes().Where(x => x.IsSubclassOf(typeof(InnerNetObject)) && x != typeof(LobbyBehaviour)).ToList();
|
||||
|
||||
public static IEnumerable<MethodBase> TargetMethods()
|
||||
{
|
||||
return InnerNetObjectTypes.Select(x => x.GetMethod(nameof(InnerNetObject.HandleRpc), AccessTools.all));
|
||||
}
|
||||
|
||||
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()];
|
||||
|
||||
var pluginNetId = reader.ReadPackedUInt32();
|
||||
var id = reader.ReadPackedUInt32();
|
||||
|
||||
var pluginId = ModList.GetByNetId(pluginNetId).Id;
|
||||
var customRpc = customRpcs.Single(x => x.PluginId == pluginId && x.Id == id);
|
||||
|
||||
customRpc.UnsafeHandle(__instance, customRpc.UnsafeRead(reader.ReadMessage()));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
_map[type] = new List<UnsafeCustomRpc>();
|
||||
}
|
||||
}
|
||||
|
||||
public static class Rpc<T> where T : UnsafeCustomRpc
|
||||
public UnsafeCustomRpc Register(UnsafeCustomRpc customRpc)
|
||||
{
|
||||
private static T? _instance;
|
||||
|
||||
public static T Instance
|
||||
if (_list.Any(x => x.UnsafePlugin == customRpc.UnsafePlugin && x.Id == customRpc.Id))
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
throw new Exception($"{typeof(T).FullName} isn't registered");
|
||||
}
|
||||
throw new ArgumentException("Rpc with that id was already registered");
|
||||
}
|
||||
|
||||
return _instance;
|
||||
customRpc.Manager = this;
|
||||
_list.Add(customRpc);
|
||||
_map[customRpc.InnerNetObjectType].Add(customRpc);
|
||||
|
||||
if (customRpc.IsSingleton)
|
||||
{
|
||||
typeof(Rpc<>).MakeGenericType(customRpc.GetType()).GetProperty("Instance")!.SetValue(null, customRpc);
|
||||
}
|
||||
|
||||
return customRpc;
|
||||
}
|
||||
|
||||
[HarmonyPatch]
|
||||
private static class HandleRpcPatch
|
||||
{
|
||||
internal static List<Type> InnerNetObjectTypes { get; } = typeof(InnerNetObject).Assembly.GetTypes().Where(x => x.IsSubclassOf(typeof(InnerNetObject)) && x != typeof(LobbyBehaviour)).ToList();
|
||||
|
||||
public static IEnumerable<MethodBase> TargetMethods()
|
||||
{
|
||||
return InnerNetObjectTypes.Select(x => x.GetMethod(nameof(InnerNetObject.HandleRpc), AccessTools.all));
|
||||
}
|
||||
|
||||
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()];
|
||||
|
||||
var pluginNetId = reader.ReadPackedUInt32();
|
||||
var id = reader.ReadPackedUInt32();
|
||||
|
||||
var pluginId = ModList.GetByNetId(pluginNetId).Id;
|
||||
var customRpc = customRpcs.Single(x => x.PluginId == pluginId && x.Id == id);
|
||||
|
||||
customRpc.UnsafeHandle(__instance, customRpc.UnsafeRead(reader.ReadMessage()));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal set
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class Rpc<T> where T : UnsafeCustomRpc
|
||||
{
|
||||
private static T? _instance;
|
||||
|
||||
public static T Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
if (_instance != null)
|
||||
{
|
||||
throw new Exception($"{typeof(T).FullName} is already registered");
|
||||
}
|
||||
throw new Exception($"{typeof(T).FullName} isn't registered");
|
||||
}
|
||||
|
||||
_instance = value;
|
||||
return _instance;
|
||||
}
|
||||
|
||||
internal set
|
||||
{
|
||||
if (_instance != null)
|
||||
{
|
||||
throw new Exception($"{typeof(T).FullName} is already registered");
|
||||
}
|
||||
|
||||
_instance = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,202 +12,201 @@ using MonoMod.RuntimeDetour;
|
||||
using MonoMod.Utils;
|
||||
using Reactor.Networking.Serialization;
|
||||
|
||||
namespace Reactor.Networking.MethodRpc
|
||||
namespace Reactor.Networking.MethodRpc;
|
||||
|
||||
public class MethodRpc : UnsafeCustomRpc
|
||||
{
|
||||
public class MethodRpc : UnsafeCustomRpc
|
||||
public delegate object HandleDelegate(InnerNetObject innerNetObject, object[] args);
|
||||
|
||||
public MethodRpc(BasePlugin plugin, MethodInfo method, uint id, SendOption option, RpcLocalHandling localHandling, bool sendImmediately) : base(plugin, id)
|
||||
{
|
||||
public delegate object HandleDelegate(InnerNetObject innerNetObject, object[] args);
|
||||
Method = method;
|
||||
LocalHandling = localHandling;
|
||||
SendOption = option;
|
||||
SendImmediately = sendImmediately;
|
||||
|
||||
public MethodRpc(BasePlugin plugin, MethodInfo method, uint id, SendOption option, RpcLocalHandling localHandling, bool sendImmediately) : base(plugin, id)
|
||||
if (!method.IsStatic)
|
||||
{
|
||||
Method = method;
|
||||
LocalHandling = localHandling;
|
||||
SendOption = option;
|
||||
SendImmediately = sendImmediately;
|
||||
|
||||
if (!method.IsStatic)
|
||||
{
|
||||
throw new NotImplementedException("Instance method rpc support is postponed until unhollower v0.5.0");
|
||||
}
|
||||
|
||||
var parameters = method.GetParameters();
|
||||
|
||||
if (method.IsStatic && parameters.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("Static method rpc requires at least one argument", nameof(method));
|
||||
}
|
||||
|
||||
var innerNetObjectType = parameters.First().ParameterType;
|
||||
|
||||
if (!typeof(InnerNetObject).IsAssignableFrom(innerNetObjectType))
|
||||
{
|
||||
throw new ArgumentException("First argument of a static method rpc has to be an InnerNetObject", nameof(method));
|
||||
}
|
||||
|
||||
InnerNetObjectType = innerNetObjectType;
|
||||
|
||||
Handle = Hook(method, parameters);
|
||||
throw new NotImplementedException("Instance method rpc support is postponed until unhollower v0.5.0");
|
||||
}
|
||||
|
||||
public MethodInfo Method { get; }
|
||||
public HandleDelegate Handle { get; }
|
||||
var parameters = method.GetParameters();
|
||||
|
||||
protected internal override bool IsSingleton => false;
|
||||
|
||||
public override Type InnerNetObjectType { get; }
|
||||
public override RpcLocalHandling LocalHandling { get; }
|
||||
public override SendOption SendOption { get; }
|
||||
public bool SendImmediately { get; }
|
||||
|
||||
public override void UnsafeWrite(MessageWriter writer, object? data)
|
||||
if (method.IsStatic && parameters.Length == 0)
|
||||
{
|
||||
var args = (object[]) data!;
|
||||
MessageSerializer.Serialize(writer, args);
|
||||
throw new ArgumentException("Static method rpc requires at least one argument", nameof(method));
|
||||
}
|
||||
|
||||
public override object UnsafeRead(MessageReader reader)
|
||||
var innerNetObjectType = parameters.First().ParameterType;
|
||||
|
||||
if (!typeof(InnerNetObject).IsAssignableFrom(innerNetObjectType))
|
||||
{
|
||||
var args = MessageSerializer.Deserialize(reader, this);
|
||||
return args;
|
||||
throw new ArgumentException("First argument of a static method rpc has to be an InnerNetObject", nameof(method));
|
||||
}
|
||||
|
||||
public override void UnsafeHandle(InnerNetObject innerNetObject, object? data)
|
||||
InnerNetObjectType = innerNetObjectType;
|
||||
|
||||
Handle = Hook(method, parameters);
|
||||
}
|
||||
|
||||
public MethodInfo Method { get; }
|
||||
public HandleDelegate Handle { get; }
|
||||
|
||||
protected internal override bool IsSingleton => false;
|
||||
|
||||
public override Type InnerNetObjectType { get; }
|
||||
public override RpcLocalHandling LocalHandling { get; }
|
||||
public override SendOption SendOption { get; }
|
||||
public bool SendImmediately { get; }
|
||||
|
||||
public override void UnsafeWrite(MessageWriter writer, object? data)
|
||||
{
|
||||
var args = (object[]) data!;
|
||||
MessageSerializer.Serialize(writer, args);
|
||||
}
|
||||
|
||||
public override object UnsafeRead(MessageReader reader)
|
||||
{
|
||||
var args = MessageSerializer.Deserialize(reader, this);
|
||||
return args;
|
||||
}
|
||||
|
||||
public override void UnsafeHandle(InnerNetObject innerNetObject, object? data)
|
||||
{
|
||||
var args = (object[]) data!;
|
||||
var result = Handle(innerNetObject, args);
|
||||
|
||||
if (result is IEnumerator enumerator)
|
||||
{
|
||||
var args = (object[]) data!;
|
||||
var result = Handle(innerNetObject, args);
|
||||
|
||||
if (result is IEnumerator enumerator)
|
||||
{
|
||||
Coroutines.Start(enumerator);
|
||||
}
|
||||
}
|
||||
|
||||
public void Send(InnerNetObject innerNetObject, object[] args)
|
||||
{
|
||||
UnsafeSend(innerNetObject, args, SendImmediately);
|
||||
}
|
||||
|
||||
private static readonly MethodInfo _sendMethod = AccessTools.Method(typeof(MethodRpc), nameof(Send));
|
||||
|
||||
/// <summary>
|
||||
/// Hooks the <paramref name="method"/> rpc with a dynamic method that sends it
|
||||
/// </summary>
|
||||
private HandleDelegate Hook(MethodInfo method, ParameterInfo[] parameters)
|
||||
{
|
||||
var detour = new Detour(method, GenerateSender());
|
||||
|
||||
// Used as target when hooking, sends the method rpc
|
||||
DynamicMethod GenerateSender()
|
||||
{
|
||||
var dynamicMethod = new DynamicMethod($"Sender<{method.GetID(simple: true)}>", method.ReturnType, parameters.Select(x => x.ParameterType).ToArray());
|
||||
|
||||
foreach (var parameter in parameters)
|
||||
{
|
||||
dynamicMethod.DefineParameter(parameter.Position, ParameterAttributes.None, parameter.Name);
|
||||
}
|
||||
|
||||
var il = dynamicMethod.GetILGenerator();
|
||||
|
||||
// :yeefuckinhaw:
|
||||
// Black magic from stackoverflow, they also said that you shouldn't do that ;)
|
||||
// Resort to Dictionary<MethodInfo, MethodRpc> if this turns out to be unreliable
|
||||
{
|
||||
var handle = GCHandle.Alloc(this);
|
||||
var ptr = GCHandle.ToIntPtr(handle);
|
||||
|
||||
if (IntPtr.Size == 4)
|
||||
{
|
||||
il.Emit(OpCodes.Ldc_I4, ptr.ToInt32());
|
||||
}
|
||||
else
|
||||
{
|
||||
il.Emit(OpCodes.Ldc_I8, ptr.ToInt64());
|
||||
}
|
||||
|
||||
il.Emit(OpCodes.Conv_I);
|
||||
|
||||
// TODO Figure out why this always throws NullReferenceException on mono
|
||||
// il.Emit(OpCodes.Ldobj, typeof(MethodRpc));
|
||||
|
||||
// Workaround for ^, speed seems to be the same
|
||||
il.Emit(OpCodes.Call, AccessTools.Method(typeof(GCHandle), nameof(GCHandle.FromIntPtr)));
|
||||
var temp = il.DeclareLocal(typeof(GCHandle));
|
||||
il.Emit(OpCodes.Stloc, temp);
|
||||
il.Emit(OpCodes.Ldloca, temp);
|
||||
il.Emit(OpCodes.Call, AccessTools.PropertyGetter(typeof(GCHandle), nameof(GCHandle.Target)));
|
||||
}
|
||||
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
|
||||
il.Emit(OpCodes.Ldc_I4, parameters.Length - 1);
|
||||
il.Emit(OpCodes.Newarr, typeof(object));
|
||||
|
||||
for (var i = 1; i < parameters.Length; i++)
|
||||
{
|
||||
var parameter = parameters[i];
|
||||
|
||||
il.Emit(OpCodes.Dup);
|
||||
il.Emit(OpCodes.Ldc_I4, i - 1);
|
||||
il.Emit(OpCodes.Ldarg, i);
|
||||
|
||||
var parameterType = parameter.ParameterType;
|
||||
if (parameterType.IsValueType)
|
||||
{
|
||||
il.Emit(OpCodes.Box, parameterType);
|
||||
}
|
||||
|
||||
il.Emit(OpCodes.Stelem_Ref);
|
||||
}
|
||||
|
||||
il.Emit(OpCodes.Call, _sendMethod);
|
||||
|
||||
if (method.ReturnType != typeof(void))
|
||||
{
|
||||
il.Emit(OpCodes.Ldnull);
|
||||
}
|
||||
|
||||
il.Emit(OpCodes.Ret);
|
||||
|
||||
return dynamicMethod;
|
||||
}
|
||||
|
||||
// Proxy translating object array to trampoline method args, used by MethodRpc to invoke original handling
|
||||
HandleDelegate GenerateHandler(DynamicMethod trampoline)
|
||||
{
|
||||
var dynamicMethod = new DynamicMethod($"Handler<{method.GetID(simple: true)}>", typeof(object), new[] { typeof(InnerNetObject), typeof(object[]) });
|
||||
dynamicMethod.DefineParameter(0, ParameterAttributes.None, "innerNetObject");
|
||||
dynamicMethod.DefineParameter(1, ParameterAttributes.None, "args");
|
||||
|
||||
var il = dynamicMethod.GetILGenerator();
|
||||
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Castclass, InnerNetObjectType);
|
||||
|
||||
for (var i = 1; i < parameters.Length; i++)
|
||||
{
|
||||
var parameter = parameters[i];
|
||||
|
||||
il.Emit(OpCodes.Ldarg_1);
|
||||
il.Emit(OpCodes.Ldc_I4, i - 1);
|
||||
il.Emit(OpCodes.Ldelem_Ref);
|
||||
|
||||
var parameterType = parameter.ParameterType;
|
||||
il.Emit(parameterType.IsValueType ? OpCodes.Unbox_Any : OpCodes.Castclass, parameterType);
|
||||
}
|
||||
|
||||
il.Emit(OpCodes.Call, trampoline);
|
||||
|
||||
if (method.ReturnType == typeof(void))
|
||||
{
|
||||
il.Emit(OpCodes.Ldnull);
|
||||
}
|
||||
|
||||
il.Emit(OpCodes.Ret);
|
||||
|
||||
return dynamicMethod.CreateDelegate<HandleDelegate>();
|
||||
}
|
||||
|
||||
return GenerateHandler((DynamicMethod) detour.GenerateTrampoline());
|
||||
Coroutines.Start(enumerator);
|
||||
}
|
||||
}
|
||||
|
||||
public void Send(InnerNetObject innerNetObject, object[] args)
|
||||
{
|
||||
UnsafeSend(innerNetObject, args, SendImmediately);
|
||||
}
|
||||
|
||||
private static readonly MethodInfo _sendMethod = AccessTools.Method(typeof(MethodRpc), nameof(Send));
|
||||
|
||||
/// <summary>
|
||||
/// Hooks the <paramref name="method"/> rpc with a dynamic method that sends it
|
||||
/// </summary>
|
||||
private HandleDelegate Hook(MethodInfo method, ParameterInfo[] parameters)
|
||||
{
|
||||
var detour = new Detour(method, GenerateSender());
|
||||
|
||||
// Used as target when hooking, sends the method rpc
|
||||
DynamicMethod GenerateSender()
|
||||
{
|
||||
var dynamicMethod = new DynamicMethod($"Sender<{method.GetID(simple: true)}>", method.ReturnType, parameters.Select(x => x.ParameterType).ToArray());
|
||||
|
||||
foreach (var parameter in parameters)
|
||||
{
|
||||
dynamicMethod.DefineParameter(parameter.Position, ParameterAttributes.None, parameter.Name);
|
||||
}
|
||||
|
||||
var il = dynamicMethod.GetILGenerator();
|
||||
|
||||
// :yeefuckinhaw:
|
||||
// Black magic from stackoverflow, they also said that you shouldn't do that ;)
|
||||
// Resort to Dictionary<MethodInfo, MethodRpc> if this turns out to be unreliable
|
||||
{
|
||||
var handle = GCHandle.Alloc(this);
|
||||
var ptr = GCHandle.ToIntPtr(handle);
|
||||
|
||||
if (IntPtr.Size == 4)
|
||||
{
|
||||
il.Emit(OpCodes.Ldc_I4, ptr.ToInt32());
|
||||
}
|
||||
else
|
||||
{
|
||||
il.Emit(OpCodes.Ldc_I8, ptr.ToInt64());
|
||||
}
|
||||
|
||||
il.Emit(OpCodes.Conv_I);
|
||||
|
||||
// TODO Figure out why this always throws NullReferenceException on mono
|
||||
// il.Emit(OpCodes.Ldobj, typeof(MethodRpc));
|
||||
|
||||
// Workaround for ^, speed seems to be the same
|
||||
il.Emit(OpCodes.Call, AccessTools.Method(typeof(GCHandle), nameof(GCHandle.FromIntPtr)));
|
||||
var temp = il.DeclareLocal(typeof(GCHandle));
|
||||
il.Emit(OpCodes.Stloc, temp);
|
||||
il.Emit(OpCodes.Ldloca, temp);
|
||||
il.Emit(OpCodes.Call, AccessTools.PropertyGetter(typeof(GCHandle), nameof(GCHandle.Target)));
|
||||
}
|
||||
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
|
||||
il.Emit(OpCodes.Ldc_I4, parameters.Length - 1);
|
||||
il.Emit(OpCodes.Newarr, typeof(object));
|
||||
|
||||
for (var i = 1; i < parameters.Length; i++)
|
||||
{
|
||||
var parameter = parameters[i];
|
||||
|
||||
il.Emit(OpCodes.Dup);
|
||||
il.Emit(OpCodes.Ldc_I4, i - 1);
|
||||
il.Emit(OpCodes.Ldarg, i);
|
||||
|
||||
var parameterType = parameter.ParameterType;
|
||||
if (parameterType.IsValueType)
|
||||
{
|
||||
il.Emit(OpCodes.Box, parameterType);
|
||||
}
|
||||
|
||||
il.Emit(OpCodes.Stelem_Ref);
|
||||
}
|
||||
|
||||
il.Emit(OpCodes.Call, _sendMethod);
|
||||
|
||||
if (method.ReturnType != typeof(void))
|
||||
{
|
||||
il.Emit(OpCodes.Ldnull);
|
||||
}
|
||||
|
||||
il.Emit(OpCodes.Ret);
|
||||
|
||||
return dynamicMethod;
|
||||
}
|
||||
|
||||
// Proxy translating object array to trampoline method args, used by MethodRpc to invoke original handling
|
||||
HandleDelegate GenerateHandler(DynamicMethod trampoline)
|
||||
{
|
||||
var dynamicMethod = new DynamicMethod($"Handler<{method.GetID(simple: true)}>", typeof(object), new[] { typeof(InnerNetObject), typeof(object[]) });
|
||||
dynamicMethod.DefineParameter(0, ParameterAttributes.None, "innerNetObject");
|
||||
dynamicMethod.DefineParameter(1, ParameterAttributes.None, "args");
|
||||
|
||||
var il = dynamicMethod.GetILGenerator();
|
||||
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Castclass, InnerNetObjectType);
|
||||
|
||||
for (var i = 1; i < parameters.Length; i++)
|
||||
{
|
||||
var parameter = parameters[i];
|
||||
|
||||
il.Emit(OpCodes.Ldarg_1);
|
||||
il.Emit(OpCodes.Ldc_I4, i - 1);
|
||||
il.Emit(OpCodes.Ldelem_Ref);
|
||||
|
||||
var parameterType = parameter.ParameterType;
|
||||
il.Emit(parameterType.IsValueType ? OpCodes.Unbox_Any : OpCodes.Castclass, parameterType);
|
||||
}
|
||||
|
||||
il.Emit(OpCodes.Call, trampoline);
|
||||
|
||||
if (method.ReturnType == typeof(void))
|
||||
{
|
||||
il.Emit(OpCodes.Ldnull);
|
||||
}
|
||||
|
||||
il.Emit(OpCodes.Ret);
|
||||
|
||||
return dynamicMethod.CreateDelegate<HandleDelegate>();
|
||||
}
|
||||
|
||||
return GenerateHandler((DynamicMethod) detour.GenerateTrampoline());
|
||||
}
|
||||
}
|
||||
@@ -5,49 +5,48 @@ using BepInEx.IL2CPP;
|
||||
using HarmonyLib;
|
||||
using Hazel;
|
||||
|
||||
namespace Reactor.Networking.MethodRpc
|
||||
namespace Reactor.Networking.MethodRpc;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public class MethodRpcAttribute : Attribute
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public class MethodRpcAttribute : Attribute
|
||||
public uint Id { get; }
|
||||
public SendOption Option { get; set; } = SendOption.Reliable;
|
||||
public RpcLocalHandling LocalHandling { get; set; } = RpcLocalHandling.Before;
|
||||
public bool SendImmediately { get; set; }
|
||||
|
||||
public MethodRpcAttribute(uint id)
|
||||
{
|
||||
public uint Id { get; }
|
||||
public SendOption Option { get; set; } = SendOption.Reliable;
|
||||
public RpcLocalHandling LocalHandling { get; set; } = RpcLocalHandling.Before;
|
||||
public bool SendImmediately { get; set; }
|
||||
Id = id;
|
||||
}
|
||||
|
||||
public MethodRpcAttribute(uint id)
|
||||
public static void Register(Assembly assembly, BasePlugin plugin)
|
||||
{
|
||||
var methods = assembly.GetTypes()
|
||||
.SelectMany(t => t.GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public));
|
||||
|
||||
foreach (var method in methods)
|
||||
{
|
||||
Id = id;
|
||||
}
|
||||
|
||||
public static void Register(Assembly assembly, BasePlugin plugin)
|
||||
{
|
||||
var methods = assembly.GetTypes()
|
||||
.SelectMany(t => t.GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public));
|
||||
|
||||
foreach (var method in methods)
|
||||
var attribute = method.GetCustomAttribute<MethodRpcAttribute>();
|
||||
if (attribute == null)
|
||||
{
|
||||
var attribute = method.GetCustomAttribute<MethodRpcAttribute>();
|
||||
if (attribute == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var customRpc = new MethodRpc(plugin, method, attribute.Id, attribute.Option, attribute.LocalHandling, attribute.SendImmediately);
|
||||
PluginSingleton<ReactorPlugin>.Instance.CustomRpcManager.Register(customRpc);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger<ReactorPlugin>.Warning($"Failed to register {method.FullDescription()}: {e}");
|
||||
}
|
||||
try
|
||||
{
|
||||
var customRpc = new MethodRpc(plugin, method, attribute.Id, attribute.Option, attribute.LocalHandling, attribute.SendImmediately);
|
||||
PluginSingleton<ReactorPlugin>.Instance.CustomRpcManager.Register(customRpc);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger<ReactorPlugin>.Warning($"Failed to register {method.FullDescription()}: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void Initialize()
|
||||
{
|
||||
ChainloaderHooks.PluginLoad += plugin => Register(plugin.GetType().Assembly, plugin);
|
||||
}
|
||||
internal static void Initialize()
|
||||
{
|
||||
ChainloaderHooks.PluginLoad += plugin => Register(plugin.GetType().Assembly, plugin);
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,16 @@
|
||||
using Hazel;
|
||||
|
||||
namespace Reactor.Networking
|
||||
namespace Reactor.Networking;
|
||||
|
||||
public static class ModDeclaration
|
||||
{
|
||||
public static class ModDeclaration
|
||||
public static void Serialize(MessageWriter writer, Mod mod)
|
||||
{
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -2,43 +2,42 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using BepInEx.IL2CPP;
|
||||
|
||||
namespace Reactor.Networking
|
||||
namespace Reactor.Networking;
|
||||
|
||||
public static class ModList
|
||||
{
|
||||
public static class ModList
|
||||
private static Dictionary<string, Mod>? _mapById;
|
||||
private static Dictionary<uint, Mod>? _mapByNetId;
|
||||
|
||||
public static ISet<Mod>? Current { get; private set; }
|
||||
|
||||
public static Mod GetById(string id)
|
||||
{
|
||||
private static Dictionary<string, Mod>? _mapById;
|
||||
private static Dictionary<uint, Mod>? _mapByNetId;
|
||||
return _mapById![id];
|
||||
}
|
||||
|
||||
public static ISet<Mod>? Current { get; private set; }
|
||||
public static Mod GetByNetId(uint netId)
|
||||
{
|
||||
return _mapByNetId![netId];
|
||||
}
|
||||
|
||||
public static Mod GetById(string id)
|
||||
{
|
||||
return _mapById![id];
|
||||
}
|
||||
internal static ISet<Mod> Update()
|
||||
{
|
||||
var i = (uint) 0;
|
||||
|
||||
public static Mod GetByNetId(uint netId)
|
||||
{
|
||||
return _mapByNetId![netId];
|
||||
}
|
||||
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())
|
||||
))
|
||||
.ToHashSet();
|
||||
|
||||
internal static ISet<Mod> Update()
|
||||
{
|
||||
var i = (uint) 0;
|
||||
_mapById = Current.ToDictionary(mod => mod.Id, mod => mod);
|
||||
_mapByNetId = Current.ToDictionary(mod => mod.NetId, mod => mod);
|
||||
|
||||
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())
|
||||
))
|
||||
.ToHashSet();
|
||||
|
||||
_mapById = Current.ToDictionary(mod => mod.Id, mod => mod);
|
||||
_mapByNetId = Current.ToDictionary(mod => mod.NetId, mod => mod);
|
||||
|
||||
return Current;
|
||||
}
|
||||
return Current;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,12 @@
|
||||
using Hazel;
|
||||
|
||||
namespace Reactor.Networking
|
||||
namespace Reactor.Networking;
|
||||
|
||||
public static class ModdedHandshakeC2S
|
||||
{
|
||||
public static class ModdedHandshakeC2S
|
||||
public static void Serialize(MessageWriter writer, int modCount)
|
||||
{
|
||||
public static void Serialize(MessageWriter writer, int modCount)
|
||||
{
|
||||
writer.Write((byte) ReactorProtocolVersion.Initial);
|
||||
writer.WritePacked(modCount);
|
||||
}
|
||||
writer.Write((byte) ReactorProtocolVersion.Initial);
|
||||
writer.WritePacked(modCount);
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,23 @@
|
||||
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();
|
||||
}
|
||||
namespace Reactor.Networking;
|
||||
|
||||
public static void Deserialize(MessageReader reader, out string serverName, out string serverVersion, out int pluginCount)
|
||||
{
|
||||
serverName = reader.ReadString();
|
||||
serverVersion = reader.ReadString();
|
||||
pluginCount = reader.ReadPackedInt32();
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -9,176 +9,175 @@ using Reactor.Extensions;
|
||||
using UnhollowerBaseLib;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Reactor.Networking.Patches
|
||||
namespace Reactor.Networking.Patches;
|
||||
|
||||
internal static class ClientPatches
|
||||
{
|
||||
internal static class ClientPatches
|
||||
[HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.HandleMessage))]
|
||||
public static class HandleMessagePatch
|
||||
{
|
||||
[HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.HandleMessage))]
|
||||
public static class HandleMessagePatch
|
||||
public static bool Prefix(InnerNetClient __instance, [HarmonyArgument(0)] MessageReader reader)
|
||||
{
|
||||
public static bool Prefix(InnerNetClient __instance, [HarmonyArgument(0)] MessageReader reader)
|
||||
if (reader.Tag == byte.MaxValue)
|
||||
{
|
||||
if (reader.Tag == byte.MaxValue)
|
||||
var flag = (ReactorMessageFlags) reader.ReadByte();
|
||||
|
||||
switch (flag)
|
||||
{
|
||||
var flag = (ReactorMessageFlags) reader.ReadByte();
|
||||
|
||||
switch (flag)
|
||||
case ReactorMessageFlags.Handshake:
|
||||
{
|
||||
case ReactorMessageFlags.Handshake:
|
||||
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()
|
||||
{
|
||||
ModdedHandshakeS2C.Deserialize(reader, out var serverName, out var serverVersion, out var pluginCount);
|
||||
expected++;
|
||||
|
||||
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()
|
||||
__instance.connection.Send(writer, () =>
|
||||
{
|
||||
expected++;
|
||||
got++;
|
||||
|
||||
__instance.connection.Send(writer, () =>
|
||||
if (got >= expected)
|
||||
{
|
||||
got++;
|
||||
|
||||
if (got >= expected)
|
||||
{
|
||||
Logger<ReactorPlugin>.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);
|
||||
Logger<ReactorPlugin>.Debug("Received all acks");
|
||||
__instance.connection.State = ConnectionState.Connected;
|
||||
}
|
||||
});
|
||||
writer.Recycle();
|
||||
}
|
||||
|
||||
writer.EndMessage();
|
||||
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);
|
||||
}
|
||||
|
||||
Send();
|
||||
|
||||
break;
|
||||
writer.EndMessage();
|
||||
}
|
||||
|
||||
case ReactorMessageFlags.PluginDeclaration:
|
||||
{
|
||||
// TODO
|
||||
break;
|
||||
}
|
||||
Send();
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
break;
|
||||
}
|
||||
|
||||
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<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)
|
||||
case ReactorMessageFlags.PluginDeclaration:
|
||||
{
|
||||
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;
|
||||
}
|
||||
// TODO
|
||||
break;
|
||||
}
|
||||
|
||||
client.LastDisconnectReason = DisconnectReasons.Custom;
|
||||
client.LastCustomDisconnect = "Server didn't respond to modded handshake";
|
||||
client.HandleDisconnect(client.LastDisconnectReason, client.LastCustomDisconnect);
|
||||
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<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>>();
|
||||
});
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.GetConnectionData))]
|
||||
public static class HandshakePatch
|
||||
private static IEnumerator Coroutine(UdpConnection connection)
|
||||
{
|
||||
public static void Prefix(ref bool useDtlsLayout)
|
||||
yield return new WaitForSeconds(3);
|
||||
|
||||
var client = AmongUsClient.Instance;
|
||||
if (client != null && client.connection != null && client.connection.Equals(connection) && client.connection.State == ConnectionState.Connecting)
|
||||
{
|
||||
// Due to reasons currently unknown, the useDtlsLayout parameter sometimes doesn't reflect whether DTLS
|
||||
// is actually supposed to be enabled. This causes a bad handshake message and a quick disconnect.
|
||||
// The field on AmongUsClient appears to be more reliable, so override this parameter with what it is supposed to be.
|
||||
Logger<ReactorPlugin>.Info($"Currently using dtls: {useDtlsLayout}. Should use dtls: {AmongUsClient.Instance.useDtls}");
|
||||
useDtlsLayout = AmongUsClient.Instance.useDtls;
|
||||
}
|
||||
var reactorPlugin = PluginSingleton<ReactorPlugin>.Instance;
|
||||
|
||||
public static void Postfix(ref Il2CppStructArray<byte> __result)
|
||||
{
|
||||
var mods = ModList.Update();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
var handshake = new MessageWriter(1000);
|
||||
|
||||
handshake.Write(__result);
|
||||
|
||||
ModdedHandshakeC2S.Serialize(
|
||||
handshake,
|
||||
mods.Count
|
||||
);
|
||||
|
||||
__result = handshake.ToByteArray(true);
|
||||
handshake.Recycle();
|
||||
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
|
||||
{
|
||||
public static void Prefix(ref bool useDtlsLayout)
|
||||
{
|
||||
// Due to reasons currently unknown, the useDtlsLayout parameter sometimes doesn't reflect whether DTLS
|
||||
// is actually supposed to be enabled. This causes a bad handshake message and a quick disconnect.
|
||||
// The field on AmongUsClient appears to be more reliable, so override this parameter with what it is supposed to be.
|
||||
Logger<ReactorPlugin>.Info($"Currently using dtls: {useDtlsLayout}. Should use dtls: {AmongUsClient.Instance.useDtls}");
|
||||
useDtlsLayout = AmongUsClient.Instance.useDtls;
|
||||
}
|
||||
|
||||
public static void Postfix(ref Il2CppStructArray<byte> __result)
|
||||
{
|
||||
var mods = ModList.Update();
|
||||
|
||||
var handshake = new MessageWriter(1000);
|
||||
|
||||
handshake.Write(__result);
|
||||
|
||||
ModdedHandshakeC2S.Serialize(
|
||||
handshake,
|
||||
mods.Count
|
||||
);
|
||||
|
||||
__result = handshake.ToByteArray(true);
|
||||
handshake.Recycle();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,20 +3,19 @@ using Hazel;
|
||||
using InnerNet;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Reactor.Networking.Patches
|
||||
namespace Reactor.Networking.Patches;
|
||||
|
||||
internal static class ServerPatches
|
||||
{
|
||||
internal static class ServerPatches
|
||||
[HarmonyPatch(typeof(InnerNetServer), nameof(InnerNetServer.OnServerConnect))]
|
||||
public static class HandshakeResponsePatch
|
||||
{
|
||||
[HarmonyPatch(typeof(InnerNetServer), nameof(InnerNetServer.OnServerConnect))]
|
||||
public static class HandshakeResponsePatch
|
||||
public static void Postfix([HarmonyArgument(0)] NewConnectionEventArgs evt)
|
||||
{
|
||||
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();
|
||||
}
|
||||
var writer = MessageWriter.Get(SendOption.Reliable);
|
||||
ModdedHandshakeS2C.Serialize(writer, "Among Us", Application.version, 0);
|
||||
evt.Connection.Send(writer);
|
||||
writer.Recycle();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,38 +1,37 @@
|
||||
using BepInEx.IL2CPP;
|
||||
|
||||
namespace Reactor.Networking
|
||||
namespace Reactor.Networking;
|
||||
|
||||
public abstract class PlayerCustomRpc<TPlugin, TData> : CustomRpc<TPlugin, PlayerControl, TData> where TPlugin : BasePlugin
|
||||
{
|
||||
public abstract class PlayerCustomRpc<TPlugin, TData> : CustomRpc<TPlugin, PlayerControl, TData> where TPlugin : BasePlugin
|
||||
protected PlayerCustomRpc(TPlugin plugin, uint id) : base(plugin, id)
|
||||
{
|
||||
protected PlayerCustomRpc(TPlugin plugin, uint id) : base(plugin, id)
|
||||
{
|
||||
}
|
||||
|
||||
public void Send(TData data, bool immediately = false)
|
||||
{
|
||||
Send(PlayerControl.LocalPlayer, data, immediately);
|
||||
}
|
||||
|
||||
public void SendTo(int targetId, TData data)
|
||||
{
|
||||
SendTo(PlayerControl.LocalPlayer, targetId, data);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class PlayerCustomRpc<TPlugin> : CustomRpc<TPlugin, PlayerControl> where TPlugin : BasePlugin
|
||||
public void Send(TData data, bool immediately = false)
|
||||
{
|
||||
protected PlayerCustomRpc(TPlugin plugin, uint id) : base(plugin, id)
|
||||
{
|
||||
}
|
||||
Send(PlayerControl.LocalPlayer, data, immediately);
|
||||
}
|
||||
|
||||
public void Send(bool immediately = false)
|
||||
{
|
||||
Send(PlayerControl.LocalPlayer, immediately);
|
||||
}
|
||||
public void SendTo(int targetId, TData data)
|
||||
{
|
||||
SendTo(PlayerControl.LocalPlayer, targetId, data);
|
||||
}
|
||||
}
|
||||
|
||||
public void SendTo(int targetId)
|
||||
{
|
||||
SendTo(PlayerControl.LocalPlayer, targetId);
|
||||
}
|
||||
public abstract class PlayerCustomRpc<TPlugin> : CustomRpc<TPlugin, PlayerControl> where TPlugin : BasePlugin
|
||||
{
|
||||
protected PlayerCustomRpc(TPlugin plugin, uint id) : base(plugin, id)
|
||||
{
|
||||
}
|
||||
|
||||
public void Send(bool immediately = false)
|
||||
{
|
||||
Send(PlayerControl.LocalPlayer, immediately);
|
||||
}
|
||||
|
||||
public void SendTo(int targetId)
|
||||
{
|
||||
SendTo(PlayerControl.LocalPlayer, targetId);
|
||||
}
|
||||
}
|
||||
@@ -4,36 +4,34 @@ using System.Reflection;
|
||||
using HarmonyLib;
|
||||
using Hazel;
|
||||
using InnerNet;
|
||||
using MonoMod.Utils;
|
||||
|
||||
namespace Reactor.Networking.Serialization
|
||||
namespace Reactor.Networking.Serialization;
|
||||
|
||||
[MessageConverter]
|
||||
public class InnerNetObjectConverter : MessageConverter<InnerNetObject>
|
||||
{
|
||||
[MessageConverter]
|
||||
public class InnerNetObjectConverter : MessageConverter<InnerNetObject>
|
||||
private static readonly MethodInfo _readNetObject = AccessTools.Method(typeof(MessageExtensions), nameof(MessageExtensions.ReadNetObject));
|
||||
private static readonly Dictionary<Type, Func<MessageReader, InnerNetObject>> _readNetObjectMap = new();
|
||||
|
||||
public static InnerNetObject ReadNetObject(MessageReader reader, Type t)
|
||||
{
|
||||
private static readonly MethodInfo _readNetObject = AccessTools.Method(typeof(MessageExtensions), nameof(MessageExtensions.ReadNetObject));
|
||||
private static readonly Dictionary<Type, Func<MessageReader, InnerNetObject>> _readNetObjectMap = new Dictionary<Type, Func<MessageReader, InnerNetObject>>();
|
||||
|
||||
public static InnerNetObject ReadNetObject(MessageReader reader, Type t)
|
||||
if (_readNetObjectMap.TryGetValue(t, out var value))
|
||||
{
|
||||
if (_readNetObjectMap.TryGetValue(t, out var value))
|
||||
{
|
||||
return value(reader);
|
||||
}
|
||||
|
||||
var @delegate = _readNetObject.MakeGenericMethod(t).CreateDelegate<Func<MessageReader, InnerNetObject>>();
|
||||
_readNetObjectMap[t] = @delegate;
|
||||
return @delegate(reader);
|
||||
return value(reader);
|
||||
}
|
||||
|
||||
public override void Write(MessageWriter writer, InnerNetObject value)
|
||||
{
|
||||
MessageExtensions.WriteNetObject(writer, value);
|
||||
}
|
||||
var @delegate = _readNetObject.MakeGenericMethod(t).CreateDelegate<Func<MessageReader, InnerNetObject>>();
|
||||
_readNetObjectMap[t] = @delegate;
|
||||
return @delegate(reader);
|
||||
}
|
||||
|
||||
public override InnerNetObject Read(MessageReader reader, Type objectType)
|
||||
{
|
||||
return ReadNetObject(reader, objectType);
|
||||
}
|
||||
public override void Write(MessageWriter writer, InnerNetObject value)
|
||||
{
|
||||
MessageExtensions.WriteNetObject(writer, value);
|
||||
}
|
||||
|
||||
public override InnerNetObject Read(MessageReader reader, Type objectType)
|
||||
{
|
||||
return ReadNetObject(reader, objectType);
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,28 @@
|
||||
using System;
|
||||
using Hazel;
|
||||
|
||||
namespace Reactor.Networking.Serialization
|
||||
namespace Reactor.Networking.Serialization;
|
||||
|
||||
public abstract class UnsafeMessageConverter
|
||||
{
|
||||
public abstract class UnsafeMessageConverter
|
||||
{
|
||||
public abstract void UnsafeWrite(MessageWriter writer, object value);
|
||||
public abstract object UnsafeRead(MessageReader reader, Type objectType);
|
||||
public abstract void UnsafeWrite(MessageWriter writer, object value);
|
||||
public abstract object UnsafeRead(MessageReader reader, Type objectType);
|
||||
|
||||
public abstract bool CanConvert(Type objectType);
|
||||
}
|
||||
public abstract bool CanConvert(Type objectType);
|
||||
}
|
||||
|
||||
public abstract class MessageConverter<T> : UnsafeMessageConverter
|
||||
{
|
||||
public abstract void Write(MessageWriter writer, T value);
|
||||
public abstract class MessageConverter<T> : UnsafeMessageConverter
|
||||
{
|
||||
public abstract void Write(MessageWriter writer, T value);
|
||||
|
||||
public abstract T Read(MessageReader reader, Type objectType);
|
||||
public abstract T Read(MessageReader reader, Type objectType);
|
||||
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return typeof(T).IsAssignableFrom(objectType);
|
||||
}
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return typeof(T).IsAssignableFrom(objectType);
|
||||
}
|
||||
|
||||
public override void UnsafeWrite(MessageWriter writer, object value) => Write(writer, (T) value);
|
||||
public override void UnsafeWrite(MessageWriter writer, object value) => Write(writer, (T) value);
|
||||
|
||||
public override object UnsafeRead(MessageReader reader, Type objectType) => Read(reader, objectType)!;
|
||||
}
|
||||
public override object UnsafeRead(MessageReader reader, Type objectType) => Read(reader, objectType)!;
|
||||
}
|
||||
@@ -2,40 +2,39 @@ using System;
|
||||
using System.Reflection;
|
||||
using HarmonyLib;
|
||||
|
||||
namespace Reactor.Networking.Serialization
|
||||
namespace Reactor.Networking.Serialization;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class MessageConverterAttribute : Attribute
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class MessageConverterAttribute : Attribute
|
||||
public static void Register(Assembly assembly)
|
||||
{
|
||||
public static void Register(Assembly assembly)
|
||||
foreach (var type in assembly.GetTypes())
|
||||
{
|
||||
foreach (var type in assembly.GetTypes())
|
||||
var attribute = type.GetCustomAttribute<MessageConverterAttribute>();
|
||||
|
||||
if (attribute != null)
|
||||
{
|
||||
var attribute = type.GetCustomAttribute<MessageConverterAttribute>();
|
||||
|
||||
if (attribute != null)
|
||||
if (!type.IsSubclassOf(typeof(UnsafeMessageConverter)))
|
||||
{
|
||||
if (!type.IsSubclassOf(typeof(UnsafeMessageConverter)))
|
||||
{
|
||||
throw new InvalidOperationException($"Type {type.FullDescription()} has {nameof(MessageConverterAttribute)} but doesn't extend {nameof(UnsafeMessageConverter)}.");
|
||||
}
|
||||
throw new InvalidOperationException($"Type {type.FullDescription()} has {nameof(MessageConverterAttribute)} but doesn't extend {nameof(UnsafeMessageConverter)}.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var messageConverter = (UnsafeMessageConverter) Activator.CreateInstance(type);
|
||||
MessageSerializer.Register(messageConverter);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger<ReactorPlugin>.Warning($"Failed to register {type.FullDescription()}: {e}");
|
||||
}
|
||||
try
|
||||
{
|
||||
var messageConverter = (UnsafeMessageConverter) Activator.CreateInstance(type);
|
||||
MessageSerializer.Register(messageConverter);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger<ReactorPlugin>.Warning($"Failed to register {type.FullDescription()}: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void Initialize()
|
||||
{
|
||||
ChainloaderHooks.PluginLoad += plugin => Register(plugin.GetType().Assembly);
|
||||
}
|
||||
internal static void Initialize()
|
||||
{
|
||||
ChainloaderHooks.PluginLoad += plugin => Register(plugin.GetType().Assembly);
|
||||
}
|
||||
}
|
||||
@@ -5,152 +5,151 @@ using Hazel;
|
||||
using Reactor.Extensions;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Reactor.Networking.Serialization
|
||||
namespace Reactor.Networking.Serialization;
|
||||
|
||||
public static class MessageSerializer
|
||||
{
|
||||
public static class MessageSerializer
|
||||
private static List<UnsafeMessageConverter> MessageConverters { get; } = new();
|
||||
|
||||
private static Dictionary<Type, UnsafeMessageConverter> MessageConverterMap { get; } = new();
|
||||
|
||||
public static void Register(UnsafeMessageConverter messageConverter)
|
||||
{
|
||||
private static List<UnsafeMessageConverter> MessageConverters { get; } = new List<UnsafeMessageConverter>();
|
||||
MessageConverters.Add(messageConverter);
|
||||
MessageConverterMap.Clear();
|
||||
}
|
||||
|
||||
private static Dictionary<Type, UnsafeMessageConverter> MessageConverterMap { get; } = new Dictionary<Type, UnsafeMessageConverter>();
|
||||
|
||||
public static void Register(UnsafeMessageConverter messageConverter)
|
||||
public static UnsafeMessageConverter? FindConverter(Type type)
|
||||
{
|
||||
if (MessageConverterMap.TryGetValue(type, out var value))
|
||||
{
|
||||
MessageConverters.Add(messageConverter);
|
||||
MessageConverterMap.Clear();
|
||||
return value;
|
||||
}
|
||||
|
||||
public static UnsafeMessageConverter? FindConverter(Type type)
|
||||
var converter = MessageConverters.SingleOrDefault(x => x.CanConvert(type));
|
||||
MessageConverterMap.Add(type, converter);
|
||||
|
||||
return converter;
|
||||
}
|
||||
|
||||
public static void Serialize(MessageWriter writer, object[] args)
|
||||
{
|
||||
foreach (var arg in args)
|
||||
{
|
||||
if (MessageConverterMap.TryGetValue(type, out var value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
var converter = MessageConverters.SingleOrDefault(x => x.CanConvert(type));
|
||||
MessageConverterMap.Add(type, converter);
|
||||
|
||||
return converter;
|
||||
}
|
||||
|
||||
public static void Serialize(MessageWriter writer, object[] args)
|
||||
{
|
||||
foreach (var arg in args)
|
||||
{
|
||||
writer.Serialize(arg);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Serialize(this MessageWriter writer, object o)
|
||||
{
|
||||
switch (o)
|
||||
{
|
||||
case int i:
|
||||
writer.WritePacked(i);
|
||||
break;
|
||||
case uint i:
|
||||
writer.WritePacked(i);
|
||||
break;
|
||||
case byte i:
|
||||
writer.Write(i);
|
||||
break;
|
||||
case float i:
|
||||
writer.Write(i);
|
||||
break;
|
||||
case sbyte i:
|
||||
writer.Write(i);
|
||||
break;
|
||||
case ushort i:
|
||||
writer.Write(i);
|
||||
break;
|
||||
case bool i:
|
||||
writer.Write(i);
|
||||
break;
|
||||
case Vector2 i:
|
||||
writer.Write(i);
|
||||
break;
|
||||
case string i:
|
||||
writer.Write(i);
|
||||
break;
|
||||
default:
|
||||
var converter = FindConverter(o.GetType());
|
||||
if (converter != null)
|
||||
{
|
||||
converter.UnsafeWrite(writer, o);
|
||||
break;
|
||||
}
|
||||
|
||||
throw new NotSupportedException("Couldn't serialize " + o.GetType());
|
||||
}
|
||||
}
|
||||
|
||||
public static object[] Deserialize(MessageReader reader, MethodRpc.MethodRpc methodRpc)
|
||||
{
|
||||
var parameters = methodRpc.Method.GetParameters();
|
||||
var args = new object[parameters.Length - 1];
|
||||
|
||||
for (var i = 1; i < parameters.Length; i++)
|
||||
{
|
||||
var parameter = parameters[i];
|
||||
args[i - 1] = reader.Deserialize(parameter.ParameterType);
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
public static object Deserialize(this MessageReader reader, Type t)
|
||||
{
|
||||
if (t == typeof(int))
|
||||
{
|
||||
return reader.ReadPackedInt32();
|
||||
}
|
||||
|
||||
if (t == typeof(uint))
|
||||
{
|
||||
return reader.ReadPackedUInt32();
|
||||
}
|
||||
|
||||
if (t == typeof(byte))
|
||||
{
|
||||
return reader.ReadByte();
|
||||
}
|
||||
|
||||
if (t == typeof(float))
|
||||
{
|
||||
return reader.ReadSingle();
|
||||
}
|
||||
|
||||
if (t == typeof(sbyte))
|
||||
{
|
||||
return reader.ReadSByte();
|
||||
}
|
||||
|
||||
if (t == typeof(ushort))
|
||||
{
|
||||
return reader.ReadUInt16();
|
||||
}
|
||||
|
||||
if (t == typeof(bool))
|
||||
{
|
||||
return reader.ReadBoolean();
|
||||
}
|
||||
|
||||
if (t == typeof(Vector2))
|
||||
{
|
||||
return reader.ReadVector2();
|
||||
}
|
||||
|
||||
if (t == typeof(string))
|
||||
{
|
||||
return reader.ReadString();
|
||||
}
|
||||
|
||||
var converter = FindConverter(t);
|
||||
if (converter != null)
|
||||
{
|
||||
return converter.UnsafeRead(reader, t);
|
||||
}
|
||||
|
||||
throw new NotSupportedException("Couldn't deserialize " + t);
|
||||
writer.Serialize(arg);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Serialize(this MessageWriter writer, object o)
|
||||
{
|
||||
switch (o)
|
||||
{
|
||||
case int i:
|
||||
writer.WritePacked(i);
|
||||
break;
|
||||
case uint i:
|
||||
writer.WritePacked(i);
|
||||
break;
|
||||
case byte i:
|
||||
writer.Write(i);
|
||||
break;
|
||||
case float i:
|
||||
writer.Write(i);
|
||||
break;
|
||||
case sbyte i:
|
||||
writer.Write(i);
|
||||
break;
|
||||
case ushort i:
|
||||
writer.Write(i);
|
||||
break;
|
||||
case bool i:
|
||||
writer.Write(i);
|
||||
break;
|
||||
case Vector2 i:
|
||||
writer.Write(i);
|
||||
break;
|
||||
case string i:
|
||||
writer.Write(i);
|
||||
break;
|
||||
default:
|
||||
var converter = FindConverter(o.GetType());
|
||||
if (converter != null)
|
||||
{
|
||||
converter.UnsafeWrite(writer, o);
|
||||
break;
|
||||
}
|
||||
|
||||
throw new NotSupportedException("Couldn't serialize " + o.GetType());
|
||||
}
|
||||
}
|
||||
|
||||
public static object[] Deserialize(MessageReader reader, MethodRpc.MethodRpc methodRpc)
|
||||
{
|
||||
var parameters = methodRpc.Method.GetParameters();
|
||||
var args = new object[parameters.Length - 1];
|
||||
|
||||
for (var i = 1; i < parameters.Length; i++)
|
||||
{
|
||||
var parameter = parameters[i];
|
||||
args[i - 1] = reader.Deserialize(parameter.ParameterType);
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
public static object Deserialize(this MessageReader reader, Type t)
|
||||
{
|
||||
if (t == typeof(int))
|
||||
{
|
||||
return reader.ReadPackedInt32();
|
||||
}
|
||||
|
||||
if (t == typeof(uint))
|
||||
{
|
||||
return reader.ReadPackedUInt32();
|
||||
}
|
||||
|
||||
if (t == typeof(byte))
|
||||
{
|
||||
return reader.ReadByte();
|
||||
}
|
||||
|
||||
if (t == typeof(float))
|
||||
{
|
||||
return reader.ReadSingle();
|
||||
}
|
||||
|
||||
if (t == typeof(sbyte))
|
||||
{
|
||||
return reader.ReadSByte();
|
||||
}
|
||||
|
||||
if (t == typeof(ushort))
|
||||
{
|
||||
return reader.ReadUInt16();
|
||||
}
|
||||
|
||||
if (t == typeof(bool))
|
||||
{
|
||||
return reader.ReadBoolean();
|
||||
}
|
||||
|
||||
if (t == typeof(Vector2))
|
||||
{
|
||||
return reader.ReadVector2();
|
||||
}
|
||||
|
||||
if (t == typeof(string))
|
||||
{
|
||||
return reader.ReadString();
|
||||
}
|
||||
|
||||
var converter = FindConverter(t);
|
||||
if (converter != null)
|
||||
{
|
||||
return converter.UnsafeRead(reader, t);
|
||||
}
|
||||
|
||||
throw new NotSupportedException("Couldn't deserialize " + t);
|
||||
}
|
||||
}
|
||||
@@ -4,74 +4,73 @@ using BepInEx.IL2CPP;
|
||||
using Hazel;
|
||||
using InnerNet;
|
||||
|
||||
namespace Reactor.Networking
|
||||
namespace Reactor.Networking;
|
||||
|
||||
public abstract class UnsafeCustomRpc
|
||||
{
|
||||
public abstract class UnsafeCustomRpc
|
||||
internal CustomRpcManager? Manager { get; set; }
|
||||
protected internal virtual bool IsSingleton => true;
|
||||
|
||||
public uint Id { get; }
|
||||
public BasePlugin UnsafePlugin { get; }
|
||||
public string PluginId { get; }
|
||||
|
||||
public abstract Type InnerNetObjectType { get; }
|
||||
|
||||
public virtual SendOption SendOption => SendOption.Reliable;
|
||||
public abstract RpcLocalHandling LocalHandling { get; }
|
||||
|
||||
protected UnsafeCustomRpc(BasePlugin plugin, uint id)
|
||||
{
|
||||
internal CustomRpcManager? Manager { get; set; }
|
||||
protected internal virtual bool IsSingleton => true;
|
||||
UnsafePlugin = plugin;
|
||||
PluginId = MetadataHelper.GetMetadata(plugin).GUID;
|
||||
Id = id;
|
||||
}
|
||||
|
||||
public uint Id { get; }
|
||||
public BasePlugin UnsafePlugin { get; }
|
||||
public string PluginId { get; }
|
||||
public abstract void UnsafeWrite(MessageWriter writer, object? data);
|
||||
public abstract object? UnsafeRead(MessageReader reader);
|
||||
public abstract void UnsafeHandle(InnerNetObject innerNetObject, object? data);
|
||||
|
||||
public abstract Type InnerNetObjectType { get; }
|
||||
public void UnsafeSend(InnerNetObject innerNetObject, object? data, bool immediately = false, int targetClientId = -1)
|
||||
{
|
||||
if (innerNetObject == null) throw new ArgumentNullException(nameof(innerNetObject));
|
||||
|
||||
public virtual SendOption SendOption => SendOption.Reliable;
|
||||
public abstract RpcLocalHandling LocalHandling { get; }
|
||||
|
||||
protected UnsafeCustomRpc(BasePlugin plugin, uint id)
|
||||
if (Manager == null)
|
||||
{
|
||||
UnsafePlugin = plugin;
|
||||
PluginId = MetadataHelper.GetMetadata(plugin).GUID;
|
||||
Id = id;
|
||||
throw new InvalidOperationException("Can't send unregistered CustomRpc");
|
||||
}
|
||||
|
||||
public abstract void UnsafeWrite(MessageWriter writer, object? data);
|
||||
public abstract object? UnsafeRead(MessageReader reader);
|
||||
public abstract void UnsafeHandle(InnerNetObject innerNetObject, object? data);
|
||||
|
||||
public void UnsafeSend(InnerNetObject innerNetObject, object? data, bool immediately = false, int targetClientId = -1)
|
||||
if (LocalHandling == RpcLocalHandling.Before)
|
||||
{
|
||||
if (innerNetObject == null) throw new ArgumentNullException(nameof(innerNetObject));
|
||||
UnsafeHandle(innerNetObject, data);
|
||||
}
|
||||
|
||||
if (Manager == null)
|
||||
{
|
||||
throw new InvalidOperationException("Can't send unregistered CustomRpc");
|
||||
}
|
||||
var writer = immediately switch
|
||||
{
|
||||
false => AmongUsClient.Instance.StartRpc(innerNetObject.NetId, CustomRpcManager.CallId, SendOption),
|
||||
true => AmongUsClient.Instance.StartRpcImmediately(innerNetObject.NetId, CustomRpcManager.CallId, SendOption, targetClientId),
|
||||
};
|
||||
|
||||
if (LocalHandling == RpcLocalHandling.Before)
|
||||
{
|
||||
UnsafeHandle(innerNetObject, data);
|
||||
}
|
||||
var pluginNetId = ModList.GetById(PluginId).NetId;
|
||||
writer.WritePacked(pluginNetId);
|
||||
writer.WritePacked(Id);
|
||||
|
||||
var writer = immediately switch
|
||||
{
|
||||
false => AmongUsClient.Instance.StartRpc(innerNetObject.NetId, CustomRpcManager.CallId, SendOption),
|
||||
true => AmongUsClient.Instance.StartRpcImmediately(innerNetObject.NetId, CustomRpcManager.CallId, SendOption, targetClientId)
|
||||
};
|
||||
writer.StartMessage(0);
|
||||
UnsafeWrite(writer, data);
|
||||
writer.EndMessage();
|
||||
|
||||
var pluginNetId = ModList.GetById(PluginId).NetId;
|
||||
writer.WritePacked(pluginNetId);
|
||||
writer.WritePacked(Id);
|
||||
|
||||
writer.StartMessage(0);
|
||||
UnsafeWrite(writer, data);
|
||||
if (immediately)
|
||||
{
|
||||
AmongUsClient.Instance.FinishRpcImmediately(writer);
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.EndMessage();
|
||||
}
|
||||
|
||||
if (immediately)
|
||||
{
|
||||
AmongUsClient.Instance.FinishRpcImmediately(writer);
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.EndMessage();
|
||||
}
|
||||
|
||||
if (LocalHandling == RpcLocalHandling.After)
|
||||
{
|
||||
UnsafeHandle(innerNetObject, data);
|
||||
}
|
||||
if (LocalHandling == RpcLocalHandling.After)
|
||||
{
|
||||
UnsafeHandle(innerNetObject, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,17 @@
|
||||
using HarmonyLib;
|
||||
using InnerNet;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Reactor.Patches
|
||||
namespace Reactor.Patches;
|
||||
|
||||
internal static class BetterGuestPatch
|
||||
{
|
||||
internal static class BetterGuestPatch
|
||||
[HarmonyPatch(typeof(SaveManager), nameof(SaveManager.ChatModeType), MethodType.Getter)]
|
||||
public static class ChatModeTypePatch
|
||||
{
|
||||
[HarmonyPatch(typeof(SaveManager), nameof(SaveManager.ChatModeType), MethodType.Getter)]
|
||||
public static class ChatModeTypePatch
|
||||
public static bool Prefix(out QuickChatModes __result)
|
||||
{
|
||||
public static bool Prefix(out QuickChatModes __result)
|
||||
{
|
||||
__result = QuickChatModes.FreeChatOrQuickChat;
|
||||
return false;
|
||||
}
|
||||
__result = QuickChatModes.FreeChatOrQuickChat;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,23 +3,22 @@ using HarmonyLib;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Reactor.Patches
|
||||
{
|
||||
/// <summary>
|
||||
/// "Fixes" an issue where empty TextBoxes have wrong cursor positions
|
||||
/// </summary>
|
||||
[HarmonyPatch(typeof(TextMeshProExtensions), nameof(TextMeshProExtensions.CursorPos))]
|
||||
internal static class CursorPosPatch
|
||||
{
|
||||
public static bool Prefix(TextMeshPro self, ref Vector2 __result)
|
||||
{
|
||||
if (self.textInfo == null || self.textInfo.lineCount == 0 || self.textInfo.lineInfo[0].characterCount <= 0)
|
||||
{
|
||||
__result = self.GetTextInfo(" ").lineInfo.First().lineExtents.max;
|
||||
return false;
|
||||
}
|
||||
namespace Reactor.Patches;
|
||||
|
||||
return true;
|
||||
/// <summary>
|
||||
/// "Fixes" an issue where empty TextBoxes have wrong cursor positions
|
||||
/// </summary>
|
||||
[HarmonyPatch(typeof(TextMeshProExtensions), nameof(TextMeshProExtensions.CursorPos))]
|
||||
internal static class CursorPosPatch
|
||||
{
|
||||
public static bool Prefix(TextMeshPro self, ref Vector2 __result)
|
||||
{
|
||||
if (self.textInfo == null || self.textInfo.lineCount == 0 || self.textInfo.lineInfo[0].characterCount <= 0)
|
||||
{
|
||||
__result = self.GetTextInfo(" ").lineInfo.First().lineExtents.max;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,32 +1,31 @@
|
||||
using HarmonyLib;
|
||||
|
||||
namespace Reactor.Patches
|
||||
{
|
||||
[HarmonyPatch]
|
||||
internal static class CustomServersPatch
|
||||
{
|
||||
[HarmonyPatch(typeof(AuthManager._CoConnect_d__4), nameof(AuthManager._CoConnect_d__4.MoveNext))]
|
||||
[HarmonyPatch(typeof(AuthManager._CoWaitForNonce_d__6), nameof(AuthManager._CoWaitForNonce_d__6.MoveNext))]
|
||||
[HarmonyPrefix]
|
||||
public static bool DisableAuthServerPatch(out bool __result)
|
||||
{
|
||||
__result = false;
|
||||
return false;
|
||||
}
|
||||
namespace Reactor.Patches;
|
||||
|
||||
[HarmonyPatch(typeof(AmongUsClient._CoJoinOnlinePublicGame_d__1), nameof(AmongUsClient._CoJoinOnlinePublicGame_d__1.MoveNext))]
|
||||
[HarmonyPrefix]
|
||||
public static void EnableUdpMatchmakingPatch(AmongUsClient._CoJoinOnlinePublicGame_d__1 __instance)
|
||||
[HarmonyPatch]
|
||||
internal static class CustomServersPatch
|
||||
{
|
||||
[HarmonyPatch(typeof(AuthManager._CoConnect_d__4), nameof(AuthManager._CoConnect_d__4.MoveNext))]
|
||||
[HarmonyPatch(typeof(AuthManager._CoWaitForNonce_d__6), nameof(AuthManager._CoWaitForNonce_d__6.MoveNext))]
|
||||
[HarmonyPrefix]
|
||||
public static bool DisableAuthServerPatch(out bool __result)
|
||||
{
|
||||
__result = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(AmongUsClient._CoJoinOnlinePublicGame_d__1), nameof(AmongUsClient._CoJoinOnlinePublicGame_d__1.MoveNext))]
|
||||
[HarmonyPrefix]
|
||||
public static void EnableUdpMatchmakingPatch(AmongUsClient._CoJoinOnlinePublicGame_d__1 __instance)
|
||||
{
|
||||
// Skip to state 1 which just calls CoJoinOnlineGameDirect
|
||||
if (__instance.__1__state == 0 && !ServerManager.Instance.IsHttp)
|
||||
{
|
||||
// Skip to state 1 which just calls CoJoinOnlineGameDirect
|
||||
if (__instance.__1__state == 0 && !ServerManager.Instance.IsHttp)
|
||||
__instance.__1__state = 1;
|
||||
__instance.__8__1 = new AmongUsClient.__c__DisplayClass1_0
|
||||
{
|
||||
__instance.__1__state = 1;
|
||||
__instance.__8__1 = new AmongUsClient.__c__DisplayClass1_0
|
||||
{
|
||||
matchmakerToken = string.Empty,
|
||||
};
|
||||
}
|
||||
matchmakerToken = string.Empty,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,66 +7,66 @@ using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace Reactor.Patches
|
||||
namespace Reactor.Patches;
|
||||
|
||||
public static class FreeNamePatch
|
||||
{
|
||||
public static class FreeNamePatch
|
||||
[HarmonyPatch(typeof(AccountManager), nameof(AccountManager.CheckAndRegenerateName))]
|
||||
internal static class ValidateRandomNamePatch
|
||||
{
|
||||
[HarmonyPatch(typeof(AccountManager), nameof(AccountManager.CheckAndRegenerateName))]
|
||||
internal static class ValidateRandomNamePatch
|
||||
public static bool Prefix()
|
||||
{
|
||||
public static bool Prefix()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
SceneManager.add_sceneLoaded((Action<Scene, LoadSceneMode>) ((scene, _) =>
|
||||
{
|
||||
if (!scene.name.Equals("MMOnline")) return;
|
||||
if (!TryMoveObjects()) return;
|
||||
|
||||
var editName = DestroyableSingleton<AccountManager>.Instance.accountTab.editNameScreen;
|
||||
var nameText = Object.Instantiate(editName.nameText.gameObject);
|
||||
|
||||
nameText.transform.localPosition += Vector3.up * 2.2f;
|
||||
|
||||
var textBox = nameText.GetComponent<TextBoxTMP>();
|
||||
textBox.outputText.alignment = TextAlignmentOptions.CenterGeoAligned;
|
||||
textBox.outputText.transform.position = nameText.transform.position;
|
||||
textBox.outputText.fontSize = 4f;
|
||||
|
||||
textBox.OnChange.AddListener((Action) (() => {
|
||||
SaveManager.PlayerName = textBox.text;
|
||||
}));
|
||||
textBox.OnEnter = textBox.OnFocusLost = textBox.OnChange;
|
||||
|
||||
textBox.Pipe.GetComponent<TextMeshPro>().fontSize = 4f;
|
||||
}));
|
||||
}
|
||||
|
||||
private static bool TryMoveObjects()
|
||||
{
|
||||
var toMove = new List<string>
|
||||
{
|
||||
"HostGameButton",
|
||||
"FindGameButton",
|
||||
"JoinGameButton"
|
||||
};
|
||||
|
||||
var yStart = Vector3.up;
|
||||
var yOffset = Vector3.down * 1.5f;
|
||||
|
||||
var gameObjects = toMove.Select(x => GameObject.Find("NormalMenu/" + x)).ToList();
|
||||
if (gameObjects.Any(x => x == null)) return false;
|
||||
|
||||
for (var i = 0; i < gameObjects.Count; i++)
|
||||
{
|
||||
gameObjects[i].transform.position = yStart + (yOffset * i);
|
||||
}
|
||||
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
SceneManager.add_sceneLoaded((Action<Scene, LoadSceneMode>) ((scene, _) =>
|
||||
{
|
||||
if (!scene.name.Equals("MMOnline")) return;
|
||||
if (!TryMoveObjects()) return;
|
||||
|
||||
var editName = DestroyableSingleton<AccountManager>.Instance.accountTab.editNameScreen;
|
||||
var nameText = Object.Instantiate(editName.nameText.gameObject);
|
||||
|
||||
nameText.transform.localPosition += Vector3.up * 2.2f;
|
||||
|
||||
var textBox = nameText.GetComponent<TextBoxTMP>();
|
||||
textBox.outputText.alignment = TextAlignmentOptions.CenterGeoAligned;
|
||||
textBox.outputText.transform.position = nameText.transform.position;
|
||||
textBox.outputText.fontSize = 4f;
|
||||
|
||||
textBox.OnChange.AddListener((Action) (() =>
|
||||
{
|
||||
SaveManager.PlayerName = textBox.text;
|
||||
}));
|
||||
textBox.OnEnter = textBox.OnFocusLost = textBox.OnChange;
|
||||
|
||||
textBox.Pipe.GetComponent<TextMeshPro>().fontSize = 4f;
|
||||
}));
|
||||
}
|
||||
|
||||
private static bool TryMoveObjects()
|
||||
{
|
||||
var toMove = new List<string>
|
||||
{
|
||||
"HostGameButton",
|
||||
"FindGameButton",
|
||||
"JoinGameButton",
|
||||
};
|
||||
|
||||
var yStart = Vector3.up;
|
||||
var yOffset = Vector3.down * 1.5f;
|
||||
|
||||
var gameObjects = toMove.Select(x => GameObject.Find("NormalMenu/" + x)).ToList();
|
||||
if (gameObjects.Any(x => x == null)) return false;
|
||||
|
||||
for (var i = 0; i < gameObjects.Count; i++)
|
||||
{
|
||||
gameObjects[i].transform.position = yStart + (yOffset * i);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,25 @@
|
||||
using System.Linq;
|
||||
using HarmonyLib;
|
||||
|
||||
namespace Reactor.Patches
|
||||
namespace Reactor.Patches;
|
||||
|
||||
internal static class PlayerIdPatch
|
||||
{
|
||||
internal static class PlayerIdPatch
|
||||
[HarmonyPatch(typeof(GameData), nameof(GameData.GetAvailableId))]
|
||||
public static class GetAvailableIdPatch
|
||||
{
|
||||
[HarmonyPatch(typeof(GameData), nameof(GameData.GetAvailableId))]
|
||||
public static class GetAvailableIdPatch
|
||||
public static bool Prefix(GameData __instance, out sbyte __result)
|
||||
{
|
||||
public static bool Prefix(GameData __instance, out sbyte __result)
|
||||
sbyte i = 0;
|
||||
|
||||
while (__instance.AllPlayers.ToArray().Any(p => p.PlayerId == i))
|
||||
{
|
||||
sbyte i = 0;
|
||||
|
||||
while (__instance.AllPlayers.ToArray().Any(p => p.PlayerId == i))
|
||||
{
|
||||
i++;
|
||||
}
|
||||
|
||||
__result = i;
|
||||
|
||||
return false;
|
||||
i++;
|
||||
}
|
||||
|
||||
__result = i;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using BepInEx;
|
||||
using BepInEx.IL2CPP;
|
||||
using HarmonyLib;
|
||||
@@ -8,68 +7,67 @@ using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace Reactor.Patches
|
||||
namespace Reactor.Patches;
|
||||
|
||||
public static class ReactorVersionShower
|
||||
{
|
||||
public static class ReactorVersionShower
|
||||
public static TextMeshPro? Text { get; private set; }
|
||||
|
||||
public static event TextUpdatedHandler? TextUpdated;
|
||||
|
||||
public delegate void TextUpdatedHandler(TextMeshPro text);
|
||||
|
||||
internal static void Initialize()
|
||||
{
|
||||
public static TextMeshPro? Text { get; private set; }
|
||||
|
||||
public static event TextUpdatedHandler? TextUpdated;
|
||||
|
||||
public delegate void TextUpdatedHandler(TextMeshPro text);
|
||||
|
||||
internal static void Initialize()
|
||||
SceneManager.add_sceneLoaded((Action<Scene, LoadSceneMode>) ((_, _) =>
|
||||
{
|
||||
SceneManager.add_sceneLoaded((Action<Scene, LoadSceneMode>) ((_, _) =>
|
||||
{
|
||||
var original = UnityEngine.Object.FindObjectOfType<VersionShower>();
|
||||
if (!original)
|
||||
return;
|
||||
var original = UnityEngine.Object.FindObjectOfType<VersionShower>();
|
||||
if (!original)
|
||||
return;
|
||||
|
||||
var gameObject = new GameObject("ReactorVersion " + Guid.NewGuid());
|
||||
gameObject.transform.parent = original.transform.parent;
|
||||
var gameObject = new GameObject("ReactorVersion " + Guid.NewGuid());
|
||||
gameObject.transform.parent = original.transform.parent;
|
||||
|
||||
var aspectPosition = gameObject.AddComponent<AspectPosition>();
|
||||
var aspectPosition = gameObject.AddComponent<AspectPosition>();
|
||||
|
||||
aspectPosition.Alignment = AspectPosition.EdgeAlignments.LeftTop;
|
||||
aspectPosition.Alignment = AspectPosition.EdgeAlignments.LeftTop;
|
||||
|
||||
var originalAspectPosition = original.GetComponent<AspectPosition>();
|
||||
var originalPosition = originalAspectPosition.DistanceFromEdge;
|
||||
originalPosition.y = 0.15f;
|
||||
originalAspectPosition.DistanceFromEdge = originalPosition;
|
||||
originalAspectPosition.AdjustPosition();
|
||||
var originalAspectPosition = original.GetComponent<AspectPosition>();
|
||||
var originalPosition = originalAspectPosition.DistanceFromEdge;
|
||||
originalPosition.y = 0.15f;
|
||||
originalAspectPosition.DistanceFromEdge = originalPosition;
|
||||
originalAspectPosition.AdjustPosition();
|
||||
|
||||
var position = originalPosition;
|
||||
position.x += 10.075f - 0.1f;
|
||||
position.y += 2.75f - 0.15f;
|
||||
position.z -= 1;
|
||||
aspectPosition.DistanceFromEdge = position;
|
||||
var position = originalPosition;
|
||||
position.x += 10.075f - 0.1f;
|
||||
position.y += 2.75f - 0.15f;
|
||||
position.z -= 1;
|
||||
aspectPosition.DistanceFromEdge = position;
|
||||
|
||||
aspectPosition.AdjustPosition();
|
||||
aspectPosition.AdjustPosition();
|
||||
|
||||
Text = gameObject.AddComponent<TextMeshPro>();
|
||||
Text.fontSize = 2;
|
||||
Text = gameObject.AddComponent<TextMeshPro>();
|
||||
Text.fontSize = 2;
|
||||
|
||||
UpdateText();
|
||||
}));
|
||||
}
|
||||
UpdateText();
|
||||
}));
|
||||
}
|
||||
|
||||
public static void UpdateText()
|
||||
public static void UpdateText()
|
||||
{
|
||||
Text!.text = "Reactor " + typeof(ReactorPlugin).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
|
||||
Text.text += "\nBepInEx " + Paths.BepInExVersion;
|
||||
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)
|
||||
{
|
||||
Text!.text = "Reactor " + typeof(ReactorPlugin).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
|
||||
Text.text += "\nBepInEx " + Paths.BepInExVersion;
|
||||
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;
|
||||
}
|
||||
__instance.Output.Destroy();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,83 +6,82 @@ using Reactor.Extensions;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace Reactor.Patches
|
||||
namespace Reactor.Patches;
|
||||
|
||||
internal class RegionInfoWatcher : IDisposable
|
||||
{
|
||||
internal class RegionInfoWatcher : IDisposable
|
||||
private FileSystemWatcher Watcher { get; }
|
||||
|
||||
public bool IgnoreNext { get; set; }
|
||||
|
||||
internal RegionInfoWatcher()
|
||||
{
|
||||
private FileSystemWatcher Watcher { get; }
|
||||
Watcher = new FileSystemWatcher(
|
||||
string.IsNullOrEmpty(Application.persistentDataPath) ? Directory.GetCurrentDirectory() : Application.persistentDataPath,
|
||||
"regionInfo.json"
|
||||
);
|
||||
|
||||
public bool IgnoreNext { get; set; }
|
||||
|
||||
internal RegionInfoWatcher()
|
||||
Watcher.Changed += (s, e) =>
|
||||
{
|
||||
Watcher = new FileSystemWatcher(
|
||||
string.IsNullOrEmpty(Application.persistentDataPath) ? Directory.GetCurrentDirectory() : Application.persistentDataPath,
|
||||
"regionInfo.json"
|
||||
);
|
||||
|
||||
Watcher.Changed += (s, e) =>
|
||||
if (new FileInfo(e.Name).Length > 0)
|
||||
{
|
||||
if (new FileInfo(e.Name).Length > 0)
|
||||
if (IgnoreNext)
|
||||
{
|
||||
if (IgnoreNext)
|
||||
IgnoreNext = false;
|
||||
return;
|
||||
}
|
||||
|
||||
Dispatcher.Instance.Enqueue(() =>
|
||||
{
|
||||
ServerManager.Instance.LoadServers();
|
||||
|
||||
Object.FindObjectOfType<RegionTextMonitor>()?.Start();
|
||||
if (Object.FindObjectOfType<RegionMenu>() is { } regionMenu)
|
||||
{
|
||||
IgnoreNext = false;
|
||||
return;
|
||||
regionMenu.OnDisable();
|
||||
regionMenu.OnEnable();
|
||||
}
|
||||
|
||||
Dispatcher.Instance.Enqueue(() =>
|
||||
{
|
||||
ServerManager.Instance.LoadServers();
|
||||
Logger<ReactorPlugin>.Info("Region file reloaded");
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Object.FindObjectOfType<RegionTextMonitor>()?.Start();
|
||||
if (Object.FindObjectOfType<RegionMenu>() is { } regionMenu)
|
||||
{
|
||||
regionMenu.OnDisable();
|
||||
regionMenu.OnEnable();
|
||||
}
|
||||
Watcher.EnableRaisingEvents = true;
|
||||
}
|
||||
|
||||
Logger<ReactorPlugin>.Info("Region file reloaded");
|
||||
});
|
||||
}
|
||||
};
|
||||
public void Dispose()
|
||||
{
|
||||
Watcher.Dispose();
|
||||
}
|
||||
|
||||
Watcher.EnableRaisingEvents = true;
|
||||
[HarmonyPatch]
|
||||
private static class WritePatch
|
||||
{
|
||||
public static MethodBase TargetMethod()
|
||||
{
|
||||
var type = Type.GetType("FileIO, Assembly-CSharp", false) ?? Type.GetType("Innersloth.IO.FileIO, Assembly-CSharp", true);
|
||||
return AccessTools.Method(type, "WriteAllText");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
public static bool Prefix(string path, string contents)
|
||||
{
|
||||
Watcher.Dispose();
|
||||
}
|
||||
|
||||
[HarmonyPatch]
|
||||
private static class WritePatch
|
||||
{
|
||||
public static MethodBase TargetMethod()
|
||||
// Among Us' region loading code unfortunately contains a call
|
||||
// to SaveServers, which will write out the region file. This
|
||||
// will lead to a positive feedback loop when detecting writes,
|
||||
// which is undesireable. So we check if the write makes a
|
||||
// change to the file on disk and if it would write the same
|
||||
// file again, stop AU from actually writing it.
|
||||
if (ServerManager.Instance && path == ServerManager.Instance.serverInfoFileJson)
|
||||
{
|
||||
var type = Type.GetType("FileIO, Assembly-CSharp", false) ?? Type.GetType("Innersloth.IO.FileIO, Assembly-CSharp", true);
|
||||
return AccessTools.Method(type, "WriteAllText");
|
||||
var continueWrite = !File.Exists(path) || File.ReadAllText(path) != contents;
|
||||
Logger<ReactorPlugin>.Debug($"Continue serverInfoFile write? {continueWrite}");
|
||||
// If we will write, ignore the next change action from the observer.
|
||||
PluginSingleton<ReactorPlugin>.Instance.RegionInfoWatcher.IgnoreNext = continueWrite;
|
||||
return continueWrite;
|
||||
}
|
||||
|
||||
public static bool Prefix(string path, string contents)
|
||||
{
|
||||
// Among Us' region loading code unfortunately contains a call
|
||||
// to SaveServers, which will write out the region file. This
|
||||
// will lead to a positive feedback loop when detecting writes,
|
||||
// which is undesireable. So we check if the write makes a
|
||||
// change to the file on disk and if it would write the same
|
||||
// file again, stop AU from actually writing it.
|
||||
if (ServerManager.Instance && path == ServerManager.Instance.serverInfoFileJson)
|
||||
{
|
||||
var continueWrite = !File.Exists(path) || File.ReadAllText(path) != contents;
|
||||
Logger<ReactorPlugin>.Debug($"Continue serverInfoFile write? {continueWrite}");
|
||||
// If we will write, ignore the next change action from the observer.
|
||||
PluginSingleton<ReactorPlugin>.Instance.RegionInfoWatcher.IgnoreNext = continueWrite;
|
||||
return continueWrite;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,47 +5,46 @@ using System.Net.Sockets;
|
||||
using HarmonyLib;
|
||||
using InnerNet;
|
||||
|
||||
namespace Reactor.Patches
|
||||
namespace Reactor.Patches;
|
||||
|
||||
[HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.Connect))]
|
||||
public static class ConnectPatch
|
||||
{
|
||||
[HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.Connect))]
|
||||
public static class ConnectPatch
|
||||
public static void Prefix(InnerNetClient __instance)
|
||||
{
|
||||
public static void Prefix(InnerNetClient __instance)
|
||||
{
|
||||
Logger<ReactorPlugin>.Info($"Joining {__instance.networkAddress}:{__instance.networkPort}");
|
||||
}
|
||||
Logger<ReactorPlugin>.Info($"Joining {__instance.networkAddress}:{__instance.networkPort}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fixes hardcoded port and filters out IPv6 in DnsRegionInfo
|
||||
/// </summary>
|
||||
[HarmonyPatch(typeof(DnsRegionInfo), nameof(DnsRegionInfo.PopulateServers))]
|
||||
public static class PopulateServersPatch
|
||||
/// <summary>
|
||||
/// Fixes hardcoded port and filters out IPv6 in DnsRegionInfo
|
||||
/// </summary>
|
||||
[HarmonyPatch(typeof(DnsRegionInfo), nameof(DnsRegionInfo.PopulateServers))]
|
||||
public static class PopulateServersPatch
|
||||
{
|
||||
public static bool Prefix(DnsRegionInfo __instance)
|
||||
{
|
||||
public static bool Prefix(DnsRegionInfo __instance)
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
var i = 0;
|
||||
var servers = Dns.GetHostAddresses(__instance.Fqdn)
|
||||
.Distinct()
|
||||
.Where(ipAddress => ipAddress.AddressFamily == AddressFamily.InterNetwork)
|
||||
.Select(ipAddress => new ServerInfo($"{__instance.Name}-{i++}", ipAddress.ToString(), __instance.Port, __instance.UseDtls))
|
||||
.ToArray();
|
||||
var i = 0;
|
||||
var servers = Dns.GetHostAddresses(__instance.Fqdn)
|
||||
.Distinct()
|
||||
.Where(ipAddress => ipAddress.AddressFamily == AddressFamily.InterNetwork)
|
||||
.Select(ipAddress => new ServerInfo($"{__instance.Name}-{i++}", ipAddress.ToString(), __instance.Port, __instance.UseDtls))
|
||||
.ToArray();
|
||||
|
||||
__instance.cachedServers = servers;
|
||||
Logger<ReactorPlugin>.Info($"Populated {__instance.Name} ({__instance.Fqdn}:{__instance.Port}) with {servers.Length} server(s) {{{servers.Select(x => x.ToString()).Join()}}}");
|
||||
}
|
||||
catch (Exception e)
|
||||
__instance.cachedServers = servers;
|
||||
Logger<ReactorPlugin>.Info($"Populated {__instance.Name} ({__instance.Fqdn}:{__instance.Port}) with {servers.Length} server(s) {{{servers.Select(x => x.ToString()).Join()}}}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger<ReactorPlugin>.Info($"Failed to populate {__instance.Name}: {e}");
|
||||
__instance.cachedServers = new[]
|
||||
{
|
||||
Logger<ReactorPlugin>.Info($"Failed to populate {__instance.Name}: {e}");
|
||||
__instance.cachedServers = new[]
|
||||
{
|
||||
new ServerInfo(__instance.Name ?? string.Empty, __instance.DefaultIp, __instance.Port, __instance.UseDtls),
|
||||
};
|
||||
}
|
||||
new ServerInfo(__instance.Name ?? string.Empty, __instance.DefaultIp, __instance.Port, __instance.UseDtls),
|
||||
};
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,43 +1,42 @@
|
||||
using System.Reflection;
|
||||
using HarmonyLib;
|
||||
|
||||
namespace Reactor.Patches
|
||||
namespace Reactor.Patches;
|
||||
|
||||
[HarmonyPatch(typeof(SplashManager), nameof(SplashManager.Start))]
|
||||
internal static class SplashSkip
|
||||
{
|
||||
[HarmonyPatch(typeof(SplashManager), nameof(SplashManager.Start))]
|
||||
internal static class SplashSkip
|
||||
private static void Prefix(SplashManager __instance)
|
||||
{
|
||||
private static void Prefix(SplashManager __instance)
|
||||
__instance.minimumSecondsBeforeSceneChange = 0;
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(SplashManager), nameof(SplashManager.Update))]
|
||||
internal static class WaitForEpicAuth
|
||||
{
|
||||
private static readonly PropertyInfo? _localUserIdProperty = typeof(EpicManager).GetProperty("localUserId", BindingFlags.Static | BindingFlags.Public);
|
||||
private static bool _loginFinished;
|
||||
|
||||
private static bool Prefix(SplashManager __instance)
|
||||
{
|
||||
if (__instance.startedSceneLoad) return true;
|
||||
|
||||
if (_localUserIdProperty != null && !_loginFinished)
|
||||
{
|
||||
__instance.minimumSecondsBeforeSceneChange = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(SplashManager), nameof(SplashManager.Update))]
|
||||
internal static class WaitForEpicAuth
|
||||
// EpicManager calls SaveManager.LoadPlayerPrefs(true) both on successful and unsuccessful EOS login
|
||||
[HarmonyPatch(typeof(SaveManager), nameof(SaveManager.LoadPlayerPrefs))]
|
||||
private static class LoadPlayerPrefsPatch
|
||||
{
|
||||
private static readonly PropertyInfo? _localUserIdProperty = typeof(EpicManager).GetProperty("localUserId", BindingFlags.Static | BindingFlags.Public);
|
||||
private static bool _loginFinished;
|
||||
|
||||
private static bool Prefix(SplashManager __instance)
|
||||
private static void Postfix(bool overrideLoad)
|
||||
{
|
||||
if (__instance.startedSceneLoad) return true;
|
||||
|
||||
if (_localUserIdProperty != null && !_loginFinished)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// EpicManager calls SaveManager.LoadPlayerPrefs(true) both on successful and unsuccessful EOS login
|
||||
[HarmonyPatch(typeof(SaveManager), nameof(SaveManager.LoadPlayerPrefs))]
|
||||
private static class LoadPlayerPrefsPatch
|
||||
{
|
||||
private static void Postfix(bool overrideLoad)
|
||||
{
|
||||
if (overrideLoad) _loginFinished = true;
|
||||
}
|
||||
if (overrideLoad) _loginFinished = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,37 +3,36 @@ using System.IO;
|
||||
using System.Reflection;
|
||||
using HarmonyLib;
|
||||
|
||||
namespace Reactor.Patches
|
||||
namespace Reactor.Patches;
|
||||
|
||||
internal static class SteamPatch
|
||||
{
|
||||
internal static class SteamPatch
|
||||
[HarmonyPatch]
|
||||
public static class RestartAppIfNecessaryPatch
|
||||
{
|
||||
[HarmonyPatch]
|
||||
public static class RestartAppIfNecessaryPatch
|
||||
public const string TypeName = "Steamworks.SteamAPI, Assembly-CSharp-firstpass";
|
||||
public const string MethodName = "RestartAppIfNecessary";
|
||||
|
||||
public static bool Prepare()
|
||||
{
|
||||
public const string TypeName = "Steamworks.SteamAPI, Assembly-CSharp-firstpass";
|
||||
public const string MethodName = "RestartAppIfNecessary";
|
||||
return Type.GetType(TypeName, false) != null;
|
||||
}
|
||||
|
||||
public static bool Prepare()
|
||||
public static MethodBase TargetMethod()
|
||||
{
|
||||
return AccessTools.Method(TypeName + ":" + MethodName);
|
||||
}
|
||||
|
||||
public static bool Prefix(out bool __result)
|
||||
{
|
||||
const string file = "steam_appid.txt";
|
||||
|
||||
if (!File.Exists(file))
|
||||
{
|
||||
return Type.GetType(TypeName, false) != null;
|
||||
File.WriteAllText(file, "945360");
|
||||
}
|
||||
|
||||
public static MethodBase TargetMethod()
|
||||
{
|
||||
return AccessTools.Method(TypeName + ":" + MethodName);
|
||||
}
|
||||
|
||||
public static bool Prefix(out bool __result)
|
||||
{
|
||||
const string file = "steam_appid.txt";
|
||||
|
||||
if (!File.Exists(file))
|
||||
{
|
||||
File.WriteAllText(file, "945360");
|
||||
}
|
||||
|
||||
return __result = false;
|
||||
}
|
||||
return __result = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
+13
-14
@@ -2,22 +2,21 @@ using System.Linq;
|
||||
using System.Reflection;
|
||||
using BepInEx.IL2CPP;
|
||||
|
||||
namespace Reactor
|
||||
namespace Reactor;
|
||||
|
||||
public static class PluginSingleton<T> where T : BasePlugin
|
||||
{
|
||||
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();
|
||||
|
||||
internal static void Initialize()
|
||||
{
|
||||
private static T? _instance;
|
||||
|
||||
public static T Instance => _instance ??= IL2CPPChainloader.Instance.Plugins.Values.Select(x => x.Instance).OfType<T>().Single();
|
||||
|
||||
internal static void Initialize()
|
||||
ChainloaderHooks.PluginLoad += plugin =>
|
||||
{
|
||||
ChainloaderHooks.PluginLoad += plugin =>
|
||||
{
|
||||
typeof(PluginSingleton<>).MakeGenericType(plugin.GetType())!
|
||||
.GetField(nameof(_instance), BindingFlags.Static | BindingFlags.NonPublic)!
|
||||
.SetValue(null, plugin);
|
||||
};
|
||||
}
|
||||
typeof(PluginSingleton<>).MakeGenericType(plugin.GetType())!
|
||||
.GetField(nameof(_instance), BindingFlags.Static | BindingFlags.NonPublic)!
|
||||
.SetValue(null, plugin);
|
||||
};
|
||||
}
|
||||
}
|
||||
+73
-74
@@ -14,93 +14,92 @@ using UnhollowerBaseLib.Attributes;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace Reactor
|
||||
namespace Reactor;
|
||||
|
||||
[BepInAutoPlugin("gg.reactor.api")]
|
||||
[BepInProcess("Among Us.exe")]
|
||||
public partial class ReactorPlugin : BasePlugin
|
||||
{
|
||||
[BepInAutoPlugin("gg.reactor.api")]
|
||||
[BepInProcess("Among Us.exe")]
|
||||
public partial class ReactorPlugin : BasePlugin
|
||||
public Harmony Harmony { get; } = new(Id);
|
||||
public CustomRpcManager CustomRpcManager { get; } = new();
|
||||
|
||||
public ConfigEntry<bool>? AllowVanillaServers { get; private set; }
|
||||
|
||||
internal RegionInfoWatcher RegionInfoWatcher { get; } = new();
|
||||
|
||||
public ReactorPlugin()
|
||||
{
|
||||
public Harmony Harmony { get; } = new Harmony(Id);
|
||||
public CustomRpcManager CustomRpcManager { get; } = new CustomRpcManager();
|
||||
PluginSingleton<BasePlugin>.Initialize();
|
||||
RegisterInIl2CppAttribute.Initialize();
|
||||
RegisterCustomRpcAttribute.Initialize();
|
||||
MessageConverterAttribute.Initialize();
|
||||
MethodRpcAttribute.Initialize();
|
||||
|
||||
public ConfigEntry<bool>? AllowVanillaServers { get; private set; }
|
||||
ChainloaderHooks.OnPluginLoad(this);
|
||||
}
|
||||
|
||||
internal RegionInfoWatcher RegionInfoWatcher { get; } = new RegionInfoWatcher();
|
||||
public override void Load()
|
||||
{
|
||||
AllowVanillaServers = Config.Bind("Features", "Allow vanilla servers", false, "Whether reactor should ignore servers not responding to modded handshake. This config is ignored if any plugin uses custom rpcs!");
|
||||
|
||||
public ReactorPlugin()
|
||||
Harmony.PatchAll();
|
||||
|
||||
this.AddComponent<ReactorComponent>().Plugin = this;
|
||||
this.AddComponent<Coroutines.Component>();
|
||||
this.AddComponent<Dispatcher>();
|
||||
|
||||
ReactorVersionShower.Initialize();
|
||||
FreeNamePatch.Initialize();
|
||||
DefaultBundle.Load();
|
||||
|
||||
SceneManager.add_sceneLoaded((Action<Scene, LoadSceneMode>) ((scene, _) =>
|
||||
{
|
||||
PluginSingleton<BasePlugin>.Initialize();
|
||||
RegisterInIl2CppAttribute.Initialize();
|
||||
RegisterCustomRpcAttribute.Initialize();
|
||||
MessageConverterAttribute.Initialize();
|
||||
MethodRpcAttribute.Initialize();
|
||||
|
||||
ChainloaderHooks.OnPluginLoad(this);
|
||||
}
|
||||
|
||||
public override void Load()
|
||||
{
|
||||
AllowVanillaServers = Config.Bind("Features", "Allow vanilla servers", false, "Whether reactor should ignore servers not responding to modded handshake. This config is ignored if any plugin uses custom rpcs!");
|
||||
|
||||
Harmony.PatchAll();
|
||||
|
||||
this.AddComponent<ReactorComponent>().Plugin = this;
|
||||
this.AddComponent<Coroutines.Component>();
|
||||
this.AddComponent<Dispatcher>();
|
||||
|
||||
ReactorVersionShower.Initialize();
|
||||
FreeNamePatch.Initialize();
|
||||
DefaultBundle.Load();
|
||||
|
||||
SceneManager.add_sceneLoaded((Action<Scene, LoadSceneMode>) ((scene, _) =>
|
||||
{
|
||||
if (scene.name == "MainMenu")
|
||||
{
|
||||
ModManager.Instance.ShowModStamp();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
public override bool Unload()
|
||||
{
|
||||
Harmony.UnpatchSelf();
|
||||
RegionInfoWatcher?.Dispose();
|
||||
|
||||
return base.Unload();
|
||||
}
|
||||
|
||||
[RegisterInIl2Cpp]
|
||||
public class ReactorComponent : MonoBehaviour
|
||||
{
|
||||
[HideFromIl2Cpp]
|
||||
public ReactorPlugin? Plugin { get; internal set; }
|
||||
|
||||
public ReactorComponent(IntPtr ptr) : base(ptr)
|
||||
if (scene.name == "MainMenu")
|
||||
{
|
||||
ModManager.Instance.ShowModStamp();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private void Update()
|
||||
public override bool Unload()
|
||||
{
|
||||
Harmony.UnpatchSelf();
|
||||
RegionInfoWatcher?.Dispose();
|
||||
|
||||
return base.Unload();
|
||||
}
|
||||
|
||||
[RegisterInIl2Cpp]
|
||||
public class ReactorComponent : MonoBehaviour
|
||||
{
|
||||
[HideFromIl2Cpp]
|
||||
public ReactorPlugin? Plugin { get; internal set; }
|
||||
|
||||
public ReactorComponent(IntPtr ptr) : base(ptr)
|
||||
{
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (Input.GetKeyDown(KeyCode.F5))
|
||||
{
|
||||
if (Input.GetKeyDown(KeyCode.F5))
|
||||
Plugin!.Log.LogInfo("Reloading all configs");
|
||||
|
||||
foreach (var pluginInfo in IL2CPPChainloader.Instance.Plugins.Values)
|
||||
{
|
||||
Plugin!.Log.LogInfo("Reloading all configs");
|
||||
|
||||
foreach (var pluginInfo in IL2CPPChainloader.Instance.Plugins.Values)
|
||||
var config = ((BasePlugin) pluginInfo.Instance).Config;
|
||||
if (!config.Any())
|
||||
{
|
||||
var config = ((BasePlugin) pluginInfo.Instance).Config;
|
||||
if (!config.Any())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
config.Reload();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Plugin.Log.LogWarning($"Exception occured during reload of {pluginInfo.Metadata.Name}: {e}");
|
||||
}
|
||||
try
|
||||
{
|
||||
config.Reload();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Plugin.Log.LogWarning($"Exception occured during reload of {pluginInfo.Metadata.Name}: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,36 +2,35 @@ using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Reactor
|
||||
namespace Reactor;
|
||||
|
||||
/// <summary>
|
||||
/// This attribute specifies plugin's side
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class ReactorPluginSideAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// This attribute specifies plugin's side
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class ReactorPluginSideAttribute : Attribute
|
||||
public PluginSide Side { get; }
|
||||
|
||||
public ReactorPluginSideAttribute(PluginSide side)
|
||||
{
|
||||
public PluginSide Side { get; }
|
||||
Side = side;
|
||||
}
|
||||
|
||||
public ReactorPluginSideAttribute(PluginSide side)
|
||||
public static PluginSide GetPluginSide(Type type)
|
||||
{
|
||||
var attribute = type.GetCustomAttribute<ReactorPluginSideAttribute>();
|
||||
if (attribute != null)
|
||||
{
|
||||
Side = side;
|
||||
return attribute.Side;
|
||||
}
|
||||
|
||||
public static PluginSide GetPluginSide(Type type)
|
||||
var metadataAttribute = type.Assembly.GetCustomAttributes<AssemblyMetadataAttribute>().SingleOrDefault(x => x.Key == "Reactor.PluginSide");
|
||||
if (metadataAttribute != null)
|
||||
{
|
||||
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;
|
||||
return Enum.Parse<PluginSide>(metadataAttribute.Value, true);
|
||||
}
|
||||
|
||||
return PluginSide.Both;
|
||||
}
|
||||
}
|
||||
@@ -4,48 +4,47 @@ using BepInEx.IL2CPP;
|
||||
using HarmonyLib;
|
||||
using Reactor.Networking;
|
||||
|
||||
namespace Reactor
|
||||
namespace Reactor;
|
||||
|
||||
/// <summary>
|
||||
/// Utility attribute for automatically registering CustomRpc
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class RegisterCustomRpcAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility attribute for automatically registering CustomRpc
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class RegisterCustomRpcAttribute : Attribute
|
||||
public uint Id { get; }
|
||||
|
||||
public RegisterCustomRpcAttribute(uint id)
|
||||
{
|
||||
public uint Id { get; }
|
||||
Id = id;
|
||||
}
|
||||
|
||||
public RegisterCustomRpcAttribute(uint id)
|
||||
{
|
||||
Id = id;
|
||||
}
|
||||
[Obsolete("You don't need to call this anymore", true)]
|
||||
public static void Register(BasePlugin plugin)
|
||||
{
|
||||
}
|
||||
|
||||
[Obsolete("You don't need to call this anymore", true)]
|
||||
public static void Register(BasePlugin plugin)
|
||||
public static void Register(Assembly assembly, BasePlugin plugin)
|
||||
{
|
||||
foreach (var type in assembly.GetTypes())
|
||||
{
|
||||
}
|
||||
var attribute = type.GetCustomAttribute<RegisterCustomRpcAttribute>();
|
||||
|
||||
public static void Register(Assembly assembly, BasePlugin plugin)
|
||||
{
|
||||
foreach (var type in assembly.GetTypes())
|
||||
if (attribute != null)
|
||||
{
|
||||
var attribute = type.GetCustomAttribute<RegisterCustomRpcAttribute>();
|
||||
|
||||
if (attribute != null)
|
||||
if (!type.IsSubclassOf(typeof(UnsafeCustomRpc)))
|
||||
{
|
||||
if (!type.IsSubclassOf(typeof(UnsafeCustomRpc)))
|
||||
{
|
||||
throw new InvalidOperationException($"Type {type.FullDescription()} has {nameof(RegisterCustomRpcAttribute)} but doesn't extend {nameof(UnsafeCustomRpc)}.");
|
||||
}
|
||||
|
||||
var customRpc = (UnsafeCustomRpc) Activator.CreateInstance(type, plugin, attribute.Id);
|
||||
PluginSingleton<ReactorPlugin>.Instance.CustomRpcManager.Register(customRpc);
|
||||
throw new InvalidOperationException($"Type {type.FullDescription()} has {nameof(RegisterCustomRpcAttribute)} but doesn't extend {nameof(UnsafeCustomRpc)}.");
|
||||
}
|
||||
|
||||
var customRpc = (UnsafeCustomRpc) Activator.CreateInstance(type, plugin, attribute.Id);
|
||||
PluginSingleton<ReactorPlugin>.Instance.CustomRpcManager.Register(customRpc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void Initialize()
|
||||
{
|
||||
ChainloaderHooks.PluginLoad += plugin => Register(plugin.GetType().Assembly, plugin);
|
||||
}
|
||||
internal static void Initialize()
|
||||
{
|
||||
ChainloaderHooks.PluginLoad += plugin => Register(plugin.GetType().Assembly, plugin);
|
||||
}
|
||||
}
|
||||
@@ -3,69 +3,68 @@ using System.Reflection;
|
||||
using HarmonyLib;
|
||||
using UnhollowerRuntimeLib;
|
||||
|
||||
namespace Reactor
|
||||
namespace Reactor;
|
||||
|
||||
/// <summary>
|
||||
/// Utility attribute for automatically calling <see cref="ClassInjector.RegisterTypeInIl2Cpp{T}()"/>
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class RegisterInIl2CppAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility attribute for automatically calling <see cref="ClassInjector.RegisterTypeInIl2Cpp{T}()"/>
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class RegisterInIl2CppAttribute : Attribute
|
||||
public Type[] Interfaces { get; }
|
||||
|
||||
public RegisterInIl2CppAttribute()
|
||||
{
|
||||
public Type[] Interfaces { get; }
|
||||
Interfaces = Type.EmptyTypes;
|
||||
}
|
||||
|
||||
public RegisterInIl2CppAttribute()
|
||||
public RegisterInIl2CppAttribute(params Type[] interfaces)
|
||||
{
|
||||
Interfaces = interfaces;
|
||||
}
|
||||
|
||||
[Obsolete("You don't need to call this anymore", true)]
|
||||
public static void Register()
|
||||
{
|
||||
}
|
||||
|
||||
private static void Register(Type type, Type[] interfaces)
|
||||
{
|
||||
var baseTypeAttribute = type.BaseType?.GetCustomAttribute<RegisterInIl2CppAttribute>();
|
||||
if (baseTypeAttribute != null)
|
||||
{
|
||||
Interfaces = Type.EmptyTypes;
|
||||
Register(type.BaseType!, baseTypeAttribute.Interfaces);
|
||||
}
|
||||
|
||||
public RegisterInIl2CppAttribute(params Type[] interfaces)
|
||||
if (ClassInjector.IsTypeRegisteredInIl2Cpp(type))
|
||||
{
|
||||
Interfaces = interfaces;
|
||||
return;
|
||||
}
|
||||
|
||||
[Obsolete("You don't need to call this anymore", true)]
|
||||
public static void Register()
|
||||
try
|
||||
{
|
||||
ClassInjector.RegisterTypeInIl2Cpp(type, new RegisterTypeOptions { Interfaces = interfaces });
|
||||
}
|
||||
|
||||
private static void Register(Type type, Type[] interfaces)
|
||||
catch (Exception e)
|
||||
{
|
||||
var baseTypeAttribute = type.BaseType?.GetCustomAttribute<RegisterInIl2CppAttribute>();
|
||||
if (baseTypeAttribute != null)
|
||||
{
|
||||
Register(type.BaseType!, baseTypeAttribute.Interfaces);
|
||||
}
|
||||
|
||||
if (ClassInjector.IsTypeRegisteredInIl2Cpp(type))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ClassInjector.RegisterTypeInIl2Cpp(type, new RegisterTypeOptions { Interfaces = interfaces });
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger<ReactorPlugin>.Warning($"Failed to register {type.FullDescription()}: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
public static void Register(Assembly assembly)
|
||||
{
|
||||
foreach (var type in assembly.GetTypes())
|
||||
{
|
||||
var attribute = type.GetCustomAttribute<RegisterInIl2CppAttribute>();
|
||||
if (attribute != null)
|
||||
{
|
||||
Register(type, attribute.Interfaces);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void Initialize()
|
||||
{
|
||||
ChainloaderHooks.PluginLoad += plugin => Register(plugin.GetType().Assembly);
|
||||
Logger<ReactorPlugin>.Warning($"Failed to register {type.FullDescription()}: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
public static void Register(Assembly assembly)
|
||||
{
|
||||
foreach (var type in assembly.GetTypes())
|
||||
{
|
||||
var attribute = type.GetCustomAttribute<RegisterInIl2CppAttribute>();
|
||||
if (attribute != null)
|
||||
{
|
||||
Register(type, attribute.Interfaces);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void Initialize()
|
||||
{
|
||||
ChainloaderHooks.PluginLoad += plugin => Register(plugin.GetType().Assembly);
|
||||
}
|
||||
}
|
||||
@@ -2,34 +2,33 @@ using Reactor.Extensions;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Reactor.Unstrip
|
||||
namespace Reactor.Unstrip;
|
||||
|
||||
internal static class DefaultBundle
|
||||
{
|
||||
internal static class DefaultBundle
|
||||
public static void Load()
|
||||
{
|
||||
public static void Load()
|
||||
using var stream = typeof(ReactorPlugin).Assembly.GetManifestResourceStream("Reactor.Assets.default.bundle")!;
|
||||
var bundle = AssetBundle.LoadFromStream(stream.AsIl2Cpp());
|
||||
|
||||
var backupShader = bundle.LoadAsset<Shader>("UI-Default");
|
||||
|
||||
if (!Graphic.defaultGraphicMaterial.shader || Graphic.defaultGraphicMaterial.shader.name != "UI/Default")
|
||||
{
|
||||
using var stream = typeof(ReactorPlugin).Assembly.GetManifestResourceStream("Reactor.Assets.default.bundle")!;
|
||||
var bundle = AssetBundle.LoadFromStream(stream.AsIl2Cpp());
|
||||
|
||||
var backupShader = bundle.LoadAsset<Shader>("UI-Default");
|
||||
|
||||
if (!Graphic.defaultGraphicMaterial.shader || Graphic.defaultGraphicMaterial.shader.name != "UI/Default")
|
||||
{
|
||||
Graphic.defaultGraphicMaterial.shader = backupShader;
|
||||
}
|
||||
|
||||
GUIExtensions.StandardResources = new DefaultControls.Resources
|
||||
{
|
||||
background = bundle.LoadAsset<Sprite>("Background")!.DontUnload(),
|
||||
checkmark = bundle.LoadAsset<Sprite>("Checkmark")!.DontUnload(),
|
||||
dropdown = bundle.LoadAsset<Sprite>("DropdownArrow")!.DontUnload(),
|
||||
knob = bundle.LoadAsset<Sprite>("Knob")!.DontUnload(),
|
||||
mask = bundle.LoadAsset<Sprite>("UIMask")!.DontUnload(),
|
||||
standard = bundle.LoadAsset<Sprite>("UISprite")!.DontUnload(),
|
||||
inputField = bundle.LoadAsset<Sprite>("InputFieldBackground")!.DontUnload()
|
||||
};
|
||||
|
||||
bundle.Unload(false);
|
||||
Graphic.defaultGraphicMaterial.shader = backupShader;
|
||||
}
|
||||
|
||||
GUIExtensions.StandardResources = new DefaultControls.Resources
|
||||
{
|
||||
background = bundle.LoadAsset<Sprite>("Background")!.DontUnload(),
|
||||
checkmark = bundle.LoadAsset<Sprite>("Checkmark")!.DontUnload(),
|
||||
dropdown = bundle.LoadAsset<Sprite>("DropdownArrow")!.DontUnload(),
|
||||
knob = bundle.LoadAsset<Sprite>("Knob")!.DontUnload(),
|
||||
mask = bundle.LoadAsset<Sprite>("UIMask")!.DontUnload(),
|
||||
standard = bundle.LoadAsset<Sprite>("UISprite")!.DontUnload(),
|
||||
inputField = bundle.LoadAsset<Sprite>("InputFieldBackground")!.DontUnload(),
|
||||
};
|
||||
|
||||
bundle.Unload(false);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user