using System.Data; using AmongUs.GameOptions; using Hazel; using UnityEngine; using Il2CppInterop.Runtime.InteropTypes.Arrays; using InnerNet; using System.Diagnostics; using System.IO; using System.Reflection; using System; using System.Security.Cryptography; using System.Text; using AmongUs.InnerNet.GameDataMessages; namespace AmongUsRevamped; public static class Utils { private static readonly DateTime Epoch = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); private static readonly DateTime StartTime = DateTime.UtcNow; private static readonly long EpochStartSeconds = (long)(StartTime - Epoch).TotalSeconds; private static readonly Stopwatch Stopwatch = Stopwatch.StartNew(); public static long TimeStamp => EpochStartSeconds + (long)Stopwatch.Elapsed.TotalSeconds; public static int allAlivePlayersCount => AllAlivePlayerControls.Count(); public static int AliveCrewmates => AllAlivePlayerControls.Count(pc => !pc.Data.Role.IsImpostor); public static int AliveImpostors => AllAlivePlayerControls.Count(pc => pc.Data.Role.IsImpostor || pc.isNew); public static bool IsLobby => AmongUsClient.Instance.GameState == InnerNetClient.GameStates.Joined; public static bool InGame => AmongUsClient.Instance && AmongUsClient.Instance.GameState == InnerNetClient.GameStates.Started; public static bool isHideNSeek => GameOptionsManager.Instance.CurrentGameOptions.GameMode == GameModes.HideNSeek; public static bool IsOnlineGame => AmongUsClient.Instance.NetworkMode == NetworkModes.OnlineGame; public static bool IsShip => ShipStatus.Instance != null; public static bool CanMove => PlayerControl.LocalPlayer?.CanMove is true; public static bool IsDead => PlayerControl.LocalPlayer?.Data?.IsDead is true; public static bool IsFreePlay => AmongUsClient.Instance.NetworkMode == NetworkModes.FreePlay; public static bool IsMeeting => InGame && (MeetingHud.Instance); public static bool GamePastRoleSelection => Main.GameTimer > 10f; public static bool HandlingGameEnd; public static byte CustomGameOverReason; public static bool CanCallMeetings; public static string ColorString(Color32 color, string str) => $"<#{color.r:x2}{color.g:x2}{color.b:x2}{color.a:x2}>{str}"; public static string ColorToHex(Color32 color) => $"#{color.r:x2}{color.g:x2}{color.b:x2}{color.a:x2}"; public static byte GetActiveMapId() => GameOptionsManager.Instance.CurrentGameOptions.MapId; public static int CheckAccessLevel(string friendCode) { if (friendCode == "") return 0; var vipFilePath = @$"{BanManager.DataPath}/AUR-DATA/VIP.txt"; var vipFriendCodes = File.ReadAllLines(vipFilePath); var moderatorFilePath = @$"{BanManager.DataPath}/AUR-DATA/Moderator.txt"; var moderatorFriendCodes = File.ReadAllLines(moderatorFilePath); var adminFilePath = @$"{BanManager.DataPath}/AUR-DATA/Admin.txt"; var adminFriendCodes = File.ReadAllLines(adminFilePath); if (adminFriendCodes.Any(code => code.Contains(friendCode))) return 3; if (moderatorFriendCodes.Any(code => code.Contains(friendCode))) return 2; if (vipFriendCodes.Any(code => code.Contains(friendCode))) return 1; return 0; } public static string GetTabName(TabGroup tab) { switch (tab) { case TabGroup.SystemSettings: return Translator.Get("systemSettings"); case TabGroup.ModerationSettings: return Translator.Get("moderationSettings"); case TabGroup.ModSettings: return Translator.Get("gameplaySettings"); case TabGroup.GamemodeSettings: return Translator.Get("gamemodeSettings"); default: return ""; } } public static bool IsCustomOption(NumberOption option) { return option.GetComponent() != null; } public static void DestroyTranslator(this GameObject obj) { var translator = obj.GetComponent(); if (translator != null) { Object.Destroy(translator); } } public static void DestroyTranslator(this MonoBehaviour obj) => obj.gameObject.DestroyTranslator(); public static void CustomSettingsChangeMessageLogic(this NotificationPopper notificationPopper, OptionItem optionItem, string text, bool playSound) { if (notificationPopper.lastMessageKey == 10000 + optionItem.Id && notificationPopper.activeMessages.Count > 0) { notificationPopper.activeMessages[notificationPopper.activeMessages.Count - 1].UpdateMessage(text); } else { notificationPopper.lastMessageKey = 10000 + optionItem.Id; LobbyNotificationMessage settingmessage = Object.Instantiate(notificationPopper.notificationMessageOrigin, Vector3.zero, Quaternion.identity, notificationPopper.transform); settingmessage.transform.localPosition = new Vector3(0f, 0f, -2f); settingmessage.SetUp(text, notificationPopper.settingsChangeSprite, notificationPopper.settingsChangeColor, new Action(() => { notificationPopper.OnMessageDestroy(settingmessage); })); notificationPopper.ShiftMessages(); notificationPopper.AddMessageToQueue(settingmessage); } if (playSound) { SoundManager.Instance.PlaySoundImmediate(notificationPopper.settingsChangeSound, false, 1f, 1f, null); } } public static string GetOptionNameSCM(this OptionItem optionItem) { if (optionItem.Name == "Enable") { int id = optionItem.Id; while (id % 10 != 0) --id; var optionItem2 = OptionItem.AllOptions.FirstOrDefault(opt => opt.Id == id); return optionItem2 != null ? optionItem2.GetName() : optionItem.GetName(); } else return optionItem.GetName(); } public static string GetRegionName(IRegionInfo region = null) { region ??= ServerManager.Instance.CurrentRegion; string name = region.Name; // Joining games shows incorrect regions if (!AmongUsClient.Instance.AmHost) { name = ""; return name; } if (AmongUsClient.Instance.NetworkMode != NetworkModes.OnlineGame) { name = "Server: Local Game"; return name; } if (region.PingServer.EndsWith("among.us", StringComparison.Ordinal)) { // Official servers name = name switch { "North America" => "Server: NA", "Europe" => "Server: EU", "Asia" => "Server: AS", _ => name }; return name; } string ip = region.Servers.FirstOrDefault()?.Ip ?? string.Empty; if (ip.Contains("aumods.us", StringComparison.Ordinal) || ip.Contains("duikbo.at", StringComparison.Ordinal)) { // Modded Servers if (ip.Contains("au-eu")) name = "Server: MEU"; else if (ip.Contains("au-as")) name = "Server: MAS"; else name = "Server: MNA"; return name; } if (name.Contains("Niko", StringComparison.OrdinalIgnoreCase)) name = name.Replace("233(", "-").TrimEnd(')'); return name; } public static ClientData GetClientById(int id) { try { return AmongUsClient.Instance.allClients.ToArray().FirstOrDefault(cd => cd.Id == id); } catch { return null; } } public static void ClearLeftoverData() { RpcSetTasksPatch.GlobalTaskIds = null; HandlingGameEnd = false; OnGameJoinedPatch.AutoStartCheck = false; Main.GameTimer = 0f; NormalGameEndChecker.canUpdateWinnerText = true; MurderPlayerPatch.misfireCount.Clear(); MurderPlayerPatch.killCount.Clear(); LateTask.Tasks.RemoveAll(x => x.name != "RehostManager"); NormalGameEndChecker.ImpCheckComplete = false; CreateOptionsPickerPatch.SetDleks2 = false; CanCallMeetings = true; PlayerControlSetRolePatch.FirstAssign = true; PlayerControlCompleteTaskPatch.playerTasksCompleted.Clear(); PlayerControlCompleteTaskPatch.tasksPerPlayer.Clear(); PlayerControlCompleteTaskPatch.tasksInitiated = false; PlayerControlCompleteTaskPatch.ignoredCompletedTasks = 0; PlayerControlCompleteTaskPatch.ignoredTasks = 0; OnPlayerJoinedPatch.JoinNum = 0; PlayerControlSetRolePatch.Seekers.Clear(); PlayerControlSetRolePatch.FirstAssign = true; PlayerControlSetRolePatch.manualSeekers.Clear(); OnGameStartPatch.PastStartScreen = false; } public static PlayerControl[] AllAlivePlayerControls { get { int count = PlayerControl.AllPlayerControls.Count; var result = new PlayerControl[count]; var i = 0; foreach (PlayerControl pc in PlayerControl.AllPlayerControls) { if (pc == null || pc.Data == null || pc.PlayerId >= 254 || pc.Data.IsDead || (pc.Data.Disconnected)) continue; result[i++] = pc; } if (i == 0) return []; Array.Resize(ref result, i); return result; } } public static bool IsActive(SystemTypes type) { try { if (Utils.IsLobby || !ShipStatus.Instance || !ShipStatus.Instance.Systems.TryGetValue(type, out ISystemType systemType)) return false; int mapId = Main.NormalOptions.MapId; switch (type) { case SystemTypes.Electrical: { if (mapId == 5) return false; var switchSystem = systemType.CastFast(); return switchSystem is { IsActive: true }; } case SystemTypes.Reactor: { switch (mapId) { case 2: return false; case 4: var heliSabotageSystem = systemType.CastFast(); return heliSabotageSystem != null && heliSabotageSystem.IsActive; default: var reactorSystemType = systemType.CastFast(); return reactorSystemType is { IsActive: true }; } } case SystemTypes.Laboratory: { if (mapId != 2) return false; var reactorSystemType = systemType.CastFast(); return reactorSystemType is { IsActive: true }; } case SystemTypes.LifeSupp: { if (mapId is 2 or 4 or 5) return false; var lifeSuppSystemType = systemType.CastFast(); return lifeSuppSystemType is { IsActive: true }; } case SystemTypes.Comms: { if (mapId is 1 or 5) { var hqHudSystemType = systemType.TryCast(); return hqHudSystemType != null && hqHudSystemType.IsActive; } var hudOverrideSystemType = systemType.CastFast(); return hudOverrideSystemType is { IsActive: true }; } case SystemTypes.HeliSabotage: { if (mapId != 4) return false; var heliSabotageSystem = systemType.CastFast(); return heliSabotageSystem != null && heliSabotageSystem.IsActive; } case SystemTypes.MushroomMixupSabotage: { if (mapId != 5) return false; var mushroomMixupSabotageSystem = systemType.CastFast(); return mushroomMixupSabotageSystem != null && mushroomMixupSabotageSystem.IsActive; } default: return false; } } catch (Exception e) { Logger.Exception(e, "IsActive"); return false; } } public static void ShowLastResult(byte playerId = byte.MaxValue) { if (InGame) { Logger.SendInGame($"Hi, you're currently in-game. Let's use this command afterwards"); return; } if (string.IsNullOrEmpty(NormalGameEndChecker.LastWinReason)) { Logger.SendInGame($"Your command was canceled due to not having the required info"); return; } PlayerControl.LocalPlayer.RpcSendChat($"{NormalGameEndChecker.LastWinReason}"); } public static bool TryGetColorId(string input, out byte colorId) { colorId = 0; if (Enum.TryParse(input, true, out var color)) { colorId = (byte)color; return true; } return false; } public static void ChatCommand(ChatController __instance, string msg, string msg2, bool multi) { if (OnGameJoinedPatch.WaitingForChat) return; OnGameJoinedPatch.WaitingForChat = true; __instance.freeChatField.textArea.Clear(); __instance.freeChatField.textArea.SetText(string.Empty); PlayerControl.LocalPlayer.RpcSendChat($"{msg}"); new LateTask(() => { if (multi && msg2 != "") { PlayerControl.LocalPlayer.RpcSendChat($"{msg2}"); } if (!multi) { OnGameJoinedPatch.WaitingForChat = false; } }, 2.2f, "ChatCommand1"); if (!multi || msg2 == "") return; new LateTask(() => { OnGameJoinedPatch.WaitingForChat = false; }, 4.4f, "ChatCommand2"); } public static void ModeratorChatCommand(string msg, string msg2, bool multi) { if (OnGameJoinedPatch.WaitingForChat) return; OnGameJoinedPatch.WaitingForChat = true; new LateTask(() => { PlayerControl.LocalPlayer.RpcSendChat($"{msg}"); }, 2.2f, "ModeratorChatCommand1"); new LateTask(() => { if (multi && msg2 != "") { PlayerControl.LocalPlayer.RpcSendChat($"{msg2}"); } if (!multi) { OnGameJoinedPatch.WaitingForChat = false; } }, 4.4f, "ModeratorChatCommand2"); if (!multi || msg2 == "") return; new LateTask(() => { OnGameJoinedPatch.WaitingForChat = false; }, 6.6f, "ModeratorChatCommand3"); } // 0 = no winner. 1 = solo winner. 2 = add winner. public static void CustomWinnerEndGame(PlayerControl winner, int winnerType) { HandlingGameEnd = true; MessageWriter writer = AmongUsClient.Instance.StartEndGame(); if (winnerType == 0) { foreach (PlayerControl pc in PlayerControl.AllPlayerControls) { CustomGameOverReason = (byte)GameOverReason.ImpostorsByVote; pc.RpcSetRole(AmongUs.GameOptions.RoleTypes.CrewmateGhost, false); } } if (winnerType == 1) { foreach (PlayerControl pc in PlayerControl.AllPlayerControls) { CustomGameOverReason = (byte)GameOverReason.ImpostorsByVote; if (pc != winner) { pc.RpcSetRole(AmongUs.GameOptions.RoleTypes.CrewmateGhost, false); } else { pc.RpcSetRole(AmongUs.GameOptions.RoleTypes.ImpostorGhost, false); } } } new LateTask(() => { ContinueEndGame((byte)CustomGameOverReason); }, 1f, "CustomWinnerEndGame"); } public static void ContinueEndGame(byte gameOverReason) { MessageWriter writer = AmongUsClient.Instance.StartEndGame(); writer.Write(gameOverReason); AmongUsClient.Instance.FinishEndGame(writer); HandlingGameEnd = false; Logger.Info($"{gameOverReason}", "ContinueEndGame"); } public static void DumpLog() { string t = DateTime.Now.ToString("yyyy-MM-dd_HH.mm.ss"); #if ANDROID var f = $"{BanManager.DataPath}/AUR-Logs/{t}"; #else string f = $"{Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory)}/AUR-logs/"; #endif string filename = $"{f}AUR-{Main.ModVersion}-{t}.log"; if (!Directory.Exists(f)) Directory.CreateDirectory(f); FileInfo file = new(@$"{Environment.CurrentDirectory}/BepInEx/LogOutput.log"); file.CopyTo(@filename); if (PlayerControl.LocalPlayer != null) { HudManager.Instance.Chat.AddChat(PlayerControl.LocalPlayer, $"/Dump command activated\n\nFile: AUR-{Main.ModVersion}-{t}"); #if !ANDROID ProcessStartInfo psi = new("Explorer.exe") { Arguments = "/e,/select," + @filename.Replace("/", "\\") }; Process.Start(psi); #endif } } private readonly static Dictionary CachedSprites = []; public static Sprite LoadSprite(string path, float pixelsPerUnit = 1f) { try { if (CachedSprites.TryGetValue(path + pixelsPerUnit, out var sprite)) return sprite; Texture2D texture = LoadTextureFromResources(path); sprite = Sprite.Create(texture, new(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f), pixelsPerUnit); sprite.hideFlags |= HideFlags.HideAndDontSave | HideFlags.DontSaveInEditor; return CachedSprites[path + pixelsPerUnit] = sprite; } catch { Logger.Error($"Failed to read Texture: {path}", "LoadSprite"); } return null; } private static unsafe Texture2D LoadTextureFromResources(string path) { try { Texture2D texture = new(2, 2, TextureFormat.ARGB32, true); Assembly assembly = Assembly.GetExecutingAssembly(); Stream stream = assembly.GetManifestResourceStream(path); var length = stream.Length; var byteTexture = new Il2CppStructArray(length); stream.Read(new Span(IntPtr.Add(byteTexture.Pointer, IntPtr.Size * 4).ToPointer(), (int)length)); ImageConversion.LoadImage(texture, byteTexture, false); return texture; } catch { Logger.Error($"Failed to read Texture: {path}", "LoadTextureFromResources"); } return null; } public static void SetChatVisible() { PlayerControl.LocalPlayer.CmdReportDeadBody(null); MeetingHud.Instance.RpcClose(); } public static MethodBase GetStateMachineMoveNext(string methodName) { var typeName = typeof(T).FullName; var stateMachine = typeof(T).GetNestedTypes().FirstOrDefault(x => x.Name.Contains(methodName)); if (stateMachine == null) { Logger.Error($"Failed to find {methodName} state machine for {typeName}", "GetStateMachineMoveNext"); return null; } var moveNext = AccessTools.Method(stateMachine, "MoveNext"); if (moveNext == null) { Logger.Error($"Failed to find MoveNext method for {typeName}.{methodName}", "GetStateMachineMoveNext"); return null; } // Logger.Info($"Found {methodName}.MoveNext", "GetStateMachineMoveNext"); return moveNext; } public static readonly Dictionary StoredRoleText = new(); public static string GetRoleText(PlayerControl p) { if (p == null) return ""; if (p.Data.RoleType == RoleTypes.Impostor || p.Data.RoleType == RoleTypes.Shapeshifter || p.Data.RoleType == RoleTypes.Phantom || p.Data.RoleType == RoleTypes.Viper || p.Data.RoleType == RoleTypes.ImpostorGhost) { return $"{p.Data.RoleType}"; } else return $"{p.Data.RoleType}"; } public static int skeldVotes; public static int miraVotes; public static int polusVotes; public static int airshipVotes; public static int fungleVotes; public static int dleksVotes; public static bool MapVoteActive; public static bool DleksEnabled; public static readonly List HasVoted = []; public static void MapVote(bool dleks, bool moderator) { if (MapVoteActive) return; DleksEnabled = dleks; MapVoteActive = true; if (dleks && moderator) Utils.ModeratorChatCommand("A map vote is active!\n\nA: Skeld\nB: Mira\nC: Polus\nD: Airship\nE: Fungle\nF: Eht Dleks\n\nType /vote 'letter' to vote.", "", false); else if (moderator) Utils.ModeratorChatCommand("A map vote is active!\n\nA: The Skeld\nB: Mira HQ\nC: Polus\nD: The Airship\nE: The Fungle\n\nType /vote 'letter' to vote.", "", false); if (dleks && !moderator) Utils.ChatCommand(DestroyableSingleton.Instance.Chat, "A map vote is active!\n\nA: Skeld\nB: Mira\nC: Polus\nD: Airship\nE: Fungle\nF: Eht Dleks\n\nType /vote 'letter' to vote.", "", false); else if (!moderator) Utils.ChatCommand(DestroyableSingleton.Instance.Chat, "A map vote is active!\n\nA: The Skeld\nB: Mira HQ\nC: Polus\nD: The Airship\nE: The Fungle\n\nType /vote 'letter' to vote.", "", false); new LateTask(() => { int highest = skeldVotes; string winner = "Skeld"; byte mapId = 0; bool tie = false; void Check(int votes, string name, byte id) { if (votes > highest) { highest = votes; winner = name; mapId = id; tie = false; } else if (votes == highest) { tie = true; } } Check(miraVotes, "Mira HQ", 1); Check(polusVotes, "Polus", 2); Check(airshipVotes, "Airship", 4); Check(fungleVotes, "The Fungle", 5); if (DleksEnabled) Check(dleksVotes, "Eht Dleks", 3); string voteResults = $"Skeld: {skeldVotes}\n" + $"Mira: {miraVotes}\n" + $"Polus: {polusVotes}\n" + $"Airship: {airshipVotes}\n" + $"Fungle: {fungleVotes}"; if (DleksEnabled) voteResults += $"\nEht Dleks: {dleksVotes}"; if (highest == 0) { ModeratorChatCommand("Lol, no one voted. Hooray for democracy?", "", false); } else if (tie) { ModeratorChatCommand($"Map vote tied. No new map selected.\n\n{voteResults}", "", false); } else { ModeratorChatCommand(SendChatPatch.ConvertNum($"Winner: {winner} ({highest})\n\n{voteResults}"), "", false); if (winner == "Eht Dleks") { CreateOptionsPickerPatch.ApplyDleks(Utils.isHideNSeek); } else { CreateOptionsPickerPatch.ClearDleks(); if (!Utils.isHideNSeek) Main.NormalOptions.MapId = mapId; else Main.HideNSeekOptions.MapId = mapId; } } skeldVotes = 0; miraVotes = 0; polusVotes = 0; airshipVotes = 0; fungleVotes = 0; dleksVotes = 0; HasVoted.Clear(); MapVoteActive = false; }, Options.MapVoteDuration.GetInt(), "MapPoll"); } }