Decompiled source of SteadyCrew v1.0.1

BepInEx/plugins/SteadyCrew.dll

Decompiled 9 hours ago
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