rework bau Handshake

This commit is contained in:
D1GQ committed 2025-04-17 19:10:03 -05:00
1 parent 6277698f72
commit 29ad77aaa4
12 files changed
+338 -108

No files matched your search

+72
View File
@@ -0,0 +1,72 @@
using System.Security.Cryptography;
namespace BetterAmongUs.Data;
internal class SharedSecretExchange
{
private readonly ECDiffieHellman dh;
private byte[] publicKey;
private int tempKey;
private byte[] sharedSecret = [];
internal SharedSecretExchange()
{
dh = ECDiffieHellman.Create(ECCurve.NamedCurves.nistP256);
publicKey = dh.ExportSubjectPublicKeyInfo();
var random = new Random();
tempKey = random.Next();
}
internal byte[] GetPublicKey()
{
return publicKey;
}
internal int GetTempKey()
{
return tempKey;
}
internal byte[] GetSharedSecret() => sharedSecret;
internal byte[] GenerateSharedSecret(byte[] otherPartyPublicKey)
{
if (sharedSecret.Length > 0) return sharedSecret;
try
{
using ECDiffieHellman otherPartyDH = ECDiffieHellman.Create();
otherPartyDH.ImportSubjectPublicKeyInfo(otherPartyPublicKey, out _);
sharedSecret = dh.DeriveKeyMaterial(otherPartyDH.PublicKey);
dh.Dispose();
return sharedSecret;
}
catch (Exception ex)
{
Logger.Error($"Error generating shared secret: {ex.Message}");
return [];
}
}
internal int GetSharedSecretHash()
{
if (sharedSecret.Length == 0)
return 0;
using SHA256 sha256 = SHA256.Create();
byte[] hashBytes = sha256.ComputeHash(sharedSecret);
int numericHash = BitConverter.ToInt32(hashBytes, 0);
return Math.Abs(numericHash);
}
internal bool HasBeenCleared { get; private set; }
internal void ClearData()
{
if (HasBeenCleared) return;
HasBeenCleared = true;
publicKey = [];
tempKey = 0;
sharedSecret = [];
}
}
+32 -8
View File
@@ -1,22 +1,47 @@
using BetterAmongUs.Modules;
using BepInEx.Unity.IL2CPP.Utils;
using BetterAmongUs.Modules;
using HarmonyLib;
using Il2CppInterop.Runtime.Attributes;
using System.Collections;
using TMPro;
using UnityEngine;
namespace BetterAmongUs.Helpers;
[MonoExtension(typeof(PlayerControl))]
internal class ExtendedPlayerControl : MonoBehaviour, IMonoExtension
internal class ExtendedPlayerControl : MonoBehaviour, IMonoExtension<PlayerControl>
{
public MonoBehaviour? BaseMono { get; set; }
internal PlayerControl? _Player { get; set; }
public PlayerControl? BaseMono { get; set; }
internal PlayerControl? _Player => BaseMono;
internal TextMeshPro? InfoTextInfo { get; set; }
internal TextMeshPro? InfoTextTop { get; set; }
internal TextMeshPro? InfoTextBottom { get; set; }
private void Start()
private void Awake()
{
MonoExtensionManager.RegisterExtension(this);
if (!MonoExtensionManager.RegisterExtension(this)) return;
this.StartCoroutine(CoAddBetterData());
}
[HideFromIl2Cpp]
private IEnumerator CoAddBetterData()
{
while (_Player?.Data == null)
{
yield return null;
}
TryCreateExtendedData(_Player.Data);
_Player.DirtyNameDelay();
}
internal static void TryCreateExtendedData(NetworkedPlayerInfo data)
{
if (data.BetterData() == null)
{
ExtendedPlayerInfo newBetterData = data.gameObject.AddComponent<ExtendedPlayerInfo>();
newBetterData.SetInfo(data);
data.DirtyNameDelay(3f);
}
}
private void OnDestroy()
@@ -64,7 +89,6 @@ internal static class PlayerControlExtension
if (pc.BetterPlayerControl() == null)
{
ExtendedPlayerControl newExtendedPc = pc.gameObject.AddComponent<ExtendedPlayerControl>();
newExtendedPc._Player = pc;
newExtendedPc.InfoTextInfo = InfoText_Info_TMP;
newExtendedPc.InfoTextTop = InfoText_T_TMP;
newExtendedPc.InfoTextBottom = InfoText_B_TMP;
+13 -44
View File
@@ -1,33 +1,38 @@
using AmongUs.GameOptions;
using BepInEx.Unity.IL2CPP.Utils;
using BetterAmongUs.Items;
using BetterAmongUs.Managers;
using BetterAmongUs.Modules;
using HarmonyLib;
using InnerNet;
using UnityEngine;
namespace BetterAmongUs.Helpers;
[MonoExtension(typeof(NetworkedPlayerInfo))]
internal class ExtendedPlayerInfo : MonoBehaviour, IMonoExtension
internal class ExtendedPlayerInfo : MonoBehaviour, IMonoExtension<NetworkedPlayerInfo>
{
public MonoBehaviour? BaseMono { get; set; }
internal ExtendedPlayerInfo()
{
HandshakeHandler = new(this);
}
public NetworkedPlayerInfo? BaseMono { get; set; }
internal NetworkedPlayerInfo? _Data => BaseMono;
private bool hasSet = false;
internal void SetInfo(NetworkedPlayerInfo data)
{
if (hasSet) return;
MyUserData = UserData.GetPlayerUserData(data);
_Data = data;
_PlayerId = data.PlayerId;
hasSet = true;
}
private float timeAccumulator = 0f;
private void Start()
private void Awake()
{
MonoExtensionManager.RegisterExtension(this);
if (!MonoExtensionManager.RegisterExtension(this)) return;
this.StartCoroutine(HandshakeHandler.CoSendSecretToPlayer());
}
private void OnDestroy()
@@ -73,9 +78,9 @@ internal class ExtendedPlayerInfo : MonoBehaviour, IMonoExtension
}
}
internal HandshakeHandler HandshakeHandler { get; }
internal UserData? MyUserData { get; private set; } = UserData.AllUsers.First();
internal byte _PlayerId { get; private set; }
internal NetworkedPlayerInfo? _Data { get; private set; }
internal string? RealName { get; private set; }
internal bool IsDirtyInfo { get; set; } = true;
internal Dictionary<byte, string> LastNameSetFor { get; set; } = [];
@@ -112,42 +117,6 @@ internal class ExtendedRoleInfo
internal static class PlayerControlDataExtension
{
[HarmonyPatch(typeof(NetworkedPlayerInfo))]
class NetworkedPlayerInfoPatch
{
[HarmonyPatch(nameof(NetworkedPlayerInfo.Init))]
[HarmonyPostfix]
internal static void Init_Postfix(NetworkedPlayerInfo __instance)
{
TryCreateExtendedData(__instance);
}
[HarmonyPatch(nameof(NetworkedPlayerInfo.Serialize))]
[HarmonyPostfix]
internal static void Serialize_Postfix(NetworkedPlayerInfo __instance)
{
__instance.DirtyNameDelay();
}
[HarmonyPatch(nameof(NetworkedPlayerInfo.Deserialize))]
[HarmonyPostfix]
internal static void Deserialize_Postfix(NetworkedPlayerInfo __instance)
{
TryCreateExtendedData(__instance);
__instance.DirtyNameDelay();
}
internal static void TryCreateExtendedData(NetworkedPlayerInfo data)
{
if (data.BetterData() == null)
{
ExtendedPlayerInfo newBetterData = data.gameObject.AddComponent<ExtendedPlayerInfo>();
newBetterData.SetInfo(data);
data.DirtyNameDelay(3f);
}
}
}
// Get BetterData from PlayerControl
internal static ExtendedPlayerInfo? BetterData(this PlayerControl player)
{
+21
View File
@@ -62,6 +62,27 @@ static class InnerNetClientHelper
return bools;
}
/// <summary>
/// Writes an array of bytes to a MessageWriter in a packed format, combining two bytes into one to save space.
/// </summary>
internal static void WriteBytes(this MessageWriter writer, IEnumerable<byte> bytesEnumerable)
{
byte[] bytes = bytesEnumerable.ToArray();
writer.Write(bytes.Length);
writer.Write(bytes);
}
/// <summary>
/// Reads an array of bytes from a MessageReader that were previously packed using WritePackedBytes.
/// </summary>
internal static byte[] ReadBytes(this MessageReader reader)
{
int count = reader.ReadInt32();
var bytes = reader.ReadBytes(count);
return [.. bytes];
}
/// <summary>
/// Writes an array of bytes to a MessageWriter in a packed format, combining two bytes into one to save space.
/// </summary>
+6 -3
View File
@@ -363,10 +363,13 @@ internal class NetworkManager
private static bool PlayerRpc(PlayerControl player, byte callId, MessageReader reader)
{
player.BetterData().AntiCheatInfo.RPCSentPS++;
if (player.BetterData().AntiCheatInfo.RPCSentPS >= ExtendedAntiCheatInfo.MaxRPCSent)
if (player.BetterData() != null)
{
return false;
player.BetterData().AntiCheatInfo.RPCSentPS++;
if (player.BetterData().AntiCheatInfo.RPCSentPS >= ExtendedAntiCheatInfo.MaxRPCSent)
{
return false;
}
}
BetterAntiCheat.HandleCheatRPCBeforeCheck(player, callId, reader);
+1 -1
View File
@@ -106,7 +106,7 @@ internal static class GameState
internal static bool IsHost => AmongUsClient.Instance != null && AmongUsClient.Instance.AmHost;
internal static bool IsPrivateOnlyLobby => (Main.PrivateOnlyLobby.Value || AmongUsClient.Instance.AmLocalHost) && IsHost;
internal static bool IsBetterHostLobby => PlayerControl.LocalPlayer.IsHost() || Main.AllPlayerControls.Any(pc => pc.IsHost() && pc.BetterData().IsBetterUser && pc.BetterData().IsVerifiedBetterUser);
internal static bool IsTOHEHostLobby => Main.AllPlayerControls.Any(pc => pc.IsHost() && pc.BetterData().IsTOHEHost);
internal static bool IsTOHEHostLobby => InGame && Main.AllPlayerControls.Any(pc => pc.IsHost() && pc.BetterData()?.IsTOHEHost == true);
internal static bool IsCanMove => PlayerControl.LocalPlayer?.CanMove is true;
internal static bool IsDead => PlayerControl.LocalPlayer?.Data?.IsDead is true;
}
+129
View File
@@ -0,0 +1,129 @@
using BetterAmongUs.Data;
using BetterAmongUs.Helpers;
using Hazel;
using System.Collections;
using UnityEngine;
namespace BetterAmongUs.Modules;
internal class HandshakeHandler(ExtendedPlayerInfo extendedPlayerInfo)
{
private readonly ExtendedPlayerInfo extendedData = extendedPlayerInfo;
internal IEnumerator CoSendSecretToPlayer()
{
if (!Main.SendBetterRpc.Value) yield break;
while (extendedData._Data?.Object == null || PlayerControl.LocalPlayer == null)
{
if (GameState.IsFreePlay) yield break;
yield return null;
}
yield return new WaitForSeconds(1f);
SendSecretToPlayer();
}
internal void ResendSecretToPlayer()
{
if (!Main.SendBetterRpc.Value) return;
if (HasSendSharedSecret && extendedData.IsVerifiedBetterUser) return;
HasSendSharedSecret = false;
SendSecretToPlayer();
}
// Local client sends to client
private void SendSecretToPlayer()
{
if (extendedData._Data.Object.IsLocalPlayer()) return;
if (HasSendSharedSecret) return;
HasSendSharedSecret = true;
var writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SendSecret, SendOption.Reliable, extendedData._Data.ClientId);
writer.WriteBytes(SharedSecret.GetPublicKey());
writer.Write(SharedSecret.GetTempKey());
AmongUsClient.Instance.FinishRpcImmediately(writer);
}
// Client receives from local client
internal void HandleSecretFromSender(MessageReader reader)
{
if (extendedData._Data?.Object?.IsLocalPlayer() == true) return;
byte[] sendersPublicKey = reader.ReadBytes();
int tempKey = reader.ReadInt32();
// Logger.Log($"Received public key ({sendersPublicKey.Length} bytes) from {_Data.PlayerName}");
byte[] secret = SharedSecret.GenerateSharedSecret(sendersPublicKey);
if (secret.Length == 0)
{
// Logger.Error("Failed to generate shared secret!");
return;
}
extendedData.IsBetterUser = true;
TryHandlePendingVerificationData();
extendedData._Data?.Object?.DirtyName();
SendSecretHashToSender(tempKey, extendedData._Data.ClientId);
ResendSecretToPlayer();
}
// Client sends back to local client
private void SendSecretHashToSender(int tempKey, int senderClientId)
{
if (!Main.SendBetterRpc.Value) return;
int hash = SharedSecret.GetSharedSecretHash();
// Logger.Log($"Sending secret hash: {hash} (tempKey: {tempKey})");
var writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.CheckSecret, SendOption.Reliable, senderClientId);
writer.Write(tempKey);
writer.Write(hash);
AmongUsClient.Instance.FinishRpcImmediately(writer);
}
internal void HandleSecretHashFromPlayer(MessageReader reader)
{
int tempKey = reader.ReadInt32();
int receivedHash = reader.ReadInt32();
_pendingVerificationData = (tempKey, receivedHash);
TryHandlePendingVerificationData();
}
internal void TryHandlePendingVerificationData()
{
if (_pendingVerificationData?.tempKey == null || _pendingVerificationData?.receivedHash == null) return;
if (SharedSecret.GetSharedSecret().Length == 0) return;
var tempKey = _pendingVerificationData?.tempKey;
var receivedHash = _pendingVerificationData?.receivedHash;
// Logger.Log($"Received hash check: TempKey={tempKey} (ours={SharedSecret.GetTempKey()}), Hash={receivedHash} (ours={SharedSecret.GetSharedSecretHash()})");
if (tempKey != SharedSecret.GetTempKey())
{
// Logger.Warning($"Invalid tempKey from {_Data.PlayerName}");
return;
}
extendedData.IsBetterUser = true;
if (receivedHash == SharedSecret.GetSharedSecretHash())
{
extendedData.IsVerifiedBetterUser = true;
extendedData._Data.Object?.DirtyName();
// Logger.Log($"Verified player: {_Data.PlayerName}");
}
else
{
// Logger.Warning($"Hash mismatch from {_Data.PlayerName}");
}
_pendingVerificationData = null;
}
private (int tempKey, int receivedHash)? _pendingVerificationData = null;
private bool HasSendSharedSecret { get; set; }
internal SharedSecretExchange SharedSecret { get; set; } = new();
}
+41 -27
View File
@@ -1,33 +1,32 @@
using Il2CppInterop.Runtime;
using System.Reflection;
using UnityEngine;
namespace BetterAmongUs.Modules;
[AttributeUsage(AttributeTargets.Class)]
internal class MonoExtensionAttribute : Attribute
{
internal Type MonoType { get; }
internal Il2CppSystem.Type MonoIl2CppType { get; }
internal MonoExtensionAttribute(Type monoType)
{
MonoType = monoType;
MonoIl2CppType = Il2CppType.From(monoType);
}
}
internal interface IMonoExtension
{
MonoBehaviour? BaseMono { get; set; }
}
internal interface IMonoExtension<T> : IMonoExtension where T : MonoBehaviour
{
new T? BaseMono { get; set; }
MonoBehaviour? IMonoExtension.BaseMono
{
get => BaseMono;
set => BaseMono = value as T;
}
}
internal static class MonoExtensionManager
{
private static readonly Dictionary<Type, List<ExtensionPair>> _extensionsByBaseType = [];
internal static T? Get<T>(MonoBehaviour mono) where T : class, IMonoExtension
{
if (mono == null) return null;
if (_extensionsByBaseType.TryGetValue(mono.GetType(), out var extensions))
{
foreach (var pair in extensions)
@@ -62,40 +61,55 @@ internal static class MonoExtensionManager
}
}
internal static bool RegisterExtension(IMonoExtension extension)
internal static bool RegisterExtension(this IMonoExtension extension)
{
var monoAttribute = extension.GetType().GetCustomAttribute<MonoExtensionAttribute>();
// Try to find IMonoExtension<T> implementation
Type? genericInterface = extension.GetType()
.GetInterfaces()
.FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IMonoExtension<>));
if (monoAttribute == null || !monoAttribute.MonoType.IsAssignableTo(typeof(MonoBehaviour)))
if (genericInterface == null)
{
if (extension is MonoBehaviour mono)
UnityEngine.Object.Destroy(mono);
return false;
}
// Get T from IMonoExtension<T>
Type monoType = genericInterface.GetGenericArguments()[0];
if (!monoType.IsAssignableTo(typeof(MonoBehaviour)))
{
if (extension is MonoBehaviour mono)
{
UnityEngine.Object.Destroy(mono);
}
return false;
}
var monoBehaviour = extension as MonoBehaviour;
if (monoBehaviour == null) return false;
if (monoBehaviour == null)
return false;
var baseComponent = monoBehaviour.GetComponent(monoAttribute.MonoIl2CppType) as MonoBehaviour;
// Get the base component (including inactive ones)
var baseComponent = monoBehaviour.GetComponentInChildren(Il2CppType.From(monoType), true) as MonoBehaviour;
if (baseComponent == null)
{
UnityEngine.Object.Destroy(monoBehaviour);
return false;
}
var existingExtensions = monoBehaviour.GetComponents(Il2CppType.From(extension.GetType()));
// Check for duplicate extensions (including inactive ones)
var existingExtensions = monoBehaviour.GetComponentsInChildren(Il2CppType.From(extension.GetType()), true);
if (existingExtensions.Length > 1)
{
UnityEngine.Object.Destroy(monoBehaviour);
return false;
}
if (!_extensionsByBaseType.TryGetValue(monoAttribute.MonoType, out var extensions))
// Register the extension
if (!_extensionsByBaseType.TryGetValue(monoType, out var extensions))
{
extensions = [];
_extensionsByBaseType[monoAttribute.MonoType] = extensions;
_extensionsByBaseType[monoType] = extensions;
}
extensions.Add(new ExtensionPair { Base = baseComponent, Extension = extension });
@@ -104,7 +118,7 @@ internal static class MonoExtensionManager
return true;
}
internal static void UnregisterExtension(IMonoExtension extension)
internal static void UnregisterExtension(this IMonoExtension extension)
{
if (extension.BaseMono == null) return;
@@ -128,7 +142,7 @@ internal static class MonoExtensionManager
private struct ExtensionPair
{
public MonoBehaviour? Base;
public IMonoExtension? Extension;
internal MonoBehaviour? Base;
internal IMonoExtension? Extension;
}
}
+18 -14
View File
@@ -1,5 +1,4 @@
using BetterAmongUs.Helpers;
using BetterAmongUs.Managers;
using Hazel;
namespace BetterAmongUs.Modules;
@@ -18,7 +17,9 @@ enum CustomRPC : int
RequestRetryVersionCheck = 81,
//Better Among Us
BetterCheck = 150,
LegacyBetterCheck = 150,
SendSecret,
CheckSecret,
}
enum HandleGameDataTags : byte
@@ -48,17 +49,6 @@ internal static class RPC
}
}
internal static void RpcBetterCheck()
{
if (!Main.SendBetterRpc.Value) return;
MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.BetterCheck, SendOption.None, -1);
messageWriter.Write(true);
messageWriter.Write(Main.ModSignature.ToString());
messageWriter.Write(Main.GetVersionText().Replace(" ", ""));
AmongUsClient.Instance.FinishRpcImmediately(messageWriter);
}
internal static void RpcSetNamePrivate(PlayerControl player, string name, PlayerControl target)
{
if (!GameState.IsHost || !GameState.IsVanillaServer)
@@ -89,25 +79,29 @@ internal static class RPC
switch (callId)
{
case (byte)CustomRPC.BetterCheck:
case (byte)CustomRPC.LegacyBetterCheck:
{
var SetBetterUser = reader.ReadBoolean();
var Signature = reader.ReadString();
var Version = reader.ReadString();
var IsVerified = Signature == Main.ModSignature.ToString();
/*
if (string.IsNullOrEmpty(Signature) || string.IsNullOrEmpty(Version))
{
BetterNotificationManager.NotifyCheat(player, $"Invalid Action RPC: {Enum.GetName((CustomRPC)callId)} called with invalid info");
break;
}
*/
player.BetterData().IsBetterUser = SetBetterUser;
/*
if (IsVerified)
{
player.BetterData().IsVerifiedBetterUser = true;
}
*/
Logger.Log($"Received better user RPC from: {player.Data.PlayerName}:{player.Data.FriendCode}:{Utils.GetHashPuid(player)} - " +
$"BetterUser: {SetBetterUser} - " +
@@ -118,6 +112,16 @@ internal static class RPC
Utils.DirtyAllNames();
}
break;
case (byte)CustomRPC.SendSecret:
{
player.BetterData().HandshakeHandler.HandleSecretFromSender(reader);
}
break;
case (byte)CustomRPC.CheckSecret:
{
player.BetterData().HandshakeHandler.HandleSecretHashFromPlayer(reader);
}
break;
/*
case (byte)CustomRPC.VersionCheck or (byte)CustomRPC.RequestRetryVersionCheck:
if (player.IsHost())
+5 -1
View File
@@ -64,7 +64,11 @@ internal static class OptionsMenuBehaviourPatch
{
if (GameState.IsInGame)
{
RPC.RpcBetterCheck();
foreach (var player in Main.AllPlayerControls)
{
if (player.IsLocalPlayer()) continue;
player.BetterData().HandshakeHandler.ResendSecretToPlayer();
}
}
}
}
-7
View File
@@ -16,13 +16,6 @@ class GamePlayManager
[HarmonyPostfix]
private static void Start_Postfix(/*LobbyBehaviour __instance*/)
{
_ = new LateTask(() =>
{
if (GameState.IsInGame)
{
RPC.RpcBetterCheck();
}
}, 1.5f, "LobbyBehaviourPatch SyncAllNames");
}
// Disabled annoying music
-3
View File
@@ -38,9 +38,6 @@ internal static class OnPlayerJoinedPatch
{
var player = Utils.PlayerFromClientId(client.Id);
// Send Better Among Us Check RPC
RPC.RpcBetterCheck();
// Auto ban player on ban list
if (BetterGameSettings.UseBanPlayerList.GetBool())
{