Decompiled source of LateJoinPlus v1.0.4

LateJoinPlus.dll

Decompiled 6 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using ExitGames.Client.Photon;
using HarmonyLib;
using LateJoinPlus.Client;
using LateJoinPlus.Core;
using LateJoinPlus.Guard;
using LateJoinPlus.Join;
using LateJoinPlus.Lobby;
using LateJoinPlus.MapSync;
using LateJoinPlus.Menu;
using LateJoinPlus.Patches.Late;
using LateJoinPlus.Settings;
using MenuLib;
using MenuLib.MonoBehaviors;
using Microsoft.CodeAnalysis;
using Photon.Pun;
using Photon.Realtime;
using Photon.Voice;
using Photon.Voice.PUN;
using Photon.Voice.Unity;
using Steamworks;
using Steamworks.Data;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyVersion("0.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace LateJoinPlus
{
	internal static class Parts
	{
		internal const string LobbyGuardGuid = "ricketywrecked.repo.lobbyguard";

		internal const string MapSyncFixGuid = "ricketywrecked.repo.mapsyncfix";

		internal static ConfigEntry<bool> GuardEnabled;

		internal static ConfigEntry<bool> MapSyncEnabled;

		private static readonly Dictionary<ConfigEntryBase, Action> Appliers = new Dictionary<ConfigEntryBase, Action>();

		internal static void Bind(ConfigFile cfg)
		{
			//IL_021f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0229: Expected O, but got Unknown
			//IL_0275: Unknown result type (might be due to invalid IL or missing references)
			//IL_027f: Expected O, but got Unknown
			GuardEnabled = cfg.Bind<bool>("Lobby Guard", "Enabled", true, "Lobby Guard on or off: blocks the known lobby-wrecking cheats (fake kicks, host takeover, voice spam), logs every player's Steam ID, and blocks/bans players on your block list. Applied live.");
			Live(cfg.Bind<bool>("Lobby Guard", "BlockHostHijack", true, "Stay in the lobby when someone forcibly makes themselves host while the real host is still there (the game would send everyone to the main menu). As host: take the host role back and ban them."), delegate(bool v)
			{
				HostHijack.Enabled = v && GuardEnabled.Value;
			});
			Live(cfg.Bind<bool>("Lobby Guard", "VoiceGuard", true, "Block voices that try to play through someone else's character, junk voice data (the giant emoji spam), and voice-only intruders who aren't in the game."), delegate(bool v)
			{
				VoiceGuard.Enabled = v && GuardEnabled.Value;
			});
			Live(cfg.Bind<bool>("Lobby Guard", "RevealSteamIds", true, "Lobbies you host publish each player's Steam ID (checked by Photon's Steam login), so fake names and fake Steam IDs can be seen through and logged. Applies to the next lobby you host."), delegate(bool v)
			{
				PublishIds.Enabled = v && GuardEnabled.Value;
			});
			Live(cfg.Bind<bool>("Lobby Guard", "AutoBan", true, "When you are the host, ban blocked players with the game's own ban (Photon's server removes them; a cheat can't ignore it)."), delegate(bool v)
			{
				LateJoinPlus.Guard.Guard.AutoBan = v;
			});
			Live(cfg.Bind<bool>("Lobby Guard", "AutoAddToBlockList", true, "Add the Steam ID of anyone caught doing something a normal game never does to BlockedSteamIds, so they're blocked and banned on sight next time."), delegate(bool v)
			{
				LateJoinPlus.Guard.Guard.AutoBlockAttackers = v;
			});
			LateJoinPlus.Guard.Guard.BlockedSteamIdsEntry = cfg.Bind<string>("Lobby Guard", "BlockedSteamIds", "", "Steam IDs (17 digits, comma-separated) to block and, as host, ban as soon as they join. Find someone's ID in the [Lobby Guard] lines of the log.");
			LateJoinPlus.Guard.Guard.BlockedSteamIdsEntry.SettingChanged += delegate
			{
				LateJoinPlus.Guard.Guard.ReloadBlockList();
			};
			Live(cfg.Bind<bool>("Lobby Guard", "EnforceHeuristics", false, "Also block (and as host ban) players caught by the guesswork checks below. Off = those are only logged."), delegate(bool v)
			{
				LateJoinPlus.Guard.Guard.EnforceHeuristics = v;
			});
			Live(cfg.Bind<int>("Lobby Guard", "FloodMessagesPerSecond", 500, new ConfigDescription("A player sending more network messages than this per second is flooding.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(100, 2000), Array.Empty<object>())), delegate(int v)
			{
				Firewall.FloodPerSecond = v;
			});
			Live(cfg.Bind<int>("Lobby Guard", "GhostSeconds", 90, new ConfigDescription("A player in the lobby this long (while a level is running) without ever getting a character is a ghost.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(30, 300), Array.Empty<object>())), delegate(int v)
			{
				Watch.GhostSeconds = v;
			});
			Live(GuardEnabled, delegate(bool v)
			{
				LateJoinPlus.Guard.Guard.On = v;
			});
			GuardEnabled.SettingChanged += delegate
			{
				ApplyGuardSwitch(cfg);
			};
			MapSyncEnabled = cfg.Bind<bool>("Map Sync", "Enabled", true, "For players who have LateJoinPlus (or Map Sync Fix): as host, send the exact list of rooms explored so far to anyone joining mid-level; when you join someone else's game mid-level, uncover the rooms their host sends. Players without mods get their map filled in by Late Join > RevealMapForJoiners instead, whatever this is set to. Applied live.");
			Live(MapSyncEnabled, delegate(bool v)
			{
				LateJoinPlus.MapSync.MapSync.MapSyncEnabled = v;
			});
			Live(cfg.Bind<bool>("Map Sync", "RevealAllRoomsWhenHostHasNoMod", false, "When you join mid-level and the host doesn't run LateJoinPlus or Map Sync Fix (so it can't send its explored rooms), uncover every room on your map instead. This also shows rooms nobody has explored yet."), delegate(bool v)
			{
				LateJoinPlus.MapSync.MapSync.RevealAllFallback = v;
			});
			ImportOldBlockList();
		}

		private static void ApplyGuardSwitch(ConfigFile cfg)
		{
			foreach (ConfigEntryBase item in from kv in (IEnumerable<KeyValuePair<ConfigDefinition, ConfigEntryBase>>)cfg
				select kv.Value into e
				where e.Definition.Section == "Lobby Guard"
				select e)
			{
				LiveApply(item);
			}
		}

		private static void Live<T>(ConfigEntry<T> entry, Action<T> apply)
		{
			Action run = delegate
			{
				apply(entry.Value);
			};
			Appliers[(ConfigEntryBase)(object)entry] = run;
			run();
			entry.SettingChanged += delegate
			{
				run();
			};
		}

		private static void LiveApply(ConfigEntryBase entry)
		{
			if (Appliers.TryGetValue(entry, out var value))
			{
				value();
			}
		}

		private static void ImportOldBlockList()
		{
			try
			{
				string path = Path.Combine(Paths.ConfigPath, "ricketywrecked.repo.lobbyguard.cfg");
				if (!File.Exists(path))
				{
					return;
				}
				string text = File.ReadAllLines(path).FirstOrDefault((string l) => l.TrimStart(Array.Empty<char>()).StartsWith("BlockedSteamIds", StringComparison.Ordinal));
				int num = text?.IndexOf('=') ?? (-1);
				if (num >= 0)
				{
					List<string> source = (from s in text.Substring(num + 1).Split(new char[3] { ',', ';', ' ' }, StringSplitOptions.RemoveEmptyEntries)
						select s.Trim() into s
						where s.Length > 0
						select s).ToList();
					HashSet<string> mine = new HashSet<string>(LateJoinPlus.Guard.Guard.BlockList);
					int num2 = source.Count((string id) => mine.Add(id));
					if (num2 > 0)
					{
						LateJoinPlus.Guard.Guard.BlockedSteamIdsEntry.Value = string.Join(",", mine);
						GuardLog.Source.LogInfo((object)$"[Lobby Guard] Copied {num2} Steam ID(s) from the separate Lobby Guard's block list.");
					}
				}
			}
			catch (Exception ex)
			{
				Log.Debug("Could not read the old Lobby Guard block list: " + ex.Message);
			}
		}

		internal static void ApplyPatches(Harmony harmony)
		{
			List<Type> obj = new List<Type>
			{
				typeof(Firewall.OnEventPatch),
				typeof(HostHijack.MasterSwitchPatch),
				typeof(VoiceGuard.SpeakerPatch),
				typeof(Watch.EnteredPatch),
				typeof(CheatRpcFilter.ExecuteRpcPatch),
				typeof(PublishIds.CreatePatch),
				typeof(PublishIds.JoinOrCreatePatch),
				typeof(PublishIds.RandomOrCreatePatch),
				typeof(LateJoinPlus.MapSync.MapSync.JoinedRoomPatch),
				typeof(LateJoinPlus.MapSync.MapSync.JoinDetectPatch),
				typeof(LateJoinPlus.MapSync.MapSync.LevelDonePatch),
				typeof(LateJoinPlus.MapSync.MapSync.RemoteAvatarPatch)
			};
			int num = 0;
			foreach (Type item in obj)
			{
				try
				{
					harmony.CreateClassProcessor(item).Patch();
					num++;
				}
				catch (Exception ex)
				{
					Log.Warn("Patch " + item.Name + " could not be applied (" + ex.Message + "). That part is disabled; everything else still works.");
				}
			}
			GuardLog.Source.LogInfo((object)string.Format("Lobby Guard part loaded (block list: {0} Steam IDs, {1}).", LateJoinPlus.Guard.Guard.BlockList.Count, GuardEnabled.Value ? "on" : "off"));
			Log.Debug(num + " Lobby Guard / Map Sync patch(es) applied.");
		}

		internal static void Tick()
		{
			if (GuardEnabled != null && GuardEnabled.Value)
			{
				Watch.Update();
			}
		}

		internal static IEnumerator TakeOver()
		{
			yield return (object)new WaitForSeconds(5f);
			(string, string)[] array = new(string, string)[2]
			{
				("ricketywrecked.repo.lobbyguard", "Lobby Guard"),
				("ricketywrecked.repo.mapsyncfix", "Map Sync Fix")
			};
			for (int i = 0; i < array.Length; i++)
			{
				var (text, text2) = array[i];
				try
				{
					if (Chainloader.PluginInfos.TryGetValue(text, out var value))
					{
						Harmony.UnpatchID(text);
						if ((Object)(object)value.Instance != (Object)null)
						{
							((Behaviour)value.Instance).enabled = false;
						}
						Log.Warn(text2 + " is part of LateJoinPlus now, so the separate " + text2 + " was switched off. You can uninstall it.");
					}
				}
				catch (Exception ex)
				{
					Log.Warn("Could not switch off the separate " + text2 + ": " + ex.Message);
				}
			}
		}
	}
	[BepInPlugin("ricketywrecked.repo.latejoinplus", "LateJoinPlus", "1.0.4")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public sealed class Plugin : BaseUnityPlugin
	{
		private sealed class Runner : MonoBehaviour
		{
			private void Update()
			{
				if (PunGate.PhotonInitialised)
				{
					Parts.Tick();
					MissingRooms.Tick();
					Handshake.Tick();
					if (PunGate.Ready)
					{
						LobbyGate.Tick();
						SteamLobbyCheck.Tick();
						RoomNamePrefix.Tick();
						PhotonCacheCleaner.Tick();
						ModuleEstimate.Tick();
						LevelSnapshot.Tick();
					}
				}
			}

			private void OnGUI()
			{
				StatusOverlay.Draw();
			}
		}

		internal const string Guid = "ricketywrecked.repo.latejoinplus";

		internal const string Version = "1.0.4";

		private static readonly Dictionary<string, string> Superseded = new Dictionary<string, string>
		{
			{ "ozoka.latejoin", "LateJoin (Ozoka Gaming)" },
			{ "chaos.holz.laterepo", "LateRepo (Chaosholz)" },
			{ "laterepo.fix.runtime", "LateRepo Fix (AST Studios)" },
			{ "jp.laterepo.introfix", "LateRepo Intro Fix (CapackMods)" },
			{ "knightmike.latejoin", "Late Join (knightmike)" },
			{ "zichen.latejoinnow", "LateJoinNow (Zichen)" },
			{ "com.repo.latejoinfixed", "LateJoinFixed (Scorpio)" },
			{ "repolatejoin.unified", "LateJoin" }
		};

		private Harmony _harmony;

		private void Awake()
		{
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Expected O, but got Unknown
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Expected O, but got Unknown
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Expected O, but got Unknown
			Log.Attach(((BaseUnityPlugin)this).Logger);
			ModConfig.Bind(((BaseUnityPlugin)this).Config);
			Parts.Bind(((BaseUnityPlugin)this).Config);
			WarnAboutSupersededMods();
			PlayerLimit.Watch();
			if (ModConfig.MaxPlayersEnabled.Value && Chainloader.PluginInfos.ContainsKey("Linkoid.Repo.RoboUnion"))
			{
				Log.Warn("RoboUnion also sets max players. Use either its MaxPlayers or ours (Late Join / MaxPlayers), not both - they overwrite each other. Turn MaxPlayersEnabled off to let RoboUnion decide.");
			}
			GameObject val = new GameObject("LateJoinPlus")
			{
				hideFlags = (HideFlags)61
			};
			Object.DontDestroyOnLoad((Object)val);
			Runner runner = val.AddComponent<Runner>();
			JoinQueue.Attach((MonoBehaviour)runner);
			SteamLobbyCheck.Attach((MonoBehaviour)(object)runner);
			_harmony = new Harmony("ricketywrecked.repo.latejoinplus");
			int num = ApplyPatches("LateJoinPlus.Patches");
			if (Chainloader.PluginInfos.ContainsKey("nickklmao.menulib"))
			{
				ApplySettingsMenu();
			}
			else
			{
				Log.Info("MenuLib isn't installed, so there's no LATE JOIN button in the main menu; the settings are in the config file (or REPOConfig).");
			}
			((MonoBehaviour)runner).StartCoroutine(Parts.TakeOver());
			PunGate.Initialised += delegate
			{
				ApplyPatches("LateJoinPlus.Patches.Late");
				Parts.ApplyPatches(_harmony);
				try
				{
					_harmony.CreateClassProcessor(typeof(SteamLobbyCheck.BroadcastPatch)).Patch();
				}
				catch (Exception ex)
				{
					Log.Warn("Steam lobby broadcast patch could not be applied (" + ex.Message + "). Invites may not work for everyone.");
				}
				Handshake.Listen();
				LevelSnapshot.Listen();
			};
			Log.Info("LateJoinPlus 1.0.4 loaded. Late join " + (ModConfig.On ? "on" : "off") + ", debug logging " + (Log.DebugOn ? "on" : "off") + ".");
			Log.Debug(num + " patch group(s) applied before Photon start.");
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private void ApplySettingsMenu()
		{
			try
			{
				SettingsMenu.Apply(_harmony, ((BaseUnityPlugin)this).Config);
			}
			catch (Exception ex)
			{
				Log.Warn("Could not add the LATE JOIN main menu button: " + ex.Message);
			}
		}

		private int ApplyPatches(string ns)
		{
			Type[] array;
			try
			{
				array = (from t in typeof(Plugin).Assembly.GetTypes()
					where t.Namespace == ns && t.GetCustomAttributes(typeof(HarmonyPatch), inherit: true).Length != 0
					select t).ToArray();
			}
			catch (Exception ex)
			{
				Log.Error("Could not enumerate patch classes: " + ex);
				return 0;
			}
			int num = 0;
			Type[] array2 = array;
			foreach (Type type in array2)
			{
				try
				{
					_harmony.CreateClassProcessor(type).Patch();
					num++;
					Log.Debug("Applied patch group " + type.Name + ".");
				}
				catch (Exception ex2)
				{
					Log.Warn("Patch group " + type.Name + " could not be applied (" + ex2.GetType().Name + ": " + ex2.Message + "). That feature is disabled; everything else still works.");
				}
			}
			return num;
		}

		private void WarnAboutSupersededMods()
		{
			List<string> list = new List<string>();
			try
			{
				foreach (KeyValuePair<string, string> item in Superseded)
				{
					if (Chainloader.PluginInfos.ContainsKey(item.Key))
					{
						list.Add(item.Value);
					}
				}
			}
			catch (Exception ex)
			{
				Log.Debug("Could not inspect the loaded plugin list: " + ex);
				return;
			}
			if (list.Count != 0)
			{
				Log.Warn("LateJoinPlus already contains the features of: " + string.Join(", ", list) + ". Remove " + ((list.Count == 1) ? "it" : "them") + " - running both leaves two mods fighting over the same lobby state.");
			}
		}
	}
	internal static class Report
	{
		private sealed class Counts
		{
			internal int Joins;

			internal int WithMod;

			internal int Loaded;

			internal int LeftEarly;

			internal int Stuck;

			internal int MapReveals;

			internal int RoomsRevealed;

			internal int ExtractionOpened;

			internal int ExtractionSkipped;

			internal int SoftlocksPrevented;

			internal int AutoPressesIgnored;

			internal int Blocked;

			internal int Banned;

			internal int SlowSteps;

			internal int SteamRebuilt;

			internal int SteamResent;

			internal int CheatsIgnored;

			internal double SlowestStepMs;

			internal readonly List<float> LoadSeconds = new List<float>();

			internal bool Empty
			{
				get
				{
					if (Joins == 0 && Blocked == 0 && Banned == 0 && SoftlocksPrevented == 0 && SlowSteps == 0 && SteamRebuilt == 0 && SteamResent == 0)
					{
						return CheatsIgnored == 0;
					}
					return false;
				}
			}

			internal void Add(Counts o)
			{
				Joins += o.Joins;
				WithMod += o.WithMod;
				Loaded += o.Loaded;
				LeftEarly += o.LeftEarly;
				Stuck += o.Stuck;
				MapReveals += o.MapReveals;
				RoomsRevealed += o.RoomsRevealed;
				ExtractionOpened += o.ExtractionOpened;
				ExtractionSkipped += o.ExtractionSkipped;
				SoftlocksPrevented += o.SoftlocksPrevented;
				AutoPressesIgnored += o.AutoPressesIgnored;
				Blocked += o.Blocked;
				Banned += o.Banned;
				SlowSteps += o.SlowSteps;
				SteamRebuilt += o.SteamRebuilt;
				SteamResent += o.SteamResent;
				CheatsIgnored += o.CheatsIgnored;
				SlowestStepMs = Math.Max(SlowestStepMs, o.SlowestStepMs);
				LoadSeconds.AddRange(o.LoadSeconds);
			}
		}

		private static Counts _level = new Counts();

		private static Counts Session = new Counts();

		private static string _levelName;

		private static int _levels;

		private static bool _quitHooked;

		private static bool Hosting
		{
			get
			{
				if (PhotonNetwork.InRoom)
				{
					return PhotonNetwork.IsMasterClient;
				}
				return false;
			}
		}

		internal static void Joined()
		{
			if (Hosting)
			{
				_level.Joins++;
			}
		}

		internal static void Loaded(float seconds, bool withMod)
		{
			_level.Loaded++;
			_level.LoadSeconds.Add(seconds);
			if (withMod)
			{
				_level.WithMod++;
			}
		}

		internal static void LeftEarly()
		{
			_level.LeftEarly++;
		}

		internal static void Stuck()
		{
			_level.Stuck++;
		}

		internal static void MapRevealed(int rooms)
		{
			_level.MapReveals++;
			_level.RoomsRevealed += rooms;
		}

		internal static void ExtractionOpened()
		{
			_level.ExtractionOpened++;
		}

		internal static void ExtractionSkipped()
		{
			_level.ExtractionSkipped++;
		}

		internal static void SoftlockPrevented()
		{
			_level.SoftlocksPrevented++;
		}

		internal static void AutoPressIgnored()
		{
			_level.AutoPressesIgnored++;
		}

		internal static void Blocked()
		{
			_level.Blocked++;
		}

		internal static void Banned()
		{
			_level.Banned++;
		}

		internal static void CheatIgnored()
		{
			_level.CheatsIgnored++;
		}

		internal static void SteamLobbyRebuilt()
		{
			_level.SteamRebuilt++;
		}

		internal static void SteamLobbyResent()
		{
			_level.SteamResent++;
		}

		internal static void SlowStep(double ms)
		{
			_level.SlowSteps++;
			_level.SlowestStepMs = Math.Max(_level.SlowestStepMs, ms);
		}

		internal static void LevelStarting()
		{
			HookQuit();
			Flush("Level '" + (_levelName ?? "?") + "'", _level);
			Session.Add(_level);
			if (!_level.Empty)
			{
				_levels++;
			}
			_level = new Counts();
			_levelName = SafeLevelName();
			if (!PhotonNetwork.InRoom && !Session.Empty)
			{
				Flush("Session (" + _levels + " level(s) with activity)", Session);
				Session = new Counts();
				_levels = 0;
			}
		}

		private static void HookQuit()
		{
			if (_quitHooked)
			{
				return;
			}
			_quitHooked = true;
			Application.quitting += delegate
			{
				try
				{
					Session.Add(_level);
					Flush("Session (" + _levels + " level(s) with activity)", Session);
				}
				catch
				{
				}
			};
		}

		private static void Flush(string what, Counts c)
		{
			if (!c.Empty)
			{
				string text = ((c.LoadSeconds.Count == 0) ? "-" : ("avg " + c.LoadSeconds.Average().ToString("0.0") + " s, slowest " + c.LoadSeconds.Max().ToString("0.0") + " s"));
				int num = Math.Max(0, c.Joins - c.Loaded - c.LeftEarly - c.Stuck);
				bool flag = c.Stuck == 0;
				Log.Info("[Report] " + what + ": " + (flag ? "OK" : "CHECK") + " | joins " + c.Joins + " (" + c.WithMod + " with LateJoinPlus), loaded " + c.Loaded + " (" + text + "), left while loading " + c.LeftEarly + ", still loading at level end " + num + ", stuck " + c.Stuck + " | map reveal " + c.MapReveals + " (" + c.RoomsRevealed + " rooms) | extraction: opened for joiner " + c.ExtractionOpened + ", already had it " + c.ExtractionSkipped + ", softlocks prevented " + c.SoftlocksPrevented + ", auto-presses ignored " + c.AutoPressesIgnored + " | Lobby Guard: blocked " + c.Blocked + ", banned " + c.Banned + ", cheats ignored " + c.CheatsIgnored + " | Steam lobby: rebuilt " + c.SteamRebuilt + ", re-sent to players " + c.SteamResent + " | slow catch-up steps " + c.SlowSteps + ((c.SlowSteps > 0) ? (" (slowest " + c.SlowestStepMs.ToString("0") + " ms)") : ""));
			}
		}

		private static string SafeLevelName()
		{
			try
			{
				return ((Object)(object)RunManager.instance != (Object)null && (Object)(object)RunManager.instance.levelCurrent != (Object)null) ? ((Object)RunManager.instance.levelCurrent).name : null;
			}
			catch
			{
				return null;
			}
		}
	}
}
namespace LateJoinPlus.Settings
{
	internal static class SettingsLink
	{
		internal static Action Open;

		internal static Action<int> OpenKick;
	}
	internal static class SettingsMenu
	{
		[HarmonyPatch(typeof(MenuPageMain), "Start")]
		internal static class MainMenuPatch
		{
			private static void Postfix(MenuPageMain __instance)
			{
				try
				{
					AddButton(__instance);
				}
				catch (Exception ex)
				{
					Log.Warn("Could not add the LATE JOIN button to the main menu: " + ex.Message);
				}
			}
		}

		internal const string MenuLibGuid = "nickklmao.menulib";

		private static ConfigFile _config;

		private static readonly (string Section, string Heading)[] Sections = new(string, string)[6]
		{
			("Late Join", "LATE JOIN"),
			("Map Sync", "MAP SYNC"),
			("Lobby Guard", "LOBBY GUARD (ANTI-CHEAT)"),
			("Display", "DISPLAY"),
			("Players", "PLAYERS"),
			("Debug", "DEBUG")
		};

		private static readonly List<Func<REPOPopupPage>> History = new List<Func<REPOPopupPage>>();

		private static REPOPopupPage _current;

		private static Func<REPOPopupPage> _currentBuild;

		private static int _selectedActor;

		private static bool Hosting
		{
			get
			{
				if (PhotonNetwork.InRoom)
				{
					return PhotonNetwork.IsMasterClient;
				}
				return false;
			}
		}

		internal static void Apply(Harmony harmony, ConfigFile config)
		{
			_config = config;
			harmony.CreateClassProcessor(typeof(MainMenuPatch)).Patch();
			SettingsLink.Open = Open;
			SettingsLink.OpenKick = OpenKickChoice;
		}

		private static void AddButton(MenuPageMain page)
		{
			//IL_0063: 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_00ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00da: Unknown result type (might be due to invalid IL or missing references)
			//IL_0130: Unknown result type (might be due to invalid IL or missing references)
			//IL_0159: Unknown result type (might be due to invalid IL or missing references)
			//IL_0163: Unknown result type (might be due to invalid IL or missing references)
			Transform val = ((Component)page).transform.Find("Buttons");
			if ((Object)(object)val == (Object)null)
			{
				Log.Debug("Main menu button column not found; no LATE JOIN button.");
			}
			else
			{
				if ((Object)(object)val.Find("Button - LATE JOIN") != (Object)null)
				{
					return;
				}
				List<Transform> list = Buttons(val);
				REPOButton val2 = MenuAPI.CreateREPOButton("LATE JOIN", (Action)Open, val, default(Vector2));
				Transform val3 = list.LastOrDefault();
				if ((Object)(object)val3 == (Object)null)
				{
					return;
				}
				TextMeshProUGUI componentInChildren = ((Component)val3).GetComponentInChildren<TextMeshProUGUI>(true);
				if ((Object)(object)componentInChildren != (Object)null)
				{
					((TMP_Text)val2.labelTMP).fontSize = ((TMP_Text)componentInChildren).fontSize;
				}
				float y = list[0].localPosition.y;
				float num = ((list.Count > 1) ? ((list[0].localPosition.y - val3.localPosition.y) / (float)(list.Count - 1)) : 30f);
				if (num < 20f || num > 60f)
				{
					num = 30f;
				}
				list.Insert(list.Count - 1, ((Component)val2).transform);
				((Component)val2).transform.SetSiblingIndex(val3.GetSiblingIndex());
				float x = val3.localPosition.x;
				float num2 = y;
				foreach (Transform item in list)
				{
					item.localPosition = new Vector3(x, num2, item.localPosition.z);
					num2 -= num;
				}
			}
		}

		private static List<Transform> Buttons(Transform column)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Expected O, but got Unknown
			List<Transform> list = new List<Transform>();
			foreach (Transform item in column)
			{
				Transform val = item;
				if (((Component)val).gameObject.activeSelf && (((Object)val).name.Contains("Menu Button") || ((Object)val).name.StartsWith("Button - ", StringComparison.Ordinal)))
				{
					list.Add(val);
				}
			}
			return list.OrderByDescending((Transform t) => t.localPosition.y).ToList();
		}

		internal static void Open()
		{
			History.Clear();
			Show(BuildMain);
		}

		private static void Push(Func<REPOPopupPage> build)
		{
			bool flag = IsOpen(_current) && _currentBuild != null;
			if (flag)
			{
				History.Add(_currentBuild);
			}
			if (!Show(build) && flag)
			{
				History.RemoveAt(History.Count - 1);
			}
		}

		private static bool Show(Func<REPOPopupPage> build)
		{
			try
			{
				REPOPopupPage val = build();
				if ((Object)(object)val == (Object)null)
				{
					return false;
				}
				CloseCurrent();
				_current = val;
				_currentBuild = build;
				val.OpenPage(false);
				return true;
			}
			catch (Exception ex)
			{
				Log.Warn("Could not open that LateJoinPlus page: " + ex.Message);
				return false;
			}
		}

		private static bool IsOpen(REPOPopupPage page)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Expected O, but got Unknown
			if ((Object)page != (Object)null)
			{
				return ((Component)page).gameObject.activeInHierarchy;
			}
			return false;
		}

		private static void CloseCurrent()
		{
			REPOPopupPage current = _current;
			_current = null;
			if (IsOpen(current))
			{
				try
				{
					current.ClosePage(true);
				}
				catch (Exception ex)
				{
					Log.Debug("Closing a LateJoinPlus page failed: " + ex.Message);
				}
			}
		}

		private static void Close(REPOPopupPage page)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Expected O, but got Unknown
			//IL_0015: Expected O, but got Unknown
			if ((Object)page != (Object)_current)
			{
				if (IsOpen(page))
				{
					page.ClosePage(true);
				}
				return;
			}
			CloseCurrent();
			_currentBuild = null;
			if (History.Count > 0)
			{
				Func<REPOPopupPage> build = History[History.Count - 1];
				History.RemoveAt(History.Count - 1);
				Show(build);
			}
		}

		private static void Reopen(REPOPopupPage page)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Expected O, but got Unknown
			//IL_0015: Expected O, but got Unknown
			if ((Object)page == (Object)_current && _currentBuild != null)
			{
				Show(_currentBuild);
			}
		}

		private static void CloseAndRefreshParent(REPOPopupPage page)
		{
			Close(page);
		}

		private static REPOPopupPage NewPage(string title, bool root = false)
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Expected O, but got Unknown
			REPOPopupPage page = MenuAPI.CreateREPOPopupPage(title, (PresetSide)0, false, true, 2f);
			page.AddElement((BuilderDelegate)delegate(Transform parent)
			{
				//IL_003e: Unknown result type (might be due to invalid IL or missing references)
				MenuAPI.CreateREPOButton(root ? "Close" : "Back", (Action)delegate
				{
					Close(page);
				}, parent, new Vector2(66f, 18f));
			});
			return page;
		}

		private static void AddButton(REPOPopupPage page, string text, Action onClick)
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Expected O, but got Unknown
			page.AddElementToScrollView((ScrollViewBuilderDelegate)((Transform sv) => ((REPOElement)MenuAPI.CreateREPOButton(text, onClick, sv, default(Vector2))).rectTransform), 0f, 0f);
		}

		private static void AddLabel(REPOPopupPage page, string text, float topPadding = 0f)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Expected O, but got Unknown
			page.AddElementToScrollView((ScrollViewBuilderDelegate)((Transform sv) => ((REPOElement)MenuAPI.CreateREPOLabel(text, sv, default(Vector2))).rectTransform), topPadding, 0f);
		}

		private static void Confirm(string header, string text, Action yes)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			MenuAPI.OpenPopup(header, Color.red, text, (Action)delegate
			{
				try
				{
					yes();
				}
				catch (Exception ex)
				{
					Log.Warn(header + " failed: " + ex.Message);
				}
			}, (Action)null);
		}

		private static REPOPopupPage BuildMain()
		{
			REPOPopupPage val = NewPage("LateJoinPlus", root: true);
			if (Hosting)
			{
				AddLabel(val, "Hosting - lobby " + LobbyAccess.Describe(LobbyAccess.Current));
			}
			else if (PhotonNetwork.InRoom)
			{
				AddLabel(val, "In someone else's lobby");
			}
			AddToggle(val, ModConfig.Enabled, "Late Join");
			AddToggle(val, Parts.GuardEnabled, "Lobby Guard");
			if (Hosting)
			{
				AddButton(val, "Lobby", delegate
				{
					Push(BuildLobby);
				});
				AddButton(val, "Players", delegate
				{
					Push(BuildPlayers);
				});
			}
			AddButton(val, "Block List", delegate
			{
				Push(BuildBlockList);
			});
			AddButton(val, "Settings", delegate
			{
				Push(BuildSettings);
			});
			if (PhotonNetwork.InRoom && !Hosting)
			{
				AddLabel(val, "Lobby and player controls are for the host.", 10f);
			}
			return val;
		}

		private static REPOPopupPage BuildLobby()
		{
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: Expected O, but got Unknown
			if (!Hosting)
			{
				return null;
			}
			REPOPopupPage page = NewPage("Lobby");
			bool flag = LobbyGate.IsPrivateLobby();
			string[] options = ((!flag) ? new string[3] { "Everyone", "Friends only", "Nobody" } : new string[2] { "Friends only", "Nobody" });
			string current = ((LobbyAccess.Current == Access.Closed) ? "Nobody" : ((LobbyAccess.Current == Access.Friends || flag) ? "Friends only" : "Everyone"));
			string note = (flag ? "Private game: friends and invited players can join." : "Friends only: off the server list, friends and invites still work.");
			page.AddElementToScrollView((ScrollViewBuilderDelegate)((Transform sv) => ((REPOElement)MenuAPI.CreateREPOSlider("Who Can Join", note, (Action<string>)delegate(string v)
			{
				LobbyAccess.Set((v == "Nobody") ? Access.Closed : ((v == "Friends only") ? Access.Friends : Access.Everyone));
			}, sv, options, current, default(Vector2), "", "", (BarBehavior)0)).rectTransform), 0f, 0f);
			AddButton(page, "Invite Friends", delegate
			{
				try
				{
					SteamManager instance = SteamManager.instance;
					if (instance != null)
					{
						instance.OpenSteamOverlayToInvite();
					}
				}
				catch (Exception ex)
				{
					Log.Debug("Could not open the Steam invite overlay: " + ex.Message);
				}
			});
			AddLabel(page, "Steam lobby", 10f);
			AddLabel(page, (SteamLobbyCheck.Players == 0) ? "No other players yet." : (SteamLobbyCheck.InLobby + " of " + SteamLobbyCheck.Players + " players in it (can invite friends)"));
			AddButton(page, "Check Steam Lobby Now", delegate
			{
				SteamLobbyCheck.CheckNow();
				StatusOverlay.ShowNotice("Checking the Steam lobby...", 4f);
			});
			AddButton(page, "Refresh", delegate
			{
				Reopen(page);
			});
			return page;
		}

		private static REPOPopupPage BuildPlayers()
		{
			if (!Hosting)
			{
				return null;
			}
			REPOPopupPage page = NewPage("Players");
			Player[] playerListOthers = PhotonNetwork.PlayerListOthers;
			if (playerListOthers.Length == 0)
			{
				AddLabel(page, "No other players.");
			}
			foreach (Player item in playerListOthers.OrderBy((Player p) => p.ActorNumber))
			{
				int actor = item.ActorNumber;
				string text = (LateJoinPlus.Guard.Guard.IsBlocked(actor) ? "  (blocked)" : (CheatRpcFilter.IsCheating(actor) ? "  (cheating)" : ""));
				AddButton(page, LateJoinPlus.Guard.Guard.Trim(item.NickName) + text, delegate
				{
					_selectedActor = actor;
					Push(BuildPlayer);
				});
			}
			AddButton(page, "Refresh", delegate
			{
				Reopen(page);
			});
			return page;
		}

		private static REPOPopupPage BuildPlayer()
		{
			int actor = _selectedActor;
			object obj;
			if (!Hosting)
			{
				obj = null;
			}
			else
			{
				Room currentRoom = PhotonNetwork.CurrentRoom;
				obj = ((currentRoom != null) ? currentRoom.GetPlayer(actor, false) : null);
			}
			Player val = (Player)obj;
			if (val == null)
			{
				StatusOverlay.ShowNotice("That player has left.", 4f);
				return null;
			}
			string name = LateJoinPlus.Guard.Guard.Trim(val.NickName);
			string steamId = LateJoinPlus.Guard.Guard.SteamIdOf(actor);
			bool flag = LateJoinPlus.Guard.Guard.IsSteamId(steamId);
			REPOPopupPage page = NewPage(name);
			AddLabel(page, "Steam ID " + (flag ? steamId : "unknown"));
			AddPlayerDetails(page, actor);
			if (CheatRpcFilter.IsCheating(actor))
			{
				AddLabel(page, "Caught cheating (their cheat messages are ignored)");
			}
			if (LateJoinPlus.Guard.Guard.OnBlockList(steamId))
			{
				AddLabel(page, "On your block list");
			}
			if (flag)
			{
				AddButton(page, "Steam Profile", delegate
				{
					//IL_0002: Unknown result type (might be due to invalid IL or missing references)
					//IL_001a: Unknown result type (might be due to invalid IL or missing references)
					try
					{
						SteamFriends.OpenUserOverlay(new SteamId
						{
							Value = ulong.Parse(steamId)
						}, "steamid");
					}
					catch (Exception ex)
					{
						Log.Debug("Could not open the Steam profile: " + ex.Message);
					}
				});
			}
			AddButton(page, "Kick", delegate
			{
				Confirm("Kick " + name + "?", "They're removed from the lobby and can join again.", delegate
				{
					if (LateJoinPlus.Guard.Guard.Kick(actor))
					{
						CloseAndRefreshParent(page);
					}
				});
			});
			AddButton(page, "Ban", delegate
			{
				Confirm("Ban " + name + "?", "They're removed and can't rejoin this lobby.", delegate
				{
					if (LateJoinPlus.Guard.Guard.Ban(actor, addToBlockList: false))
					{
						CloseAndRefreshParent(page);
					}
				});
			});
			if (flag)
			{
				AddButton(page, "Ban + Block List", delegate
				{
					Confirm("Ban and block " + name + "?", "They're removed now and banned from every lobby you host.", delegate
					{
						if (LateJoinPlus.Guard.Guard.Ban(actor, addToBlockList: true))
						{
							CloseAndRefreshParent(page);
						}
					});
				});
			}
			return page;
		}

		private static void AddPlayerDetails(REPOPopupPage page, int actor)
		{
			AddLabel(page, "Late-join mod: " + PlayerInfo.LateJoinText(actor));
			AddLabel(page, "GameTools: " + (PlayerInfo.HasGameTools(actor) ? "yes" : "no"));
			AddLabel(page, "Game version: " + PlayerInfo.VersionText(actor));
		}

		internal static void OpenKickChoice(int actor)
		{
			object obj;
			if (!Hosting)
			{
				obj = null;
			}
			else
			{
				Room currentRoom = PhotonNetwork.CurrentRoom;
				obj = ((currentRoom != null) ? currentRoom.GetPlayer(actor, false) : null);
			}
			if (obj == null)
			{
				StatusOverlay.ShowNotice("That player has left.", 4f);
				return;
			}
			History.Clear();
			Show(() => BuildKickChoice(actor));
		}

		private static REPOPopupPage BuildKickChoice(int actor)
		{
			object obj;
			if (!Hosting)
			{
				obj = null;
			}
			else
			{
				Room currentRoom = PhotonNetwork.CurrentRoom;
				obj = ((currentRoom != null) ? currentRoom.GetPlayer(actor, false) : null);
			}
			Player val = (Player)obj;
			if (val == null)
			{
				return null;
			}
			string text = LateJoinPlus.Guard.Guard.Trim(val.NickName);
			bool num = LateJoinPlus.Guard.Guard.IsSteamId(LateJoinPlus.Guard.Guard.SteamIdOf(actor));
			REPOPopupPage page = NewPage("Remove " + text + "?", root: true);
			if (CheatRpcFilter.IsCheating(actor))
			{
				AddLabel(page, "Caught cheating");
			}
			AddPlayerDetails(page, actor);
			AddButton(page, "Kick (can rejoin)", delegate
			{
				LateJoinPlus.Guard.Guard.Kick(actor);
				Close(page);
			});
			AddButton(page, "Ban (this lobby)", delegate
			{
				LateJoinPlus.Guard.Guard.Ban(actor, addToBlockList: false);
				Close(page);
			});
			if (num)
			{
				AddButton(page, "Ban + Block List", delegate
				{
					LateJoinPlus.Guard.Guard.Ban(actor, addToBlockList: true);
					Close(page);
				});
			}
			AddButton(page, "Cancel", delegate
			{
				Close(page);
			});
			return page;
		}

		private static REPOPopupPage BuildBlockList()
		{
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Expected O, but got Unknown
			REPOPopupPage page = NewPage("Block List");
			AddLabel(page, "Blocked players are banned when they join your lobby.");
			page.AddElementToScrollView((ScrollViewBuilderDelegate)((Transform sv) => ((REPOElement)MenuAPI.CreateREPOInputField("Add Steam ID", (Action<string>)delegate(string v)
			{
				string text = (v ?? "").Trim();
				if (LateJoinPlus.Guard.Guard.Block(text))
				{
					Reopen(page);
				}
				else if (text.Length > 0)
				{
					StatusOverlay.ShowNotice("Not a Steam ID (17 digits).", 4f);
				}
			}, sv, default(Vector2), true, "17-digit Steam ID", "")).rectTransform), 0f, 0f);
			List<string> list = LateJoinPlus.Guard.Guard.BlockList.OrderBy((string text) => LateJoinPlus.Guard.Guard.NameOf(text) ?? ("~" + text)).ToList();
			if (list.Count == 0)
			{
				AddLabel(page, "Nobody is blocked.", 10f);
			}
			foreach (string id in list)
			{
				string who = LateJoinPlus.Guard.Guard.NameOf(id);
				AddButton(page, (who ?? "Unknown") + "  " + id, delegate
				{
					Confirm("Unblock " + (who ?? id) + "?", "They can join your lobbies again.", delegate
					{
						LateJoinPlus.Guard.Guard.Unblock(id);
						Reopen(page);
					});
				});
			}
			return page;
		}

		private static REPOPopupPage BuildSettings()
		{
			REPOPopupPage val = NewPage("Settings");
			(string, string)[] sections = Sections;
			for (int i = 0; i < sections.Length; i++)
			{
				var (text, text2) = sections[i];
				if (Entries(text).Count <= 0)
				{
					continue;
				}
				string s = text;
				string h = text2;
				AddButton(val, text, delegate
				{
					Push(() => BuildSection(s, h));
				});
			}
			return val;
		}

		private static REPOPopupPage BuildSection(string section, string heading)
		{
			REPOPopupPage val = NewPage(section);
			AddLabel(val, heading);
			foreach (ConfigEntryBase item in Entries(section))
			{
				AddEntry(val, item);
			}
			return val;
		}

		private static List<ConfigEntryBase> Entries(string section)
		{
			return (from kv in (IEnumerable<KeyValuePair<ConfigDefinition, ConfigEntryBase>>)_config
				select kv.Value into e
				where e.Definition.Section == section && (!(e.Definition.Key == "Enabled") || !(e is ConfigEntry<bool>))
				select e).ToList();
		}

		private static void AddToggle(REPOPopupPage page, ConfigEntry<bool> entry, string label)
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Expected O, but got Unknown
			if (entry == null)
			{
				return;
			}
			page.AddElementToScrollView((ScrollViewBuilderDelegate)((Transform sv) => ((REPOElement)MenuAPI.CreateREPOToggle(label, (Action<bool>)delegate(bool v)
			{
				entry.Value = v;
			}, sv, default(Vector2), "ON", "OFF", entry.Value)).rectTransform), 0f, 0f);
		}

		private static void AddEntry(REPOPopupPage page, ConfigEntryBase entry)
		{
			//IL_0225: Unknown result type (might be due to invalid IL or missing references)
			//IL_0239: Expected O, but got Unknown
			//IL_028b: Unknown result type (might be due to invalid IL or missing references)
			//IL_029f: Expected O, but got Unknown
			//IL_0164: Unknown result type (might be due to invalid IL or missing references)
			//IL_0178: Expected O, but got Unknown
			//IL_0208: Unknown result type (might be due to invalid IL or missing references)
			//IL_021c: Expected O, but got Unknown
			string label = Label(entry.Definition.Key);
			ConfigDescription description = entry.Description;
			string description2 = ((description != null) ? description.Description : null) ?? "";
			if (!(entry is ConfigEntry<bool> entry2))
			{
				ConfigEntry<int> val = entry as ConfigEntry<int>;
				if (val == null)
				{
					ConfigEntry<float> val2 = entry as ConfigEntry<float>;
					if (val2 == null)
					{
						ConfigEntry<string> val3 = entry as ConfigEntry<string>;
						if (val3 != null)
						{
							page.AddElementToScrollView((ScrollViewBuilderDelegate)((Transform sv) => ((REPOElement)MenuAPI.CreateREPOInputField(label, (Action<string>)delegate(string v)
							{
								val3.Value = v ?? "";
							}, sv, default(Vector2), true, "", val3.Value)).rectTransform), 0f, 0f);
						}
						else
						{
							if (!entry.SettingType.IsEnum)
							{
								return;
							}
							string[] options = Enum.GetNames(entry.SettingType);
							page.AddElementToScrollView((ScrollViewBuilderDelegate)((Transform sv) => ((REPOElement)MenuAPI.CreateREPOSlider(label, Short(description2), (Action<string>)delegate(string v)
							{
								entry.BoxedValue = Enum.Parse(entry.SettingType, v);
							}, sv, options, entry.BoxedValue.ToString(), default(Vector2), "", "", (BarBehavior)0)).rectTransform), 0f, 0f);
						}
						return;
					}
					ConfigDescription description3 = entry.Description;
					AcceptableValueRange<float> val4 = ((description3 != null) ? description3.AcceptableValues : null) as AcceptableValueRange<float>;
					float min = val4?.MinValue ?? 0f;
					float max = val4?.MaxValue ?? Math.Max(1f, val2.Value * 2f);
					page.AddElementToScrollView((ScrollViewBuilderDelegate)((Transform sv) => ((REPOElement)MenuAPI.CreateREPOSlider(label, Short(description2), (Action<float>)delegate(float v)
					{
						val2.Value = v;
					}, sv, default(Vector2), min, max, 2, val2.Value, "", "", (BarBehavior)0)).rectTransform), 0f, 0f);
					return;
				}
				ConfigDescription description4 = entry.Description;
				AcceptableValueRange<int> val5 = ((description4 != null) ? description4.AcceptableValues : null) as AcceptableValueRange<int>;
				int min2 = val5?.MinValue ?? 0;
				int max2 = val5?.MaxValue ?? Math.Max(100, val.Value * 2);
				page.AddElementToScrollView((ScrollViewBuilderDelegate)((Transform sv) => ((REPOElement)MenuAPI.CreateREPOSlider(label, Short(description2), (Action<int>)delegate(int v)
				{
					val.Value = v;
				}, sv, default(Vector2), min2, max2, val.Value, "", "", (BarBehavior)0)).rectTransform), 0f, 0f);
			}
			else
			{
				AddToggle(page, entry2, label);
			}
		}

		private static string Label(string key)
		{
			return Regex.Replace(key, "(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])", " ");
		}

		private static string Short(string description)
		{
			int num = description.IndexOf(". ", StringComparison.Ordinal);
			if (num <= 0)
			{
				return description;
			}
			return description.Substring(0, num + 1);
		}
	}
}
namespace LateJoinPlus.Menu
{
	internal static class InviteButton
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static UnityAction <0>__OpenInviteOverlay;
		}

		private const string CloneName = "LateJoinPlus_InviteButton";

		private static readonly FieldInfo OriginalPositionField = Reflect.Field(typeof(MenuButton), "originalButtonPosition");

		private const string SettingsCloneName = "LateJoinPlus_SettingsButton";

		private static bool AdminLayoutPresent => Chainloader.PluginInfos.ContainsKey("ricketywrecked.repo.adminmenubuttonfix");

		internal static void TryAdd(MenuPageEsc page)
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Expected O, but got Unknown
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Expected O, but got Unknown
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Expected O, but got Unknown
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Expected O, but got Unknown
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d0: Expected O, but got Unknown
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fd: Expected O, but got Unknown
			//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dd: Expected O, but got Unknown
			//IL_0165: Unknown result type (might be due to invalid IL or missing references)
			//IL_016a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0174: Unknown result type (might be due to invalid IL or missing references)
			//IL_018f: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0132: Unknown result type (might be due to invalid IL or missing references)
			//IL_0137: Unknown result type (might be due to invalid IL or missing references)
			//IL_013d: Expected O, but got Unknown
			if (!PunGate.Ready || !ModConfig.On || (Object)page == (Object)null || ModConfig.InviteButton == null || !ModConfig.InviteButton.Value)
			{
				return;
			}
			if (!PhotonNetwork.InRoom || !LobbyGate.SceneAllowsJoin())
			{
				Log.Debug("Skipping the invite button: this scene does not accept joins.");
				return;
			}
			try
			{
				MenuButton val = FindQuitButton(page);
				if ((Object)val == (Object)null)
				{
					Log.Debug("Could not find the quit button to anchor the invite button to.");
					return;
				}
				Transform parent = ((Component)val).transform.parent;
				if ((Object)parent == (Object)null || (Object)parent.Find("LateJoinPlus_InviteButton") != (Object)null)
				{
					return;
				}
				GameObject val2 = Object.Instantiate<GameObject>(((Component)val).gameObject, parent);
				((Object)val2).name = "LateJoinPlus_InviteButton";
				MenuButtonPopUp component = val2.GetComponent<MenuButtonPopUp>();
				if ((Object)component != (Object)null)
				{
					Object.Destroy((Object)component);
				}
				Relabel(val2, "INVITE");
				Button component2 = val2.GetComponent<Button>();
				if ((Object)component2 != (Object)null)
				{
					((Behaviour)component2).enabled = true;
					((Selectable)component2).interactable = true;
					((UnityEventBase)component2.onClick).RemoveAllListeners();
					ButtonClickedEvent onClick = component2.onClick;
					object obj = <>O.<0>__OpenInviteOverlay;
					if (obj == null)
					{
						UnityAction val3 = OpenInviteOverlay;
						<>O.<0>__OpenInviteOverlay = val3;
						obj = (object)val3;
					}
					((UnityEvent)onClick).AddListener((UnityAction)obj);
				}
				val2.transform.SetSiblingIndex(((Component)val).transform.GetSiblingIndex());
				if (AdminLayoutPresent)
				{
					Vector3 localPosition = val2.transform.localPosition;
					localPosition.y = ((Component)val).transform.localPosition.y + 1f;
					val2.transform.localPosition = localPosition;
					Reflect.Set(OriginalPositionField, val2.GetComponent<MenuButton>(), Vector2.op_Implicit(localPosition));
				}
				((MonoBehaviour)page).StartCoroutine(PlaceNextFrame(val2, ((Component)val).gameObject, "INVITE"));
				Log.Debug("Added an INVITE button to the escape menu.");
			}
			catch (Exception ex)
			{
				Log.Debug("Could not add the invite button: " + ex);
			}
		}

		internal static void TryAddSettings(MenuPageEsc page, Action open)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Expected O, but got Unknown
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Expected O, but got Unknown
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Expected O, but got Unknown
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Expected O, but got Unknown
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Expected O, but got Unknown
			//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d6: Expected O, but got Unknown
			//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: Expected O, but got Unknown
			//IL_0121: Unknown result type (might be due to invalid IL or missing references)
			//IL_012c: Expected O, but got Unknown
			//IL_0102: Unknown result type (might be due to invalid IL or missing references)
			//IL_010c: Expected O, but got Unknown
			//IL_0154: Unknown result type (might be due to invalid IL or missing references)
			//IL_0159: Unknown result type (might be due to invalid IL or missing references)
			//IL_0163: Unknown result type (might be due to invalid IL or missing references)
			//IL_017e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0190: Unknown result type (might be due to invalid IL or missing references)
			//IL_0192: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)page == (Object)null || open == null || !PhotonNetwork.InRoom)
			{
				return;
			}
			try
			{
				MenuButton val = FindQuitButton(page);
				Transform val2 = (((Object)val != (Object)null) ? ((Component)val).transform.parent : null);
				if ((Object)val2 == (Object)null || (Object)val2.Find("LateJoinPlus_SettingsButton") != (Object)null)
				{
					return;
				}
				GameObject val3 = Object.Instantiate<GameObject>(((Component)val).gameObject, val2);
				((Object)val3).name = "LateJoinPlus_SettingsButton";
				MenuButtonPopUp component = val3.GetComponent<MenuButtonPopUp>();
				if ((Object)component != (Object)null)
				{
					Object.Destroy((Object)component);
				}
				Relabel(val3, "LATE JOIN");
				Button component2 = val3.GetComponent<Button>();
				if ((Object)component2 != (Object)null)
				{
					((Behaviour)component2).enabled = true;
					((Selectable)component2).interactable = true;
					((UnityEventBase)component2.onClick).RemoveAllListeners();
					((UnityEvent)component2.onClick).AddListener((UnityAction)delegate
					{
						open();
					});
				}
				Transform val4 = val2.Find("LateJoinPlus_InviteButton");
				val3.transform.SetSiblingIndex(((Object)val4 != (Object)null) ? val4.GetSiblingIndex() : ((Component)val).transform.GetSiblingIndex());
				if (AdminLayoutPresent)
				{
					Vector3 localPosition = val3.transform.localPosition;
					localPosition.y = ((Component)val).transform.localPosition.y + 2f;
					val3.transform.localPosition = localPosition;
					Reflect.Set(OriginalPositionField, val3.GetComponent<MenuButton>(), Vector2.op_Implicit(localPosition));
				}
				((MonoBehaviour)page).StartCoroutine(PlaceNextFrame(val3, ((Component)val).gameObject, "LATE JOIN"));
				Log.Debug("Added a LATE JOIN button to the escape menu.");
			}
			catch (Exception ex)
			{
				Log.Debug("Could not add the LATE JOIN button to the escape menu: " + ex);
			}
		}

		private static void OpenInviteOverlay()
		{
			try
			{
				SteamManager instance = SteamManager.instance;
				if ((Object)(object)instance != (Object)null)
				{
					instance.OpenSteamOverlayToInvite();
				}
			}
			catch (Exception ex)
			{
				Log.Debug("Could not open the Steam invite overlay: " + ex);
			}
		}

		private static IEnumerator PlaceNextFrame(GameObject clone, GameObject anchor, string label)
		{
			yield return null;
			if (AdminLayoutPresent)
			{
				yield return null;
				if ((Object)clone != (Object)null && (Object)anchor != (Object)null && Mathf.Abs(clone.transform.localPosition.y - anchor.transform.localPosition.y) > 5f)
				{
					Relabel(clone, label);
					MenuButton component = clone.GetComponent<MenuButton>();
					if ((Object)component != (Object)null)
					{
						Reflect.Set(OriginalPositionField, component, Vector2.op_Implicit(clone.transform.localPosition));
						try
						{
							component.ResizeToText();
						}
						catch (Exception ex)
						{
							Log.Debug("Invite button resize failed: " + ex);
						}
					}
					Log.Debug(label + " placed in the pause menu column by the re-layout.");
					yield break;
				}
			}
			if ((Object)clone == (Object)null || (Object)anchor == (Object)null)
			{
				yield break;
			}
			RectTransform component2 = clone.GetComponent<RectTransform>();
			RectTransform component3 = anchor.GetComponent<RectTransform>();
			if ((Object)component2 == (Object)null || (Object)component3 == (Object)null)
			{
				yield break;
			}
			component2.anchorMin = component3.anchorMin;
			component2.anchorMax = component3.anchorMax;
			component2.pivot = component3.pivot;
			component2.sizeDelta = component3.sizeDelta;
			((Transform)component2).localScale = ((Transform)component3).localScale;
			((Transform)component2).localPosition = FreeSlotBelow(clone, component3);
			Relabel(clone, label);
			MenuButton component4 = clone.GetComponent<MenuButton>();
			if (!((Object)component4 != (Object)null))
			{
				yield break;
			}
			Reflect.Set(OriginalPositionField, component4, Vector2.op_Implicit(((Transform)component2).localPosition));
			try
			{
				component4.ResizeToText();
			}
			catch (Exception ex2)
			{
				Log.Debug("Invite button resize failed: " + ex2);
			}
		}

		private static Vector3 FreeSlotBelow(GameObject clone, RectTransform anchor)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Expected O, but got Unknown
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Expected O, but got Unknown
			//IL_0038: Expected O, but got Unknown
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Expected O, but got Unknown
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: Unknown result type (might be due to invalid IL or missing references)
			//IL_010e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0118: Unknown result type (might be due to invalid IL or missing references)
			List<float> list = new List<float>();
			foreach (Transform item in ((Transform)anchor).parent)
			{
				Transform val = item;
				if ((Object)((Component)val).gameObject != (Object)clone && ((Component)val).gameObject.activeSelf && (Object)((Component)val).GetComponent<MenuButton>() != (Object)null)
				{
					list.Add(val.localPosition.y);
				}
			}
			list.Sort();
			float num = float.MaxValue;
			for (int i = 1; i < list.Count; i++)
			{
				float num2 = list[i] - list[i - 1];
				if (num2 > 1f && num2 < num)
				{
					num = num2;
				}
			}
			if (num == float.MaxValue)
			{
				num = 32f;
			}
			float num3 = ((list.Count > 0) ? list[0] : ((Transform)anchor).localPosition.y);
			return new Vector3(((Transform)anchor).localPosition.x, num3 - num, ((Transform)anchor).localPosition.z);
		}

		private static void Relabel(GameObject buttonObject, string label)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Expected O, but got Unknown
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Expected O, but got Unknown
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Expected O, but got Unknown
			if ((Object)buttonObject == (Object)null)
			{
				return;
			}
			MenuButton component = buttonObject.GetComponent<MenuButton>();
			if ((Object)component != (Object)null)
			{
				component.buttonTextString = label;
			}
			TextMeshProUGUI[] componentsInChildren = buttonObject.GetComponentsInChildren<TextMeshProUGUI>(true);
			foreach (TextMeshProUGUI val in componentsInChildren)
			{
				if (!((Object)val == (Object)null))
				{
					((TMP_Text)val).SetText(label, true);
					((TMP_Text)val).ForceMeshUpdate(false, false);
				}
			}
		}

		private static MenuButton FindQuitButton(MenuPageEsc page)
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Expected O, but got Unknown
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Expected O, but got Unknown
			MenuButton[] componentsInChildren = ((Component)page).GetComponentsInChildren<MenuButton>(true);
			MenuButton val = null;
			MenuButton[] array = componentsInChildren;
			foreach (MenuButton val2 in array)
			{
				if (!((Object)val2 == (Object)null))
				{
					string text = (val2.buttonTextString ?? string.Empty).ToUpperInvariant().Replace(" ", string.Empty);
					if (text == "QUITGAME")
					{
						return val2;
					}
					if ((Object)val == (Object)null && text.Contains("QUIT"))
					{
						val = val2;
					}
				}
			}
			return val;
		}
	}
	internal static class StatusOverlay
	{
		private const float LineHeight = 22f;

		private const float Margin = 12f;

		private static readonly Color Green;

		private static readonly Color Red;

		private static readonly Color Yellow;

		private static readonly Regex ColorTags;

		private static readonly List<string> Lines;

		private static GUIStyle _style;

		private static GUIStyle _shadow;

		private static string _notice;

		private static float _noticeUntil;

		private static string _noticeLine;

		private static bool _noticeLogged;

		private static Texture2D _backing;

		private static Texture2D Backing
		{
			get
			{
				//IL_0005: Unknown result type (might be due to invalid IL or missing references)
				//IL_0010: Expected O, but got Unknown
				//IL_0014: Unknown result type (might be due to invalid IL or missing references)
				//IL_001e: Expected O, but got Unknown
				//IL_0039: Unknown result type (might be due to invalid IL or missing references)
				if ((Object)_backing == (Object)null)
				{
					_backing = new Texture2D(1, 1);
					_backing.SetPixel(0, 0, new Color(0f, 0f, 0f, 0.6f));
					_backing.Apply();
				}
				return _backing;
			}
		}

		internal static void ShowNotice(string text, float seconds)
		{
			_notice = text;
			_noticeUntil = Time.unscaledTime + seconds;
			_noticeLogged = false;
		}

		internal static void Draw()
		{
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Invalid comparison between Unknown and I4
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Expected O, but got Unknown
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d6: Expected O, but got Unknown
			//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_011c: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_02cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_02dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_034a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0354: Expected O, but got Unknown
			//IL_034f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0419: Unknown result type (might be due to invalid IL or missing references)
			//IL_0420: Unknown result type (might be due to invalid IL or missing references)
			//IL_0396: Unknown result type (might be due to invalid IL or missing references)
			//IL_0297: Unknown result type (might be due to invalid IL or missing references)
			//IL_03bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_03eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_020f: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fe: Unknown result type (might be due to invalid IL or missing references)
			bool flag = ModConfig.ShowStatus != null && ModConfig.ShowStatus.Value;
			bool flag2 = ModConfig.ShowJoinStatus != null && ModConfig.ShowJoinStatus.Value && ModConfig.On;
			bool flag3 = _notice != null && Time.unscaledTime < _noticeUntil;
			if ((!flag && !flag2 && !flag3) || Event.current == null || (int)Event.current.type != 7 || !PunGate.PhotonInitialised || !PhotonNetwork.InRoom || PhotonNetwork.CurrentRoom == null)
			{
				return;
			}
			if (_style == null || _style.fontSize != 16)
			{
				_style = new GUIStyle(GUI.skin.label)
				{
					fontSize = 16,
					richText = true
				};
				_style.normal.textColor = Color.white;
				_shadow = new GUIStyle(_style);
				_shadow.normal.textColor = new Color(0f, 0f, 0f, 0.7f);
			}
			float num = 22f;
			Lines.Clear();
			_noticeLine = null;
			if (flag3)
			{
				_noticeLine = Paint(_notice, Yellow);
				Lines.Add(_noticeLine);
			}
			if (flag)
			{
				Room currentRoom = PhotonNetwork.CurrentRoom;
				bool flag4 = currentRoom.IsOpen && (currentRoom.MaxPlayers == 0 || currentRoom.PlayerCount < currentRoom.MaxPlayers);
				bool flag5 = LobbyGate.IsPrivateLobby();
				string text = (flag5 ? "Private" : (LobbyGate.IsMatchmaking() ? "Matchmaking" : "Public"));
				Lines.Add("Players: " + currentRoom.PlayerCount + "/" + ((currentRoom.MaxPlayers > 0) ? currentRoom.MaxPlayers : currentRoom.PlayerCount));
				Lines.Add("Room: " + text + " - " + (flag4 ? Paint("Open", Green) : Paint("Closed", Red)));
				Lines.Add("Name: " + (flag5 ? "Private Game" : RoomName(currentRoom)));
			}
			int count = Lines.Count;
			if (flag2)
			{
				foreach (var (text2, phase) in JoinStatus.Current())
				{
					Lines.Add(text2 + ": " + Paint(phase.ToString(), PhaseColor(phase)));
				}
			}
			Color color = GUI.color;
			Matrix4x4 matrix = GUI.matrix;
			GUI.color = Color.white;
			GUI.contentColor = Color.white;
			GUI.matrix = Matrix4x4.identity;
			float num2 = (float)Screen.height - 12f - (float)Lines.Count * num - 4f;
			for (int i = 0; i < Lines.Count; i++)
			{
				string text3 = Lines[i];
				float num3 = Mathf.Min((float)Screen.width - 24f, _style.CalcSize(new GUIContent(ColorTags.Replace(text3, ""))).x + 12f);
				if (text3 == _noticeLine || i >= count)
				{
					GUI.DrawTexture(new Rect(6f, num2 - 2f, num3 + 4f, num + 2f), (Texture)(object)Backing);
				}
				GUI.Label(new Rect(13f, num2 + 1f, num3, num + 4f), ColorTags.Replace(text3, ""), _shadow);
				GUI.Label(new Rect(12f, num2, num3, num + 4f), text3, _style);
				num2 += num;
			}
			GUI.color = color;
			GUI.matrix = matrix;
			if (flag3 && !_noticeLogged)
			{
				_noticeLogged = true;
				Log.Debug("Drew the notice bottom-left (screen " + Screen.width + "x" + Screen.height + ").");
			}
		}

		private static Color PhaseColor(JoinStatus.Phase phase)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: 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)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			return (Color)(phase switch
			{
				JoinStatus.Phase.Left => Red, 
				JoinStatus.Phase.Joined => Green, 
				_ => Yellow, 
			});
		}

		private static string Paint(string text, Color color)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			return "<color=#" + ColorUtility.ToHtmlStringRGB(color) + ">" + text + "</color>";
		}

		private static string RoomName(Room room)
		{
			try
			{
				if (((Dictionary<object, object>)(object)((RoomInfo)room).CustomProperties).TryGetValue("server_name", out var value) && value is string text && !string.IsNullOrEmpty(text))
				{
					return text;
				}
			}
			catch (Exception)
			{
			}
			return room.Name;
		}

		static StatusOverlay()
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			Green = new Color(0.65f, 0.95f, 0.65f);
			Red = new Color(1f, 0.66f, 0.66f);
			Yellow = new Color(1f, 0.91f, 0.63f);
			ColorTags = new Regex("</?color[^>]*>");
			Lines = new List<string>();
		}
	}
}
namespace LateJoinPlus.MapSync
{
	internal static class MapSync
	{
		[HarmonyPatch(typeof(NetworkConnect), "OnJoinedRoom")]
		internal static class JoinedRoomPatch
		{
			private static void Postfix()
			{
				_joinedRoomAt = Time.realtimeSinceStartup;
			}
		}

		[HarmonyPatch(typeof(RunManager), "UpdateLevel")]
		internal static class JoinDetectPatch
		{
			private static void Postfix()
			{
				try
				{
					if (PhotonNetwork.InRoom && !PhotonNetwork.IsMasterClient)
					{
						Room currentRoom = PhotonNetwork.CurrentRoom;
						string obj = ((currentRoom != null) ? currentRoom.Name : null);
						bool flag = obj != _lastRoomName;
						_lastRoomName = obj;
						if (flag && SemiFunc.RunIsLevel() && Time.realtimeSinceStartup - _joinedRoomAt < 10f)
						{
							_lateJoinPending = true;
						}
					}
				}
				catch (Exception e)
				{
					Suppressed.Report("Late-join map sync (join detect)", e);
				}
			}
		}

		[HarmonyPatch(typeof(LevelGenerator), "GenerateDone")]
		internal static class LevelDonePatch
		{
			private static void Postfix(LevelGenerator __instance)
			{
				try
				{
					if (!PhotonNetwork.InRoom || !SemiFunc.IsMultiplayer() || (Object)(object)__instance == (Object)(object)_lastLevel)
					{
						return;
					}
					_lastLevel = __instance;
					if (!_subscribed)
					{
						PhotonNetwork.NetworkingClient.EventReceived += OnEvent;
						_subscribed = true;
					}
					_receivedThisLevel = false;
					Pending.Clear();
					if (_lateJoinPending && !PhotonNetwork.IsMasterClient && SemiFunc.RunIsLevel())
					{
						_lateJoinPending = false;
						MapSyncLog.Source.LogInfo((object)("[Late-join map sync] Joined mid-level" + (MapSyncEnabled ? "; waiting for the host's explored rooms." : ".")));
						if (MapSyncEnabled)
						{
							((MonoBehaviour)__instance).StartCoroutine(JoinerRoutine());
						}
					}
				}
				catch (Exception e)
				{
					Suppressed.Report("Late-join map sync (level done)", e);
				}
			}
		}

		[HarmonyPatch(typeof(PlayerAvatar), "Start")]
		internal static class RemoteAvatarPatch
		{
			private static void Postfix(PlayerAvatar __instance)
			{
				try
				{
					if (MapSyncEnabled && PhotonNetwork.IsMasterClient && SemiFunc.RunIsLevel() && !((Object)(object)__instance == (Object)null) && !((Object)(object)__instance.photonView == (Object)null) && !__instance.photonView.IsMine && __instance.photonView.Owner != null && !((Object)(object)LevelGenerator.Instance == (Object)null) && LevelGenerator.Instance.Generated)
					{
						GameDirector instance = GameDirector.instance;
						if ((Object)(object)instance != (Object)null)
						{
							((MonoBehaviour)instance).StartCoroutine(HostSend(__instance.photonView.Owner.ActorNumber));
						}
					}
				}
				catch (Exception e)
				{
					Suppressed.Report("Late-join map sync (host)", e);
				}
			}
		}

		internal static bool RevealAllFallback;

		internal static bool MapSyncEnabled = true;

		private const byte EventCode = 163;

		private const string Tag = "RW.MapSync";

		private const float MatchDistance = 0.25f;

		private static readonly FieldRef<RoomVolume, bool> Explored = AccessTools.FieldRefAccess<RoomVolume, bool>("Explored");

		private static bool _subscribed;

		private static string _lastRoomName;

		private static LevelGenerator _lastLevel;

		private static bool _lateJoinPending;

		private static float _joinedRoomAt = -1000f;

		private static bool _receivedThisLevel;

		private static readonly List<Vector3> Pending = new List<Vector3>();

		private static IEnumerator HostSend(int actor)
		{
			float[] array = new float[2] { 4f, 8f };
			foreach (float delay in array)
			{
				yield return (object)new WaitForSeconds(delay);
				if (!PhotonNetwork.InRoom || !PhotonNetwork.IsMasterClient || PhotonNetwork.CurrentRoom.GetPlayer(actor, false) == null)
				{
					break;
				}
				try
				{
					List<float> list = new List<float>();
					RoomVolume[] array2 = Object.FindObjectsOfType<RoomVolume>(true);
					foreach (RoomVolume val in array2)
					{
						if ((Object)(object)val != (Object)null && Explored.Invoke(val))
						{
							Vector3 position = ((Component)val).transform.position;
							list.Add(position.x);
							list.Add(position.y);
							list.Add(position.z);
						}
					}
					object[] obj = new object[2]
					{
						"RW.MapSync",
						list.ToArray()
					};
					RaiseEventOptions val2 = new RaiseEventOptions();
					val2.TargetActors = new int[1] { actor };
					PhotonNetwork.RaiseEvent((byte)163, (object)obj, val2, SendOptions.SendReliable);
					if (delay == 4f)
					{
						MapSyncLog.Source.LogInfo((object)$"[Late-join map sync] Sent {list.Count / 3} explored rooms to a player who joined mid-level.");
					}
				}
				catch (Exception e)
				{
					Suppressed.Report("Late-join map sync (send)", e);
				}
			}
		}

		private static void OnEvent(EventData ev)
		{
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (ev.Code != 163 || PhotonNetwork.IsMasterClient)
				{
					return;
				}
				Player masterClient = PhotonNetwork.MasterClient;
				if (masterClient != null && ev.Sender == masterClient.ActorNumber && ev.CustomData is object[] array && array.Length == 2 && array[0] is string text && !(text != "RW.MapSync") && array[1] is float[] array2)
				{
					_receivedThisLevel = true;
					for (int i = 0; i + 2 < array2.Length; i += 3)
					{
						Pending.Add(new Vector3(array2[i], array2[i + 1], array2[i + 2]));
					}
				}
			}
			catch (Exception e)
			{
				Suppressed.Report("Late-join map sync (receive)", e);
			}
		}

		private static IEnumerator JoinerRoutine()
		{
			float waited = 0f;
			while (((Object)(object)LevelGenerator.Instance == (Object)null || !LevelGenerator.Instance.Generated) && waited < 60f)
			{
				yield return (object)new WaitForSeconds(0.5f);
				waited += 0.5f;
			}
			yield return (object)new WaitForSeconds(3f);
			int uncovered = 0;
			for (float t = 0f; t < 30f; t += 1f)
			{
				uncovered += ApplyPending();
				if (!_receivedThisLevel && t >= 15f)
				{
					break;
				}
				yield return (object)new WaitForSeconds(1f);
			}
			if (_receivedThisLevel)
			{
				MapSyncLog.Source.LogInfo((object)$"[Late-join map sync] Joined mid-level; the host's map was synced ({uncovered} rooms uncovered).");
				yield break;
			}
			if (!RevealAllFallback)
			{
				MapSyncLog.Source.LogInfo((object)"[Late-join map sync] Joined mid-level, but the host doesn't run Map Sync Fix, so its explored rooms can't be synced (RevealAllRoomsWhenHostHasNoMod is off).");
				yield break;
			}
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			int num4 = 0;
			RoomVolume[] array = Object.FindObjectsOfType<RoomVolume>(true);
			foreach (RoomVolume val in array)
			{
				if (!((Object)(object)val == (Object)null))
				{
					num++;
					if (Explored.Invoke(val))
					{
						num2++;
					}
					else if ((Object)(object)val.MapModule == (Object)null)
					{
						num3++;
					}
					else if (val.SetExplored())
					{
						num4++;
					}
				}
			}
			MapSyncLog.Source.LogInfo((object)$"[Late-join map sync] Joined mid-level and the host doesn't run Map Sync Fix; uncovered the rest of the map (RevealAllRoomsWhenHostHasNoMod): {num4} rooms uncovered now, {num2} already uncovered, {num3} with no map cover (truck/extraction/start), {num} room volumes in total.");
		}

		private static int ApplyPending()
		{
			//IL_004e: 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)
			if (Pending.Count == 0)
			{
				return 0;
			}
			int num = 0;
			RoomVolume[] array = Object.FindObjectsOfType<RoomVolume>(true);
			for (int num2 = Pending.Count - 1; num2 >= 0; num2--)
			{
				bool flag = false;
				bool flag2 = true;
				RoomVolume[] array2 = array;
				foreach (RoomVolume val in array2)
				{
					if ((Object)(object)val == (Object)null || Vector3.Distance(((Component)val).transform.position, Pending[num2]) > 0.25f)
					{
						continue;
					}
					flag = true;
					if (!Explored.Invoke(val))
					{
						if ((Object)(object)val.MapModule == (Object)null)
						{
							flag2 = false;
						}
						else if (val.SetExplored())
						{
							num++;
						}
					}
				}
				if (flag && flag2)
				{
					Pending.RemoveAt(num2);
				}
			}
			return num;
		}
	}
	internal static class MapSyncLog
	{
		internal static readonly ManualLogSource Source = Logger.CreateLogSource("Map Sync");
	}
	internal static class Suppressed
	{
		private static readonly Dictionary<string, int> Counts = new Dictionary<string, int>();

		internal static void Report(string site, Exception e)
		{
			int value;
			lock (Counts)
			{
				Counts.TryGetValue(site, out value);
				value = (Counts[site] = value + 1);
			}
			if (value == 1 || value % 1000 == 0)
			{
				MapSyncLog.Source.LogWarning((object)$"Suppressed exception in {site} ({value} so far): {e.GetType().Name}: {e.Message}");
			}
		}
	}
}
namespace LateJoinPlus.Lobby
{
	internal static class LobbyGate
	{
		private static readonly FieldInfo PrivateLobbyField = Reflect.Field(typeof(SteamManager), "privateLobby");

		private static readonly FieldInfo LobbyTypeField = Reflect.Field(typeof(GameManager), "lobbyType");

		private const float TransitionCooldown = 4f;

		private static float _cooldownUntil;

		private static LevelGenerator _staleGenerator;

		private static float _nextTick;

		private static bool? _appliedState;

		internal static bool SelfLocking;

		internal static bool InTransition => Time.unscaledTime < _cooldownUntil;

		internal static bool SceneAllowsJoin()
		{
			try
			{
				if (SemiFunc.RunIsLobbyMenu())
				{
					return true;
				}
				if (SemiFunc.RunIsLobby())
				{
					return ModConfig.JoinInLobby.Value;
				}
				if (SemiFunc.RunIsShop())
				{
					return ModConfig.JoinInShop.Value;
				}
				if (SemiFunc.RunIsLevel())
				{
					return ModConfig.JoinInLevel.Value;
				}
				return false;
			}
			catch (Exception ex)
			{
				Log.Debug("Scene gate could not be evaluated: " + ex);
				return false;
			}
		}

		internal static bool ReadyForJoiners()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Expected O, but got Unknown
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Expected O, but got Unknown
			//IL_003b: Expected O, but got Unknown
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Expected O, but got Unknown
			//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_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Invalid comparison between Unknown and I4
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Invalid comparison between Unknown and I4
			try
			{
				if ((Object)LevelGenerator.Instance == (Object)null || !LevelGenerator.Instance.Generated)
				{
					return false;
				}
				if ((Object)LevelGenerator.Instance == (Object)_staleGenerator)
				{
					return false;
				}
				if ((Object)GameDirector.instance == (Object)null)
				{
					return false;
				}
				gameState currentState = GameDirector.instance.currentState;
				return (int)currentState == 2 || (int)currentState == 7;
			}
			catch (Exception ex)
			{
				Log.Debug("Ready gate could not be evaluated: " + ex);
				return false;
			}
		}

		internal static bool JoinAllowedNow()
		{
			if (PunGate.Ready && PhotonNetwork.IsMasterClient && PhotonNetwork.InRoom && !InTransition && LobbyAccess.Current != Access.Closed && SceneAllowsJoin())
			{
				return ReadyForJoiners();
			}
			return false;
		}

		internal static void BeginTransition()
		{
			_appliedState = null;
			_staleGenerator = LevelGenerator.Instance;
			_cooldownUntil = Time.unscaledTime + 4f;
			Apply("level transition started", force: true);
		}

		internal static void Tick()
		{
			if (PunGate.Ready && !(Time.unscaledTime < _nextTick))
			{
				_nextTick = Time.unscaledTime + 1f;
				Apply("periodic refresh");
				if (_appliedState == true && PhotonNetwork.IsMasterClient && PhotonNetwork.CurrentRoom != null)
				{
					EnsureSceneProperty(PhotonNetwork.CurrentRoom);
				}
				KeepAccess();
			}
		}

		private static void KeepAccess()
		{
			Room currentRoom = PhotonNetwork.CurrentRoom;
			if (_appliedState.HasValue && PhotonNetwork.IsMasterClient && currentRoom != null && !InTransition && (_appliedState.Value ? (currentRoom.IsVisible != Advertised()) : currentRoom.IsOpen) && LobbyAccess.Current != Access.Everyone)
			{
				Log.Debug("The game changed the room's open/listed state; set it back (lobby is " + LobbyAccess.Describe(LobbyAccess.Current) + ").");
				Apply("lobby access kept", force: true);
			}
		}

		private static void EnsureSceneProperty(Room room, bool force = false)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Expected O, but got Unknown
			try
			{
				Scene activeScene = SceneManager.GetActiveScene();
				string name = ((Scene)(ref activeScene)).name;
				((Dictionary<object, object>)(object)((RoomInfo)room).CustomProperties).TryGetValue("curScn", out var value);
				bool flag = value is string text && text == name;
				if (!flag || force)
				{
					Hashtable val = new Hashtable();
					((Dictionary<object, object>)(object)val).Add((object)"curScn", (object)name);
					room.SetCustomProperties(val, (Hashtable)null, (WebFlags)null);
					if (flag)
					{
						Log.Debug("Re-wrote the room scene property '" + name + "' before opening.");
						return;
					}
					Log.Info("Room scene property was '" + (value ?? "unset")?.ToString() + "' while we are in '" + name + "'; corrected so late joiners load the right scene.");
				}
			}
			catch (Exception ex)
			{
				Log.Debug("Could not check the room scene property: " + ex);
			}
		}

		internal static void Apply(string reason, bool force = false)
		{
			if (!PunGate.Ready || !PhotonNetwork.IsMasterClient || !PhotonNetwork.InRoom)
			{
				return;
			}
			bool flag = JoinAllowedNow();
			if (force || !_appliedState.HasValue || _appliedState.Value != flag)
			{
				_appliedState = flag;
				if (flag)
				{
					OpenRoom(reason);
				}
				else
				{
					CloseRoom(reason);
				}
			}
		}

		private static bool Advertised()
		{
			if (!IsPrivateLobby())
			{
				return LobbyAccess.Current == Access.Everyone;
			}
			return false;
		}

		private static void OpenRoom(string reason)
		{
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Expected O, but got Unknown
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Expected O, but got Unknown
			bool flag = Advertised();
			try
			{
				Room currentRoom = PhotonNetwork.CurrentRoom;
				if (currentRoom != null)
				{
					EnsureSceneProperty(currentRoom, force: true);
					currentRoom.IsOpen = true;
					currentRoom.IsVisible = flag;
				}
				if ((Object)SteamManager.instance != (Object)null)
				{
					SteamManager.instance.UnlockLobby(flag);
				}
				if (IsMatchmaking() && (Object)GameManager.instance != (Object)null)
				{
					GameManager.instance.SetConnectRandom(flag);
				}
				Log.Debug("Lobby open for late joins (" + reason + "), advertised=" + flag + ".");
			}
			catch (Exception ex)
			{
				Log.Debug("Could not open the lobby after " + reason + ": " + ex);
			}
		}

		private static void CloseRoom(string reason)
		{
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Expected O, but got Unknown
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Expected O, but got Unknown
			try
			{
				Room currentRoom = PhotonNetwork.CurrentRoom;
				if (currentRoom != null)
				{
					currentRoom.IsOpen = false;
					currentRoom.IsVisible = false;
				}
				if (IsMatchmaking() && (Object)GameManager.instance != (Object)null)
				{
					GameManager.instance.SetConnectRandom(false);
				}
				if ((Object)SteamManager.instance != (Object)null)
				{
					SelfLocking = true;
					try
					{
						SteamManager.instance.LockLobby();
					}
					finally
					{
						SelfLocking = false;
					}
				}
				Log.Debug("Lobby closed to new joins (" + reason + ").");
			}
			catch (Exception ex)
			{
				Log.Debug("Could not close the lobby after " + reason + ": " + ex);
			}
		}

		internal static bool IsPrivateLobby()
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Expected O, but got Unknown
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Invalid comparison between Unknown and I4
			LobbyTypes? val = LobbyType();
			if (val.HasValue)
			{
				return (int)val.Value == 0;
			}
			if ((Object)SteamManager.instance != (Object)null && PrivateLobbyField != null)
			{
				return Reflect.Get(PrivateLobbyField, SteamManager.instance, fallback: true);
			}
			return true;
		}

		internal static bool IsMatchmaking()
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Invalid comparison between Unknown and I4
			LobbyTypes? val = LobbyType();
			if (val.HasValue)
			{
				return (int)val.Value == 2;
			}
			return false;
		}

		private static LobbyTypes? LobbyType()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Expected O, but got Unknown
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)GameManager.instance == (Object)null || LobbyTypeField == null)
			{
				return null;
			}
			try
			{
				object value = LobbyTypeField.GetValue(GameManager.instance);
				if (value is LobbyTypes)
				{
					return (LobbyTypes)value;
				}
			}
			catch
			{
			}
			return null;
		}
	}
	internal static class PhotonCacheCleaner
	{
		private static readonly FieldInfo RemoveFilterField = Reflect.Field(typeof(PhotonNetwork), "removeFilter");

		private static readonly FieldInfo KeyByteSevenField = Reflect.Field(typeof(PhotonNetwork), "keyByteSeven");

		private static readonly FieldInfo ServerCleanOptionsField = Reflect.Field(typeof(PhotonNetwork), "ServerCleanOptions");

		private static readonly MethodInfo RaiseEventInternalMethod = Reflect.Method(typeof(PhotonNetwork), "RaiseEventInternal");

		private static readonly HashSet<int> DestroyedThisLevel = new HashSet<int>();

		private static readonly HashSet<int> DestroyedWhileLoading = new HashSet<int>();

		private static HashSet<int> ClearedLastTime = new HashSet<int>();

		private static float _queueRunningSince = -1f;

		internal static bool Available
		{
			get
			{
				if (RemoveFilterField != null && KeyByteSevenField != null && ServerCleanOptionsField != null)
				{
					return RaiseEventInternalMethod != null;
				}
				return false;
			}
		}

		internal static void ClearSceneCache()
		{
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: Expected O, but got Unknown
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: Expected O, but got Unknown
			//IL_00ca: Expected O, but got Unknown
			//IL_0102: Unknown result type (might be due to invalid IL or missing references)
			//IL_010d: Expected O, but got Unknown
			//IL_0192: Unknown result type (might be due to invalid IL or missing references)
			//IL_019d: Expected O, but got Unknown
			if (!PunGate.Ready || !PhotonNetwork.IsMasterClient)
			{
				return;
			}
			if (!Available)
			{
				Log.Debug("Photon cache clearing is unavailable on this Photon build; skipping.");
				return;
			}
			PhotonView[] array;
			try
			{
				array = Object.FindObjectsOfType<PhotonView>(true);
			}
			catch (Exception ex)
			{
				Log.Debug("Could not enumerate PhotonViews: " + ex);
				return;
			}
			int num = 0;
			int num2 = 0;
			HashSet<int> hashSet = new HashSet<int>();
			PhotonView val = RunManagerView();
			PhotonView[] array2 = array;
			foreach (PhotonView val2 in array2)
			{
				if ((Object)val2 == (Object)null)
				{
					continue;
				}
				Scene scene = ((Component)val2).gameObject.scene;
				if (((Scene)(ref scene)).buildIndex == -1)
				{
					num2++;
					continue;
				}
				if (ClearOne(val2))
				{
					num++;
					hashSet.Add(val2.InstantiationId);
				}
				if ((Object)val2 != (Object)val)
				{
					ClearBufferedRpcs(val2);
				}
			}
			int num3 = 0;
			foreach (int item in DestroyedThisLevel)
			{
				if ((Object)PhotonView.Find(item) == (Object)null && ClearById(item, item))
				{
					num3++;
				}
			}
			DestroyedThisLevel.Clear();
			if (num3 > 0)
			{
				Log.Debug("Also cleared " + num3 + " cached spawn(s) of objects destroyed during the level (so late joiners don't get them as ghosts).");
			}
			int num4 = 0;
			foreach (int item2 in DestroyedWhileLoading)
			{
				if (!ClearedLastTime.Contains(item2) && (Object)PhotonView.Find(item2) == (Object)null && ClearById(item2, item2))
				{
					num4++;
				}
			}
			DestroyedWhileLoading.Clear();
			ClearedLastTime = hashSet;
			if (num4 > 0)
			{
				Log.Debug("Also cleared " + num4 + " cached spawn(s) of objects destroyed while the last level was loading.");
			}
			if (num > 0)
			{
				Log.Debug("Cleared " + num + " buffered instantiation(s) from the finished level, left " + num2 + " persistent object(s) alone.");
			}
			else
			{
				Log.Debug("No buffered Photon instantiations needed clearing.");
			}
		}

		internal static void ResetDestroyed()
		{
			DestroyedThisLevel.Clear();
		}

		internal static void NoteDestroyedWhileLoading(int instantiationId)
		{
			if (instantiationId > 0)
			{
				DestroyedWhileLoading.Add(instantiationId);
			}
		}

		internal static void Tick()
		{
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Expected O, but got Unknown
			if (!PhotonNetwork.IsMessageQueueRunning)
			{
				_queueRunningSince = -1f;
				return;
			}
			if (_queueRunningSince < 0f)
			{
				_queueRunningSince = Time.unscaledTime;
			}
			if (DestroyedWhileLoading.Count == 0 || Time.unscaledTime - _queueRunningSince < 2f || !PunGate.Ready || !PhotonNetwork.InRoom || !PhotonNetwork.IsMasterClient)
			{
				return;
			}
			int num = 0;
			foreach (int item in DestroyedWhileLoading)
			{
				if (!ClearedLastTime.Contains(item) && (Object)PhotonView.Find(item) == (Object)null && ClearById(item, item))
				{
					num++;
				}
			}
			DestroyedWhileLoading.Clear();
			if (num > 0)
			{
				Log.Debug("Cleared " + num + " cached spawn(s) of objects destroyed while this level was loading.");
			}
		}

		internal static void NoteDestroyed(int instantiationId)
		{
			if (instantiationId > 0)
			{
				DestroyedThisLevel.Add(instantiationId);
			}
		}

		internal static bool ClearOne(PhotonView view)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Expected O, but got Unknown
			if ((Object)view == (Object)null || !Available)
			{
				return false;
			}
			if (!view.isRuntimeInstantiated || view.InstantiationId < 1)
			{
				return false;
			}
			return ClearById(view.InstantiationId, view.ViewID);
		}

		internal static bool ClearById(int instantiationId, int viewIdForLog)
		{
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Expected O, but got Unknown
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Expected O, but got Unknown
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			if (!Available || instantiationId < 1)
			{
				return false;
			}
			try
			{
				object value = RemoveFilterField.GetValue(null);
				Hashtable val = (Hashtable)((value is Hashtable) ? value : null);
				object value2 = KeyByteSevenField.GetValue(null);
				object value3 = ServerCleanOptionsField.GetValue(null);
				RaiseEventOptions val2 = (RaiseEventOptions)((value3 is RaiseEventOptions) ? value3 : null);
				if (val == null || value2 == null || val2 == null)
				{
					return false;
				}
				val[value2] = instantiationId;
				val2.CachingOption = (EventCaching)6;
				return RaiseInternal(202, val, val2);
			}
			catch (Exception ex)
			{
				Log.Debug("Could not clear Photon cache for view " + viewIdForLog + ": " + ex);
				return false;
			}
		}

		internal static bool RaiseInternal(byte code, object data, RaiseEventOptions options)
		{
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			if (RaiseEventInternalMethod == null)
			{
				return false;
			}
			try
			{
				RaiseEventInternalMethod.Invoke(null, new object[4]
				{
					code,
					data,
					options,
					SendOptions.SendReliable
				});
				return true;
			}
			catch (Exception ex)
			{
				Log.Debug("Photon internal event " + code + " failed: " + ex);
				return false;
			}
		}

		internal static PhotonView RunManagerView()
		{
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Expected O, but got Unknown
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Expected O, but got Unknown
			try
			{
				object obj = Reflect.Field(typeof(RunManager), "runManagerPUN")?.GetValue(RunManager.instance);
				object obj3;
				if (obj != null)
				{
					object obj2 = Reflect.Field(obj.GetType(), "photonView")?.GetValue(obj);
					PhotonView val = (PhotonView)((obj2 is PhotonView) ? obj2 : null);
					if ((Object)(object)val != (Object)null)
					{
						obj3 = val;
						goto IL_0068;
					}
				}
				obj3 = null;
				goto IL_0068;
				IL_0068:
				return (PhotonView)obj3;
			}
			catch
			{
				return null;
			}
		}

		internal static void ClearBufferedRpcs(PhotonView view)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Expected O, but got Unknown
			if ((Object)view == (Object)null || view.ViewID == 0)
			{
				return;
			}
			try
			{
				PhotonNetwork.RemoveBufferedRPCs(view.ViewID, (string)null, (int[])null);
			}
			catch (Exception ex)
			{
				Log.Debug("Could not clear buffered RPCs for view " + view.ViewID + ": " + ex);
			}
		}

		internal static void ClearStaleLevelUpdateRpc()
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Expected O, but got Unknown
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Expected O, but got Unknown
			if (!PunGate.Ready || !PhotonNetwork.IsMasterClient || (Object)RunManager.instance == (Object)null)
			{
				return;
			}
			try
			{
				PhotonView val = RunManagerView();
				if (!((Object)val == (Object)null))
				{
					PhotonNetwork.RemoveBufferedRPCs(val.ViewID, "UpdateLevelRPC", (int[])null);
					PhotonNetwork.RemoveBufferedRPCs(val.ViewID, "AllPlayersDeadRPC", (int[])null);
					Log.Debug("Removed the previous level's buffered UpdateLevelRPC and AllPlayersDeadRPC.");
				}
			}
			catch (Exception ex)
			{
				Log.Debug("Could not remove the stale level update RPC: " + ex);
			}
		}

		internal static void ClearStaleGenerationRpcs(LevelGenerator generator)
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Expected O, but got Unknown
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Expected O, but got Unknown
			if (!PunGate.Ready || !PhotonNetwork.IsMasterClient || (Object)generator == (Object)null)
			{
				return;
			}
			if (generator.Generated)
			{
				Log.Debug("Generation already reported done; leaving its buffered RPCs alone.");
				return;
			}
			try
			{
				PhotonView photonView = generator.PhotonView;
				if (!((Object)photonView == (Object)null) && photonView.ViewID != 0)
				{
					PhotonNetwork.RemoveBufferedRPCs(photonView.ViewID, (string)null, (int[])null);
					Log.Debug("Removed the previous level's buffered generation RPCs.");
				}
			}
			catch (Exception ex)
			{
				Log.Debug("Could not remove stale generation RPCs: " + ex);
			}
		}
	}
	internal static class PlayerLimit
	{
		internal static void Watch()
		{
			ModConfig.MaxPlayers.SettingChanged += delegate
			{
				Apply();
			};
			ModConfig.MaxPlayersEnabled.SettingChanged += delegate
			{
				Apply();
			};
		}

		internal static void Apply()
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Expected O, but got Unknown
			if (!ModConfig.MaxPlayersEnabled.Value || (Object)GameManager.instance == (Object)null)
			{
				return;
			}
			int value = ModConfig.MaxPlayers.Value;
			try
			{
				GameManager.instance.SetMaxPlayers(value);
				Log.Debug("Max players set to " + GameManager.instance.maxPlayers + ".");
			}
			catch (Exception ex)
			{
				Log.Debug("Could not set max players: " + ex);
			}
		}
	}
	internal enum Access
	{
		Everyone,
		Friends,
		Closed
	}
	internal static class LobbyAccess
	{
		private static Access _current;

		private static string _room;

		internal static Access Current
		{
			get
			{
				object obj;
				if (!PhotonNetwork.InRoom)
				{
					obj = null;
				}
				else
				{
					Room currentRoom = PhotonNetwork.CurrentRoom;
					obj = ((currentRoom != null) ? currentRoom.Name : null);
				}
				string text = (string)obj;
				if (text != _room)
				{
					_room = text;
					_current = Access.Everyone;
				}
				return _current;
			}
		}

		internal static string Describe(Access access)
		{
			switch (access)
			{
			case Access.Friends:
				return "friends and invites only";
			case Access.Closed:
				return "closed to new players";
			default:
				if (!LobbyGate.IsPrivateLobby())
				{
					return "open to everyone";
				}
				return "open to friends and invites (private game)";
			}
		}

		internal static bool Set(Access access)
		{
			if (!PhotonNetwork.InRoom || !PhotonNetwork.IsMasterClient)
			{
				return false;
			}
			if (Current == access)
			{
				return true;
			}
			_current = access;
			LobbyGate.Apply("lobby access changed", force: true);
			Log.Info("Lobby is now " + Describe(access) + ".");
			StatusOverlay.ShowNotice("Lobby: " + Describe(access), 6f);
			return true;
		}
	}
	internal static class RoomNamePrefix
	{
		private const string Key = "server_name";

		private const float CheckEvery = 2f;

		private static float _next;

		private static string _applied;

		private static string _room;

		internal static void Tick()
		{
			//IL_0125: Unknown result type (might be due to invalid IL or missing references)
			//IL_012a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0137: Expected O, but got Unknown
			//IL_013e: Expected O, but got Unknown
			if (Time.unscaledTime < _next)
			{
				return;
			}
			_next = Time.unscaledTime + 2f;
			try
			{
				Room currentRoom = PhotonNetwork.CurrentRoom;
				if (currentRoom == null || !PhotonNetwork.IsMasterClient || !(((RoomInfo)currentRoom).CustomProperties[(object)"server_name"] is string { Length: not 0 } text))
				{
					return;
				}
				if (currentRoom.Name != _room)
				{
					_room = currentRoom.Name;
					_applied = null;
				}
				string text2 = ModConfig.RoomNamePrefix?.Value?.Trim() ?? "";
				string text3 = Strip(text, _applied);
				if (_applied == null)
				{
					text3 = Strip(text, text2);
				}
				int num;
				string text4;
				if (ModConfig.On && text2.Length > 0)
				{
					if (!ModConfig.JoinInLevel.Value && !ModConfig.JoinInShop.Value)
					{
						num = (ModConfig.JoinInLobby.Value ? 1 : 0);
						if (num == 0)
						{
							goto IL_00fe;
						}
					}
					else
					{
						num = 1;
					}
					if (text3.IndexOf(text2, StringComparison.OrdinalIgnoreCase) < 0)
					{
						text4 = text2 + " " + text3;
						goto IL_010d;
					}
				}
				else
				{
					num = 0;
				}
				goto IL_00fe;
				IL_00fe:
				text4 = text3;
				goto IL_010d;
				IL_010d:
				string text5 = text4;
				_applied = ((num != 0) ? text2 : null);
				if (text5 != text)
				{
					Hashtable val = new Hashtable();
					((Dictionary<object, object>)val).Add((object)"server_name", (object)text5);
					currentRoom.SetCustomProperties(val, (Hashtable)null, (WebFlags)null);
					Log.Info("Server list name is now '" + text5 + "'.");
				}
			}
			catch (Exception ex)
			{
				Log.Debug("Room name prefix update failed: " + ex.Message);
			}
		}

		private static string Strip(string name, string prefix)
		{
			if (string.IsNullOrEmpty(prefix) || !name.StartsWith(prefix + " ", StringComparison.Ordinal))
			{
				return name;
			}
			return name.Substring(prefix.Length + 1);
		}
	}
	internal static class SteamLobbyCheck
	{
		private sealed class Missing
		{
			internal float Since;

			internal float SentAt;

			internal ulong Lobby;

			internal bool GaveUp;
		}

		[HarmonyPatch(typeof(RunManagerPUN), "SendJoinSteamLobby")]
		internal static class BroadcastPatch
		{
			private static bool Prefix()
			{
				try
				{
					if (!Active)
					{
						return true;
					}
					SteamManager.instance.UnlockLobby(true);
					LobbyGate.Apply("Steam lobby ready", force: true);
					RemoveBroadcast();
					_next = 0f;
					Log.Debug("Skipped the game's Steam lobby broadcast (it throws players already in the lobby out of it); players not in it are sent it one by one.");
					return false;
				}
				catch (Exception ex)
				{
					Log.Debug("Steam lobby broadcast check failed: " + ex.Message);
					return true;
				}
			}
		}

		private const float CheckEvery = 2f;

		private const float LostGrace = 10f;

		private const float LostWarnAfter = 4f;

		private static bool _lostWarned;

		private const float FirstSendAfter = 5f;

		private const float BounceAfter = 10f;

		private const float RebuildCooldown = 120f;

		private const float LoadingGrace = 60f;

		private static LevelGenerator _level;

		private static float _levelSince;

		private static readonly FieldInfo LobbyField = Reflect.Field(typeof(SteamManager), "currentLobby"