Reformat code

This commit is contained in:
js6pak committed 2022-08-30 19:49:49 +02:00
1 parent c2047b033e
commit 5004ac8d5b
52 files changed
+2126 -2180

No files matched your search

+91 -92
View File
@@ -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
View File
@@ -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);
}
}
}
+5 -6
View File
@@ -1,8 +1,7 @@
namespace Reactor.Example
namespace Reactor.Example;
public enum CustomRpcCalls : uint
{
public enum CustomRpcCalls : uint
{
Example,
MethodRpcExample
}
Example,
MethodRpcExample,
}
+31 -32
View File
@@ -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}");
}
}
+30 -31
View File
@@ -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}\"");
}
}
+27 -28
View File
@@ -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,
}
+12 -13
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
}
}
}
+97 -98
View File
@@ -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>();
}
}
+31 -32
View File
@@ -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
View File
@@ -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())
{
}
}
+75 -76
View File
@@ -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);
}
+29 -30
View File
@@ -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);
}
}
+28 -29
View File
@@ -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 -4
View File
@@ -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
View File
@@ -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);
}
+65 -66
View File
@@ -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);
}
}
+92 -93
View File
@@ -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;
}
}
}
+181 -182
View File
@@ -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);
}
}
+10 -11
View File
@@ -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);
}
}
+30 -31
View File
@@ -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;
}
}
+6 -7
View File
@@ -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);
}
}
+18 -19
View File
@@ -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();
}
}
+137 -138
View File
@@ -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();
}
}
}
+10 -11
View File
@@ -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();
}
}
}
+26 -27
View File
@@ -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);
}
}
+52 -53
View File
@@ -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);
}
}
}
+8 -10
View File
@@ -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;
}
}
}
+15 -16
View File
@@ -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;
}
}
+23 -24
View File
@@ -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,
};
}
}
}
+57 -57
View File
@@ -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;
}
}
+14 -15
View File
@@ -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;
}
}
}
+48 -50
View File
@@ -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;
}
}
}
+58 -59
View File
@@ -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;
}
}
}
+32 -33
View File
@@ -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;
}
}
+28 -29
View File
@@ -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;
}
}
}
+23 -24
View File
@@ -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
View File
@@ -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
View File
@@ -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}");
}
}
}
+22 -23
View File
@@ -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;
}
}
+30 -31
View File
@@ -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);
}
}
+50 -51
View File
@@ -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);
}
}
+24 -25
View File
@@ -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);
}
}