Allow host only mods to send Support Flags

This commit is contained in:
D1GQ committed 2026-05-29 05:08:33 -05:00
1 parent 8107cd4462
commit 744010b82d
9 files changed
+178 -29

No files matched your search

@@ -0,0 +1,64 @@
using HarmonyLib;
using Hazel;
namespace BetterAmongUs.Examples;
/// <summary>
/// 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.
/// </summary>
[HarmonyPatch]
internal static class SendBAUSupportFlagsExamplePatch
{
/// <summary>
/// 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.
/// </summary>
/// <param name="__instance">The NetworkedPlayerInfo instance being serialized.</param>
/// <param name="writer">The MessageWriter used for serialization.</param>
/// <remarks>
/// 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.
/// <para>
/// <b>Important:</b> This functionality only works on protocol version +25 (modded protocol)!
/// </para>
/// </remarks>
[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!
}
/// <summary>
/// Generates a deterministic integer hash from a string for use as a flag identifier.
/// </summary>
/// <param name="input">The input string to hash</param>
/// <returns>
/// A 32-bit integer hash value. Returns 0 for null or empty strings.
/// </returns>
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;
}
}
+13
View File
@@ -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<object>()]);
InstanceAttribute.RegisterAll();
OutfitData.Initialize();
SceneManager.add_sceneLoaded((Action<Scene, LoadSceneMode>)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();
}
}
}
/// <summary>
/// Sets up the console window for logging.
/// </summary>
+2
View File
@@ -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";
}
}
+5 -5
View File
@@ -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);
}
/// <summary>
@@ -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();
}
+44 -1
View File
@@ -221,6 +221,7 @@ public static class BAUModdedSupportFlags
public static string Disable_CustomColorBlindText = "gameplay.disable.customcolorblindtext";
private static readonly HashSet<string> _flags = [];
private static readonly HashSet<int> _tempFlags = [];
private static bool _initialized = false;
private static readonly BAUSupportVar<string[]> BAUSupportFlags = new("bau:flags");
@@ -262,6 +263,23 @@ public static class BAUModdedSupportFlags
_flags.Add(flag);
}
/// <summary>
/// Manually adds a temporary flag to the internal temporary flag collection.
/// </summary>
/// <param name="flag">The flag hash to add to the collection.</param>
internal static void AddTempFlag(int flag)
{
_tempFlags.Add(flag);
}
/// <summary>
/// CLears all temporary flags.
/// </summary>
internal static void ClearTempFlags()
{
_tempFlags.Clear();
}
/// <summary>
/// Checks if a specific flag has been declared by any loaded mod.
/// </summary>
@@ -269,6 +287,31 @@ public static class BAUModdedSupportFlags
/// <returns><c>true</c> if the flag is present; otherwise, <c>false</c>.</returns>
public static bool HasFlag(string flag)
{
return _flags.Contains(flag);
return _flags.Contains(flag) || _tempFlags.Contains(GetFlagHash(flag));
}
/// <summary>
/// Generates a integer hash from a string for use as a flag identifier.
/// </summary>
/// <param name="input">The input string to hash. Can be null or empty.</param>
/// <returns>
/// A integer hash value. Returns 0 for null or empty strings.
/// </returns>
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;
}
}
+3 -8
View File
@@ -17,18 +17,13 @@ internal sealed class ExtendedPlayerControl : MonoBehaviour, IMonoExtension<Play
/// </summary>
public PlayerControl? BaseMono { get; set; }
/// <summary>
/// Gets the PlayerControl instance.
/// </summary>
internal PlayerControl? _Player => BaseMono;
private void Awake()
{
if (!this.RegisterExtension())
return;
this.StartCoroutine(CoAddBetterData());
_Player.gameObject.AddComponent<PlayerInfoDisplay>().Init(_Player);
BaseMono.gameObject.AddComponent<PlayerInfoDisplay>().Init(BaseMono);
}
/// <summary>
@@ -38,12 +33,12 @@ internal sealed class ExtendedPlayerControl : MonoBehaviour, IMonoExtension<Play
[HideFromIl2Cpp]
private IEnumerator CoAddBetterData()
{
while (_Player == null || _Player.Data == null)
while (BaseMono == null || BaseMono.Data == null)
{
yield return null;
}
TryCreateExtendedData(_Player.Data);
TryCreateExtendedData(BaseMono.Data);
}
/// <summary>
+42 -7
View File
@@ -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<Network
}
catch (Exception ex)
{
Logger_.Error("Handshake disabled: " + ex.Message);
Logger_.Log("Handshake disabled: " + ex.Message);
HandshakeHandler = null;
}
}
public NetworkedPlayerInfo? BaseMono { get; set; }
internal NetworkedPlayerInfo? _Data => BaseMono;
private bool hasSet = false;
/// <summary>
@@ -75,17 +76,17 @@ internal sealed class ExtendedPlayerInfo : MonoBehaviour, IMonoExtension<Network
if (AntiCheatInfo.RPCSentPS > 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<Network
}
}
internal void Deserialize(MessageReader reader)
{
if (BaseMono.OwnerId != -2)
return;
if (reader.BytesRemaining > 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 { }
}
}
/// <summary>
/// Gets the handshake handler for this player.
/// </summary>
@@ -113,7 +136,7 @@ internal sealed class ExtendedPlayerInfo : MonoBehaviour, IMonoExtension<Network
/// <summary>
/// Gets the player's real name.
/// </summary>
internal string RealName => _Data?.PlayerName ?? "???";
internal string RealName => BaseMono?.PlayerName ?? "???";
/// <summary>
/// Gets or sets the last name set for this player.
@@ -245,6 +268,18 @@ internal sealed class ExtendedRoleInfo
/// </summary>
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);
}
}
/// <summary>
/// Gets extended player data from a PlayerControl.
/// </summary>
+2 -6
View File
@@ -15,10 +15,6 @@ internal static class RPC
/// </summary>
internal const RpcCalls CUSTOM_RPC_CALL = RpcCalls.CancelPet;
/// <summary>
/// The flag string used to identify custom RPC messages packed within vanilla RPC calls.
/// </summary>
internal const string CUSTOM_RPC_FLAG = "bau:rpc";
/// <summary>
/// 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;
}
+3 -2
View File
@@ -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");
}