Use different method for custom RPCs due to custom RPCs being blocked soon

This commit is contained in:
D1GQ committed 2026-02-01 02:26:34 -06:00
1 parent b739472c21
commit bc32429f0c
14 files changed
+196 -87

No files matched your search

+93 -1
View File
@@ -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
}
}
/// <summary>
/// Determines whether the next string value in the specified reader matches the provided string without advancing
/// the reader's position.
/// </summary>
/// <param name="reader">The message reader to inspect for the presence of the string. Cannot be null.</param>
/// <param name="string">The string to compare against the next value in the reader. If null or empty, the method returns <see
/// langword="true"/>.</param>
/// <returns>true if the next string value in the reader matches the specified string; otherwise, false.</returns>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="reader"/> is null.</exception>
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;
}
}
/// <summary>
/// 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
/// <param name="boolsEnumerable">The boolean values to write.</param>
internal static void WriteBooleans(this MessageWriter writer, IEnumerable<bool> boolsEnumerable)
{
bool[] bools = boolsEnumerable.ToArray();
bool[] bools = [.. boolsEnumerable];
writer.Write(bools.Length);
+8 -7
View File
@@ -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;
}
+15 -5
View File
@@ -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))))
{
@@ -40,7 +40,6 @@ internal sealed class CheckAppearHandler : RPCHandler
{
if (!GameState.IsHost)
{
LogRpcInfo($"Non-host attempted CheckAppear RPC.");
return false;
}
@@ -12,7 +12,6 @@ internal sealed class CheckColorHandler : RPCHandler
{
if (!GameState.IsHost)
{
LogRpcInfo($"Non-host attempted CheckColor RPC");
return false;
}
@@ -60,7 +60,6 @@ internal sealed class CheckMurderHandler : RPCHandler
{
if (!GameState.IsHost)
{
LogRpcInfo($"Non-host attempted CheckMurder RPC");
return false;
}
@@ -15,7 +15,6 @@ internal sealed class CheckNameHandler : RPCHandler
{
if (!GameState.IsHost)
{
LogRpcInfo($"Non-host attempted CheckName RPC");
return false;
}
@@ -49,7 +49,6 @@ internal sealed class CheckProtectHandler : RPCHandler
{
if (!GameState.IsHost)
{
LogRpcInfo($"Non-host attempted CheckProtect RPC");
return false;
}
@@ -52,7 +52,6 @@ internal sealed class CheckShapeshiftHandler : RPCHandler
{
if (!GameState.IsHost)
{
LogRpcInfo($"Non-host attempted CheckShapeshift RPC");
return false;
}
@@ -12,7 +12,6 @@ internal sealed class CheckSporeHandler : RPCHandler
{
if (!GameState.IsHost)
{
LogRpcInfo($"Non-host attempted CheckSpore RPC");
return false;
}
@@ -40,7 +40,6 @@ internal sealed class CheckVanishHandler : RPCHandler
{
if (!GameState.IsHost)
{
LogRpcInfo($"Non-host attempted CheckVanish RPC");
return false;
}
@@ -12,7 +12,6 @@ internal sealed class CheckZiplineHandler : RPCHandler
{
if (!GameState.IsHost)
{
LogRpcInfo($"Non-host attempted CheckZipline RPC");
return false;
}
+11 -8
View File
@@ -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);
}
/// <summary>
@@ -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);
}
/// <summary>
+69 -57
View File
@@ -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;
/// <summary>
/// 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.
/// </summary>
internal static class RPC
{
/// <summary>
/// Processes custom RPC messages received from other players.
/// The flag string used to identify custom RPC messages packed within vanilla RPC calls.
/// </summary>
/// <param name="player">The player who sent the RPC.</param>
/// <param name="callId">The ID of the RPC call.</param>
/// <param name="oldReader">The message reader containing the RPC data.</param>
/// <remarks>
/// Handles both defined custom RPCs and protects against unknown RPCs in modded lobbies.
/// </remarks>
internal static void HandleCustomRPC(PlayerControl player, byte callId, MessageReader oldReader)
internal const string CUSTOM_RPC_FLAG = "bau:rpc";
/// <summary>
/// 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.
/// </summary>
/// <param name="customRPC">The custom RPC type to send.</param>
/// <param name="action">A delegate that writes the custom RPC payload to the message writer.</param>
/// <param name="targetClientId">The specific client ID to target, or -1 to broadcast to all clients.</param>
internal static void SendCustomRpc(CustomRPC customRPC, Action<MessageWriter> 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);
}
/// <summary>
/// Handles incoming custom RPC messages by extracting and processing them
/// from the packed SetNamePlateStr RPC call.
/// </summary>
/// <param name="player">The player who sent the RPC message.</param>
/// <param name="oldReader">The message reader containing the RPC data.</param>
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();
}
/// <summary>
/// Determines whether a MessageReader contains a packed custom RPC message.
/// </summary>
/// <param name="reader">The MessageReader to check for custom RPC content.</param>
/// <returns>
/// <c>true</c> if the reader contains a custom RPC flag and custom RPC data;
/// otherwise, <c>false</c>.
/// </returns>
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 = $"<color=#278720>{Icon}</color><color=#0ed400><b>{Translator.GetString("BAU")}</b></color><color=#278720>{Icon}</color>";
Utils.DisconnectSelf(string.Format(Translator.GetString("ModdedLobbyMsg"), BAU));
}
return true;
}
}
catch { }
catch
{
return false;
}
}
return false;
}
}