mirror of
https://github.com/NuclearPowered/Reactor.git
synced 2026-10-19 15:51:54 +00:00
Rework debugger (#79)
This commit is contained in:
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Reactor.Debugger.Window.Tabs;
|
||||||
|
|
||||||
|
internal abstract class BaseTab
|
||||||
|
{
|
||||||
|
public abstract string Name { get; }
|
||||||
|
|
||||||
|
public abstract void OnGUI();
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user