Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of SteadyCrew v1.0.1
BepInEx/plugins/SteadyCrew.dll
Decompiled 9 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Text; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using GameNetcodeStuff; using HarmonyLib; using Steamworks; using Steamworks.Data; using TMPro; using Unity.Collections; using Unity.Netcode; using Unity.Netcode.Transports.UTP; using UnityEngine; using UnityEngine.Audio; using UnityEngine.Events; using UnityEngine.SceneManagement; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: AssemblyVersion("0.0.0.0")] namespace SteadyCrew; internal static class Crew { public const int Pool = 8; public const int Vanilla = 4; public const int Protocol = 1; public const string LobbyKey = "steadycrew"; private const uint HashBase = 1548615680u; public static bool Active; private static readonly FieldInfo HashField = typeof(NetworkObject).GetField("GlobalObjectIdHash", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); public static string Token => "sc" + 1 + "x" + 8; public static int Slots { get { StartOfRound instance = StartOfRound.Instance; if (instance == null) { return 4; } PlayerControllerB[] allPlayerScripts = instance.allPlayerScripts; if (allPlayerScripts == null) { return 4; } return allPlayerScripts.Length; } } public static bool Expanded { get { if (Active) { return Slots > 4; } return false; } } public static int HostLimit => Mathf.Clamp(Plugin.MaxPlayers.Value, 1, 8); public static int ApprovalLimit() { if (!Expanded) { return 4; } return Mathf.Min(HostLimit, Slots); } public static int LobbyCap(Lobby lobby) { try { if (!Active || string.IsNullOrEmpty(((Lobby)(ref lobby)).GetData("steadycrew"))) { return 4; } int maxMembers = ((Lobby)(ref lobby)).MaxMembers; return (maxMembers > 0) ? maxMembers : 4; } catch (Exception) { return 4; } } private static bool WantExpand() { //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) if (!Active) { return false; } NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null) { return false; } if (singleton.IsServer) { return true; } GameNetworkManager instance = GameNetworkManager.Instance; if ((Object)(object)instance == (Object)null) { return false; } if (instance.disableSteam) { return true; } if (!instance.currentLobby.HasValue) { return false; } Lobby value = instance.currentLobby.Value; string data = ((Lobby)(ref value)).GetData("steadycrew"); if (string.IsNullOrEmpty(data)) { return false; } if (data != Token) { Plugin.Warn("This lobby runs a different SteadyCrew build (" + data + ", ours " + Token + "); joining with the vanilla player count."); return false; } return true; } public static void OnPopulate(Scene scene) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) StartOfRound instance = StartOfRound.Instance; if (instance != null && !((Object)(object)instance == (Object)null)) { Scene scene2 = ((Component)instance).gameObject.scene; if (((Scene)(ref scene2)).handle == ((Scene)(ref scene)).handle && instance.allPlayerObjects != null && instance.allPlayerObjects.Length == 4 && WantExpand()) { Expand(instance); } } } private static void Expand(StartOfRound sor) { int num = sor.allPlayerObjects.Length; GameObject val = sor.allPlayerObjects[num - 1]; Transform parent = val.transform.parent; HashSet<uint> hashSet = new HashSet<uint>(); NetworkObject[] array = Object.FindObjectsOfType<NetworkObject>(true); foreach (NetworkObject obj in array) { hashSet.Add((uint)HashField.GetValue(obj)); } GameObject[] array2 = (GameObject[])(object)new GameObject[8]; PlayerControllerB[] array3 = (PlayerControllerB[])(object)new PlayerControllerB[8]; Array.Copy(sor.allPlayerObjects, array2, num); Array.Copy(sor.allPlayerScripts, array3, num); uint num2 = 1548615680u; for (int j = num; j < 8; j++) { GameObject val2 = Object.Instantiate<GameObject>(val, parent); ((Object)val2).name = "Player (" + j + ")"; NetworkObject[] componentsInChildren = val2.GetComponentsInChildren<NetworkObject>(true); foreach (NetworkObject obj2 in componentsInChildren) { for (; hashSet.Contains(num2); num2++) { } HashField.SetValue(obj2, num2); hashSet.Add(num2); } PlayerControllerB component = val2.GetComponent<PlayerControllerB>(); component.playerClientId = (ulong)j; component.playerUsername = "Player #" + j; component.isPlayerControlled = false; component.isPlayerDead = false; array2[j] = val2; array3[j] = component; } sor.allPlayerObjects = array2; sor.allPlayerScripts = array3; Plugin.Say("Player pool grown from " + num + " to " + 8 + " (" + (NetworkManager.Singleton.IsServer ? ("host, up to " + HostLimit + " players") : "client") + ")."); SyncArrays(); } public static void VerifyRegistered(Scene scene, Dictionary<uint, Dictionary<int, NetworkObject>> placed) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) StartOfRound instance = StartOfRound.Instance; if (instance == null || (Object)(object)instance == (Object)null || placed == null) { return; } Scene scene2 = ((Component)instance).gameObject.scene; if (((Scene)(ref scene2)).handle != ((Scene)(ref scene)).handle || instance.allPlayerObjects.Length <= 4) { return; } for (int i = 4; i < instance.allPlayerObjects.Length; i++) { NetworkObject[] componentsInChildren = instance.allPlayerObjects[i].GetComponentsInChildren<NetworkObject>(false); foreach (NetworkObject val in componentsInChildren) { bool? isSceneObject = val.IsSceneObject; if (!isSceneObject.HasValue || isSceneObject.Value) { uint key = (uint)HashField.GetValue(val); if (!placed.TryGetValue(key, out var value)) { value = (placed[key] = new Dictionary<int, NetworkObject>()); } if (!value.ContainsKey(((Scene)(ref scene)).handle)) { value[((Scene)(ref scene)).handle] = val; Plugin.Warn("Netcode skipped " + ((Object)val).name + " while collecting scene objects; registered it by hand."); } } } } } public static void SyncArrays() { //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Expected O, but got Unknown //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_0190: Expected O, but got Unknown StartOfRound instance = StartOfRound.Instance; if (instance == null || (Object)(object)instance == (Object)null || instance.allPlayerScripts == null) { return; } int num = instance.allPlayerScripts.Length; if (num <= 4) { return; } if (instance.gameStats != null) { PlayerStats[] allPlayerStats = instance.gameStats.allPlayerStats; int num2 = ((allPlayerStats != null) ? allPlayerStats.Length : 0); if (num2 < num) { PlayerStats[] array = (PlayerStats[])(object)new PlayerStats[num]; if (allPlayerStats != null) { Array.Copy(allPlayerStats, array, num2); } for (int i = num2; i < num; i++) { array[i] = new PlayerStats(); } instance.gameStats.allPlayerStats = array; } } Transform[] playerSpawnPositions = instance.playerSpawnPositions; if (playerSpawnPositions != null && playerSpawnPositions.Length > 0 && playerSpawnPositions.Length < num) { Transform[] array2 = (Transform[])(object)new Transform[num]; for (int j = 0; j < num; j++) { array2[j] = playerSpawnPositions[(j < playerSpawnPositions.Length) ? j : (j % playerSpawnPositions.Length)]; } instance.playerSpawnPositions = array2; } if ((Object)(object)instance.mapScreen != (Object)null && instance.mapScreen.radarTargets != null) { List<TransformAndName> radarTargets = instance.mapScreen.radarTargets; for (int k = 4; k < num; k++) { Transform transform = ((Component)instance.allPlayerScripts[k]).transform; bool flag = false; for (int l = 0; l < radarTargets.Count; l++) { if (radarTargets[l] != null && (Object)(object)radarTargets[l].transform == (Object)(object)transform) { flag = true; break; } } if (!flag) { radarTargets.Insert(Mathf.Min(k, radarTargets.Count), new TransformAndName(transform, instance.allPlayerScripts[k].playerUsername, false)); } } } SoundManager instance2 = SoundManager.Instance; if (!((Object)(object)instance2 != (Object)null)) { return; } instance2.playerVoicePitchTargets = Grow(instance2.playerVoicePitchTargets, num, 1f); instance2.playerVoicePitches = Grow(instance2.playerVoicePitches, num, 1f); instance2.playerVoicePitchLerpSpeed = Grow(instance2.playerVoicePitchLerpSpeed, num, 3f); instance2.playerVoiceVolumes = Grow(instance2.playerVoiceVolumes, num, 0.5f); AudioMixerGroup[] playerVoiceMixers = instance2.playerVoiceMixers; if (playerVoiceMixers == null || playerVoiceMixers.Length >= num) { return; } AudioMixerGroup val = null; try { AudioMixerGroup[] array3 = instance2.diageticMixer.FindMatchingGroups("Master"); if (array3 != null && array3.Length > 0) { val = array3[0]; } } catch (Exception) { } AudioMixerGroup[] array4 = (AudioMixerGroup[])(object)new AudioMixerGroup[num]; Array.Copy(playerVoiceMixers, array4, playerVoiceMixers.Length); for (int m = playerVoiceMixers.Length; m < num; m++) { array4[m] = val; } instance2.playerVoiceMixers = array4; } public static float[] Grow(float[] a, int n, float fallback) { if (a != null && a.Length >= n) { return a; } float[] array = new float[n]; int num = ((a != null) ? a.Length : 0); if (num > 0) { Array.Copy(a, array, num); } float num2 = ((num > 0) ? a[0] : fallback); for (int i = num; i < n; i++) { array[i] = num2; } return array; } public static int[] Grow(int[] a, int n, int fallback) { if (a != null && a.Length >= n) { return a; } int[] array = new int[n]; int num = ((a != null) ? a.Length : 0); if (num > 0) { Array.Copy(a, array, num); } int num2 = ((num > 0) ? a[0] : fallback); for (int i = num; i < n; i++) { array[i] = num2; } return array; } public static string RagdollTag(string format, object playerObjectId) { int num = ((playerObjectId is int) ? ((int)playerObjectId) : 0); if (num >= 4) { return "PlayerRagdoll"; } return string.Format(format, playerObjectId); } } internal static class Voice { public static bool SetFloat(AudioMixer mixer, string name, float value) { if (Parse(name, out var index, out var volume) && index >= 4) { if (volume) { Apply(index, value); } return true; } return mixer.SetFloat(name, value); } private static bool Parse(string name, out int index, out bool volume) { index = -1; volume = false; if (name == null || name.Length < 12 || name[0] != 'P') { return false; } int num; if (name.StartsWith("PlayerVolume", StringComparison.Ordinal)) { volume = true; num = 12; } else { if (!name.StartsWith("PlayerPitch", StringComparison.Ordinal)) { return false; } num = 11; } if (num >= name.Length) { return false; } int num2 = 0; for (int i = num; i < name.Length; i++) { char c = name[i]; if (c < '0' || c > '9') { return false; } num2 = num2 * 10 + (c - 48); } index = num2; return true; } private static void Apply(int index, float db) { StartOfRound instance = StartOfRound.Instance; if (instance != null && index < instance.allPlayerScripts.Length) { AudioSource currentVoiceChatAudioSource = instance.allPlayerScripts[index].currentVoiceChatAudioSource; if (!((Object)(object)currentVoiceChatAudioSource == (Object)null)) { float num = ((db <= -79f) ? 0f : Mathf.Pow(10f, db / 20f)); currentVoiceChatAudioSource.volume = Mathf.Clamp01(num); } } } } internal static class CrewPatches { private static readonly Type Me = typeof(CrewPatches); private static readonly MethodInfo SlotsGetter = AccessTools.PropertyGetter(typeof(Crew), "Slots"); private static readonly MethodInfo ApprovalLimit = AccessTools.Method(typeof(Crew), "ApprovalLimit", (Type[])null, (Type[])null); private static readonly MethodInfo LobbyCap = AccessTools.Method(typeof(Crew), "LobbyCap", (Type[])null, (Type[])null); private static readonly MethodInfo MixerSetFloat = AccessTools.Method(typeof(AudioMixer), "SetFloat", new Type[2] { typeof(string), typeof(float) }, (Type[])null); private static readonly MethodInfo MixerShim = AccessTools.Method(typeof(Voice), "SetFloat", (Type[])null, (Type[])null); private static readonly MethodInfo RagdollTag = AccessTools.Method(typeof(Crew), "RagdollTag", (Type[])null, (Type[])null); private static readonly MethodInfo FormatOne = AccessTools.Method(typeof(string), "Format", new Type[2] { typeof(string), typeof(object) }, (Type[])null); public static void Apply() { Plugin.Patch(Plugin.M(typeof(NetworkSceneManager), "PopulateScenePlacedObjects"), Me, "PopulatePrefix", "PopulatePostfix", null, null); Fours("StartOfRound", "OnClientConnect"); Fours("StartOfRound", "SyncShipUnlockablesServerRpc"); Fours("StartOfRound", "SyncShipUnlockablesClientRpc"); Fours("GameNetcodeStuff.PlayerControllerB", "SendNewPlayerValuesServerRpc"); Fours("EnemyAI", "GetClosestPlayer"); Fours("EnemyAI", "GetAllPlayersInLineOfSight"); Fours("CaveDwellerAI", "GetAllPlayerBodiesInLineOfSight"); Fours("BushWolfEnemy", "GetClosestPlayerToNest"); Fours("SpringManAI", "Update"); Fours("DressGirlAI", "ChoosePlayerToHaunt"); Fours("ButlerEnemyAI", "Start"); Fours("ForestGiantAI", "Start"); Fours("CrawlerAI", "Start"); Fours("BlobAI", "Start"); Plugin.Patch(AccessTools.Method(typeof(HUDManager), "SyncAllPlayerLevelsServerRpc", Type.EmptyTypes, (Type[])null), Me, null, null, "T_Slots", null); Plugin.Patch(Plugin.M(typeof(SoundManager), "Start"), Me, null, "ArraysPostfix", null, null); Plugin.Patch(Plugin.M(typeof(StartOfRound), "Start"), Me, "ArraysPrefix", null, null, null); Plugin.Patch(Plugin.M(typeof(ShipTeleporter), "Awake"), Me, null, "TeleporterPostfix", null, null); Plugin.Patch(Plugin.M(typeof(SoundManager), "SetPlayerVoiceFilters"), Me, null, null, "T_Mixer", null); Plugin.Patch(Plugin.M(typeof(SoundManager), "SetPlayerPitch"), Me, null, null, "T_Mixer", null); Plugin.Patch(Plugin.M(typeof(DeadBodyInfo), "Start"), Me, null, null, "T_RagdollTag", null); Plugin.Patch(Plugin.M(typeof(PlayerPhysicsRegion), "OnTriggerStay"), Me, "PhysicsRegionPrefix", null, null, null); Plugin.Patch(Plugin.M(typeof(SteamMatchmaking), "CreateLobbyAsync"), Me, "CreateLobbyPrefix", null, null, null); Plugin.Patch(Plugin.M(typeof(GameNetworkManager), "SteamMatchmaking_OnLobbyCreated"), Me, null, "LobbyCreatedPostfix", null, null); Plugin.Patch(Plugin.M(typeof(GameNetworkManager), "LobbyDataIsJoinable"), Me, null, null, "T_Lobby", null); Plugin.Patch(Plugin.M(typeof(GameNetworkManager), "SetConnectionDataBeforeConnecting"), Me, null, "ConnectionDataPostfix", null, null); Plugin.Patch(Plugin.M(typeof(GameNetworkManager), "ConnectionApproval"), Me, null, "ApprovalPostfix", "T_Approval", null); } private static void Fours(string typeName, string method) { Type type = AccessTools.TypeByName(typeName); if (type == null) { Plugin.Warn("type " + typeName + " is gone from the game - skipped"); } else { Plugin.Patch(AccessTools.Method(type, method, (Type[])null, (Type[])null), Me, null, null, "T_Slots", null); } } private static void PopulatePrefix(Scene sceneToFilterBy) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) try { Crew.OnPopulate(sceneToFilterBy); } catch (Exception ex) { Plugin.Log.LogError((object)("growing the player pool failed: " + ex)); } } private static void PopulatePostfix(Scene sceneToFilterBy, Dictionary<uint, Dictionary<int, NetworkObject>> ___ScenePlacedObjects) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) try { Crew.VerifyRegistered(sceneToFilterBy, ___ScenePlacedObjects); } catch (Exception ex) { Plugin.Log.LogError((object)("checking the player pool failed: " + ex)); } } private static bool IsCountUse(CodeInstruction next) { OpCode opcode = next.opcode; if (opcode == OpCodes.Blt || opcode == OpCodes.Blt_S || opcode == OpCodes.Bge || opcode == OpCodes.Bge_S || opcode == OpCodes.Bne_Un || opcode == OpCodes.Bne_Un_S || opcode == OpCodes.Beq || opcode == OpCodes.Beq_S || opcode == OpCodes.Bgt || opcode == OpCodes.Bgt_S || opcode == OpCodes.Ble || opcode == OpCodes.Ble_S || opcode == OpCodes.Clt || opcode == OpCodes.Cgt || opcode == OpCodes.Ceq) { return true; } if (opcode == OpCodes.Newarr) { Type type = next.operand as Type; if (type != typeof(object)) { return type != typeof(string); } return false; } if (opcode == OpCodes.Newobj) { ConstructorInfo constructorInfo = next.operand as ConstructorInfo; if (constructorInfo != null && constructorInfo.DeclaringType.IsGenericType) { return constructorInfo.DeclaringType.GetGenericTypeDefinition() == typeof(List<>); } return false; } return false; } private static IEnumerable<CodeInstruction> Replace(IEnumerable<CodeInstruction> source, MethodBase original, MethodInfo with) { List<CodeInstruction> list = new List<CodeInstruction>(source); int num = 0; for (int i = 0; i < list.Count - 1; i++) { if (!(list[i].opcode != OpCodes.Ldc_I4_4) && IsCountUse(list[i + 1])) { list[i].opcode = OpCodes.Call; list[i].operand = with; num++; } } if (num == 0) { Plugin.Warn(original.DeclaringType.Name + "." + original.Name + ": no player-count constant found (already patched by another mod, or the game changed)."); } else { Plugin.Dbg(original.DeclaringType.Name + "." + original.Name + ": " + num + " constant(s) now follow the player pool."); } return list; } private static IEnumerable<CodeInstruction> T_Slots(IEnumerable<CodeInstruction> instructions, MethodBase original) { return Replace(instructions, original, SlotsGetter); } private static IEnumerable<CodeInstruction> T_Approval(IEnumerable<CodeInstruction> instructions, MethodBase original) { return Replace(instructions, original, ApprovalLimit); } private static IEnumerable<CodeInstruction> T_Lobby(IEnumerable<CodeInstruction> instructions, MethodBase original) { //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Expected O, but got Unknown List<CodeInstruction> list = new List<CodeInstruction>(instructions); int num = 0; for (int i = 0; i < list.Count - 1; i++) { if (!(list[i].opcode != OpCodes.Ldc_I4_4)) { OpCode opcode = list[i + 1].opcode; if (!(opcode != OpCodes.Bge) || !(opcode != OpCodes.Bge_S) || !(opcode != OpCodes.Blt) || !(opcode != OpCodes.Blt_S)) { list[i].opcode = OpCodes.Ldarg_1; list[i].operand = null; list.Insert(i + 1, new CodeInstruction(OpCodes.Call, (object)LobbyCap)); num++; i++; } } } if (num == 0) { Plugin.Warn("LobbyDataIsJoinable: member limit check not found."); } return list; } private static IEnumerable<CodeInstruction> T_Mixer(IEnumerable<CodeInstruction> instructions) { foreach (CodeInstruction c in instructions) { if ((c.opcode == OpCodes.Callvirt || c.opcode == OpCodes.Call) && c.operand is MethodInfo && (MethodInfo)c.operand == MixerSetFloat) { c.opcode = OpCodes.Call; c.operand = MixerShim; } yield return c; } } private static IEnumerable<CodeInstruction> T_RagdollTag(IEnumerable<CodeInstruction> instructions) { bool armed = false; foreach (CodeInstruction c in instructions) { if (c.opcode == OpCodes.Ldstr && c.operand as string == "PlayerRagdoll{0}") { armed = true; } else if (armed && c.opcode == OpCodes.Call && c.operand is MethodInfo && (MethodInfo)c.operand == FormatOne) { c.operand = RagdollTag; armed = false; } yield return c; } } private static void ArraysPrefix() { Guarded(Crew.SyncArrays); } private static void ArraysPostfix() { Guarded(Crew.SyncArrays); } private static void TeleporterPostfix(ShipTeleporter __instance, ref int[] ___playersBeingTeleported) { try { ___playersBeingTeleported = Crew.Grow(___playersBeingTeleported, Crew.Slots, -1); } catch (Exception ex) { Plugin.Log.LogError((object)("ShipTeleporter: " + ex)); } } private static void Guarded(Action a) { try { a(); } catch (Exception ex) { Plugin.Log.LogError((object)ex.ToString()); } } private static bool PhysicsRegionPrefix(PlayerPhysicsRegion __instance, Collider other) { try { if ((Object)(object)other == (Object)null || ((Component)other).gameObject.layer != 20 || ((Object)((Component)other).gameObject).name != "spine.002") { return true; } DeadBodyInfo componentInParent = ((Component)other).GetComponentInParent<DeadBodyInfo>(); if ((Object)(object)componentInParent == (Object)null || componentInParent.playerObjectId < 4) { return true; } if ((Object)(object)GameNetworkManager.Instance.localPlayerController == (Object)null) { return true; } if (!componentInParent.isParentedToPhysicsRegion) { componentInParent.SetPhysicsParent(__instance.physicsTransform, __instance.physicsCollider); } return false; } catch (Exception) { return true; } } private static void CreateLobbyPrefix(ref int maxMembers) { maxMembers = Crew.HostLimit; } private static void LobbyCreatedPostfix(ref Lobby lobby) { try { ((Lobby)(ref lobby)).SetData("steadycrew", Crew.Token); if (((Lobby)(ref lobby)).MaxMembers != Crew.HostLimit) { ((Lobby)(ref lobby)).MaxMembers = Crew.HostLimit; } Plugin.Say("Steam lobby created for up to " + Crew.HostLimit + " players."); } catch (Exception ex) { Plugin.Log.LogError((object)("lobby setup: " + ex)); } } private static void ConnectionDataPostfix() { try { NetworkManager singleton = NetworkManager.Singleton; if (!((Object)(object)singleton == (Object)null) && singleton.NetworkConfig.ConnectionData != null) { string text = Encoding.ASCII.GetString(singleton.NetworkConfig.ConnectionData); if (!text.Contains("," + Crew.Token)) { singleton.NetworkConfig.ConnectionData = Encoding.ASCII.GetBytes(text + "," + Crew.Token); } } } catch (Exception ex) { Plugin.Log.LogError((object)("connection data: " + ex)); } } private static void ApprovalPostfix(ConnectionApprovalRequest request, ConnectionApprovalResponse response) { try { if (!response.Approved || !Crew.Expanded) { return; } NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton != (Object)null && request.ClientNetworkId == singleton.LocalClientId) { return; } string text = ((request.Payload != null) ? Encoding.ASCII.GetString(request.Payload) : ""); string text2 = null; string[] array = text.Split(','); foreach (string text3 in array) { if (text3.StartsWith("sc", StringComparison.Ordinal)) { text2 = text3; } } if (!(text2 == Crew.Token)) { response.Approved = false; response.Reason = ((text2 == null) ? "This lobby needs the SteadyCrew mod (1.0.1)." : ("SteadyCrew version mismatch: host " + Crew.Token + ", you " + text2 + ".")); Plugin.Say("Join refused: " + response.Reason); } } catch (Exception ex) { Plugin.Log.LogError((object)("approval: " + ex)); } } } internal static class FlowGuard { private enum Wait { LevelGen, DoorsLoad, Unload, Revive, Fired } private class WaitState { public string label; public float start = -1f; public float lastPoll = -100f; public float lastNotice; } private static readonly Type Me = typeof(FlowGuard); private static readonly Dictionary<MethodBase, Wait> waits = new Dictionary<MethodBase, Wait>(); private static readonly WaitState[] states = new WaitState[5] { new WaitState { label = "level generation" }, new WaitState { label = "moon loading" }, new WaitState { label = "moon unloading" }, new WaitState { label = "end-of-round revive" }, new WaitState { label = "end-of-quota revive" } }; private static readonly HashSet<ulong> expected = new HashSet<ulong>(); private static readonly List<ulong> missing = new List<ulong>(); private static float driftSince = -1f; private static NetworkSceneManager hooked; private static readonly Dictionary<ulong, float> sceneDone = new Dictionary<ulong, float>(); private static readonly FieldInfo StageField = typeof(NetworkBehaviour).GetField("__rpc_exec_stage", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); private static readonly MethodInfo GenerateRpc = AccessTools.Method(typeof(RoundManager), "GenerateNewLevelClientRpc", (Type[])null, (Type[])null); private static readonly MethodInfo FinishRpc = AccessTools.Method(typeof(RoundManager), "FinishGeneratingNewLevelClientRpc", (Type[])null, (Type[])null); private static AsyncOperation loadOp; private static float loadDoneAt = -1f; private static object[] heldGenerate; private static bool heldFinish; private static bool replaying; private static bool generatedLate; private static float heldSince; private static readonly FieldInfo DungeonField = AccessTools.Field(typeof(RoundManager), "dungeonGenerator"); private static float orbitSince; private static readonly Dictionary<ulong, float> connectedAt = new Dictionary<ulong, float>(); private static bool reviveCalled; private static bool passTimeCalled; private static bool quotaCalled; private static bool coroutineStarted; private static readonly FieldInfo RevivedField = AccessTools.Field(typeof(StartOfRound), "playersRevived"); private static readonly MethodInfo PassTime = AccessTools.Method(typeof(StartOfRound), "PassTimeToNextDay", (Type[])null, (Type[])null); private static readonly MethodInfo EndStats = AccessTools.Method(typeof(StartOfRound), "GetEndgameStatsInOrder", (Type[])null, (Type[])null); private static FieldInfo machineThis; private static FieldInfo machineBodies; private static FieldInfo machinePlayers; private static float nextTick; private static bool lastInShip; private static float phaseSince; private static bool orbitRepairDone; private static float livingDriftSince = -1f; private static float allDeadSince = -1f; private static readonly HashSet<int> deadAtOrbit = new HashSet<int>(); private static bool On => Plugin.GuardEnabled.Value; private static float Now => Time.realtimeSinceStartup; public static void Apply() { HookWaits(typeof(StartOfRound), "<unloadSceneForAllPlayers>b__", Wait.Unload); HookWaits(typeof(StartOfRound), "<OpenShipDoors>b__", Wait.DoorsLoad); HookWaits(typeof(StartOfRound), "<EndOfGame>b__", Wait.Revive); HookWaits(typeof(StartOfRound), "<playersFiredGameOver>b__", Wait.Fired); HookWaits(typeof(RoundManager), "<LoadNewLevelWait>b__", Wait.LevelGen); Plugin.Patch(Plugin.M(typeof(StartOfRound), "SceneManager_OnLoad"), Me, null, "SceneLoadStartedPostfix", null, null); Plugin.Patch(Plugin.M(typeof(StartOfRound), "SceneManager_OnLoadComplete1"), Me, null, "SceneLoadDonePostfix", null, null); Plugin.Patch(GenerateRpc, Me, "GeneratePrefix", null, null, null); Plugin.Patch(FinishRpc, Me, "FinishPrefix", null, null, null); Plugin.Patch(Plugin.M(typeof(StartMatchLever), "StartGame"), Me, "LaunchPrefix", null, null, null); Plugin.Patch(Plugin.M(typeof(StartOfRound), "StartGameServerRpc"), Me, "LaunchPrefix", null, null, null); Plugin.Patch(Plugin.M(typeof(StartOfRound), "SetShipReadyToLand"), Me, null, "OrbitPostfix", null, null); Plugin.Patch(Plugin.M(typeof(StartOfRound), "Start"), Me, null, "OrbitPostfix", null, null); Plugin.Patch(Plugin.M(typeof(StartOfRound), "OnClientConnect"), Me, null, "ClientConnectPostfix", null, null); Plugin.Patch(Plugin.M(typeof(StartOfRound), "ReviveDeadPlayers"), Me, "RevivePrefix", null, "T_Revive", "ReviveFinalizer"); Plugin.Patch(Plugin.M(typeof(StartOfRound), "EndOfGameClientRpc"), Me, "EndRpcPrefix", null, null, "EndRpcFinalizer"); Plugin.Patch(Plugin.M(typeof(StartOfRound), "WritePlayerNotes"), Me, null, null, null, "Swallow"); Plugin.Patch(Plugin.M(typeof(HUDManager), "FillEndGameStats"), Me, null, null, null, "Swallow"); Plugin.Patch(Plugin.M(typeof(StartOfRound), "OnShipLandedMiscEvents"), Me, null, null, null, "Swallow"); Plugin.Patch(Plugin.M(typeof(StartOfRound), "PassTimeToNextDay"), Me, "PassTimePrefix", null, null, null); Plugin.Patch(Plugin.M(typeof(TimeOfDay), "SetNewProfitQuota"), Me, "QuotaPrefix", null, null, null); Plugin.Patch(Plugin.M(typeof(StartOfRound), "EndOfGame"), Me, null, "EndCoroutinePostfix", null, null); Type type = null; Type[] nestedTypes = typeof(StartOfRound).GetNestedTypes(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (Type type2 in nestedTypes) { if (type2.Name.StartsWith("<EndOfGame>d__", StringComparison.Ordinal)) { type = type2; } } if (type == null) { Plugin.Warn("EndOfGame coroutine class not found - its recovery is off."); return; } machineThis = type.GetField("<>4__this", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); machineBodies = type.GetField("bodiesInsured", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); machinePlayers = type.GetField("connectedPlayersOnServer", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); Plugin.Patch(type.GetMethod("MoveNext", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic), Me, null, null, null, "EndStepFinalizer"); } private static void HookWaits(Type type, string prefix, Wait kind) { int num = 0; MethodInfo[] methods = type.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (!methodInfo.Name.StartsWith(prefix, StringComparison.Ordinal) || methodInfo.ReturnType != typeof(bool) || methodInfo.GetParameters().Length != 0) { continue; } bool flag = false; foreach (CodeInstruction originalInstruction in PatchProcessor.GetOriginalInstructions((MethodBase)methodInfo, (ILGenerator)null)) { FieldInfo fieldInfo = originalInstruction.operand as FieldInfo; if (fieldInfo != null && fieldInfo.Name == "connectedPlayers") { flag = true; break; } } if (flag && Plugin.Patch(methodInfo, Me, null, "WaitPostfix", null, null)) { waits[methodInfo] = kind; num++; } } if (num == 0) { Plugin.Warn("wait point '" + prefix + "' not found - " + states[(int)kind].label + " is not guarded."); } } private static HashSet<ulong> Expected() { expected.Clear(); NetworkManager singleton = NetworkManager.Singleton; StartOfRound instance = StartOfRound.Instance; if ((Object)(object)singleton == (Object)null || (Object)(object)instance == (Object)null || !singleton.IsServer) { return expected; } expected.Add(singleton.LocalClientId); IReadOnlyList<ulong> connectedClientsIds = singleton.ConnectedClientsIds; for (int i = 0; i < connectedClientsIds.Count; i++) { if (instance.ClientPlayerList.ContainsKey(connectedClientsIds[i])) { expected.Add(connectedClientsIds[i]); } } return expected; } private static string NameOf(ulong clientId) { try { StartOfRound instance = StartOfRound.Instance; if (instance.ClientPlayerList.TryGetValue(clientId, out var value) && value >= 0 && value < instance.allPlayerScripts.Length) { return instance.allPlayerScripts[value].playerUsername; } } catch (Exception) { } return "client " + clientId; } private static string Names(List<ulong> ids) { string[] array = new string[ids.Count]; for (int i = 0; i < ids.Count; i++) { array[i] = NameOf(ids[i]); } return string.Join(", ", array); } private static void Reconcile(bool now) { NetworkManager singleton = NetworkManager.Singleton; StartOfRound instance = StartOfRound.Instance; GameNetworkManager instance2 = GameNetworkManager.Instance; if ((Object)(object)singleton == (Object)null || (Object)(object)instance == (Object)null || (Object)(object)instance2 == (Object)null || !singleton.IsServer || !singleton.IsListening) { driftSince = -1f; return; } int count = Expected().Count; if (instance2.connectedPlayers == count && instance.connectedPlayersAmount == count - 1) { driftSince = -1f; return; } if (driftSince < 0f) { driftSince = Now; } if (now || !(Now - driftSince < 3f)) { Plugin.Warn("Player counters drifted (connectedPlayers " + instance2.connectedPlayers + ", connectedPlayersAmount " + instance.connectedPlayersAmount + ") while " + count + " player(s) are really in the game - corrected."); instance2.connectedPlayers = count; instance.connectedPlayersAmount = count - 1; driftSince = -1f; } } private static void HookScenes() { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Expected O, but got Unknown NetworkManager singleton = NetworkManager.Singleton; if (!((Object)(object)singleton == (Object)null) && singleton.IsServer && singleton.SceneManager != null && hooked != singleton.SceneManager) { hooked = singleton.SceneManager; hooked.OnSceneEvent += new SceneEventDelegate(OnSceneEvent); sceneDone.Clear(); connectedAt.Clear(); } } private static void OnSceneEvent(SceneEvent e) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Expected I4, but got Unknown SceneEventType sceneEventType = e.SceneEventType; switch ((int)sceneEventType) { case 0: case 1: sceneDone.Clear(); break; case 6: case 7: case 8: sceneDone[e.ClientId] = Now; break; case 2: case 3: case 4: case 5: break; } } private static float Limit(Wait kind) { switch (kind) { case Wait.LevelGen: case Wait.DoorsLoad: return Plugin.LoadTimeout.Value; case Wait.Unload: return Plugin.UnloadTimeout.Value; default: return Plugin.ReviveTimeout.Value; } } private static bool CanCatchUp(Wait kind) { if (kind != Wait.LevelGen) { return kind != Wait.DoorsLoad; } return false; } private static List<ulong> ListFor(Wait kind) { switch (kind) { case Wait.LevelGen: return RoundManager.Instance.playersFinishedGeneratingFloor; case Wait.DoorsLoad: case Wait.Unload: return StartOfRound.Instance.fullyLoadedPlayers; default: return null; } } private static void WaitPostfix(MethodBase __originalMethod, ref bool __result) { try { if (!On || !waits.TryGetValue(__originalMethod, out var value)) { return; } WaitState waitState = states[(int)value]; float now = Now; if (now - waitState.lastPoll > 2f) { waitState.start = now; waitState.lastNotice = now; } waitState.lastPoll = now; if (__result) { return; } Reconcile(now: false); HashSet<ulong> hashSet = Expected(); List<ulong> list = ListFor(value); StartOfRound instance = StartOfRound.Instance; missing.Clear(); bool flag; if (list != null) { foreach (ulong item in hashSet) { if (!list.Contains(item)) { if (value != Wait.LevelGen && sceneDone.TryGetValue(item, out var value2) && value2 >= waitState.start - 0.5f && now - value2 > 3f) { list.Add(item); Plugin.Warn(NameOf(item) + " finished " + waitState.label + " but its report never arrived - counted anyway."); } else { missing.Add(item); } } } flag = missing.Count == 0; } else { flag = Revived(instance) >= hashSet.Count; } if (flag) { Plugin.Dbg(waitState.label + ": everyone really connected has reported."); __result = true; return; } float num = now - waitState.start; float num2 = Limit(value); string text = ((list != null) ? Names(missing) : (hashSet.Count - Revived(instance) + " player(s)")); if (num2 > 0f && num > num2) { if (CanCatchUp(value)) { Plugin.Warn("Waited " + num.ToString("0") + "s for " + text + " (" + waitState.label + ") - going on, they will catch up."); if (list != null) { foreach (ulong item2 in missing) { list.Add(item2); } } else { SetRevived(instance, hashSet.Count); } Announce("[SteadyCrew] no longer waiting for " + text + " (" + waitState.label + ")."); __result = true; return; } Plugin.Warn("Waited " + num.ToString("0") + "s for " + text + " (" + waitState.label + ") - disconnecting them so the round can start; they can join again."); Announce("[SteadyCrew] " + text + " did not finish loading in " + num2.ToString("0") + "s and was disconnected."); NetworkManager singleton = NetworkManager.Singleton; ulong[] array = missing.ToArray(); foreach (ulong num3 in array) { if (num3 != singleton.LocalClientId) { try { singleton.DisconnectClient(num3, "Your game did not finish loading the moon in time. Join again while the ship is in orbit."); } catch (Exception ex) { Plugin.Log.LogError((object)("disconnecting client " + num3 + ": " + ex)); } } } waitState.start = now; } else if (now - waitState.lastNotice > 15f) { waitState.lastNotice = now; Plugin.Say("Still waiting (" + num.ToString("0") + "s" + ((num2 > 0f) ? (" of " + num2.ToString("0") + "s") : "") + ") on " + waitState.label + ": " + text); if (num > 25f) { Announce("[SteadyCrew] waiting for " + text + " (" + waitState.label + ", " + num.ToString("0") + "s)."); } } } catch (Exception ex2) { Plugin.Log.LogError((object)("wait guard: " + ex2)); } } private static void Announce(string text) { try { if (Plugin.AnnounceInChat.Value && (Object)(object)HUDManager.Instance != (Object)null && NetworkManager.Singleton.IsServer) { HUDManager.Instance.AddTextToChatOnServer(text, -1); } } catch (Exception) { } } private static bool Executing(NetworkBehaviour nb) { if (StageField != null) { return Convert.ToInt32(StageField.GetValue(nb)) == 1; } return false; } private static bool StillLoading() { if (loadOp == null) { return false; } if (!loadOp.isDone) { loadDoneAt = -1f; return true; } if (loadDoneAt < 0f) { loadDoneAt = Now; } return Now - loadDoneAt < 2f; } private static void SceneLoadStartedPostfix(StartOfRound __instance, AsyncOperation __3) { if (!((NetworkBehaviour)__instance).IsServer) { loadOp = __3; loadDoneAt = -1f; } } private static void SceneLoadDonePostfix(StartOfRound __instance) { if (!((NetworkBehaviour)__instance).IsServer) { loadOp = null; } } private static bool GenerationRunning(RoundManager rm) { Object val = (Object)((DungeonField != null) ? /*isinst with value type is only supported in some contexts*/: null); if (val != (Object)null && (Object)(object)rm.currentLevel != (Object)null && rm.currentLevel.spawnEnemiesAndScrap) { return !rm.dungeonCompletedGenerating; } return false; } private static bool GeneratePrefix(RoundManager __instance, int randomSeed, int levelID, int moldIterations, int moldStartPosition, int[] syncDestroyedMold) { try { if (!On || replaying || ((NetworkBehaviour)__instance).IsServer || !Executing((NetworkBehaviour)(object)__instance)) { return true; } heldGenerate = null; heldFinish = false; generatedLate = false; if (!StillLoading()) { return true; } heldGenerate = new object[5] { randomSeed, levelID, moldIterations, moldStartPosition, syncDestroyedMold }; heldSince = Now; Plugin.Warn("The host started generating the level while this machine is still loading the moon - holding that until the moon is in."); return false; } catch (Exception ex) { Plugin.Log.LogError((object)("late loader: " + ex)); return true; } } private static bool FinishPrefix(RoundManager __instance) { try { if (!On || replaying || ((NetworkBehaviour)__instance).IsServer || !Executing((NetworkBehaviour)(object)__instance)) { return true; } if (heldGenerate == null && (!generatedLate || !GenerationRunning(__instance))) { return true; } heldFinish = true; Plugin.Warn("The host finished the landing before this machine generated the level - holding that step as well."); return false; } catch (Exception ex) { Plugin.Log.LogError((object)("late loader: " + ex)); return true; } } private static void Replay(NetworkBehaviour target, MethodInfo rpc, object[] args) { object value = StageField.GetValue(target); replaying = true; try { StageField.SetValue(target, Enum.ToObject(StageField.FieldType, 1)); rpc.Invoke(target, args); } catch (Exception ex) { Plugin.Log.LogError((object)("late loader replay of " + rpc.Name + ": " + (ex.InnerException ?? ex))); } finally { StageField.SetValue(target, value); replaying = false; } } private static void LateLoaderTick() { if (heldGenerate == null && !heldFinish) { return; } RoundManager instance = RoundManager.Instance; if ((Object)(object)instance == (Object)null || ((NetworkBehaviour)instance).IsServer || StageField == null || GenerateRpc == null || FinishRpc == null) { heldGenerate = null; heldFinish = false; } else if (heldGenerate != null) { if (!StillLoading()) { object[] args = heldGenerate; heldGenerate = null; generatedLate = true; Plugin.Warn("Moon loaded " + (Now - heldSince).ToString("0") + "s after the host asked for the level - generating it now."); Replay((NetworkBehaviour)(object)instance, GenerateRpc, args); } } else if (heldFinish && !GenerationRunning(instance)) { heldFinish = false; Plugin.Warn("Level generated - finishing the landing on this machine."); Replay((NetworkBehaviour)(object)instance, FinishRpc, new object[0]); } } private static void OrbitPostfix() { orbitSince = Now; } private static void ClientConnectPostfix(ulong clientId) { connectedAt[clientId] = Now; } private static void LaunchPrefix() { try { if (!On) { return; } StartOfRound instance = StartOfRound.Instance; NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)instance == (Object)null || (Object)(object)singleton == (Object)null || !singleton.IsListening || !instance.inShipPhase || instance.travellingToNewLevel) { return; } List<ulong> fullyLoadedPlayers = instance.fullyLoadedPlayers; if (!singleton.IsServer) { foreach (ulong key in instance.ClientPlayerList.Keys) { if (!fullyLoadedPlayers.Contains(key)) { fullyLoadedPlayers.Add(key); } } ulong num = ulong.MaxValue; while (fullyLoadedPlayers.Count < instance.connectedPlayersAmount + 1) { fullyLoadedPlayers.Add(num); num--; } return; } Reconcile(now: true); float now = Now; foreach (ulong item in Expected()) { if (!fullyLoadedPlayers.Contains(item)) { float value; float num2 = (connectedAt.TryGetValue(item, out value) ? Mathf.Max(orbitSince, value) : orbitSince); if (sceneDone.ContainsKey(item) || now - num2 > Plugin.LaunchGrace.Value) { fullyLoadedPlayers.Add(item); Plugin.Warn(NameOf(item) + " was never marked as loaded in orbit - counted as loaded so the lever works."); } } } } catch (Exception ex) { Plugin.Log.LogError((object)("lever guard: " + ex)); } } private static int Revived(StartOfRound sor) { return (int)RevivedField.GetValue(sor); } private static void SetRevived(StartOfRound sor, int value) { RevivedField.SetValue(sor, value); } private static void RevivePrefix() { reviveCalled = true; } private static void PassTimePrefix() { passTimeCalled = true; } private static void QuotaPrefix() { quotaCalled = true; } private static void EndCoroutinePostfix() { coroutineStarted = true; } private static void EndRpcPrefix() { reviveCalled = false; passTimeCalled = false; quotaCalled = false; coroutineStarted = false; } private static Exception Swallow(Exception __exception, MethodBase __originalMethod) { if (__exception != null) { Plugin.Log.LogError((object)(__originalMethod.DeclaringType.Name + "." + __originalMethod.Name + " threw; carrying on without it: " + __exception)); } return null; } private static Exception EndRpcFinalizer(StartOfRound __instance, Exception __exception, int bodiesInsured, int connectedPlayersOnServer, int scrapCollectedOnServer) { if (__exception == null) { return null; } Plugin.Log.LogError((object)("EndOfGameClientRpc threw: " + __exception)); try { if (On && !coroutineStarted && (Object)(object)Runner.Instance != (Object)null) { Plugin.Warn("The end-of-round sequence never started on this machine - running the recovery sequence."); ((MonoBehaviour)Runner.Instance).StartCoroutine(FinishEndOfGame(__instance, connectedPlayersOnServer)); } } catch (Exception ex) { Plugin.Log.LogError((object)("end-of-round recovery: " + ex)); } return null; } private static Exception EndStepFinalizer(object __instance, Exception __exception, ref bool __result) { if (__exception == null) { return null; } Plugin.Log.LogError((object)("The end-of-round sequence threw and would have stopped here: " + __exception)); try { __result = false; if (On && (Object)(object)Runner.Instance != (Object)null) { StartOfRound sor = (StartOfRound)((machineThis != null) ? ((object)/*isinst with value type is only supported in some contexts*/) : ((object)StartOfRound.Instance)); int connectedPlayersOnServer = ((machinePlayers != null) ? ((int)machinePlayers.GetValue(__instance)) : 0); ((MonoBehaviour)Runner.Instance).StartCoroutine(FinishEndOfGame(sor, connectedPlayersOnServer)); } } catch (Exception ex) { Plugin.Log.LogError((object)("end-of-round recovery: " + ex)); } return null; } private static IEnumerator FinishEndOfGame(StartOfRound sor, int connectedPlayersOnServer) { yield return (object)new WaitForSeconds(1f); if ((Object)(object)sor == (Object)null) { yield break; } if (!reviveCalled) { try { sor.ReviveDeadPlayers(); } catch (Exception ex) { Plugin.Log.LogError((object)("recovery revive: " + ex)); } try { RoundManager.Instance.ResetEnemyVariables(); } catch (Exception ex2) { Plugin.Log.LogError((object)("recovery enemies: " + ex2)); } } yield return (object)new WaitForSeconds(2f); if ((Object)(object)sor == (Object)null) { yield break; } if (!passTimeCalled) { try { PassTime.Invoke(sor, new object[1] { connectedPlayersOnServer }); } catch (Exception ex3) { Plugin.Log.LogError((object)("recovery day change: " + ex3)); } } yield return (object)new WaitForSeconds(1.7f); if ((Object)(object)sor == (Object)null) { yield break; } try { HUDManager.Instance.HideHUD(false); } catch (Exception) { } sor.shipIsLeaving = false; if (!((NetworkBehaviour)sor).IsServer) { try { sor.PlayerHasRevivedServerRpc(); } catch (Exception ex5) { Plugin.Log.LogError((object)("recovery report: " + ex5)); } Plugin.Say("End-of-round recovery finished (client)."); yield break; } SetRevived(sor, Revived(sor) + 1); float start = Now; while ((Object)(object)sor != (Object)null && Revived(sor) < Expected().Count && Now - start < Plugin.ReviveTimeout.Value) { yield return null; } if ((Object)(object)sor == (Object)null) { yield break; } SetRevived(sor, 0); try { TimeOfDay instance = TimeOfDay.Instance; bool flag = instance.timeUntilDeadline <= 0f; if ((float)(instance.profitQuota - instance.quotaFulfilled) <= 0f || sor.isChallengeFile) { if (!sor.isChallengeFile && !quotaCalled) { instance.SetNewProfitQuota(); } sor.AllPlayersHaveRevivedClientRpc(); } else if (flag) { sor.FirePlayersAfterDeadlineClientRpc((int[])EndStats.Invoke(sor, null), false); } else { sor.AllPlayersHaveRevivedClientRpc(); } } catch (Exception ex6) { Plugin.Log.LogError((object)("recovery host tail: " + ex6)); } Plugin.Say("End-of-round recovery finished (host)."); } private static IEnumerable<CodeInstruction> T_Revive(IEnumerable<CodeInstruction> instructions, ILGenerator generator) { List<CodeInstruction> list = new List<CodeInstruction>(instructions); int num = -1; for (int i = 0; i < list.Count; i++) { if (list[i].opcode == OpCodes.Ldstr && list[i].operand as string == "Reviving players G") { num = i; break; } } if (num < 0) { Plugin.Warn("ReviveDeadPlayers changed shape - early-return fix not applied."); return list; } Label label = generator.DefineLabel(); list[num].labels.Add(label); int num2 = 0; for (int j = 0; j < num; j++) { if (!(list[j].opcode != OpCodes.Ret)) { list[j].opcode = OpCodes.Br; list[j].operand = label; num2++; } } Plugin.Dbg("ReviveDeadPlayers: " + num2 + " early return(s) now continue with the next player."); return list; } private static Exception ReviveFinalizer(Exception __exception) { if (__exception == null) { return null; } Plugin.Log.LogError((object)("ReviveDeadPlayers threw part-way; finishing the revive by hand: " + __exception)); try { Recovery.FinishRevive(); } catch (Exception ex) { Plugin.Log.LogError((object)("manual revive: " + ex)); } return null; } public static void Tick() { if (!On || Time.unscaledTime < nextTick) { return; } nextTick = Time.unscaledTime + 0.5f; StartOfRound instance = StartOfRound.Instance; GameNetworkManager instance2 = GameNetworkManager.Instance; NetworkManager singleton = NetworkManager.Singleton; if (instance == null || (Object)(object)instance == (Object)null || (Object)(object)instance2 == (Object)null || (Object)(object)singleton == (Object)null || !singleton.IsListening || (Object)(object)instance2.localPlayerController == (Object)null) { livingDriftSince = (allDeadSince = -1f); return; } if (singleton.IsServer) { HookScenes(); Reconcile(now: false); } else { try { LateLoaderTick(); } catch (Exception ex) { Plugin.Log.LogError((object)("late loader: " + ex)); } } float now = Now; if (instance.inShipPhase != lastInShip) { lastInShip = instance.inShipPhase; phaseSince = now; orbitRepairDone = false; deadAtOrbit.Clear(); if (instance.inShipPhase) { for (int i = 0; i < instance.allPlayerScripts.Length; i++) { if (instance.allPlayerScripts[i].isPlayerDead) { deadAtOrbit.Add(i); } } } } if (instance.inShipPhase && !instance.shipIsLeaving && !orbitRepairDone && deadAtOrbit.Count > 0 && now - phaseSince > 8f && heldGenerate == null && !heldFinish && !StillLoading()) { orbitRepairDone = true; string text = null; foreach (int item in deadAtOrbit) { if (item < instance.allPlayerScripts.Length && instance.allPlayerScripts[item].isPlayerDead) { text = ((text == null) ? "" : (text + ", ")) + instance.allPlayerScripts[item].playerUsername; } } if (text != null) { Plugin.Warn("In orbit but still dead on this machine: " + text + " - reviving."); try { instance.ReviveDeadPlayers(); } catch (Exception ex2) { Plugin.Log.LogError((object)("orbit revive: " + ex2)); } } } if (!Plugin.LivingAudit.Value || instance.inShipPhase || !instance.shipHasLanded || instance.shipIsLeaving) { livingDriftSince = (allDeadSince = -1f); return; } int num = 0; for (int j = 0; j < instance.allPlayerScripts.Length; j++) { if (instance.allPlayerScripts[j].isPlayerControlled && !instance.allPlayerScripts[j].isPlayerDead) { num++; } } if (num != instance.livingPlayers) { if (livingDriftSince < 0f) { livingDriftSince = now; } else if (now - livingDriftSince > 4f) { Plugin.Warn("livingPlayers was " + instance.livingPlayers + " with " + num + " player(s) actually alive - corrected."); instance.livingPlayers = num; livingDriftSince = -1f; } } else { livingDriftSince = -1f; } if (num == 0 && !instance.shipLeftAutomatically) { if (allDeadSince < 0f) { allDeadSince = now; } else if (now - allDeadSince > 5f) { Plugin.Warn("Everyone is dead and the ship is still waiting - sending it off."); allDeadSince = -1f; instance.livingPlayers = 0; instance.allPlayersDead = true; try { instance.ShipLeaveAutomatically(false); } catch (Exception ex3) { Plugin.Log.LogError((object)("auto leave: " + ex3)); } } } else { allDeadSince = -1f; } } } internal static class Recovery { private static void Set(object target, string field, object value) { try { FieldInfo fieldInfo = AccessTools.Field(target.GetType(), field); if (fieldInfo == null) { return; } if (value != null && !fieldInfo.FieldType.IsInstanceOfType(value)) { if (!(value is IConvertible) || !typeof(IConvertible).IsAssignableFrom(fieldInfo.FieldType)) { return; } value = Convert.ChangeType(value, fieldInfo.FieldType); } fieldInfo.SetValue(target, value); } catch (Exception) { } } private static void Call(object target, string method, params object[] args) { try { MethodInfo methodInfo = AccessTools.Method(target.GetType(), method, (Type[])null, (Type[])null); if (methodInfo != null && methodInfo.GetParameters().Length == args.Length) { methodInfo.Invoke(target, args); } } catch (Exception) { } } public static void FinishRevive() { StartOfRound instance = StartOfRound.Instance; instance.allPlayersDead = false; for (int i = 0; i < instance.allPlayerScripts.Length; i++) { try { ReviveOne(instance, i); } catch (Exception ex) { Plugin.Log.LogError((object)("manual revive of player " + i + ": " + ex)); } } PlayerControllerB localPlayerController = GameNetworkManager.Instance.localPlayerController; if ((Object)(object)localPlayerController != (Object)null) { Set(localPlayerController, "bleedingHeavily", false); Set(localPlayerController, "criticallyInjured", false); try { if ((Object)(object)localPlayerController.playerBodyAnimator != (Object)null) { localPlayerController.playerBodyAnimator.SetBool("Limp", false); } } catch (Exception) { } localPlayerController.health = 100; try { HUDManager.Instance.UpdateHealthUI(100, false); } catch (Exception) { } Set(localPlayerController, "spectatedPlayerScript", null); try { ((Behaviour)HUDManager.Instance.audioListenerLowPass).enabled = false; } catch (Exception) { } Call(instance, "SetSpectateCameraToGameOverMode", false, localPlayerController); } try { RagdollGrabbableObject[] array = Object.FindObjectsOfType<RagdollGrabbableObject>(); foreach (RagdollGrabbableObject val in array) { if (!((GrabbableObject)val).isHeld && ((NetworkBehaviour)instance).IsServer) { if (((NetworkBehaviour)val).NetworkObject.IsSpawned) { ((NetworkBehaviour)val).NetworkObject.Despawn(true); } else { Object.Destroy((Object)(object)((Component)val).gameObject); } } } DeadBodyInfo[] array2 = Object.FindObjectsOfType<DeadBodyInfo>(true); foreach (DeadBodyInfo val2 in array2) { Object.Destroy((Object)(object)((Component)val2).gameObject); } } catch (Exception ex5) { Plugin.Log.LogError((object)("manual revive cleanup: " + ex5)); } instance.livingPlayers = instance.connectedPlayersAmount + 1; instance.allPlayersDead = false; Call(instance, "UpdatePlayerVoiceEffects"); Call(instance, "ResetMiscValues"); } private static void ReviveOne(StartOfRound sor, int i) { //IL_0338: Unknown result type (might be due to invalid IL or missing references) //IL_017d: Unknown result type (might be due to invalid IL or missing references) PlayerControllerB val = sor.allPlayerScripts[i]; if (!val.isPlayerDead && !val.isPlayerControlled) { return; } Set(val, "isClimbingLadder", false); Set(val, "clampLooking", false); Set(val, "inVehicleAnimation", false); Set(val, "disableMoveInput", false); Call(val, "ResetZAndXRotation"); try { ((Collider)val.thisController).enabled = true; } catch (Exception) { } val.health = 100; Set(val, "hasBeenCriticallyInjured", false); Set(val, "disableLookInput", false); Set(val, "disableInteract", false); try { ((Behaviour)val.nightVisionRadar).enabled = false; } catch (Exception) { } if (val.isPlayerDead) { val.isPlayerDead = false; Set(val, "enemyWaitingForBodyRagdoll", null); val.isPlayerControlled = true; val.isInElevator = true; val.isInHangarShipRoom = true; val.isInsideFactory = false; Set(val, "parentedToElevatorLastFrame", false); Set(val, "overrideGameOverSpectatePivot", null); Call(sor, "SetPlayerObjectExtrapolate", false); try { val.TeleportPlayer((Vector3)AccessTools.Method(typeof(StartOfRound), "GetPlayerSpawnPosition", (Type[])null, (Type[])null).Invoke(sor, new object[2] { i, false }), false, 0f, false, true); } catch (Exception) { } Set(val, "setPositionOfDeadPlayer", false); try { val.DisablePlayerModel(sor.allPlayerObjects[i], true, true); } catch (Exception) { } try { ((Behaviour)val.helmetLight).enabled = false; } catch (Exception) { } Call(val, "Crouch", false); Set(val, "criticallyInjured", false); try { if ((Object)(object)val.playerBodyAnimator != (Object)null) { val.playerBodyAnimator.SetBool("Limp", false); } } catch (Exception) { } Set(val, "bleedingHeavily", false); Set(val, "activatingItem", false); Set(val, "twoHanded", false); Set(val, "inShockingMinigame", false); Set(val, "inSpecialInteractAnimation", false); Set(val, "freeRotationInInteractAnimation", false); Set(val, "disableSyncInAnimation", false); Set(val, "inAnimationWithEnemy", null); Set(val, "holdingWalkieTalkie", false); Set(val, "speakingToWalkieTalkie", false); Set(val, "isSinking", false); Set(val, "isUnderwater", false); Set(val, "sinkingValue", 0f); try { val.statusEffectAudio.Stop(); } catch (Exception) { } Call(val, "DisableJetpackControlsLocally"); try { val.mapRadarDotAnimator.SetBool("dead", false); } catch (Exception) { } Set(val, "externalForceAutoFade", Vector3.zero); Set(val, "carryWeight", 1f); if (((NetworkBehaviour)val).IsOwner) { try { HUDManager instance = HUDManager.Instance; Call(instance, "SetCracksOnVisor", 100f); instance.gasHelmetAnimator.SetBool("gasEmitting", false); Set(val, "hasBegunSpectating", false); instance.RemoveSpectateUI(); instance.gameOverAnimator.SetTrigger("revive"); } catch (Exception ex9) { Plugin.Log.LogError((object)("manual revive HUD: " + ex9)); } Set(val, "hinderedMultiplier", 1f); Set(val, "isMovementHindered", 0); Set(val, "sourcesCausingSinking", 0); Call(sor, "SendChangedWeightEvent"); try { val.reverbPreset = sor.shipReverb; } catch (Exception) { } } } Set(val, "voiceMuffledByEnemy", false); try { SoundManager instance2 = SoundManager.Instance; instance2.earsRingingTimer = 0f; if (i < instance2.playerVoicePitchTargets.Length) { instance2.playerVoicePitchTargets[i] = 1f; } instance2.SetPlayerPitch(1f, i); } catch (Exception) { } } } internal static class NetTuning { private class Item { public byte[] data; public SendType type; } private class Backlog { public Connection connection; public readonly Queue<Item> queue = new Queue<Item>(); public long bytes; public float since; public int total; } private const long MaxBacklogBytes = 67108864L; private const float MaxBacklogSeconds = 60f; private static readonly Type Me = typeof(NetTuning); private static bool steamDone; private static float nextSteamTry; private static readonly Dictionary<uint, Backlog> backlogs = new Dictionary<uint, Backlog>(); private static readonly List<uint> finished = new List<uint>(); private static bool flushing; private static int otherFailures; private static float nextFailureNote; private static readonly PropertyInfo IsSceneObject = AccessTools.Property(typeof(NetworkObject), "IsSceneObject"); private static readonly List<ulong> deadIds = new List<ulong>(); private static readonly MethodInfo GetSceneObject = AccessTools.Method(typeof(NetworkObject), "GetMessageSceneObject", (Type[])null, (Type[])null); private static MethodInfo serializeSceneObject; private static readonly List<NetworkObject> snapshot = new List<NetworkObject>(); private static readonly List<KeyValuePair<NetworkObject, Exception>> offenders = new List<KeyValuePair<NetworkObject, Exception>>(); private static readonly HashSet<int> hidden = new HashSet<int>(); private static readonly List<ulong> toDrop = new List<ulong>(); public static void Apply() { if (Plugin.NetEnabled.Value) { Plugin.Patch(Plugin.M(typeof(GameNetworkManager), "Start"), Me, null, "NetConfigPostfix", null, null); if (Plugin.ResendDropped.Value) { MethodInfo target = AccessTools.Method(typeof(Connection), "SendMessage", new Type[4] { typeof(byte[]), typeof(int), typeof(int), typeof(SendType) }, (Type[])null); Plugin.Patch(target, Me, "SendPrefix", "SendPostfix", null, null); } if (Plugin.JoinSanitizer.Value) { Plugin.Patch(Plugin.M(typeof(GameNetworkManager), "ConnectionApproval"), Me, "SanitizePrefix", null, null, null); Plugin.Patch(Plugin.M(typeof(NetworkSceneManager), "SynchronizeNetworkObjects"), Me, "BeforeSyncPrefix", null, null, null); Plugin.Patch(Plugin.M(typeof(NetworkConnectionManager), "HandleConnectionApproval"), Me, null, null, null, "ApprovalFinalizer"); } } } private static void NetConfigPostfix(GameNetworkManager __instance) { try { NetworkManager component = ((Component)__instance).GetComponent<NetworkManager>(); if (!((Object)(object)component == (Object)null) && component.NetworkConfig != null) { int value = Plugin.ApprovalTimeoutSeconds.Value; if (value > component.NetworkConfig.ClientConnectionBufferTimeout) { component.NetworkConfig.ClientConnectionBufferTimeout = value; } if (component.NetworkConfig.LoadSceneTimeOut < 240) { component.NetworkConfig.LoadSceneTimeOut = 240; } UnityTransport val = ((Component)__instance).GetComponent<UnityTransport>(); if ((Object)(object)val == (Object)null) { NetworkTransport networkTransport = component.NetworkConfig.NetworkTransport; val = (UnityTransport)(object)((networkTransport is UnityTransport) ? networkTransport : null); } if ((Object)(object)val != (Object)null && val.MaxPacketQueueSize < 1024) { val.MaxPacketQueueSize = 1024; } Plugin.Dbg("Netcode: handshake timeout " + component.NetworkConfig.ClientConnectionBufferTimeout + "s, scene timeout " + component.NetworkConfig.LoadSceneTimeOut + "s."); } } catch (Exception ex) { Plugin.Log.LogError((object)("netcode config: " + ex)); } } private static void ApplySteam() { if (steamDone || Time.unscaledTime < nextSteamTry) { return; } nextSteamTry = Time.unscaledTime + 1f; if (!SteamClient.IsValid) { return; } steamDone = true; try { int num = Plugin.SteamSendBufferKB.Value * 1024; if (num > 0 && SteamNetworkingUtils.SendBufferSize < num) { SteamNetworkingUtils.SendBufferSize = num; } int num2 = Plugin.SteamTimeoutSeconds.Value * 1000; if (num2 > 0) { if (SteamNetworkingUtils.Timeout < num2) { SteamNetworkingUtils.Timeout = num2; } if (SteamNetworkingUtils.ConnectionTimeout < num2) { SteamNetworkingUtils.ConnectionTimeout = num2; } } int num3 = Plugin.SteamSendRateKB.Value * 1024; if (num3 > 0) { MethodInfo methodInfo = AccessTools.Method(typeof(SteamNetworkingUtils), "SetConfigInt", (Type[])null, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(typeof(SteamNetworkingUtils), "GetConfigInt", (Type[])null, (Type[])null); Type type = ((methodInfo != null) ? methodInfo.GetParameters()[0].ParameterType : null); if (type != null && methodInfo2 != null) { string[] array = new string[2] { "SendRateMin", "SendRateMax" }; foreach (string value in array) { object obj = Enum.Parse(type, value); if ((int)methodInfo2.Invoke(null, new object[1] { obj }) < num3) { methodInfo.Invoke(null, new object[2] { obj, num3 }); } } } } Plugin.Say("Steam sockets: send buffer " + SteamNetworkingUtils.SendBufferSize / 1024 + " KB, timeout " + SteamNetworkingUtils.Timeout / 1000 + "s" + ((num3 > 0) ? (", send rate " + num3 / 1024 + " KB/s") : "") + "."); } catch (Exception ex) { Plugin.Log.LogError((object)("steam sockets config: " + ex)); } } private static void Enqueue(Backlog b, byte[] data, int offset, int length, SendType type) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) byte[] array = new byte[length]; Buffer.BlockCopy(data, offset, array, 0, length); b.queue.Enqueue(new Item { data = array, type = type }); b.bytes += length; b.total++; } private static bool SendPrefix(ref Connection __instance, byte[] data, int offset, int length, SendType sendType, ref Result __result) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) if (flushing || backlogs.Count == 0 || (sendType & 8) == 0) { return true; } if (!backlogs.TryGetValue(((Connection)(ref __instance)).Id, out var value)) { return true; } Enqueue(value, data, offset, length, sendType); __result = (Result)1; return false; } private static void SendPostfix(ref Connection __instance, byte[] data, int offset, int length, SendType sendType, Result __result) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Invalid comparison between Unknown and I4 //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Invalid comparison between Unknown and I4 //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) if (flushing || (int)__result == 1 || (sendType & 8) == 0) { return; } if ((int)__result != 25) { otherFailures++; if (Time.unscaledTime > nextFailureNote) { nextFailureNote = Time.unscaledTime + 10f; Plugin.Dbg(string.Concat("Steam refused ", otherFailures, " reliable message(s), last result ", __result, ".")); otherFailures = 0; } } else { if (!backlogs.TryGetValue(((Connection)(ref __instance)).Id, out var value)) { Backlog backlog = new Backlog(); backlog.connection = __instance; backlog.since = Time.unscaledTime; value = backlog; backlogs[((Connection)(ref __instance)).Id] = value; Plugin.Warn("Steam send buffer is full for connection " + ((Connection)(ref __instance)).Id + " - holding reliable messages instead of losing them."); } Enqueue(value, data, offset, length, sendType); } } private static void Flush() { //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Invalid comparison between Unknown and I4 //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Invalid comparison between Unknown and I4 if (backlogs.Count == 0) { return; } flushing = true; try { finished.Clear(); foreach (KeyValuePair<uint, Backlog> backlog in backlogs) { Backlog value = backlog.Value; bool flag = false; while (value.queue.Count > 0) { Item item = value.queue.Peek(); Result val = ((Connection)(ref value.connection)).SendMessage(item.data, 0, item.data.Length, item.type); if ((int)val == 25) { break; } value.queue.Dequeue(); value.bytes -= item.data.Length; if ((int)val != 1) { flag = true; break; } } if (flag) { Plugin.Warn("Connection " + backlog.Key + " closed with " + value.queue.Count + " held message(s)."); finished.Add(backlog.Key); } else if (value.queue.Count == 0) { Plugin.Say("Connection " + backlog.Key + " caught up: " + value.total + " held message(s) delivered after " + (Time.unscaledTime - value.since).ToString("0.0") + "s."); finished.Add(backlog.Key); } else if (value.bytes > 67108864 || Time.unscaledTime - value.since > 60f) { Plugin.Warn("Connection " + backlog.Key + " did not drain (" + value.bytes / 1024 + " KB held for " + (Time.unscaledTime - value.since).ToString("0") + "s) - giving up on it."); finished.Add(backlog.Key); } } foreach (uint item2 in finished) { backlogs.Remove(item2); } } finally { flushing = false; } } public static void Tick() { if (Plugin.NetEnabled.Value) { ApplySteam(); Flush(); DropFailedJoins(); } } private static void SanitizePrefix() { try { NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null || !singleton.IsServer || singleton.SpawnManager == null) { return; } NetworkSpawnManager spawnManager = singleton.SpawnManager; deadIds.Clear(); foreach (KeyValuePair<ulong, NetworkObject> spawnedObject in spawnManager.SpawnedObjects) { if ((Object)(object)spawnedObject.Value == (Object)null) { deadIds.Add(spawnedObject.Key); } } foreach (ulong deadId in deadIds) { spawnManager.SpawnedObjects.Remove(deadId); } int num = spawnManager.SpawnedObjectsList.RemoveWhere((NetworkObject o) => (Object)(object)o == (Object)null); if (deadIds.Count > 0 || num > 0) { Plugin.Warn("Removed " + Math.Max(deadIds.Count, num) + " destroyed object(s) Netcode still listed as spawned."); } if (IsSceneObject == null) { return; } foreach (NetworkObject spawnedObjects in spawnManager.SpawnedObjectsList) { if (!spawnedObjects.IsSceneObject.HasValue) { IsSceneObject.SetValue(spawnedObjects, new bool?(false), null); Plugin.Warn("Spawned object '" + ((Object)spawnedObjects).name + "' had no scene/prefab flag and would have broken the join - marked as a prefab instance."); } } } catch (Exception ex) { Plugin.Log.LogError((object)("join sanitizer: " + ex)); } } private static bool Never(ulong clientId) { return false; } private static void BeforeSyncPrefix(ulong clientId) { SanitizePrefix(); try { DryRun(clientId); } catch (Exception ex) { Plugin.Log.LogError((object)("join guard: " + ex)); } } private static void DryRun(ulong clientId) { //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_0269: Unknown result type (might be due to invalid IL or missing references) //IL_0273: Expected O, but got Unknown NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null || !singleton.IsServer || singleton.SpawnManager == null || GetSceneObject == null) { return; } if (serializeSceneObject == null) { serializeSceneObject = AccessTools.Method(GetSceneObject.ReturnType, "Serialize", (Type[])null, (Type[])null); } if (serializeSceneObject == null) { return; } snapshot.Clear(); snapshot.AddRange(singleton.SpawnManager.SpawnedObjectsList); offenders.Clear(); FastBufferWriter val = default(FastBufferWriter); ((FastBufferWriter)(ref val))..ctor(8192, (Allocator)2, 16777216); try { object[] parameters = new object[1] { clientId }; object[] parameters2 = new object[1] { val }; foreach (NetworkObject item in snapshot) { if (!((Object)(object)item == (Object)null) && !hidden.Contains(((Object)item).GetInstanceID())) { try { ((FastBufferWriter)(ref val)).Seek(0); ((FastBufferWriter)(ref val)).Truncate(0); serializeSceneObject.Invoke(GetSceneObject.Invoke(item, parameters), parameters2); } catch (Exception ex) { offenders.Add(new KeyValuePair<NetworkObject, Exception>(item, (ex is TargetInvocationException && ex.InnerException != null) ? ex.InnerException : ex)); } } } } finally { ((FastBufferWriter)(ref val)).Dispose(); } if (offenders.Count == 0) { return; } if (offenders.Count > Math.Max(20, snapshot.Count / 10)) { Plugin.Warn("Join guard: " + offenders.Count + " of " + snapshot.Count + " objects failed the pre-check (first: " + offenders[0].Value.Message + ") - not trusting it, nothing hidden."); return; } foreach (KeyValuePair<NetworkObject, Exception> offender in offenders) { NetworkObject key = offender.Key; hidden.Add(((Object)key).GetInstanceID()); key.CheckObjectVisibility = new VisibilityDelegate(Never); Plugin.Warn("Join guard: object " + Describe(key) + " cannot be sent to a joining client (" + offender.Value.GetType().Name + ": " + offender.Value.Message + "). Left out of the join so that it does not break it."); } } private static string Describe(NetworkObject no) { try { List<string> list = new List<string>(); Transform val = ((Component)no).transform; int num = 0; while ((Object)(object)val != (Object)null && num < 8) { list.Add(((Object)val).name); num++; val = val.parent; } list.Reverse(); HashSet<string> hashSet = new HashSet<string>(); MonoBehaviour[] componentsInChildren = ((Component)no).GetComponentsInChildren<MonoBehaviour>(true); foreach (MonoBehaviour val2 in componentsInChildren) { if ((Object)(object)val2 != (Object)null && hashSet.Count < 12) { hashSet.Add(((object)val2).GetType().Name + " [" + ((object)val2).GetType().Assembly.GetName().Name + "]"); } } string[] array = new string[hashSet.Count]; hashSet.CopyTo(array); return "'" + string.Join("/", list.ToArray()) + "' id " + no.NetworkObjectId + ", scripts: " + string.Join(", ", array); } catch (Exception) { return "id " + no.NetworkObjectId; } } private static Exception ApprovalFinalizer(ulong ownerClientId, Exception __exception) { if (__exception == null) { return null; } Plugin.Log.LogError((object)("Netcode failed while letting client " + ownerClientId + " in; dropping that client so it can join again instead of hanging: " + __exception)); if (!toDrop.Contains(ownerClientId)) { toDrop.Add(ownerClientId); } return null; } private static void DropFailedJoins() { if (toDrop.Count == 0) { return; } ulong[] array = toDrop.ToArray(); toDrop.Clear(); NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null || !singleton.IsServer || !singleton.IsListening) { return; } ulong[] array2 = array; foreach (ulong num in array2) { if (num != singleton.LocalClientId) { try { singleton.DisconnectClient(num, "The host could not send you the game state. Join again."); } catch (Exception ex) { Plugin.Log.LogError((object)("dropping client " + num + ": " + ex)); } } } } } [BepInPlugin("sweethiggs.steadycrew", "SteadyCrew", "1.0.1")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { public const string Guid = "sweethiggs.steadycrew"; public const string Name = "SteadyCrew"; public const string Version = "1.0.1"; public const string MoreCompanyGuid = "me.swipez.melonloader.morecompany"; internal const BindingFlags Any = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; internal static ManualLogSource Log; internal static Harmony H; internal static ConfigEntry<bool> CrewEnabled; internal static ConfigEntry<int> MaxPlayers; internal static ConfigEntry<bool> GuardEnabled; internal static ConfigEntry<float> LoadTimeout; internal static ConfigEntry<float> UnloadTimeout; internal static ConfigEntry<float> ReviveTimeout; internal static ConfigEntry<float> LaunchGrace; internal static ConfigEntry<bool> LivingAudit; internal static ConfigEntry<bool> AnnounceInChat; internal static ConfigEntry<bool> NetEnabled; internal static ConfigEntry<int> SteamSendBufferKB; internal static ConfigEntry<int> SteamSendRateKB; internal static ConfigEntry<int> SteamTimeoutSeconds; internal static ConfigEntry<bool> ResendDropped; internal static ConfigEntry<int> ApprovalTimeoutSeconds; internal static ConfigEntry<bool> JoinSanitizer; internal static ConfigEntry<bool> Verbose; private void Awake() { //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Expected O, but got Unknown //IL_02a2: Unknown result type (might be due to invalid IL or missing references) //IL_02ac: Expected O, but got Unknown //IL_0310: Unknown result type (might be due to invalid IL or missing references) //IL_0316: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; CrewEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Crew", "Enabled", true, "Bigger lobbies. Every player in the lobby needs SteadyCrew with this on. Turned off automatically while MoreCompany is installed."); MaxPlayers = ((BaseUnityPlugin)this).Config.Bind<int>("Crew", "MaxPlayers", 6, new ConfigDescription("How many players may join a lobby you host. Only the host's value matters.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(2, 8), new object[0])); GuardEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("FlowGuard", "Enabled", true, "Stops one stalled client or a drifted counter from freezing the round (ship not leaving, nobody revived, lever doing nothing)."); LoadTimeout = ((BaseUnityPlugin)this).Config.Bind<float>("FlowGuard", "LoadTimeout", 300f, "Host: seconds to wait for a client to load the moon and generate the level. The host never lands without a client that is still loading; one that is still not ready after this long is disconnected so the others can play. 0 = wait forever."); UnloadTimeout = ((BaseUnityPlugin)this).Config.Bind<float>("FlowGuard", "UnloadTimeout", 60f, "Host: seconds to wait for every client to unload the moon after take-off before continuing; a slow client catches up. 0 = wait forever."); ReviveTimeout = ((BaseUnityPlugin)this).Config.Bind<float>("FlowGuard", "ReviveTimeout", 30f, "Host: seconds to wait for every client to report its end-of-round revive."); LaunchGrace = ((BaseUnityPlugin)this).Config.Bind<float>("FlowGuard", "LaunchGrace", 12f, "Host: seconds in orbit after which a client that never reported as loaded no longer blocks the lever."); LivingAudit = ((BaseUnityPlugin)this).Config.Bind<bool>("FlowGuard", "LivingAudit", true, "Recount living players from the real player states, so the ship leaves when everyone is dead."); AnnounceInChat = ((BaseUnityPlugin)this).Config.Bind<bool>("FlowGuard", "AnnounceInChat", true, "Host: write a chat line when the guard stops waiting for somebody."); NetEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Network", "Enabled", true, "Steam transport and Netcode tuning."); SteamSendBufferKB = ((BaseUnityPlugin)this).Config.Bind<int>("Network", "SteamSendBufferKB", 4096, "Steam send buffer per connection, in KB. The Steam default is 512; when it overflows the game silently loses reliable messages. 0 = leave alone."); SteamSendRateKB = ((BaseUnityPlugin)this).Config.Bind<int>("Network", "SteamSendRateKB", 1024, "Steam send rate limit per connection, in KB/s. The Steam default is 256. 0 = leave alone."); SteamTimeoutSeconds = ((BaseUnityPlugin)this).Config.Bind<int>("Network", "SteamTimeoutSeconds", 25, "Seconds without packets before Steam drops a connection. The Steam default is 10. 0 = leave alone."); ResendDropped = ((BaseUnityPlugin)this).Config.Bind<bool>("Network", "ResendDroppedReliable", true, "Queue and resend reliable messages Steam refused because the send buffer was full, instead of losing them."); ApprovalTimeoutSeconds = ((BaseUnityPlugin)this).Config.Bind<int>("Network", "ApprovalTimeoutSeconds", 30, "Netcode connection handshake timeout. The default is 10. 0 = leave alone."); JoinSanitizer = ((BaseUnityPlugin)this).Config.Bind<bool>("Network", "JoinSanitizer", true, "Host: before a client is let in, repair spawned network objects that would make Netcode throw while synchronizing it."); Verbose = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "Verbose", false, "Extra logging."); bool flag = Chainloader.PluginInfos.ContainsKey("me.swipez.melonloader.morecompany"); Crew.Active = CrewEnabled.Value && !flag; if (flag && CrewEnabled.Value) { Log.LogWarning((object)"MoreCompany is installed, so SteadyCrew leaves the lobby size to it. FlowGuard and Network stay on. Remove MoreCompany to use SteadyCrew lobbies."); } H = new Harmony("sweethiggs.steadycrew"); if (Crew.Active) { Safely("Crew", CrewPatches.Apply); Safely("Ui", UiPatches.Apply); } Safely("FlowGuard", FlowGuard.Apply); Safely("Net", NetTuning.Apply); GameObject val = new GameObject("SteadyCrewRunner"); ((Object)val).hideFlags = (HideFlags)61; Object.DontDestroyOnLoad((Object)(object)val); val.AddComponent<Runner>(); Log.LogInfo((object)("SteadyCrew 1.0.1 loaded. Crew: " + (Crew.Active ? ("on, up to " + MaxPlayers.Value + " players") : "off") + "; FlowGuard: " + (GuardEnabled.Value ? "on" : "off") + "; Network: " + (NetEnabled.Value ? "on" : "off"))); } private static void Safely(string what, Action a) { try { a(); } catch (Exception ex) { Log.LogError((object)(what + " setup failed: " + ex)); } } internal static void Say(string s) { Log.LogInfo((object)s); } internal static void Warn(string s) { Log.LogWarning((object)s); } internal static void Dbg(string s) { if (Verbose != null && Verbose.Value) { Log.LogInfo((object)("[v] " + s)); } } internal static bool Patch(MethodBase target, Type owner, string prefix, string postfix, string transpiler, string finalizer) { if (target == null) { Warn("patch target missing for " + (prefix ?? postfix ?? transpiler ?? finalizer) + " - skipped"); return false; } try { H.Patch(target, Hm(owner, prefix), Hm(owner, postfix), Hm(owner, transpiler), Hm(owner, finalizer), (HarmonyMethod)null); return true; } catch (Exception ex) { Warn("could not patch " + target.DeclaringType.Name + "." + target.Name + ": " + ex.Message); return false; } } private static HarmonyMethod Hm(Type owner, string name) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Expected O, but got Unknown if (name == null) { return null; } MethodInfo method = owner.GetMethod(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (method == null) { throw new MissingMethodException(owner.Name, name); } return new HarmonyMethod(method); } internal static MethodInfo M(Type t, string name) { return AccessTools.Method(t, name, (Type[])null, (Type[])null); } internal static MethodInfo M(Type t, string name, Type[] args) { return AccessTools.Method(t, name, args, (Type[])null); } } public class Runner : MonoBehaviour { internal static Runner Instance; private static readonly HashSet<string> reported = new HashSet<string>(); private void Awake() { Instance = this; } private void Update() { try { NetTuning.Tick(); } catch (Exception e) { Once("net", e); } try { FlowGuard.Tick(); } catch (Exception e2) { Once("guard", e2); } if (Crew.Active) { try { UiPatches.Tick(); } catch (Exception e3) { Once("ui", e3); } } } private static void Once(string key, Exception e) { if (reported.Add(key + e.GetType().Name + e.Message)) { Plugin.Log.LogError((object)("[" + key + "] " + e)); } } } internal static class UiPatches { private class Rows { public readonly List<RectTransform[]> rows = new List<RectTransform[]>(); private Vector2[] origin; private Vector2[] step; private Vector3[] scale; public void Capture() { //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) int num = rows[0].Length; origin = (Vector2[])(object)new Vector2[num]; step = (Vector2[])(object)new Vector2[num]; scale = (Vector3[])(object)new Vector3[num]; for (int i = 0; i < num; i++) { ref Vector2 reference = ref origin[i]; reference = rows[0][i].anchoredPosition; ref Vector2 reference2 = ref step[i]; reference2 = ((rows.Count > 1) ? (rows[1][i].anchoredPosition - rows[0][i].anchoredPosition) : Vector2.zero); ref Vector3 reference3 = ref scale[i]; reference3 = ((Transform)rows[0][i]).localScale; } } public void Pack(List<int> visible) { //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) if (origin == null) { return; } float num = ((visible.Count > 4) ? (4f / (float)visible.Count) : 1f); for (int i = 0; i < visible.Count; i++) { RectTransform[] array = rows[visible[i]]; for (int j = 0; j < array.Length; j++) { if (!((Object)(object)array[j] == (Object)null)) { array[j].anchoredPosition = origin[j] + step[j] * ((float)i * num); ((Transform)array[j]).localScale = scale[j] * num; } } } } public void Reset() { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) if (origin == null) { return; } for (int i = 0; i < rows.Count && i < 4; i++) { for (int j = 0; j < rows[i].Length; j++) { if (!((Object)(object)rows[i][j] == (Object)null)) { rows[i][j].anchoredPosition = origin[j] + step[j] * (float)i; ((Transform)rows[i][j]).localScale = scale[j]; } } } } } private const float FirstPage = 5.5f; private const float NextPage = 3f; private const float ShownFor = 20f; private const int BoxesThatFit = 6; private static readonly Type Me = typeof(UiPatches); private static readonly FieldInfo KickTarget = AccessTools.Field(typeof(QuickMenuManager), "playerObjToKick"); private static readonly FieldInfo SpectateBoxes = AccessTools.Field(typeof(HUDManager), "spectatingPlayerBoxes"); private static Rows menuRows; private static QuickMenuManager menuOwner; private static HUDManager statOwner; private static TextMeshProUGUI[] viewNames; private static TextMeshProUGUI[] viewNotes; private static Image[] viewStates; private static TextMeshProUGUI header; private static string headerText; private static float statFill = -100f; private static readonly List<int> occupied = new List<int>(); private static float nextLobbyPass; public static void Apply() { Plugin.Patch(Plugin.M(typeof(QuickMenuManager), "Start"), Me, null, "QuickMenuStartPostfix", null, null); Plugin.Patch(Plugin.M(typeof(QuickMenuManager), "AddUserToPlayerList"), Me, "AddUserPrefix", null, null, null); Plugin.Patch(Plugin.M(typeof(QuickMenuManager), "RemoveUserFromPlayerList"), Me, "RemoveUserPrefix", null, null, null); Plugin.Patch(Plugin.M(typeof(QuickMenuManager), "ConfirmKickUserFromServer"), Me, "ConfirmKickPrefix", null, null, null); Plugin.Patch(Plugin.M(typeof(QuickMenuManager), "OpenQuickMenu"), Me, null, "OpenQuickMenuPostfix", null, null); Plugin.Patch(Plugin.M(typeof(HUDManager), "Start"), Me, null, "HudStartPostfix", null, null); Plugin.Patch(Plugin.M(typeof(HUDManager), "FillEndGameStats"), Me, "FillStatsPrefix", "FillStatsPostfix", null, null); Plugin.Patch(Plugin.M(typeof(HUDManager), "UpdateBoxesSpectateUI"), Me, null, "SpectateBoxesPostfix", null, null); } private static Transform CommonParent(params Transform[] ts) { Transform val = ts[0]; while ((Object)(object)val != (Object)null) { bool flag = true; foreach (Transform val2 in ts) { if ((Object)(object)val2 != (Object)(object)val && !val2.IsChildOf(val)) { flag = false; break; } } if (flag) { return val; } val = val.parent; } return null; } private static Transform Mirror(Transform original, Transform originalRoot, Transform cloneRoot) { if ((Object)(object)original == (Object)(object)originalRoot) { return cloneRoot; } if ((Object)(object)original == (Object)null || !original.IsChildOf(originalRoot)) { return null; } List<int> list = new List<int>(); Transform val = original; while ((Object)(object)val != (Object)(object)originalRoot) { list.Add(val.GetSiblingIndex()); val = val.parent; } Transform val2 = cloneRoot; for (int num = list.Count - 1; num >= 0; num--) { if (list[num] >= val2.childCount) { return null; } val2 = val2.GetChild(list[num]); } return val2; } private static T MirrorComponent<T>(T original, Transform originalRoot, Transform cloneRoot) where T : Component { if ((Object)(object)original == (Object)null) { return default(T); } Transform val = Mirror(((Component)original).transform, originalRoot, cloneRoot); if (!((Object)(object)val != (Object)null)) { return default(T); } return ((Component)val).GetComponent<T>(); } private static void QuickMenuStartPostfix(QuickMenuManager __instance) { try { EnsureMenu(__instance); } catch (Exception ex) { Plugin.Log.LogError((object)("player list: " + ex)); } } private static void EnsureMenu(QuickMenuManager qm) { //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Expected O, but got Unknown //IL_025e: Unknown result type (might be due to invalid IL or missing references) //IL_0268: Expected O, but got Unknown //IL_027f: Unknown result type (might be due to invalid IL or missing references) //IL_0286: Expected O, but got Unknown int slots = Crew.Slots; PlayerListSlot[] playerListSlots = qm.playerListSlots; if (slots <= 4 || playerListSlots == null || playerListSlots.Length == 0 || (playerListSlots.Length >= slots && (Object)(object)menuOwner == (Object)(object)qm)) { return; } int num = playerListSlots.Length; PlayerListSlot[] array = (PlayerListSlot[])(object)new PlayerListSlot[slots]; Array.Copy(playerListSlots, array, Math.Min(num, slots)); PlayerListSlot val = playerListSlots[num - 1]; Transform transform = val.slotContainer.transform; for (int i = num; i < slots; i++) { UnityAction val2 = null; GameObject val3 = Object.Instantiate<GameObject>(val.slotContainer, transform.parent); ((Object)val3).name = ((Object)val.slotContainer).name + " (" + i + ")"; PlayerListSlot val4 = new PlayerListSlot(); val4.slotContainer = val3; Transform val5 = Mirror(((Object)(object)val.volumeSliderContainer != (Object)null) ? val.volumeSliderContainer.transform : null, transform, val3.transform); val4.volumeSliderContainer = (((Object)(object)val5 != (Object)null) ? ((Component)val5).gameObject : val.volumeSliderContainer); val5 = Mirror(((Object)(object)val.KickUserButton != (Object)null) ? val.KickUserButton.transform : null, transform, val3.transform); val4.KickUserButton = (((Object)(object)val5 != (Object)null) ? ((Component)val5).gameObject : val.KickUserButton); val4.usernameHeader = MirrorComponent<TextMeshProUGUI>(val.usernameHeader, transform, val3.transform) ?? val.usernameHeader; val4.volumeSlider = MirrorComponent<Slider>(val.volumeSlider, transform, val3.transform) ?? val.volumeSlider; val4.isConnected = false; val3.SetActive(false); int player = i; Button[] componentsInChildren = val3.GetComponentsInChildren<Button>(true); foreach (Button val6 in componentsInChildren) { bool flag = (Object)(object)val4.KickUserButton != (Object)null && ((Object)(object)((Component)val6).gameObject == (Object)(object)val4.KickUserButton || ((Component)val6).transform.IsChildOf(val4.KickUserButton.transform)); val6.onClick = new ButtonClickedEvent(); if (!flag) { continue; } ButtonClickedEvent onClick = val6.onClick; if (val2 == null) { val2 = (UnityAction)delegate { qm.KickUserFromServer(player); }; } ((UnityEvent)onClick).AddListener(val2); } array[i] = val4; } qm.playerListSlots = array; menuOwner = qm; menuRows = new Rows(); for (int num2 = 0; num2 < slots; num2++) { menuRows.rows.Add((RectTransform[])(object)new RectTransform[1] { array[num2].slotContainer.GetComponent<RectTransform>() }); } if ((Object)(object)menuRows.rows[0][0] != (Object)null) { menuRows.Capture(); } else { menuRows = null; } Plugin.Dbg("Player list grown to " + slots + " rows."); } private static void LayoutMenu(QuickMenuManager qm) { if (menuRows == null || (Object)(object)menuOwner != (Object)(object)qm) { return; } List<int> list = new List<int>(); bool flag = false; for (int i = 0; i < qm.playerListSlots.Length; i++) { if (qm.playerListSlots[i].isConnected) { list.Add(i); if (i >= 4) { flag = true; } } } if (flag) { menuRows.Pack(list); } else { menuRows.Reset(); } } private static bool AddUserPrefix(QuickMenuManager __instance, ulong steamId, string playerName, int playerObjectId) { try { if (playerObjectId < 4) { return true; } EnsureMenu(__instance); PlayerListSlot[] playerListSlots = __instance.playerListSlots; if (playerObjectId >= playerListSlots.Length) { return false; } PlayerListSlot val = playerListSlots[playerObjectId]; if ((Object)(object)val.KickUserButton != (Object)null) { val.KickUserButton.SetActive(((NetworkBehaviour)StartOfRound.Instance).IsServer); } val.slotContainer.SetActive(true); val.isConnected = true; val.playerSteamId = steamId; if ((Object)(object)val.usernameHeader != (Object)null) { ((TMP_Text)val.usernameHeader).text = playerName; } PlayerControllerB localPlayerController = GameNetworkManager.Instance.localPlayerController; if ((Object)(object)localPlayerController != (Object)null && (Object)(object)val.volumeSliderContainer != (Object)null) { val.volumeSliderContainer.SetActive(playerObjectId != (int)localPlayerController.playerClientId); } LayoutMenu(__instance); return false; } catch (Exception ex) { Plugin.Log.LogError((object)("player list add: " + ex)); return false; } } private static bool RemoveUserPrefix(QuickMenuManager __instance, int playerObjectId) { if (playerObjectId >= 0) { return playerObjectId < __instance.playerListSlots.Length; } return false; } private static bool ConfirmKickPrefix(QuickMenuManager __instance) { try { int num = (int)KickTarget.GetValue(__instance); if (num < 4 || num >= Crew.Slots) { return true; } StartOfRound.Instance.KickPlayer(num); __instance.ConfirmKickUserPanel.SetActive(false); return false; } catch (Exception ex) { Plugin.Log.LogError((object)("kick: " + ex)); return true; } } private static void OpenQuickMenuPostfix(QuickMenuManager __instance) { try { EnsureMenu(__instance); LayoutMenu(__instance); } catch (Exception ex) { Plugin.Log.LogError((object)("player list layout: " + ex)); } } private static void HudStartPostfix(HUDManager __instance) { try { Crew.SyncArrays(); EnsureStats(__instance); } catch (Exception ex) { Plugin.Log.LogError((object)("report rows: " + ex)); } } private static void EnsureStats(HUDManager hud) { //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Expected O, but got Unknown int slots = Crew.Slots; EndOfGameStatUIElements statsUIElements = hud.statsUIElements; if (slots <= 4 || statsUIElements == null || statsUIElements.playerNamesText == null || statsUIElements.playerNamesText.Length == 0 || ((Object)(object)statOwner == (Object)(object)hud && statsUIElements.playerNamesText.Length >= slots)) { return; } int num = Math.Min(statsUIElements.playerNamesText.Length, Math.Min(statsUIElements.playerStates.Length, statsUIElements.playerNotesText.Length)); TextMeshProUGUI[] array = (TextMeshProUGUI[])(object)new TextMeshProUGUI[slots]; Image[] array2 = (Image[])(object)new Image[slots]; TextMeshProUGUI[] array3 = (TextMeshProUGUI[])(object)new TextMeshProUGUI[slots]; try { GameObject val = new GameObject("SteadyCrewReportModel", new Type[1] { typeof(RectTransform) }); val.transform.SetParent(((TMP_Text)statsUIElements.playerNamesText[0]).transform.parent.parent, false); val.SetActive(false); for (int i = 0; i < slots; i++) { int num2 = Math.Min(i, num - 1); array[i] = Object.Instantiate<GameObject>(((Component)statsUIElements.playerNamesText[num2]).gameObject, val.transform).GetComponent<TextMeshProUGUI>(); array2[i] = Object.Instantiate<GameObject>(((Component)statsUIElements.playerStates[num2]).gameObject, val.transform).GetComponent<Image>(); array3[i] = Object.Instantiate<GameObject>(((Component)statsUIElements.playerNotesText[num2]).gameObject, val.transform).GetComponent<TextMeshProUGUI>(); ((TMP_Text)array[i]).text = ""; ((TMP_Text)array3[i]).text = ""; ((Behaviour)array2[i]).enabled = false; } viewNames = (TextMeshProUGUI[])(object)new TextMeshProUGUI[num]; viewStates = (Image[])(object)new Image[num]; viewNotes = (TextMeshProUGUI[])(object)new TextMeshProUGUI[num]; Array.Cop