Rework debugger (#79)

This commit is contained in:
js6pak authored and GitHub committed 2024-03-02 13:02:53 +01:00
1 parent d82268b88a
commit 5db20ca9de
27 files changed
+1068 -180

No files matched your search

@@ -0,0 +1,82 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.IO.Pipes;
using InnerNet;
using Reactor.Debugger.AutoJoin.Messages;
using Reactor.Utilities;
namespace Reactor.Debugger.AutoJoin;
internal sealed class AutoJoinClientConnection : AutoJoinConnection
{
public const string PipeName = "Reactor.Debugger.AutoJoin";
private AutoJoinClientConnection(NamedPipeClientStream pipe) : base(pipe)
{
}
protected override void Handle(BinaryReader reader, MessageType messageType)
{
switch (messageType)
{
case MessageType.JoinGame:
{
Handle(JoinGameMessage.Deserialize(reader));
break;
}
default:
{
throw new ArgumentOutOfRangeException(nameof(messageType), messageType, null);
}
}
}
private static void Handle(in JoinGameMessage message)
{
var (address, port, gameCode) = message;
Dispatcher.Instance.Enqueue(() =>
{
var gameCodeText = gameCode == InnerNetServer.LocalGameId ? "<local>" : GameCode.IntToGameName(gameCode);
Info($"Joining {gameCodeText} on {address}:{port}");
if (gameCode == InnerNetServer.LocalGameId)
{
AmongUsClient.Instance.NetworkMode = NetworkModes.LocalGame;
AmongUsClient.Instance.GameId = gameCode;
AmongUsClient.Instance.StartCoroutine(AmongUsClient.Instance.CoConnectToGameServer(MatchMakerModes.Client, address, port, null));
}
else
{
AmongUsClient.Instance.StartCoroutine(AmongUsClient.Instance.CoJoinOnlineGameDirect(gameCode, address, port, null));
}
});
}
public void SendRequestJoin()
{
Write(default(RequestJoinGameMessage));
}
public static bool TryConnect([NotNullWhen(true)] out AutoJoinClientConnection? client)
{
var pipeClient = new NamedPipeClientStream(".", PipeName, PipeDirection.InOut, PipeOptions.Asynchronous);
try
{
pipeClient.Connect(100);
client = new AutoJoinClientConnection(pipeClient);
return true;
}
catch (TimeoutException)
{
pipeClient.Dispose();
client = null;
return false;
}
}
}
@@ -0,0 +1,61 @@
using System;
using System.IO;
using System.IO.Pipes;
using System.Threading.Tasks;
using Reactor.Debugger.AutoJoin.Messages;
namespace Reactor.Debugger.AutoJoin;
internal abstract class AutoJoinConnection : IDisposable
{
private readonly BinaryWriter _writer;
protected AutoJoinConnection(PipeStream pipe)
{
_writer = new BinaryWriter(pipe);
Task.Run(() =>
{
try
{
var reader = new BinaryReader(pipe);
while (pipe.IsConnected)
{
try
{
var messageType = (MessageType) reader.ReadByte();
Debug($"Received {messageType}");
Handle(reader, messageType);
}
catch (EndOfStreamException)
{
break;
}
}
pipe.Dispose();
Disconnected?.Invoke();
}
catch (Exception e)
{
Error(e);
}
});
}
public event Action? Disconnected;
protected abstract void Handle(BinaryReader reader, MessageType messageType);
public void Write<T>(in T message) where T : IMessage<T>
{
Debug($"Writing {message}");
_writer.Write(message);
}
public void Dispose()
{
_writer.Dispose();
}
}
@@ -0,0 +1,113 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO.Pipes;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using InnerNet;
using Reactor.Debugger.AutoJoin.Messages;
namespace Reactor.Debugger.AutoJoin;
internal sealed class AutoJoinConnectionListener : IDisposable
{
private readonly CancellationTokenSource _cancellationTokenSource = new();
private readonly Mutex _mutex;
private AutoJoinConnectionListener(Mutex mutex)
{
_mutex = mutex;
Task.Run(async () =>
{
while (!_cancellationTokenSource.IsCancellationRequested)
{
try
{
Info("Listening for connections");
var pipeServer = new NamedPipeServerStream(
AutoJoinClientConnection.PipeName,
PipeDirection.InOut,
NamedPipeServerStream.MaxAllowedServerInstances,
PipeTransmissionMode.Byte,
PipeOptions.Asynchronous
);
try
{
await pipeServer.WaitForConnectionAsync(_cancellationTokenSource.Token);
}
catch (OperationCanceledException)
{
pipeServer.Dispose();
break;
}
Info("Client connected");
var client = new AutoJoinServerConnection(pipeServer);
Clients.Add(client);
client.Disconnected += () =>
{
Clients.Remove(client);
client.Dispose();
};
}
catch (Exception e)
{
Error(e);
}
}
Info("Stopped");
Stopped?.Invoke();
});
}
public event Action? Stopped;
public List<AutoJoinServerConnection> Clients { get; } = new();
public void Dispose()
{
_cancellationTokenSource.Cancel();
_cancellationTokenSource.Dispose();
foreach (var client in Clients)
{
client.Dispose();
}
Clients.Clear();
_mutex.Dispose();
}
public void SendJoinMe(InnerNetClient innerNetClient)
{
var joinGameMessage = JoinGameMessage.From(innerNetClient);
foreach (var client in Clients)
{
client.Write(joinGameMessage);
}
}
public static bool TryStart([NotNullWhen(true)] out AutoJoinConnectionListener? connectionListener)
{
var mutex = new Mutex(false, $@"Global\{AutoJoinClientConnection.PipeName}");
if (mutex.WaitOne(100))
{
connectionListener = new AutoJoinConnectionListener(mutex);
return true;
}
mutex.Dispose();
connectionListener = null;
return false;
}
}
@@ -0,0 +1,66 @@
using HarmonyLib;
using InnerNet;
namespace Reactor.Debugger.AutoJoin;
internal static class AutoJoinConnectionManager
{
public static AutoJoinConnectionListener? Server { get; set; }
public static AutoJoinClientConnection? Client { get; set; }
public static void StartOrConnect()
{
if (AutoJoinConnectionListener.TryStart(out var connectionListener))
{
Info("Started an AutoJoin session");
Server = connectionListener;
Server.Stopped += () =>
{
if (Server == connectionListener)
{
Server = null;
}
};
}
else if (AutoJoinClientConnection.TryConnect(out var client))
{
Info("Connected to an AutoJoin session");
Client = client;
client.Disconnected += () =>
{
if (Client == client)
{
Client = null;
Info("Disconnected from an AutoJoin session, trying to reconnect");
StartOrConnect();
}
};
}
else
{
Error("Failed to start or connect to an AutoJoin session");
}
}
[HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.Start))]
public static class ConnectPatch
{
public static void Postfix(InnerNetClient __instance)
{
if (!DebuggerConfig.JoinGameOnStart.Value) return;
if (__instance.TryCast<AmongUsClient>() is { } amongUsClient)
{
if (Server != null)
{
amongUsClient.StartCoroutine(amongUsClient.CoCreateOnlineGame());
}
else if (Client is { } client)
{
client.SendRequestJoin();
}
}
}
}
}
@@ -0,0 +1,38 @@
using System;
using System.IO;
using System.IO.Pipes;
using Reactor.Debugger.AutoJoin.Messages;
namespace Reactor.Debugger.AutoJoin;
internal sealed class AutoJoinServerConnection : AutoJoinConnection
{
public AutoJoinServerConnection(NamedPipeServerStream pipe) : base(pipe)
{
}
protected override void Handle(BinaryReader reader, MessageType messageType)
{
switch (messageType)
{
case MessageType.RequestJoinGame:
{
Handle(RequestJoinGameMessage.Deserialize(reader));
break;
}
default:
{
throw new ArgumentOutOfRangeException(nameof(messageType), messageType, null);
}
}
}
private void Handle(in RequestJoinGameMessage message)
{
if (AmongUsClient.Instance.AmConnected)
{
Write(JoinGameMessage.From(AmongUsClient.Instance));
}
}
}
@@ -0,0 +1,12 @@
using System.IO;
namespace Reactor.Debugger.AutoJoin.Messages;
internal static class Extensions
{
public static void Write<T>(this BinaryWriter writer, in T message) where T : IMessage<T>
{
writer.Write((byte) T.Type);
message.Serialize(writer);
}
}
@@ -0,0 +1,15 @@
#pragma warning disable CA2252 // TODO Remove after BepInEx updates the .NET version
using System.IO;
namespace Reactor.Debugger.AutoJoin.Messages;
internal interface IMessage<TSelf> where TSelf : IMessage<TSelf>
{
static abstract MessageType Type { get; }
void Serialize(BinaryWriter writer);
static abstract TSelf Deserialize(BinaryReader reader);
string ToString();
}
@@ -0,0 +1,40 @@
using System.IO;
using InnerNet;
namespace Reactor.Debugger.AutoJoin.Messages;
internal readonly record struct JoinGameMessage(
string Address,
ushort Port,
int GameCode
) : IMessage<JoinGameMessage>
{
public static MessageType Type => MessageType.JoinGame;
public void Serialize(BinaryWriter writer)
{
writer.Write(Address);
writer.Write(Port);
writer.Write(GameCode);
}
public static JoinGameMessage Deserialize(BinaryReader reader)
{
return new JoinGameMessage
{
Address = reader.ReadString(),
Port = reader.ReadUInt16(),
GameCode = reader.ReadInt32(),
};
}
public static JoinGameMessage From(InnerNetClient innerNetClient)
{
return new JoinGameMessage
{
Address = innerNetClient.networkAddress,
Port = (ushort) innerNetClient.networkPort,
GameCode = innerNetClient.GameId,
};
}
}
@@ -0,0 +1,7 @@
namespace Reactor.Debugger.AutoJoin.Messages;
internal enum MessageType : byte
{
JoinGame,
RequestJoinGame,
}
@@ -0,0 +1,17 @@
using System.IO;
namespace Reactor.Debugger.AutoJoin.Messages;
internal readonly record struct RequestJoinGameMessage : IMessage<RequestJoinGameMessage>
{
public static MessageType Type => MessageType.RequestJoinGame;
public void Serialize(BinaryWriter writer)
{
}
public static RequestJoinGameMessage Deserialize(BinaryReader reader)
{
return default;
}
}
+26
View File
@@ -0,0 +1,26 @@
using BepInEx.Configuration;
namespace Reactor.Debugger;
internal static class DebuggerConfig
{
public const string FeaturesSection = "Features";
public const string AutoJoinSection = "AutoJoin";
public static ConfigEntry<bool> DisableGameEnd { get; private set; } = null!;
public static ConfigEntry<bool> RedirectLogger { get; private set; } = null!;
public static ConfigEntry<bool> AutoPlayAgain { get; private set; } = null!;
public static ConfigEntry<bool> DisableTimeout { get; private set; } = null!;
public static ConfigEntry<bool> JoinGameOnStart { get; private set; } = null!;
public static void Bind(ConfigFile config)
{
DisableGameEnd = config.Bind(FeaturesSection, nameof(DisableGameEnd), false, "Stops the game from ending regardless of conditions");
RedirectLogger = config.Bind(FeaturesSection, nameof(RedirectLogger), false, "Redirect base game Logger calls into BepInEx logging");
AutoPlayAgain = config.Bind(FeaturesSection, nameof(AutoPlayAgain), true, "Automatically calls Play Again after game ends");
DisableTimeout = config.Bind(FeaturesSection, nameof(DisableTimeout), false, "Disable the network disconnection timeout");
JoinGameOnStart = config.Bind(AutoJoinSection, nameof(JoinGameOnStart), false, "Automatically hosts/joins a game on start");
}
}
+8 -104
View File
@@ -1,16 +1,10 @@
using System; global using static Reactor.Utilities.Logger<Reactor.Debugger.DebuggerPlugin>;
using System.Linq;
using AmongUs.Data;
using AmongUs.GameOptions;
using BepInEx; using BepInEx;
using BepInEx.Unity.IL2CPP; using BepInEx.Unity.IL2CPP;
using HarmonyLib; using HarmonyLib;
using Il2CppInterop.Runtime.Attributes; using Reactor.Debugger.AutoJoin;
using Il2CppInterop.Runtime.InteropTypes.Arrays; using Reactor.Debugger.Patches;
using InnerNet; using Reactor.Debugger.Window;
using Reactor.Utilities.Attributes;
using Reactor.Utilities.ImGui;
using UnityEngine;
namespace Reactor.Debugger; namespace Reactor.Debugger;
@@ -20,107 +14,17 @@ namespace Reactor.Debugger;
public partial class DebuggerPlugin : BasePlugin public partial class DebuggerPlugin : BasePlugin
{ {
public Harmony Harmony { get; } = new(Id); public Harmony Harmony { get; } = new(Id);
public DebuggerComponent Component { get; private set; } = null!;
public override void Load() public override void Load()
{ {
Component = this.AddComponent<DebuggerComponent>(); DebuggerConfig.Bind(Config);
var maxImpostors = (Il2CppStructArray<int>) Enumerable.Repeat((int) byte.MaxValue, byte.MaxValue).ToArray(); this.AddComponent<DebuggerWindow>();
GameOptionsData.MaxImpostors = GameOptionsData.RecommendedImpostors = maxImpostors;
NormalGameOptionsV07.MaxImpostors = NormalGameOptionsV07.RecommendedImpostors = maxImpostors;
HideNSeekGameOptionsV07.MaxImpostors = maxImpostors;
var minPlayers = (Il2CppStructArray<int>) Enumerable.Repeat(1, byte.MaxValue).ToArray(); GameOptionsPatches.Initialize();
GameOptionsData.MinPlayers = minPlayers;
NormalGameOptionsV07.MinPlayers = minPlayers;
HideNSeekGameOptionsV07.MinPlayers = minPlayers;
Harmony.PatchAll(); Harmony.PatchAll();
}
[RegisterInIl2Cpp] AutoJoinConnectionManager.StartOrConnect();
public class DebuggerComponent : MonoBehaviour
{
[HideFromIl2Cpp]
public bool DisableGameEnd { get; set; }
[HideFromIl2Cpp]
public DragWindow TestWindow { get; }
public DebuggerComponent(IntPtr ptr) : base(ptr)
{
TestWindow = new DragWindow(new Rect(20, 20, 0, 0), "Debugger", () =>
{
GUILayout.Label("Name: " + DataManager.Player.Customization.Name);
DisableGameEnd = GUILayout.Toggle(DisableGameEnd, "Disable game end");
if (ShipStatus.Instance && AmongUsClient.Instance.AmHost)
{
if (GUILayout.Button("Force game end"))
{
ShipStatus.Instance.enabled = false;
GameManager.Instance.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,
};
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.F1))
{
TestWindow.Enabled = !TestWindow.Enabled;
}
}
private void OnGUI()
{
TestWindow.OnGUI();
}
} }
} }
-76
View File
@@ -1,76 +0,0 @@
using System.Linq;
using AmongUs.GameOptions;
using HarmonyLib;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Reactor.Utilities;
using UnityEngine;
namespace Reactor.Debugger;
internal static class Patches
{
[HarmonyPatch(typeof(GameStartManager), nameof(GameStartManager.Update))]
public static class UpdatePatch
{
public static void Prefix(GameStartManager __instance)
{
__instance.MinPlayers = 1;
}
}
[HarmonyPatch(typeof(GameStartManager), nameof(GameStartManager.Update))]
public static class CountdownPatch
{
public static void Prefix(GameStartManager __instance)
{
__instance.countDownTimer = 0;
}
}
[HarmonyPatch(typeof(StatsManager), nameof(StatsManager.AmBanned), MethodType.Getter)]
public static class AmBannedPatch
{
public static void Postfix(out bool __result)
{
__result = false;
}
}
[HarmonyPatch]
[HarmonyPriority(Priority.First)]
public static class CheckEndCriteriaPatch
{
[HarmonyPatch(typeof(LogicGameFlow), nameof(LogicGameFlow.CheckEndCriteria))]
[HarmonyPatch(typeof(LogicGameFlowNormal), nameof(LogicGameFlow.CheckEndCriteria))]
[HarmonyPatch(typeof(LogicGameFlowHnS), nameof(LogicGameFlow.CheckEndCriteria))]
public static bool Prefix()
{
return !PluginSingleton<DebuggerPlugin>.Instance.Component.DisableGameEnd;
}
}
[HarmonyPatch(typeof(GameSettingMenu), nameof(GameSettingMenu.Start))]
public static class GameSettingMenuPatch
{
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);
}
}
[HarmonyPatch(typeof(GameOptionsMenu), nameof(GameOptionsMenu.Start))]
[HarmonyPriority(Priority.First)]
public static class GameOptionsMenuPatch
{
public static void Postfix(GameOptionsMenu __instance)
{
if (GameOptionsManager.Instance.CurrentGameOptions.GameMode != GameModes.Normal) return;
__instance.Children
.Single(o => o.Title == StringNames.GameNumImpostors)
.Cast<NumberOption>()
.ValidRange = new FloatRange(0, byte.MaxValue);
}
}
}
@@ -0,0 +1,14 @@
using HarmonyLib;
namespace Reactor.Debugger.Patches;
[HarmonyPatch(typeof(EndGameNavigation), nameof(EndGameNavigation.ShowDefaultNavigation))]
internal static class AutoPlayAgainPatch
{
public static void Postfix(EndGameNavigation __instance)
{
if (!DebuggerConfig.AutoPlayAgain.Value) return;
__instance.NextGame();
}
}
@@ -0,0 +1,18 @@
using HarmonyLib;
namespace Reactor.Debugger.Patches;
[HarmonyPatch]
[HarmonyPriority(Priority.First)]
internal static class DisableGameEndPatch
{
private static bool Enabled => DebuggerConfig.DisableGameEnd.Value;
[HarmonyPatch(typeof(LogicGameFlow), nameof(LogicGameFlow.CheckEndCriteria))]
[HarmonyPatch(typeof(LogicGameFlowNormal), nameof(LogicGameFlow.CheckEndCriteria))]
[HarmonyPatch(typeof(LogicGameFlowHnS), nameof(LogicGameFlow.CheckEndCriteria))]
public static bool Prefix()
{
return !Enabled;
}
}
@@ -0,0 +1,27 @@
using HarmonyLib;
using Hazel.Udp;
namespace Reactor.Debugger.Patches;
[HarmonyPatch]
internal static class DisableTimeoutPatch
{
private static bool Enabled => DebuggerConfig.DisableTimeout.Value;
[HarmonyPatch(typeof(UdpConnection), nameof(UdpConnection.HandleKeepAlive))]
[HarmonyPrefix]
public static bool DisableKeepAlive()
{
return !Enabled;
}
[HarmonyPatch(typeof(UnityUdpClientConnection), nameof(UnityUdpClientConnection.ConnectAsync))]
[HarmonyPostfix]
public static void DisableTimeout(UnityUdpClientConnection __instance)
{
if (!Enabled) return;
__instance.DisconnectTimeoutMs = int.MaxValue;
__instance.ResendLimit = 0;
}
}
@@ -0,0 +1,70 @@
using System.Linq;
using AmongUs.GameOptions;
using HarmonyLib;
using Il2CppInterop.Runtime;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppSystem.Reflection;
using UnityEngine;
namespace Reactor.Debugger.Patches;
[HarmonyPatch]
internal static class GameOptionsPatches
{
public static void Initialize()
{
var maxImpostors = (Il2CppStructArray<int>) Enumerable.Repeat((int) byte.MaxValue, byte.MaxValue).ToArray();
GameOptionsData.MaxImpostors = maxImpostors;
NormalGameOptionsV07.MaxImpostors = maxImpostors;
HideNSeekGameOptionsV07.MaxImpostors = maxImpostors;
var minPlayers = (Il2CppStructArray<int>) Enumerable.Repeat(1, byte.MaxValue).ToArray();
GameOptionsData.MinPlayers = minPlayers;
NormalGameOptionsV07.MinPlayers = minPlayers;
HideNSeekGameOptionsV07.MinPlayers = minPlayers;
}
[HarmonyPatch(typeof(GameSettingMenu), nameof(GameSettingMenu.Start))]
[HarmonyPrefix]
public static void UnlockAllOptions(GameSettingMenu __instance)
{
__instance.HideForOnline = new Il2CppReferenceArray<Transform>(0);
}
[HarmonyPatch(typeof(NumberOption), nameof(NumberOption.OnEnable))]
[HarmonyPostfix]
public static void UnlockOptionRange(NumberOption __instance)
{
__instance.ValidRange.min = float.MinValue;
__instance.ValidRange.max = float.MaxValue;
}
[HarmonyPatch(typeof(CreateOptionsPicker), nameof(CreateOptionsPicker.SetImpostorButtons))]
public static class DisableImpostorCountReset
{
private static readonly MethodInfo _refreshMethod = Il2CppType.Of<CreateOptionsPicker>().GetMethod("Refresh", BindingFlags.Public | BindingFlags.Instance);
public static bool Prefix()
{
foreach (var stackFrame in new Il2CppSystem.Diagnostics.StackTrace().GetFrames())
{
if (_refreshMethod.Equals(stackFrame.GetMethod()))
{
return false;
}
}
return true;
}
}
[HarmonyPatch(typeof(CrewVisualizer), nameof(CrewVisualizer.SetCrewSize))]
[HarmonyPrefix]
public static void SetCrewSizePatch(int numPlayers, ref int numImpostors)
{
if (numImpostors >= numPlayers)
{
numImpostors = 0;
}
}
}
@@ -0,0 +1,21 @@
using HarmonyLib;
namespace Reactor.Debugger.Patches;
[HarmonyPatch(typeof(GameStartManager))]
internal static class GameStartManagerPatches
{
[HarmonyPatch(nameof(GameStartManager.Start))]
[HarmonyPrefix]
public static void StartPatch(GameStartManager __instance)
{
__instance.MinPlayers = 1;
}
[HarmonyPatch(nameof(GameStartManager.Update))]
[HarmonyPrefix]
public static void UpdatePatch(GameStartManager __instance)
{
__instance.countDownTimer = 0;
}
}
@@ -0,0 +1,14 @@
using HarmonyLib;
namespace Reactor.Debugger.Patches;
[HarmonyPatch]
internal static class MiscellaneousPatches
{
[HarmonyPatch(typeof(StatsManager), nameof(StatsManager.AmBanned), MethodType.Getter)]
[HarmonyPrefix]
public static void AmBannedPatch(out bool __result)
{
__result = false;
}
}
@@ -0,0 +1,39 @@
using System;
using System.Net;
using HarmonyLib;
using InnerNet;
namespace Reactor.Debugger.Patches;
[HarmonyPatch]
internal static class RandomizeFreeplayServerPortPatch
{
private static ushort? _lastPort;
[HarmonyPatch(typeof(InnerNetServer), nameof(InnerNetServer.StartAsLocalServer))]
public static class StartServerPatch
{
public static void Prefix(InnerNetServer __instance)
{
var port = (ushort) Random.Shared.Next(1024, IPEndPoint.MaxPort);
_lastPort = port;
__instance.Port = port;
}
public static void Postfix(InnerNetServer __instance)
{
__instance.Port = Constants.GamePlayPort;
}
}
[HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.SetEndpoint))]
[HarmonyPrefix]
public static void SetEndpointPatch(InnerNetClient __instance, string addr, ref ushort port)
{
if (_lastPort != null && __instance.NetworkMode == NetworkModes.FreePlay)
{
port = _lastPort.Value;
_lastPort = null;
}
}
}
@@ -0,0 +1,74 @@
using System;
using System.Globalization;
using System.Text;
using BepInEx.Logging;
using HarmonyLib;
using Object = UnityEngine.Object;
namespace Reactor.Debugger.Patches;
[HarmonyPatch(typeof(Logger))]
internal static class RedirectLoggerPatch
{
private static readonly ManualLogSource _log = BepInEx.Logging.Logger.CreateLogSource("Among Us");
private static bool Enabled => DebuggerConfig.RedirectLogger.Value;
private static void Log(Logger logger, Logger.Level level, Il2CppSystem.Object message, Object? context = null)
{
var finalMessage = new StringBuilder();
if (logger.category != Logger.Category.None) finalMessage.Append(CultureInfo.InvariantCulture, $"[{logger.category}] ");
if (!string.IsNullOrEmpty(logger.tag)) finalMessage.Append(CultureInfo.InvariantCulture, $"[{logger.tag}] ");
if (context != null) finalMessage.Append(CultureInfo.InvariantCulture, $"[{context.name} ({context.GetIl2CppType().FullName})]");
finalMessage.Append(CultureInfo.InvariantCulture, $" {message.ToString()} ");
_log.Log(
level switch
{
Logger.Level.Debug => LogLevel.Debug,
Logger.Level.Error => LogLevel.Error,
Logger.Level.Warning => LogLevel.Warning,
Logger.Level.Info => LogLevel.Info,
_ => throw new ArgumentOutOfRangeException(nameof(level), level, null),
},
finalMessage
);
}
[HarmonyPatch(nameof(Logger.Debug))]
[HarmonyPrefix]
public static bool DebugPatch(Logger __instance, Il2CppSystem.Object message, Object? context)
{
if (!Enabled) return true;
Log(__instance, Logger.Level.Debug, message, context);
return false;
}
[HarmonyPatch(nameof(Logger.Info))]
[HarmonyPrefix]
public static bool InfoPatch(Logger __instance, Il2CppSystem.Object message, Object? context)
{
if (!Enabled) return true;
Log(__instance, Logger.Level.Info, message, context);
return false;
}
[HarmonyPatch(nameof(Logger.Warning))]
[HarmonyPrefix]
public static bool WarningPatch(Logger __instance, Il2CppSystem.Object message, Object? context)
{
if (!Enabled) return true;
Log(__instance, Logger.Level.Warning, message, context);
return false;
}
[HarmonyPatch(nameof(Logger.Error))]
[HarmonyPrefix]
public static bool ErrorPatch(Logger __instance, Il2CppSystem.Object message, Object? context)
{
if (!Enabled) return true;
Log(__instance, Logger.Level.Error, message, context);
return false;
}
}
+36
View File
@@ -0,0 +1,36 @@
using System;
using System.Collections;
using Il2CppInterop.Runtime;
using InnerNet;
namespace Reactor.Debugger.Utilities;
internal static class Extensions
{
public static IEnumerator CoCreateLocalGame(this AmongUsClient client)
{
try
{
client.NetworkMode = NetworkModes.LocalGame;
InnerNetServer.Instance.StartAsServer();
client.SetEndpoint(Constants.LocalNetAddress, Constants.GamePlayPort, false);
client.MainMenuScene = "MatchMaking";
client.OnlineScene = "OnlineGame";
}
catch (Il2CppException e)
{
Error(e);
DisconnectPopup.Instance.ShowCustom(e.Message[..e.Message.IndexOf("\n--- BEGIN IL2CPP STACK TRACE ---\n", StringComparison.Ordinal)]);
MatchMaker.Instance.NotConnecting();
yield break;
}
client.Connect(MatchMakerModes.HostAndClient, null);
yield return client.WaitForConnectionOrFail();
DestroyableSingleton<MatchMaker>.Instance.NotConnecting();
}
}
+70
View File
@@ -0,0 +1,70 @@
using System;
using AmongUs.Data;
using Il2CppInterop.Runtime.Attributes;
using Reactor.Debugger.Window.Tabs;
using Reactor.Utilities.Attributes;
using Reactor.Utilities.ImGui;
using UnityEngine;
namespace Reactor.Debugger.Window;
[RegisterInIl2Cpp]
internal sealed class DebuggerWindow : MonoBehaviour
{
private readonly DragWindow _window;
[HideFromIl2Cpp]
public BaseTab[] Tabs { get; } =
{
new ConfigTab(),
new GameTab(),
new AutoJoinTab(),
};
[HideFromIl2Cpp]
public BaseTab SelectedTab { get; private set; }
public DebuggerWindow(IntPtr ptr) : base(ptr)
{
SelectedTab = Tabs[0];
_window = new DragWindow(new Rect(20, 20, 0, 0), "Reactor.Debugger", () =>
{
var clientName = DataManager.Player.Customization.Name;
if (AmongUsClient.Instance && AmongUsClient.Instance.AmHost) clientName += " (host)";
GUILayout.Label("Name: " + clientName);
GUILayout.BeginHorizontal();
{
foreach (var tab in Tabs)
{
if (GUILayout.Toggle(tab == SelectedTab, tab.Name, new GUIStyle(GUI.skin.button)))
{
SelectedTab = tab;
}
}
}
GUILayout.EndHorizontal();
GUILayout.Space(5f);
SelectedTab.OnGUI();
})
{
Enabled = false,
};
}
public void Update()
{
if (Input.GetKeyDown(KeyCode.F1))
{
_window.Enabled = !_window.Enabled;
}
}
public void OnGUI()
{
_window.OnGUI();
}
}
@@ -0,0 +1,68 @@
using BepInEx.Unity.IL2CPP.Utils;
using Reactor.Debugger.AutoJoin;
using Reactor.Debugger.Utilities;
using UnityEngine;
namespace Reactor.Debugger.Window.Tabs;
internal sealed class AutoJoinTab : BaseTab
{
public override string Name => "AutoJoin";
public override void OnGUI()
{
if (AutoJoinConnectionManager.Server is { } server)
{
GUILayout.Label($"Hosting ({server.Clients.Count} clients)");
if (GUILayout.Button("Stop"))
{
server.Dispose();
}
if (AmongUsClient.Instance)
{
if (GUILayout.Button("Create an online game"))
{
AmongUsClient.Instance.StartCoroutine(AmongUsClient.Instance.CoCreateOnlineGame());
}
if (GUILayout.Button("Create a local game"))
{
AmongUsClient.Instance.StartCoroutine(AmongUsClient.Instance.CoCreateLocalGame());
}
if (
AmongUsClient.Instance.AmConnected &&
AmongUsClient.Instance.NetworkMode != NetworkModes.FreePlay &&
GUILayout.Button("Join me")
)
{
server.SendJoinMe(AmongUsClient.Instance);
}
}
}
else if (AutoJoinConnectionManager.Client is { } client)
{
GUILayout.Label("Connected");
if (GUILayout.Button("Disconnect"))
{
AutoJoinConnectionManager.Client = null;
client.Dispose();
}
if (GUILayout.Button("Request join"))
{
client.SendRequestJoin();
}
}
else
{
if (GUILayout.Button("Connect"))
{
AutoJoinConnectionManager.StartOrConnect();
}
}
}
}
+8
View File
@@ -0,0 +1,8 @@
namespace Reactor.Debugger.Window.Tabs;
internal abstract class BaseTab
{
public abstract string Name { get; }
public abstract void OnGUI();
}
+29
View File
@@ -0,0 +1,29 @@
using System.Linq;
using BepInEx.Configuration;
using Reactor.Utilities;
using UnityEngine;
namespace Reactor.Debugger.Window.Tabs;
internal sealed class ConfigTab : BaseTab
{
public override string Name => "Config";
public override void OnGUI()
{
var configs = PluginSingleton<ReactorPlugin>.Instance.Config.Concat(PluginSingleton<DebuggerPlugin>.Instance.Config);
foreach (var section in configs.GroupBy(e => e.Key.Section))
{
GUILayout.Label(section.Key);
foreach (var (definition, entry) in section)
{
if (entry is ConfigEntry<bool> booleanEntry)
{
booleanEntry.Value = GUILayout.Toggle(booleanEntry.Value, definition.Key);
}
}
}
}
}
+95
View File
@@ -0,0 +1,95 @@
using AmongUs.GameOptions;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using UnityEngine;
using Object = UnityEngine.Object;
namespace Reactor.Debugger.Window.Tabs;
internal sealed class GameTab : BaseTab
{
public override string Name => "Game";
public override void OnGUI()
{
var localPlayer = PlayerControl.LocalPlayer;
if (!localPlayer)
{
return;
}
var amHost = AmongUsClient.Instance.AmHost;
var position = localPlayer.transform.position;
GUILayout.Label($"Position: ({position.x:F3}, {position.y:F3})");
var physics = localPlayer.MyPhysics;
GUILayout.Label($"Speed: {physics.Speed}");
physics.Speed = GUILayout.HorizontalSlider(physics.Speed, 0, 25);
localPlayer.Collider.enabled = GUILayout.Toggle(localPlayer.Collider.enabled, "Collisions");
if (ShipStatus.Instance)
{
if (amHost && GUILayout.Button("Force game end"))
{
ShipStatus.Instance.enabled = false;
GameManager.Instance.RpcEndGame(GameOverReason.ImpostorDisconnect, false);
}
if (MeetingHud.Instance)
{
if (GUILayout.Button("Stop the meeting"))
{
MeetingHud.Instance.RpcClose();
}
}
else
{
if (GUILayout.Button("Call a meeting"))
{
localPlayer.CmdReportDeadBody(null);
}
}
if (TutorialManager.InstanceExists)
{
var newIsImpostor = GUILayout.Toggle(localPlayer.Data.Role.IsImpostor, "Is Impostor");
if (localPlayer.Data.Role.IsImpostor != newIsImpostor)
{
localPlayer.RpcSetRole(newIsImpostor ? RoleTypes.Impostor : RoleTypes.Crewmate);
}
if (GUILayout.Button("Spawn a dummy"))
{
SpawnDummy();
}
}
}
}
private static void SpawnDummy()
{
var playerControl = Object.Instantiate(TutorialManager.Instance.PlayerPrefab);
var playerId = playerControl.PlayerId = (byte) GameData.Instance.GetAvailableId();
GameData.Instance.AddPlayer(playerControl);
AmongUsClient.Instance.Spawn(playerControl);
playerControl.transform.position = PlayerControl.LocalPlayer.transform.position;
playerControl.GetComponent<DummyBehaviour>().enabled = true;
playerControl.NetTransform.enabled = false;
playerControl.SetName($"{TranslationController.Instance.GetString(StringNames.Dummy)} {playerId}");
var color = (byte) (playerId % Palette.PlayerColors.Length);
playerControl.SetColor(color);
playerControl.SetHat(HatManager.Instance.allHats[playerId % HatManager.Instance.allHats.Count].ProdId, playerControl.Data.DefaultOutfit.ColorId);
playerControl.SetPet(HatManager.Instance.allPets[playerId % HatManager.Instance.allPets.Count].ProdId);
playerControl.SetSkin(HatManager.Instance.allSkins[playerId % HatManager.Instance.allSkins.Count].ProdId, color);
playerControl.SetVisor(HatManager.Instance.allVisors[playerId % HatManager.Instance.allVisors.Count].ProdId, color);
playerControl.SetNamePlate(HatManager.Instance.allNamePlates[playerId % HatManager.Instance.allNamePlates.Count].ProdId);
playerControl.Data.PlayerLevel = playerId;
GameData.Instance.RpcSetTasks(playerControl.PlayerId, new Il2CppStructArray<byte>(0));
}
}