diff --git a/examples/moddedsupport/SendBAUSupportFlagsExamplePatch.cs b/examples/moddedsupport/SendBAUSupportFlagsExamplePatch.cs
new file mode 100644
index 0000000..8f666dc
--- /dev/null
+++ b/examples/moddedsupport/SendBAUSupportFlagsExamplePatch.cs
@@ -0,0 +1,64 @@
+using HarmonyLib;
+using Hazel;
+
+namespace BetterAmongUs.Examples;
+
+///
+/// Example patch that demonstrates how to send BetterAmongUs modded support flags to BAU users.
+/// This patch hooks into the player info serialization process to transmit custom flags that control various BAU features.
+///
+[HarmonyPatch]
+internal static class SendBAUSupportFlagsExamplePatch
+{
+ ///
+ /// Postfix patch that runs after NetworkedPlayerInfo.Serialize.
+ /// When the host is serializing the player data, this method writes
+ /// BAU support flags into the message stream to communicate mod capabilities to the BAU client.
+ ///
+ /// The NetworkedPlayerInfo instance being serialized.
+ /// The MessageWriter used for serialization.
+ ///
+ /// This example sends the "command.force.bau.prefix" flag which forces all BAU commands
+ /// to use the "bau:" prefix. The flag is hashed using GetFlagHash for efficient transmission.
+ ///
+ /// Important: This functionality only works on protocol version +25 (modded protocol)!
+ ///
+ ///
+ [HarmonyPatch(typeof(NetworkedPlayerInfo), nameof(NetworkedPlayerInfo.Serialize))]
+ [HarmonyPriority(Priority.Last)]
+ [HarmonyPostfix]
+ internal static void Serialize_Postfix(NetworkedPlayerInfo __instance, MessageWriter writer)
+ {
+ if (!AmongUsClient.Instance.AmHost || __instance.OwnerId != -2)
+ return;
+
+ writer.Write("bau:flags"); // Mark message as BAU flags
+ writer.WritePacked(1); // Amount of flags we are sending
+ writer.WritePacked(GetFlagHash("command.force.bau.prefix")); // Send flag hash, most use GetFlagHash with the exact logic in this example!
+ }
+
+ ///
+ /// Generates a deterministic integer hash from a string for use as a flag identifier.
+ ///
+ /// The input string to hash
+ ///
+ /// A 32-bit integer hash value. Returns 0 for null or empty strings.
+ ///
+ internal static int GetFlagHash(string input)
+ {
+ if (string.IsNullOrEmpty(input)) return 0;
+
+ int hash = 17;
+ hash = hash * 31 + input.Length;
+
+ for (int i = 0; i < input.Length; i++)
+ {
+ char c = input[i];
+ hash = hash * 31 + c;
+ hash = hash * 31 + i;
+ }
+
+ hash = hash * 31 + input.Length;
+ return hash;
+ }
+}
\ No newline at end of file
diff --git a/src/BAUPlugin.cs b/src/BAUPlugin.cs
index 77dfd50..467e5c0 100644
--- a/src/BAUPlugin.cs
+++ b/src/BAUPlugin.cs
@@ -18,6 +18,7 @@ using HarmonyLib;
using Il2CppInterop.Runtime.Injection;
using System.Reflection;
using UnityEngine;
+using UnityEngine.SceneManagement;
namespace BetterAmongUs;
@@ -160,6 +161,7 @@ internal class BAUPlugin : BasePlugin
BAUModdedSupportEvents.OnBAUOptionsLoadedEvent.InvokeAll([.. OptionItem.AllOptions.Cast()]);
InstanceAttribute.RegisterAll();
OutfitData.Initialize();
+ SceneManager.add_sceneLoaded((Action)OnSceneLoaded);
if (File.Exists(BetterDataManager.Files.logFilePath))
File.WriteAllText(BetterDataManager.Files.previousLogFilePath, File.ReadAllText(BetterDataManager.Files.logFilePath));
@@ -171,6 +173,17 @@ internal class BAUPlugin : BasePlugin
Logger_.Log($"BetterAmongUs {BetterAmongUsVersion}-{ModInfo.BuildDate} - [{AppVersion} --> {SupportedVersions}] {Utils.GetPlatformName(PlatformData.Platform)}");
}
+ private static void OnSceneLoaded(Scene scene, LoadSceneMode _)
+ {
+ if (AmongUsClient.Instance != null)
+ {
+ if (scene.name == AmongUsClient.Instance.MainMenuScene)
+ {
+ BAUModdedSupportFlags.ClearTempFlags();
+ }
+ }
+ }
+
///
/// Sets up the console window for logging.
///
diff --git a/src/ModInfo.cs b/src/ModInfo.cs
index 77bdff3..645d26f 100644
--- a/src/ModInfo.cs
+++ b/src/ModInfo.cs
@@ -108,5 +108,7 @@ internal static class ModInfo
internal static class Constants
{
internal const int MAX_CHAT_TEXT = 120;
+ internal const string BAU_CUSTOM_RPC_FLAG = "bau:rpc";
+ internal const string BAU_MODDED_PROTOCOL_FLAG = "bau:flags";
}
}
\ No newline at end of file
diff --git a/src/Modules/HandshakeHandler.cs b/src/Modules/HandshakeHandler.cs
index 277cb51..ee6b21d 100644
--- a/src/Modules/HandshakeHandler.cs
+++ b/src/Modules/HandshakeHandler.cs
@@ -40,7 +40,7 @@ internal sealed class HandshakeHandler
{
if (!BAUConfigs.SendBetterRpc.Value) yield break;
- while (_extendedData._Data == null || _extendedData._Data.Object == null || PlayerControl.LocalPlayer == null)
+ while (_extendedData.BaseMono == null || _extendedData.BaseMono.Object == null || PlayerControl.LocalPlayer == null)
{
if (GameState.IsFreePlay) yield break;
yield return null;
@@ -71,7 +71,7 @@ internal sealed class HandshakeHandler
// Local client sends to client
private void SendSecretToPlayer()
{
- if (_extendedData._Data.Object.IsLocalPlayer())
+ if (_extendedData.BaseMono.Object.IsLocalPlayer())
return;
if (HasSendSharedSecret)
@@ -84,7 +84,7 @@ internal sealed class HandshakeHandler
writer.Write(SharedSecret.CryptoAvailable);
writer.WriteBytes(SharedSecret.GetPublicKey());
writer.Write(SharedSecret.GetTempKey());
- }, _extendedData._Data.ClientId);
+ }, _extendedData.BaseMono.ClientId);
}
///
@@ -94,7 +94,7 @@ internal sealed class HandshakeHandler
// Client receives from local client
internal void HandleSecretFromSender(MessageReader reader)
{
- if (_extendedData._Data?.Object?.IsLocalPlayer() == true)
+ if (_extendedData.BaseMono?.Object?.IsLocalPlayer() == true)
return;
bool senderSupportsCrypto = reader.ReadBoolean();
@@ -116,7 +116,7 @@ internal sealed class HandshakeHandler
_extendedData.IsBetterUser = true;
TryHandlePendingVerificationData();
- SendSecretHashToSender(tempKey, _extendedData._Data.ClientId);
+ SendSecretHashToSender(tempKey, _extendedData.BaseMono.ClientId);
ResendSecretToPlayer();
}
diff --git a/src/Modules/Support/BAUModdedSupportFlags.cs b/src/Modules/Support/BAUModdedSupportFlags.cs
index db3939b..b0ed772 100644
--- a/src/Modules/Support/BAUModdedSupportFlags.cs
+++ b/src/Modules/Support/BAUModdedSupportFlags.cs
@@ -221,6 +221,7 @@ public static class BAUModdedSupportFlags
public static string Disable_CustomColorBlindText = "gameplay.disable.customcolorblindtext";
private static readonly HashSet _flags = [];
+ private static readonly HashSet _tempFlags = [];
private static bool _initialized = false;
private static readonly BAUSupportVar BAUSupportFlags = new("bau:flags");
@@ -262,6 +263,23 @@ public static class BAUModdedSupportFlags
_flags.Add(flag);
}
+ ///
+ /// Manually adds a temporary flag to the internal temporary flag collection.
+ ///
+ /// The flag hash to add to the collection.
+ internal static void AddTempFlag(int flag)
+ {
+ _tempFlags.Add(flag);
+ }
+
+ ///
+ /// CLears all temporary flags.
+ ///
+ internal static void ClearTempFlags()
+ {
+ _tempFlags.Clear();
+ }
+
///
/// Checks if a specific flag has been declared by any loaded mod.
///
@@ -269,6 +287,31 @@ public static class BAUModdedSupportFlags
/// true if the flag is present; otherwise, false .
public static bool HasFlag(string flag)
{
- return _flags.Contains(flag);
+ return _flags.Contains(flag) || _tempFlags.Contains(GetFlagHash(flag));
+ }
+
+ ///
+ /// Generates a integer hash from a string for use as a flag identifier.
+ ///
+ /// The input string to hash. Can be null or empty.
+ ///
+ /// A integer hash value. Returns 0 for null or empty strings.
+ ///
+ internal static int GetFlagHash(string input)
+ {
+ if (string.IsNullOrEmpty(input)) return 0;
+
+ int hash = 17;
+ hash = hash * 31 + input.Length;
+
+ for (int i = 0; i < input.Length; i++)
+ {
+ char c = input[i];
+ hash = hash * 31 + c;
+ hash = hash * 31 + i;
+ }
+
+ hash = hash * 31 + input.Length;
+ return hash;
}
}
\ No newline at end of file
diff --git a/src/Mono/Extended/ExtendedPlayerControl.cs b/src/Mono/Extended/ExtendedPlayerControl.cs
index a293b0c..a4d6fb1 100644
--- a/src/Mono/Extended/ExtendedPlayerControl.cs
+++ b/src/Mono/Extended/ExtendedPlayerControl.cs
@@ -17,18 +17,13 @@ internal sealed class ExtendedPlayerControl : MonoBehaviour, IMonoExtension
public PlayerControl? BaseMono { get; set; }
- ///
- /// Gets the PlayerControl instance.
- ///
- internal PlayerControl? _Player => BaseMono;
-
private void Awake()
{
if (!this.RegisterExtension())
return;
this.StartCoroutine(CoAddBetterData());
- _Player.gameObject.AddComponent().Init(_Player);
+ BaseMono.gameObject.AddComponent().Init(BaseMono);
}
///
@@ -38,12 +33,12 @@ internal sealed class ExtendedPlayerControl : MonoBehaviour, IMonoExtension
diff --git a/src/Mono/Extended/ExtendedPlayerInfo.cs b/src/Mono/Extended/ExtendedPlayerInfo.cs
index f7a09e5..9ba567d 100644
--- a/src/Mono/Extended/ExtendedPlayerInfo.cs
+++ b/src/Mono/Extended/ExtendedPlayerInfo.cs
@@ -2,6 +2,9 @@
using BetterAmongUs.Helpers;
using BetterAmongUs.Managers;
using BetterAmongUs.Modules;
+using BetterAmongUs.Modules.Support;
+using HarmonyLib;
+using Hazel;
using Il2CppInterop.Runtime.Attributes;
using InnerNet;
using UnityEngine;
@@ -21,15 +24,13 @@ internal sealed class ExtendedPlayerInfo : MonoBehaviour, IMonoExtension BaseMono;
-
private bool hasSet = false;
///
@@ -75,17 +76,17 @@ internal sealed class ExtendedPlayerInfo : MonoBehaviour, IMonoExtension 0)
{
- bool flag = _Data.IsCheater();
+ bool flag = BaseMono.IsCheater();
if (AntiCheatInfo.RPCSentPS >= ExtendedAntiCheatInfo.MAX_RPC_SENT && !flag)
{
BetterNotificationManager.NotifyCheat(
- _Data.Object,
+ BaseMono.Object,
Translator.GetString("AntiCheat.Reason.RPCSentPS"),
Translator.GetString("AntiCheat.UnauthorizedAction")
);
- Logger_.LogCheat($"{_Data.Object.ExtendedData().RealName} {AntiCheatInfo.RPCSentPS} Sent.");
+ Logger_.LogCheat($"{BaseMono.Object.ExtendedData().RealName} {AntiCheatInfo.RPCSentPS} Sent.");
}
timeAccumulator += time;
@@ -98,6 +99,28 @@ internal sealed class ExtendedPlayerInfo : MonoBehaviour, IMonoExtension 0)
+ {
+ try
+ {
+ if (reader.ReadString() == ModInfo.Constants.BAU_MODDED_PROTOCOL_FLAG)
+ {
+ int flagCount = reader.ReadPackedInt32();
+ for (int i = 0; i < flagCount; i++)
+ {
+ BAUModdedSupportFlags.AddTempFlag(reader.ReadPackedInt32());
+ }
+ }
+ }
+ catch { }
+ }
+ }
+
///
/// Gets the handshake handler for this player.
///
@@ -113,7 +136,7 @@ internal sealed class ExtendedPlayerInfo : MonoBehaviour, IMonoExtension
/// Gets the player's real name.
///
- internal string RealName => _Data?.PlayerName ?? "???";
+ internal string RealName => BaseMono?.PlayerName ?? "???";
///
/// Gets or sets the last name set for this player.
@@ -245,6 +268,18 @@ internal sealed class ExtendedRoleInfo
///
internal static class PlayerControlDataExtension
{
+ [HarmonyPatch(typeof(NetworkedPlayerInfo))]
+ class NetworkedPlayerInfoPatch
+ {
+ [HarmonyPatch(nameof(NetworkedPlayerInfo.Deserialize))]
+ [HarmonyPriority(Priority.Last)]
+ [HarmonyPostfix]
+ internal static void Deserialize_Postfix(NetworkedPlayerInfo __instance, MessageReader reader)
+ {
+ __instance.ExtendedData()?.Deserialize(reader);
+ }
+ }
+
///
/// Gets extended player data from a PlayerControl.
///
diff --git a/src/Network/RPC.cs b/src/Network/RPC.cs
index 7031652..c82d306 100644
--- a/src/Network/RPC.cs
+++ b/src/Network/RPC.cs
@@ -15,10 +15,6 @@ internal static class RPC
///
internal const RpcCalls CUSTOM_RPC_CALL = RpcCalls.CancelPet;
- ///
- /// The flag string used to identify custom RPC messages packed within vanilla RPC calls.
- ///
- internal const string CUSTOM_RPC_FLAG = "bau:rpc";
///
/// Sends a custom RPC message packed within a vanilla SetNamePlateStr RPC call.
@@ -32,7 +28,7 @@ internal static class RPC
{
AmongUsClient.Instance.SendRpcImmediately(PlayerControl.LocalPlayer.NetId, CUSTOM_RPC_CALL, SendOption.Reliable, writer =>
{
- writer.Write(CUSTOM_RPC_FLAG); // Flag to check if its a rpc packed into SetNamePlateStr
+ writer.Write(ModInfo.Constants.BAU_CUSTOM_RPC_FLAG); // Flag to check if its a rpc packed into SetNamePlateStr
writer.Write((byte)customRPC);
action(writer);
}, targetClientId);
@@ -120,7 +116,7 @@ internal static class RPC
{
try
{
- if (reader.ReadString() == CUSTOM_RPC_FLAG)
+ if (reader.ReadString() == ModInfo.Constants.BAU_CUSTOM_RPC_FLAG)
{
return true;
}
diff --git a/src/Patches/Client/ClientPatch.cs b/src/Patches/Client/ClientPatch.cs
index 858d976..6e1c4cd 100644
--- a/src/Patches/Client/ClientPatch.cs
+++ b/src/Patches/Client/ClientPatch.cs
@@ -35,7 +35,7 @@ internal static class ClientPatch
[HarmonyPatch(typeof(SignInStatusComponent), nameof(SignInStatusComponent.SetOnline))]
[HarmonyPrefix]
- private static bool SignInStatusComponent_SetOnline_Prefix(SignInStatusComponent __instance)
+ private static bool SignInStatusComponent_SetOnline_Prefix()
{
// Get supported Among Us versions for BAU
var varSupportedVersions = ModInfo.SupportedAmongUsVersions;
@@ -80,10 +80,11 @@ internal static class ClientPatch
[HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.ExitGame))]
[HarmonyPostfix]
- private static void AmongUsClient_ExitGame_Postfix([HarmonyArgument(0)] DisconnectReasons reason)
+ private static void AmongUsClient_ExitGame_Postfix(DisconnectReasons reason)
{
// Hide custom loading bar when exiting game
CustomLoadingBarManager.ToggleLoadingBar(false);
+
Logger_.Log($"Client has left game for: {Enum.GetName(reason)}", "AmongUsClientPatch");
}