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"); }