using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using AmongUs.GameOptions; using EHR.Modules; using EHR.Roles; using HarmonyLib; using Hazel; using Il2CppInterop.Runtime.InteropTypes.Arrays; using InnerNet; using UnityEngine; namespace EHR; public class CustomRpcSender { public enum State { BeforeInit = 0, // Cannot do anything before initialization Ready, // Ready to send - StartMessage and SendMessage can be executed InRootPackedMessage, // State where only GameDataTo submessages can be started InRootMessage, // State between StartMessage and EndMessage - StartRpc and EndMessage can be executed InRpc, // State between StartRpc and EndRpc - Write and EndRpc can be executed Finished // Nothing can be done after sending } private readonly List doneStreams = []; private readonly bool isUnsafe; private readonly bool log; public readonly string name; private readonly OnSendDelegateType onSendDelegate; public readonly SendOption sendOption; public bool checkLength; // 0~: targetClientId (GameDataTo) // -1: All players (GameData) // -2: Not set private int currentRpcTarget; public bool packed; private State currentState/* = State.BeforeInit*/; public int messages; public MessageWriter stream; private CustomRpcSender(string name, SendOption sendOption, bool isUnsafe, bool log) { stream = MessageWriter.Get(sendOption); this.name = name; this.isUnsafe = isUnsafe; this.sendOption = sendOption; this.log = log; currentRpcTarget = -2; packed = false; checkLength = true; onSendDelegate = () => { }; messages = 0; currentState = State.Ready; if (log) Logger.Info($"\"{name}\" is ready", "CustomRpcSender"); } public State CurrentState { get => currentState; set { if (isUnsafe) currentState = value; else Logger.Warn("CurrentState can only be overwritten when isUnsafe is true", "CustomRpcSender"); } } public static CustomRpcSender Create(string name = "No Name Sender", SendOption sendOption = SendOption.None, bool isUnsafe = false, bool log = true) { return new(name, sendOption, isUnsafe, log); } public CustomRpcSender AutoStartRpc( uint targetNetId, RpcCalls rpcCall, int targetClientId = -1, [CallerFilePath] string callerPath = "", [CallerLineNumber] int callerLine = 0) { // ReSharper disable ExplicitCallerInfoArgument return AutoStartRpc(targetNetId, (byte)rpcCall, targetClientId, callerPath, callerLine); // ReSharper restore ExplicitCallerInfoArgument } public CustomRpcSender AutoStartRpc( uint targetNetId, byte callId, int targetClientId = -1, [CallerFilePath] string callerPath = "", [CallerLineNumber] int callerLine = 0) { if (targetClientId == -2) targetClientId = -1; if (currentState is not State.Ready and not State.InRootPackedMessage and not State.InRootMessage) { var errorMsg = $"Tried to start RPC automatically, but State is not Ready or InRootPackedMessage or InRootMessage (in: \"{name}\", state: {currentState}) (called from {callerPath}:{callerLine})"; if (isUnsafe) Logger.Warn(errorMsg, "CustomRpcSender.Warn"); else throw new InvalidOperationException(errorMsg); } if (currentState == State.InRootPackedMessage && targetClientId < 0) { var errorMsg = $"Tried to start RPC automatically, but State is InRootPackedMessage and the requested targetClientId is negative. Only GameDataTo messages can be started in this state. (in: \"{name}\", state: {currentState}) (called from {callerPath}:{callerLine})"; if (isUnsafe) Logger.Warn(errorMsg, "CustomRpcSender.Warn"); else throw new InvalidOperationException(errorMsg); } if (currentRpcTarget != targetClientId) { // StartMessage processing if (currentState == State.InRootMessage) EndMessage(startNew: !packed && messages < AmongUsClient.Instance.GetMaxMessagePackingLimit()); else if (messages > 0) // state is Ready or InRootPackedMessage { if (currentState == State.InRootPackedMessage) { stream.EndMessage(); currentState = State.Ready; doneStreams.Add(stream); stream = MessageWriter.Get(sendOption); messages = 0; StartPackedMessage(); // assume the next message should be in a PackedGameDataTo message as well } else // state is Ready { doneStreams.Add(stream); stream = MessageWriter.Get(sendOption); messages = 0; } } StartMessage(targetClientId); } StartRpc(targetNetId, callId); return this; } public void SendMessage(bool dispose = false) { if (!dispose) { if (currentState == State.InRootMessage) EndMessage(); if (currentState == State.InRootPackedMessage) { static int GetPackedIntSize(int value) { uint v = (uint)value; int count = 0; do { v >>= 7; count++; } while (v != 0); return count; } if (3 + GetPackedIntSize(AmongUsClient.Instance.GameId) >= stream.Length) dispose = true; else EndMessage(); } if (currentState != State.Ready) { var errorMsg = $"Tried to send RPC but State is not Ready (in: \"{name}\", state: {currentState})"; if (isUnsafe) Logger.Warn(errorMsg, "CustomRpcSender.Warn"); else throw new InvalidOperationException(errorMsg); } } if (stream.Length > 1200 && !dispose) Logger.Msg($"Large packet \"{name}\" is sending ({stream.Length} bytes)", "CustomRpcSender"); else if (log || stream.Length > 3) Logger.Info($"\"{name}\" is finished (Length: {stream.Length}, dispose: {dispose}, sendOption: {sendOption})", "CustomRpcSender"); if (!dispose) { if (doneStreams.Count > 0) { var sb = new StringBuilder(" + Lengths: "); doneStreams.ForEach(x => { if (x.Length > 1200) Logger.Msg($"Large reliable packet \"{name}\" is sending ({x.Length} bytes)", "CustomRpcSender"); else if (log || x.Length > 3) sb.Append($" | {x.Length}"); AmongUsClient.Instance.SendOrDisconnect(x); x.Recycle(); }); Logger.Info(sb.ToString(), "CustomRpcSender"); doneStreams.Clear(); } AmongUsClient.Instance.SendOrDisconnect(stream); onSendDelegate(); } packed = false; currentRpcTarget = -2; messages = 0; currentState = State.Finished; stream.Recycle(); } private CustomRpcSender Write(Action action) { if (currentState != State.InRpc) { var errorMsg = $"Tried to write RPC, but State is not InRpc (in: \"{name}\")"; if (isUnsafe) Logger.Warn(errorMsg, "CustomRpcSender.Warn"); else throw new InvalidOperationException(errorMsg); } action(stream); return this; } private delegate void OnSendDelegateType(); #region Start/End Message public CustomRpcSender StartMessage(int targetClientId = -1) { if (currentState is not State.Ready and not State.InRootPackedMessage) { var errorMsg = $"Tried to start Message but State is not Ready or InRootPackedMessage (in: \"{name}\")"; if (isUnsafe) Logger.Warn(errorMsg, "CustomRpcSender.Warn"); else throw new InvalidOperationException(errorMsg); } if (currentState == State.InRootPackedMessage && targetClientId < 0) { var errorMsg = $"Tried to start RPC, but State is InRootPackedMessage and the requested targetClientId is negative. Only GameDataTo messages can be started in this state. (in: \"{name}\")"; if (isUnsafe) Logger.Warn(errorMsg, "CustomRpcSender.Warn"); else throw new InvalidOperationException(errorMsg); } if (checkLength && stream.Length > 500) { if (currentState == State.InRootPackedMessage) { stream.EndMessage(); doneStreams.Add(stream); stream = MessageWriter.Get(sendOption); messages = 0; currentState = State.Ready; StartPackedMessage(); } else { doneStreams.Add(stream); stream = MessageWriter.Get(sendOption); messages = 0; } } if (targetClientId < 0) { // RPC for everyone stream.StartMessage(5); stream.Write(AmongUsClient.Instance.GameId); } else { // RPC (Desync) to a specific client stream.StartMessage(6); stream.Write(AmongUsClient.Instance.GameId); stream.WritePacked(targetClientId); } currentRpcTarget = targetClientId; currentState = State.InRootMessage; return this; } public CustomRpcSender StartPackedMessage() { if (currentState != State.Ready) { var errorMsg = $"Tried to start Message but State is not Ready (in: \"{name}\")"; if (isUnsafe) Logger.Warn(errorMsg, "CustomRpcSender.Warn"); else throw new InvalidOperationException(errorMsg); } if (checkLength && stream.Length > 500) { doneStreams.Add(stream); stream = MessageWriter.Get(sendOption); messages = 0; } stream.StartMessage(26); stream.WritePacked(AmongUsClient.Instance.GameId); currentRpcTarget = -2; currentState = State.InRootPackedMessage; packed = true; return this; } public CustomRpcSender EndMessage(bool startNew = false) { if (currentState is not State.InRootMessage and not State.InRootPackedMessage) { var errorMsg = $"Tried to exit Message but State is not InRootMessage or InRootPackedMessage (in: \"{name}\")"; if (isUnsafe) Logger.Warn(errorMsg, "CustomRpcSender.Warn"); else throw new InvalidOperationException(errorMsg); } bool wasPackedContext = packed; bool closingPackedRoot = currentState == State.InRootPackedMessage; stream.EndMessage(); if (closingPackedRoot) packed = false; if (startNew) { if (wasPackedContext && !closingPackedRoot) { // Close outer packed root too stream.EndMessage(); } doneStreams.Add(stream); stream = MessageWriter.Get(sendOption); messages = 0; currentState = State.Ready; currentRpcTarget = -2; if (wasPackedContext) StartPackedMessage(); return this; } currentRpcTarget = -2; currentState = packed ? State.InRootPackedMessage : State.Ready; return this; } #endregion #region Start/End Rpc public CustomRpcSender StartRpc(uint targetNetId, RpcCalls rpcCall) { return StartRpc(targetNetId, (byte)rpcCall); } public CustomRpcSender StartRpc( uint targetNetId, byte callId) { if (currentState != State.InRootMessage) { var errorMsg = $"Tried to start RPC but State is not InRootMessage (in: \"{name}\")"; if (isUnsafe) Logger.Warn(errorMsg, "CustomRpcSender.Warn"); else throw new InvalidOperationException(errorMsg); } if (messages >= AmongUsClient.Instance.GetMaxMessagePackingLimit()) { EndMessage(startNew: true); StartMessage(currentRpcTarget); } messages++; stream.StartMessage(2); stream.WritePacked(targetNetId); stream.Write(callId); currentState = State.InRpc; return this; } public CustomRpcSender EndRpc() { if (currentState != State.InRpc) { var errorMsg = $"Tried to terminate RPC but State is not InRpc (in: \"{name}\")"; if (isUnsafe) Logger.Warn(errorMsg, "CustomRpcSender.Warn"); else throw new InvalidOperationException(errorMsg); } stream.EndMessage(); currentState = State.InRootMessage; return this; } #endregion // Write #region PublicWriteMethods public CustomRpcSender Write(float val) { return Write(w => w.Write(val)); } public CustomRpcSender Write(string val) { return Write(w => w.Write(val)); } public CustomRpcSender Write(ulong val) { return Write(w => w.Write(val)); } public CustomRpcSender Write(int val) { return Write(w => w.Write(val)); } public CustomRpcSender Write(uint val) { return Write(w => w.Write(val)); } public CustomRpcSender Write(ushort val) { return Write(w => w.Write(val)); } public CustomRpcSender Write(byte val) { return Write(w => w.Write(val)); } public CustomRpcSender Write(sbyte val) { return Write(w => w.Write(val)); } public CustomRpcSender Write(bool val) { return Write(w => w.Write(val)); } public CustomRpcSender Write(Il2CppStructArray bytes) { return Write(w => w.Write(bytes)); } public CustomRpcSender Write(Il2CppStructArray bytes, int offset, int length) { return Write(w => w.Write(bytes, offset, length)); } public CustomRpcSender WriteBytesAndSize(Il2CppStructArray bytes) { return Write(w => w.WriteBytesAndSize(bytes)); } public CustomRpcSender WritePacked(int val) { return Write(w => w.WritePacked(val)); } public CustomRpcSender WritePacked(uint val) { return Write(w => w.WritePacked(val)); } public CustomRpcSender WriteNetObject(InnerNetObject obj) { return Write(w => w.WriteNetObject(obj)); } public CustomRpcSender WriteVector2(Vector2 vector2) { return Write(w => NetHelpers.WriteVector2(vector2, w)); } #endregion } public static class CustomRpcSenderExtensions { public static void RpcSetName(ref CustomRpcSender sender, PlayerControl player, PlayerControl seer, string name) { bool seerIsNull = !seer; int targetClientId = seerIsNull ? -1 : seer.OwnerId; name = name.Replace("color=", string.Empty); switch (seerIsNull) { case true when Main.LastNotifyNames.Where(x => x.Key.Item1 == player.PlayerId).All(x => x.Value == name): case false when Main.LastNotifyNames[(player.PlayerId, seer.PlayerId)] == name: return; case true: Main.EnumeratePlayerControls().Do(x => Main.LastNotifyNames[(player.PlayerId, x.PlayerId)] = name); break; default: Main.LastNotifyNames[(player.PlayerId, seer.PlayerId)] = name; break; } sender.checkLength = false; if (sender.stream.Length + GetSetNameRpcSize(player.NetId, name) > 1100) { bool packed = sender.packed; sender.SendMessage(); sender = CustomRpcSender.Create(sender.name, sender.sendOption); if (packed) sender.StartPackedMessage(); } sender.AutoStartRpc(player.NetId, RpcCalls.SetName, targetClientId) .Write(player.Data.NetId) .Write(name) .Write(false) .EndRpc(); return; static int GetSetNameRpcSize(uint netId, string playerName) { int byteCount = System.Text.Encoding.UTF8.GetByteCount(playerName); return 3 + PackedUIntSize(netId) + 1 + 4 + PackedUIntSize((uint)byteCount) + byteCount + 1; } static int PackedUIntSize(uint value) { return value switch { < 0x80 => 1, < 0x4000 => 2, < 0x20_0000 => 3, < 0x1000_0000 => 4, _ => 5 }; } } extension(CustomRpcSender sender) { public bool RpcSetRole(PlayerControl player, RoleTypes role, int targetClientId = -1, bool noRpcForSelf = true, bool changeRoleMap = false) { if (AmongUsClient.Instance.ClientId == targetClientId && noRpcForSelf) { player.SetRole(role); return false; } sender.AutoStartRpc(player.NetId, RpcCalls.SetRole, targetClientId) .Write((ushort)role) .Write(true) .EndRpc(); if (changeRoleMap) { try { if (targetClientId != -1) ChangeRoleMapForClient(Utils.GetClientById(targetClientId).Character.PlayerId); else Main.PlayerStates.Keys.Do(ChangeRoleMapForClient); } catch (Exception e) { Utils.ThrowException(e); } void ChangeRoleMapForClient(byte id) { (byte, byte) key = (id, player.PlayerId); if (StartGameHostPatch.RpcSetRoleReplacer.RoleMap.TryGetValue(key, out (RoleTypes RoleType, CustomRoles CustomRole) pair)) { pair.RoleType = role; StartGameHostPatch.RpcSetRoleReplacer.RoleMap[key] = pair; } } } return true; } public bool RpcGuardAndKill(PlayerControl killer, PlayerControl target = null, bool forObserver = false, bool fromSetKCD = false) { if (!AmongUsClient.Instance.AmHost) { StackFrame caller = new(1, false); MethodBase callerMethod = caller.GetMethod(); string callerMethodName = callerMethod?.Name; string callerClassName = callerMethod?.DeclaringType?.FullName; Logger.Warn($"Modded non-host client activated RpcGuardAndKill from {callerClassName}.{callerMethodName}", "RpcGuardAndKill"); return false; } if (!target) target = killer; var returnValue = false; // Check Observer if (!forObserver && !MeetingStates.FirstMeeting) { foreach (PlayerControl x in Main.EnumeratePlayerControls()) { if (x.Is(CustomRoles.Observer) && killer.PlayerId != x.PlayerId && sender.RpcGuardAndKill(x, target, true)) returnValue = true; } } // Host if (killer.AmOwner) killer.MurderPlayer(target, MurderResultFlags.FailedProtected); // Other Clients else { sender.AutoStartRpc(killer.NetId, RpcCalls.MurderPlayer, killer.OwnerId); sender.WriteNetObject(target); sender.Write((int)MurderResultFlags.FailedProtected); sender.EndRpc(); if (Options.CurrentGameMode == CustomGameMode.Standard && !MeetingStates.FirstMeeting && !AntiBlackout.SkipTasks && !ExileController.Instance && GameStates.IsInTask && killer.IsBeginner() && Main.GotShieldAnimationInfoThisGame.Add(killer.PlayerId)) sender.Notify(killer, Translator.GetString("PleaseStopBeingDumb"), 10f); returnValue = true; } if (!fromSetKCD) killer.AddKillTimerToDict(true); return returnValue; } public bool SetKillCooldown(PlayerControl player, float time = -1f, PlayerControl target = null, bool forceAnime = false) { if (!player) return false; Logger.Info($"{player.GetNameWithRole()}'s KCD set to {(Math.Abs(time - -1f) < 0.5f ? Main.AllPlayerKillCooldown[player.PlayerId] : time)}s", "SetKCD"); if (player.GetCustomRole().UsesPetInsteadOfKill()) { if (Math.Abs(time - -1f) < 0.5f) player.AddKCDAsAbilityCD(); else player.AddAbilityCD((int)Math.Round(time)); if (player.GetCustomRole() is not CustomRoles.Necromancer and not CustomRoles.Deathknight and not CustomRoles.Renegade and not CustomRoles.Sidekick) return false; } if (!player.CanUseKillButton() && !AntiBlackout.SkipTasks && !IntroCutsceneDestroyPatch.PreventKill) return false; player.AddKillTimerToDict(cd: time); if (!target) target = player; if (time >= 0f) Main.AllPlayerKillCooldown[player.PlayerId] = time * 2; else Main.AllPlayerKillCooldown[player.PlayerId] *= 2; var returnValue = false; if (player.Is(CustomRoles.Glitch) && Main.PlayerStates[player.PlayerId].Role is Glitch gc) { gc.LastKill = Utils.TimeStamp + ((int)(time / 2) - Glitch.KillCooldown.GetInt()); gc.KCDTimer = (int)(time / 2); } else if (forceAnime || !player.IsModdedClient() || !Options.DisableShieldAnimations.GetBool()) { returnValue |= sender.SyncSettings(player); returnValue |= sender.RpcGuardAndKill(player, target, fromSetKCD: true); } else { time = Main.AllPlayerKillCooldown[player.PlayerId] / 2; if (player.AmOwner) PlayerControl.LocalPlayer.SetKillTimer(time); else { sender.AutoStartRpc(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetKillTimer, player.OwnerId); sender.Write(time); sender.EndRpc(); returnValue = true; } foreach (PlayerControl x in Main.EnumeratePlayerControls()) { if (x.Is(CustomRoles.Observer) && target.PlayerId != x.PlayerId && sender.RpcGuardAndKill(x, target, true, true)) returnValue = true; } } if (player.GetCustomRole() is not CustomRoles.Inhibitor and not CustomRoles.Saboteur) { player.ResetKillCooldown(sync: false); LateTask.New(player.SyncSettings, 1f, log: false); } return returnValue; } public bool RpcResetAbilityCooldown(PlayerControl target) { if (!AmongUsClient.Instance.AmHost) return false; Logger.Info($"Reset Ability Cooldown for {target.name} (ID: {target.PlayerId})", "RpcResetAbilityCooldown"); if (target.Is(CustomRoles.Glitch) && Main.PlayerStates[target.PlayerId].Role is Glitch gc) { gc.LastHack = Utils.TimeStamp; gc.HackCDTimer = 10; return false; } if (target.AmOwner) { // If target is host target.Data.Role.SetCooldown(); return false; } if (target.IsModdedClient()) { sender.AutoStartRpc(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.ResetAbilityCooldown, target.OwnerId); sender.EndRpc(); return true; } sender.AutoStartRpc(target.NetId, RpcCalls.ProtectPlayer, target.OwnerId); sender.WriteNetObject(target); sender.Write(0); sender.EndRpc(); return true; } public void RpcDesyncUpdateSystem(PlayerControl target, SystemTypes systemType, int amount) { sender.AutoStartRpc(ShipStatus.Instance.NetId, RpcCalls.UpdateSystem, target.OwnerId); sender.Write((byte)systemType); sender.WriteNetObject(target); sender.Write((byte)amount); sender.EndRpc(); } public bool Notify(PlayerControl pc, string text, float time = 6f, bool overrideAll = false, bool log = true, bool setName = true) { if (!AmongUsClient.Instance.AmHost || !pc) return false; if (!GameStates.IsInTask) return false; if (!text.Contains("")) text = Utils.ColorString(Color.white, text); if (!text.Contains("{text}"; long expireTS = Utils.TimeStamp + (long)time; bool alreadyContainsKey = false; if (overrideAll || !NameNotifyManager.Notifies.TryGetValue(pc.PlayerId, out Dictionary notifies)) NameNotifyManager.Notifies[pc.PlayerId] = new() { { text, expireTS } }; else { alreadyContainsKey = notifies.ContainsKey(text); notifies[text] = expireTS; } bool returnValue = pc.IsNonHostModdedClient(); if (returnValue) NameNotifyManager.SendRPC(sender, pc.PlayerId, text, expireTS, overrideAll); if (alreadyContainsKey) { if (log) Logger.Info($"Extended name notify for {pc.GetNameWithRole().RemoveHtmlTags()}: {text} ({time}s)", "Name Notify"); return returnValue; } if (setName) returnValue |= Utils.WriteSetNameRpcsToSender(ref sender, false, false, false, false, false, false, pc, [pc], [], out bool senderWasCleared) && !senderWasCleared; if (log) Logger.Info($"New name notify for {pc.GetNameWithRole().RemoveHtmlTags()}: {text} ({time}s)", "Name Notify"); return returnValue; } public bool TP(PlayerControl pc, Vector2 location, bool noCheckState = false, bool log = true) { if (!AmongUsClient.Instance.AmHost) return false; CustomNetworkTransform nt = pc.NetTransform; if (!noCheckState) { if (pc.Is(CustomRoles.AntiTP)) return false; if (pc.inVent || pc.inMovingPlat || pc.onLadder || !pc.IsAlive() || pc.MyPhysics.Animations.IsPlayingAnyLadderAnimation() || pc.MyPhysics.Animations.IsPlayingEnterVentAnimation()) { if (log) Logger.Warn($"Target ({pc.GetNameWithRole().RemoveHtmlTags()}) is in an un-teleportable state - Teleporting canceled", "TP"); return false; } if (FastVector2.DistanceWithinRange(pc.Pos(), location, 0.5f)) { if (log) Logger.Warn($"Target ({pc.GetNameWithRole().RemoveHtmlTags()}) is too close to the destination - Teleporting canceled", "TP"); return false; } } nt.SnapTo(location, (ushort)(nt.lastSequenceId + 328)); nt.SetDirtyBit(uint.MaxValue); var newSid = (ushort)(nt.lastSequenceId + 8); sender.AutoStartRpc(nt.NetId, RpcCalls.SnapTo); sender.WriteVector2(location); sender.Write(newSid); sender.EndRpc(); if (log) Logger.Info($"{pc.GetNameWithRole().RemoveHtmlTags()} => {location}", "TP"); CheckInvalidMovementPatch.LastPosition[pc.PlayerId] = location; CheckInvalidMovementPatch.ExemptedPlayers.Add(pc.PlayerId); if (sender.sendOption == SendOption.Reliable) Utils.NumSnapToCallsThisRound++; return true; } public bool NotifyRolesSpecific(PlayerControl seer, PlayerControl target, out CustomRpcSender newSender, out bool senderWasCleared) { senderWasCleared = false; newSender = sender; if (!AmongUsClient.Instance.AmHost || !seer || seer.Data.Disconnected || (seer.IsModdedClient() && (seer.IsHost() || Options.CurrentGameMode == CustomGameMode.Standard)) || (!SetUpRoleTextPatch.IsInIntro && GameStates.IsLobby)) return false; var hasValue = Utils.WriteSetNameRpcsToSender(ref sender, false, false, false, false, false, false, seer, [seer], [target], out senderWasCleared) && !senderWasCleared; newSender = sender; return hasValue; } public bool RpcExiled(PlayerControl target, bool autoStartRpc = true, int targetClientId = -1, bool exileForHost = true) { if (exileForHost) target.Exiled(); if (autoStartRpc) sender.AutoStartRpc(target.NetId, RpcCalls.Exiled, targetClientId).EndRpc(); else sender.StartRpc(target.NetId, RpcCalls.Exiled).EndRpc(); return true; } public bool RpcExileV2(PlayerControl target, bool autoStartRpc = true, int targetClientId = -1, bool exileForHost = true) { sender.RpcExiled(target, autoStartRpc, targetClientId, exileForHost); FixedUpdatePatch.LoversSuicide(target.PlayerId); return true; } public bool SyncSettings(PlayerControl player) { if (GameStates.CurrentServerType == GameStates.ServerType.Vanilla) { player.SyncSettings(); return false; } var optionsender = GameOptionsSender.AllSenders.OfType().FirstOrDefault(x => x.player.PlayerId == player.PlayerId); if (optionsender == null) return false; var options = optionsender.BuildGameOptions(); if (player.AmOwner) { foreach (GameLogicComponent com in GameManager.Instance.LogicComponents) { if (com.TryCast(out LogicOptions lo)) lo.SetGameOptions(options); } GameOptionsManager.Instance.CurrentGameOptions = options; return false; } var logicOptions = GameManager.Instance.LogicOptions; var id = GameManager.Instance.LogicComponents.IndexOf(logicOptions); if (sender.CurrentState == CustomRpcSender.State.InRootMessage) sender.EndMessage(); var writer = sender.stream; writer.StartMessage(6); { writer.Write(AmongUsClient.Instance.GameId); writer.WritePacked(player.OwnerId); writer.StartMessage(1); { writer.WritePacked(GameManager.Instance.NetId); writer.StartMessage((byte)id); { writer.WriteBytesAndSize(logicOptions.gameOptionsFactory.ToBytes(options, AprilFoolsMode.IsAprilFoolsModeToggledOn)); } writer.EndMessage(); } writer.EndMessage(); } writer.EndMessage(); return true; } public bool SyncGeneralOptions(PlayerControl player) { if (!AmongUsClient.Instance.AmHost || !GameStates.IsInGame || !Utils.DoRPC) return false; sender.AutoStartRpc(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncGeneralOptions); sender.Write(player.PlayerId); sender.WritePacked((int)player.GetCustomRole()); sender.Write(Main.PlayerStates[player.PlayerId].IsDead); sender.WritePacked((int)Main.PlayerStates[player.PlayerId].deathReason); sender.Write(Main.AllPlayerKillCooldown[player.PlayerId]); sender.Write(Main.AllPlayerSpeed[player.PlayerId]); sender.EndRpc(); return true; } } } // Rather not send the packet than get kicked immediately after sending it [HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.SendOrDisconnect))] static class PreventLargePacketKickPatch { public static bool Prefix([HarmonyArgument(0)] MessageWriter msg) { if (msg.Length <= 1200) return true; var serverType = GameStates.CurrentServerType; if (serverType is GameStates.ServerType.Local or GameStates.ServerType.Vanilla || (serverType == GameStates.ServerType.Niko && msg.Length > 1400)) { Logger.Warn($" Blocked large packet from sending (size: {msg.Length})", nameof(PreventLargePacketKickPatch)); return false; } return true; } }