diff --git a/src/Helpers/InnerNetClientHelper.cs b/src/Helpers/InnerNetClientHelper.cs
index 305235a..c41f294 100644
--- a/src/Helpers/InnerNetClientHelper.cs
+++ b/src/Helpers/InnerNetClientHelper.cs
@@ -2,6 +2,7 @@
using Hazel;
using InnerNet;
using System.Globalization;
+using System.Text;
namespace BetterAmongUs.Helpers;
@@ -38,6 +39,97 @@ internal static class InnerNetClientHelper
}
}
+ ///
+ /// Determines whether the next string value in the specified reader matches the provided string without advancing
+ /// the reader's position.
+ ///
+ /// The message reader to inspect for the presence of the string. Cannot be null.
+ /// The string to compare against the next value in the reader. If null or empty, the method returns .
+ /// true if the next string value in the reader matches the specified string; otherwise, false.
+ /// Thrown if is null.
+ internal static bool HasString(this MessageReader reader, string @string)
+ {
+ if (reader == null) throw new ArgumentNullException(nameof(reader));
+ if (string.IsNullOrEmpty(@string)) return true;
+
+ // Save current position
+ int originalPosition = reader.Position;
+ int originalReadHead = reader.readHead;
+
+ try
+ {
+ // Check if we have enough bytes for at least the packed int length
+ if (reader.BytesRemaining < 1)
+ return false;
+
+ // Try to read the packed int length without modifying internal state
+ int lengthOffset = reader.Position;
+ int lengthReadHead = reader.readHead;
+
+ // Read packed int manually without affecting the reader's state
+ bool readMore = true;
+ int shift = 0;
+ uint stringLength = 0;
+
+ while (readMore)
+ {
+ if (lengthOffset >= reader.Length)
+ return false; // Not enough data
+
+ byte b = reader.Buffer[reader.Offset + lengthOffset];
+ lengthOffset++;
+
+ if (b >= 0x80)
+ {
+ readMore = true;
+ b ^= 0x80;
+ }
+ else
+ {
+ readMore = false;
+ }
+
+ stringLength |= (uint)(b << shift);
+ shift += 7;
+
+ // Sanity check: string length shouldn't be ridiculously large
+ if (stringLength > reader.Buffer.Length || stringLength > 1024 * 1024) // 1MB max
+ return false;
+ }
+
+ // Check if we have enough bytes for the string content
+ if (reader.Length - lengthOffset < (int)stringLength)
+ return false;
+
+ // Compare the string
+ if (stringLength != @string.Length)
+ return false;
+
+ byte[] stringBytes = Encoding.UTF8.GetBytes(@string);
+
+ // Compare byte by byte
+ for (int i = 0; i < stringLength; i++)
+ {
+ if (reader.Buffer[reader.Offset + lengthOffset + i] != stringBytes[i])
+ return false;
+ }
+
+ return true;
+ }
+ catch
+ {
+ // If anything goes wrong, assume the string is not present
+ return false;
+ }
+ finally
+ {
+ // Restore original position
+ reader._position = originalPosition;
+ reader.readHead = originalReadHead;
+ }
+ }
+
///
/// Writes an array of boolean values to a MessageWriter in a packed format to save space.
/// Each byte stores up to 8 boolean values as bits.
@@ -46,7 +138,7 @@ internal static class InnerNetClientHelper
/// The boolean values to write.
internal static void WriteBooleans(this MessageWriter writer, IEnumerable boolsEnumerable)
{
- bool[] bools = boolsEnumerable.ToArray();
+ bool[] bools = [.. boolsEnumerable];
writer.Write(bools.Length);
diff --git a/src/Managers/NetworkManager.cs b/src/Managers/NetworkManager.cs
index 49013c0..3e8d6f2 100644
--- a/src/Managers/NetworkManager.cs
+++ b/src/Managers/NetworkManager.cs
@@ -271,17 +271,18 @@ internal static class NetworkManager
break;
}
+ if (innerNetObject is PlayerControl player && player != null)
+ {
+ if (rpcCall == (byte)RpcCalls.SetNamePlateStr)
+ {
+ RPC.HandleCustomRPC(player, reader);
+ }
+ }
+
if (Enum.IsDefined(typeof(RpcCalls), rpcCall))
{
innerNetObject?.HandleRpc(rpcCall, reader);
}
- else
- {
- if (innerNetObject is PlayerControl player && player != null)
- {
- RPC.HandleCustomRPC(player, rpcCall, reader);
- }
- }
break;
}
diff --git a/src/Modules/AntiCheat/BetterAntiCheat.cs b/src/Modules/AntiCheat/BetterAntiCheat.cs
index 6ffc221..9b8bac2 100644
--- a/src/Modules/AntiCheat/BetterAntiCheat.cs
+++ b/src/Modules/AntiCheat/BetterAntiCheat.cs
@@ -5,6 +5,7 @@ using BetterAmongUs.Helpers;
using BetterAmongUs.Managers;
using BetterAmongUs.Modules.Support;
using BetterAmongUs.Mono;
+using BetterAmongUs.Network;
using BetterAmongUs.Patches.Gameplay.UI.Settings;
using HarmonyLib;
using Hazel;
@@ -188,6 +189,15 @@ internal static class BetterAntiCheat
return false;
}
+ if (callId == (byte)RpcCalls.SetNamePlateStr)
+ {
+ if (RPC.IsPackedCustomRpc(reader))
+ {
+ reader.Recycle();
+ return false;
+ }
+ }
+
if (!player.IsHost())
{
if (callId is (byte)RpcCalls.SetTasks
@@ -207,11 +217,11 @@ internal static class BetterAntiCheat
if (GameState.IsInGamePlay)
{
if (callId is (byte)RpcCalls.SetColor
- or (byte)RpcCalls.SetHat_Deprecated
- or (byte)RpcCalls.SetSkin_Deprecated
- or (byte)RpcCalls.SetVisor_Deprecated
- or (byte)RpcCalls.SetPet_Deprecated
- or (byte)RpcCalls.SetNamePlate_Deprecated)
+ or (byte)RpcCalls.SetHatStr
+ or (byte)RpcCalls.SetSkinStr
+ or (byte)RpcCalls.SetVisorStr
+ or (byte)RpcCalls.SetPetStr
+ or (byte)RpcCalls.SetNamePlateStr)
{
if (BetterNotificationManager.NotifyCheat(player, string.Format(Translator.GetString("AntiCheat.InvalidSetRPC"), Enum.GetName((RpcCalls)callId))))
{
diff --git a/src/Modules/AntiCheat/RPCHandlers/CheckAppearHandler.cs b/src/Modules/AntiCheat/RPCHandlers/CheckAppearHandler.cs
index bea7e19..b60a080 100644
--- a/src/Modules/AntiCheat/RPCHandlers/CheckAppearHandler.cs
+++ b/src/Modules/AntiCheat/RPCHandlers/CheckAppearHandler.cs
@@ -40,7 +40,6 @@ internal sealed class CheckAppearHandler : RPCHandler
{
if (!GameState.IsHost)
{
- LogRpcInfo($"Non-host attempted CheckAppear RPC.");
return false;
}
diff --git a/src/Modules/AntiCheat/RPCHandlers/CheckColorHandler.cs b/src/Modules/AntiCheat/RPCHandlers/CheckColorHandler.cs
index 5331804..339d67f 100644
--- a/src/Modules/AntiCheat/RPCHandlers/CheckColorHandler.cs
+++ b/src/Modules/AntiCheat/RPCHandlers/CheckColorHandler.cs
@@ -12,7 +12,6 @@ internal sealed class CheckColorHandler : RPCHandler
{
if (!GameState.IsHost)
{
- LogRpcInfo($"Non-host attempted CheckColor RPC");
return false;
}
diff --git a/src/Modules/AntiCheat/RPCHandlers/CheckMurderHandler.cs b/src/Modules/AntiCheat/RPCHandlers/CheckMurderHandler.cs
index cd086fb..7ce5c15 100644
--- a/src/Modules/AntiCheat/RPCHandlers/CheckMurderHandler.cs
+++ b/src/Modules/AntiCheat/RPCHandlers/CheckMurderHandler.cs
@@ -60,7 +60,6 @@ internal sealed class CheckMurderHandler : RPCHandler
{
if (!GameState.IsHost)
{
- LogRpcInfo($"Non-host attempted CheckMurder RPC");
return false;
}
diff --git a/src/Modules/AntiCheat/RPCHandlers/CheckNameHandler.cs b/src/Modules/AntiCheat/RPCHandlers/CheckNameHandler.cs
index 129d8cb..a4e4417 100644
--- a/src/Modules/AntiCheat/RPCHandlers/CheckNameHandler.cs
+++ b/src/Modules/AntiCheat/RPCHandlers/CheckNameHandler.cs
@@ -15,7 +15,6 @@ internal sealed class CheckNameHandler : RPCHandler
{
if (!GameState.IsHost)
{
- LogRpcInfo($"Non-host attempted CheckName RPC");
return false;
}
diff --git a/src/Modules/AntiCheat/RPCHandlers/CheckProtectHandler.cs b/src/Modules/AntiCheat/RPCHandlers/CheckProtectHandler.cs
index a69ab05..85facfe 100644
--- a/src/Modules/AntiCheat/RPCHandlers/CheckProtectHandler.cs
+++ b/src/Modules/AntiCheat/RPCHandlers/CheckProtectHandler.cs
@@ -49,7 +49,6 @@ internal sealed class CheckProtectHandler : RPCHandler
{
if (!GameState.IsHost)
{
- LogRpcInfo($"Non-host attempted CheckProtect RPC");
return false;
}
diff --git a/src/Modules/AntiCheat/RPCHandlers/CheckShapeshiftHandler.cs b/src/Modules/AntiCheat/RPCHandlers/CheckShapeshiftHandler.cs
index 38227c7..7244e3d 100644
--- a/src/Modules/AntiCheat/RPCHandlers/CheckShapeshiftHandler.cs
+++ b/src/Modules/AntiCheat/RPCHandlers/CheckShapeshiftHandler.cs
@@ -52,7 +52,6 @@ internal sealed class CheckShapeshiftHandler : RPCHandler
{
if (!GameState.IsHost)
{
- LogRpcInfo($"Non-host attempted CheckShapeshift RPC");
return false;
}
diff --git a/src/Modules/AntiCheat/RPCHandlers/CheckSporeHandler.cs b/src/Modules/AntiCheat/RPCHandlers/CheckSporeHandler.cs
index 5556466..585ae73 100644
--- a/src/Modules/AntiCheat/RPCHandlers/CheckSporeHandler.cs
+++ b/src/Modules/AntiCheat/RPCHandlers/CheckSporeHandler.cs
@@ -12,7 +12,6 @@ internal sealed class CheckSporeHandler : RPCHandler
{
if (!GameState.IsHost)
{
- LogRpcInfo($"Non-host attempted CheckSpore RPC");
return false;
}
diff --git a/src/Modules/AntiCheat/RPCHandlers/CheckVanishHandler.cs b/src/Modules/AntiCheat/RPCHandlers/CheckVanishHandler.cs
index 960b6cf..7878f70 100644
--- a/src/Modules/AntiCheat/RPCHandlers/CheckVanishHandler.cs
+++ b/src/Modules/AntiCheat/RPCHandlers/CheckVanishHandler.cs
@@ -40,7 +40,6 @@ internal sealed class CheckVanishHandler : RPCHandler
{
if (!GameState.IsHost)
{
- LogRpcInfo($"Non-host attempted CheckVanish RPC");
return false;
}
diff --git a/src/Modules/AntiCheat/RPCHandlers/CheckZiplineHandler.cs b/src/Modules/AntiCheat/RPCHandlers/CheckZiplineHandler.cs
index e73eb04..569b6b7 100644
--- a/src/Modules/AntiCheat/RPCHandlers/CheckZiplineHandler.cs
+++ b/src/Modules/AntiCheat/RPCHandlers/CheckZiplineHandler.cs
@@ -12,7 +12,6 @@ internal sealed class CheckZiplineHandler : RPCHandler
{
if (!GameState.IsHost)
{
- LogRpcInfo($"Non-host attempted CheckZipline RPC");
return false;
}
diff --git a/src/Modules/HandshakeHandler.cs b/src/Modules/HandshakeHandler.cs
index 70f55a2..361cebd 100644
--- a/src/Modules/HandshakeHandler.cs
+++ b/src/Modules/HandshakeHandler.cs
@@ -3,6 +3,7 @@ using BetterAmongUs.Data;
using BetterAmongUs.Enums;
using BetterAmongUs.Helpers;
using BetterAmongUs.Mono;
+using BetterAmongUs.Network;
using Hazel;
using Il2CppInterop.Runtime.Attributes;
using System.Collections;
@@ -71,10 +72,11 @@ internal sealed class HandshakeHandler
if (HasSendSharedSecret) return;
HasSendSharedSecret = true;
- var writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SendSecretToPlayer, SendOption.Reliable, extendedData._Data.ClientId);
- writer.WriteBytes(SharedSecret.GetPublicKey());
- writer.Write(SharedSecret.GetTempKey());
- AmongUsClient.Instance.FinishRpcImmediately(writer);
+ RPC.SendCustomRpc(CustomRPC.SendSecretToPlayer, writer =>
+ {
+ writer.WriteBytes(SharedSecret.GetPublicKey());
+ writer.Write(SharedSecret.GetTempKey());
+ }, extendedData._Data.ClientId);
}
///
@@ -116,10 +118,11 @@ internal sealed class HandshakeHandler
int hash = SharedSecret.GetSharedSecretHash();
// Logger.Log($"Sending secret hash: {hash} (tempKey: {tempKey})");
- var writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.CheckSecretHashFromPlayer, SendOption.Reliable, senderClientId);
- writer.Write(tempKey);
- writer.Write(hash);
- AmongUsClient.Instance.FinishRpcImmediately(writer);
+ RPC.SendCustomRpc(CustomRPC.CheckSecretHashFromPlayer, writer =>
+ {
+ writer.Write(tempKey);
+ writer.Write(hash);
+ }, senderClientId);
}
///
diff --git a/src/Network/RPC.cs b/src/Network/RPC.cs
index 08d891b..8ba1648 100644
--- a/src/Network/RPC.cs
+++ b/src/Network/RPC.cs
@@ -1,6 +1,6 @@
-using BetterAmongUs.Enums;
+using AmongUs.Data;
+using BetterAmongUs.Enums;
using BetterAmongUs.Helpers;
-using BetterAmongUs.Modules;
using BetterAmongUs.Mono;
using Hazel;
@@ -8,86 +8,98 @@ namespace BetterAmongUs.Network;
///
/// Handles custom RPC (Remote Procedure Call) messages for BetterAmongUs.
+/// This class provides methods for sending and receiving custom RPC messages
+/// packed within vanilla RPC calls to maintain compatibility with vanilla servers.
///
internal static class RPC
{
///
- /// Processes custom RPC messages received from other players.
+ /// The flag string used to identify custom RPC messages packed within vanilla RPC calls.
///
- /// The player who sent the RPC.
- /// The ID of the RPC call.
- /// The message reader containing the RPC data.
- ///
- /// Handles both defined custom RPCs and protects against unknown RPCs in modded lobbies.
- ///
- internal static void HandleCustomRPC(PlayerControl player, byte callId, MessageReader oldReader)
+ internal const string CUSTOM_RPC_FLAG = "bau:rpc";
+
+ ///
+ /// Sends a custom RPC message packed within a vanilla SetNamePlateStr RPC call.
+ /// This method is used to maintain compatibility with vanilla Among Us servers
+ /// while allowing custom RPC communication.
+ ///
+ /// The custom RPC type to send.
+ /// A delegate that writes the custom RPC payload to the message writer.
+ /// The specific client ID to target, or -1 to broadcast to all clients.
+ internal static void SendCustomRpc(CustomRPC customRPC, Action action, int targetClientId = -1)
{
- if (player == null || player.IsLocalPlayer() || player.Data == null || Enum.IsDefined(typeof(RpcCalls), callId)) return;
+ var writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)RpcCalls.SetNamePlateStr, SendOption.Reliable, targetClientId);
- if (Enum.IsDefined(typeof(CustomRPC), (int)unchecked(callId)))
+ writer.Write(DataManager.Player.Customization.NamePlate);
+ writer.Write(PlayerControl.LocalPlayer.GetNextRpcSequenceId(RpcCalls.SetNamePlateStr));
+
+ writer.Write(CUSTOM_RPC_FLAG); // Flag to check if its a rpc packed into SetNamePlateStr
+ writer.Write((byte)customRPC);
+ action(writer);
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
+ }
+
+ ///
+ /// Handles incoming custom RPC messages by extracting and processing them
+ /// from the packed SetNamePlateStr RPC call.
+ ///
+ /// The player who sent the RPC message.
+ /// The message reader containing the RPC data.
+ internal static void HandleCustomRPC(PlayerControl player, MessageReader oldReader)
+ {
+ if (player == null || player.IsLocalPlayer() || player.Data == null) return;
+
+ MessageReader reader = MessageReader.Get(oldReader);
+
+ _ = reader.ReadString();
+ _ = reader.ReadByte();
+
+ if (IsPackedCustomRpc(reader))
{
- MessageReader reader = MessageReader.Get(oldReader);
-
- switch (callId)
+ CustomRPC customRPC = (CustomRPC)reader.ReadByte();
+ switch (customRPC)
{
- /*
- 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} - " +
- $"Version: {Version} - " +
- $"Verified: {IsVerified} - " +
- $"Signature: {Signature}");
-
- Utils.DirtyAllNames();
- }
- break;
- */
- case (byte)CustomRPC.SendSecretToPlayer:
+ case CustomRPC.SendSecretToPlayer:
{
player.BetterData().HandshakeHandler.HandleSecretFromSender(reader);
}
break;
- case (byte)CustomRPC.CheckSecretHashFromPlayer:
+ case CustomRPC.CheckSecretHashFromPlayer:
{
player.BetterData().HandshakeHandler.HandleSecretHashFromPlayer(reader);
}
break;
}
}
- else if (!Enum.IsDefined(typeof(CustomRPC), (int)unchecked(callId)))
+
+ reader.Recycle();
+ }
+
+ ///
+ /// Determines whether a MessageReader contains a packed custom RPC message.
+ ///
+ /// The MessageReader to check for custom RPC content.
+ ///
+ /// true if the reader contains a custom RPC flag and custom RPC data;
+ /// otherwise, false.
+ ///
+ internal static bool IsPackedCustomRpc(MessageReader reader)
+ {
+ if (reader.BytesRemaining > 0)
{
try
{
- if (!GameState.IsHost)
+ if (reader.ReadString() == CUSTOM_RPC_FLAG)
{
- if (player.IsHost())
- {
- var Icon = Translator.GetString("BAUMark");
- var BAU = $"{Icon}{Translator.GetString("BAU")}{Icon}";
- Utils.DisconnectSelf(string.Format(Translator.GetString("ModdedLobbyMsg"), BAU));
- }
+ return true;
}
}
- catch { }
+ catch
+ {
+ return false;
+ }
}
+
+ return false;
}
}
\ No newline at end of file