Decompiled source of MiSideMultiplayer v1.0.1

MiSideMultiplayer.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Unity.IL2CPP;
using Coffee.UIEffects;
using HarmonyLib;
using Il2CppInterop.Runtime;
using Il2CppInterop.Runtime.Attributes;
using Il2CppInterop.Runtime.Injection;
using Il2CppInterop.Runtime.InteropTypes;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppSystem;
using Il2CppSystem.Collections;
using Il2CppSystem.Collections.Generic;
using MiSideMultiplayer.API;
using MiSideMultiplayer.API.Credits;
using MiSideMultiplayer.API.Events;
using MiSideMultiplayer.API.Factories;
using MiSideMultiplayer.API.Notifications;
using MiSideMultiplayer.API.Patchers;
using MiSideMultiplayer.API.Patches;
using MiSideMultiplayer.API.Players;
using MiSideMultiplayer.Runtime;
using MiSideMultiplayer.Runtime.Loading;
using MiSideMultiplayer.Runtime.Lobby;
using MiSideMultiplayer.Runtime.Login;
using MiSideMultiplayer.Runtime.Login.Stages;
using MiSideMultiplayer.Runtime.Menu;
using MiSideMultiplayer.Runtime.Menu.Music;
using MiSideMultiplayer.Runtime.Menu.Patches;
using MiSideMultiplayer.Runtime.Menu.UI;
using MiSideMultiplayer.Runtime.Settings;
using MiSideMultiplayer.Runtime.UI;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.EventSystems;
using UnityEngine.Events;
using UnityEngine.Networking;
using UnityEngine.Rendering;
using UnityEngine.Rendering.PostProcessing;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("MiSideMultiplayer")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("MiSideMultiplayer")]
[assembly: AssemblyTitle("MiSideMultiplayer")]
[assembly: AssemblyVersion("1.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 MiSideMultiplayer
{
	internal static class WorldPlayerUpdateGuard
	{
		private static bool _initialized;

		private static bool _waitingLogged;

		public static void Initialize(Harmony harmony)
		{
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Expected O, but got Unknown
			if (_initialized || harmony == null)
			{
				return;
			}
			_initialized = true;
			try
			{
				MethodInfo method = typeof(WorldPlayer).GetMethod("Update", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
				if (method == null)
				{
					DiagnosticLog.Warning("WorldPlayerUpdateGuard: WorldPlayer.Update() not found.");
					return;
				}
				MethodInfo method2 = typeof(WorldPlayerUpdateGuard).GetMethod("UpdatePrefix", BindingFlags.Static | BindingFlags.NonPublic);
				harmony.Patch((MethodBase)method, new HarmonyMethod(method2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				DiagnosticLog.Info("WorldPlayerUpdateGuard: patched WorldPlayer.Update() to skip frames without a bound local player/camera.");
			}
			catch (Exception ex)
			{
				DiagnosticLog.Warning("WorldPlayerUpdateGuard: patch failed: " + ex.GetType().Name + ": " + ex.Message);
			}
		}

		private static bool UpdatePrefix()
		{
			try
			{
				if ((Object)(object)GlobalTag.gameController != (Object)null && (Object)(object)GlobalTag.player != (Object)null && (Object)(object)GlobalTag.cameraPlayer != (Object)null)
				{
					_waitingLogged = false;
					return true;
				}
				if (!_waitingLogged)
				{
					DiagnosticLog.Verbose("WorldPlayerUpdateGuard: skipped WorldPlayer.Update() while scene globals are being cleared or rebound.");
					_waitingLogged = true;
				}
				return false;
			}
			catch
			{
				return false;
			}
		}
	}
	public sealed class ChatController : IDisposable
	{
		private sealed class ChatLine
		{
			public string Text;

			public string Color;

			public float ReceivedAt;
		}

		private const float MessageFadeSeconds = 10f;

		private const int ClosedMessageCount = 4;

		private const float ProximityDistance = 30f;

		private const int MaxMessageLength = 512;

		private const float MinecraftChatWidth = 640f;

		private const float MinecraftInputWidth = 1280f;

		private const float ClosedChatBottomOffset = 80f;

		private const float ChatBackgroundOpacity = 0.55f;

		private readonly RpcDispatcher dispatcher;

		private readonly NetworkManager networkManager;

		private readonly string localPlayerId;

		private readonly Func<string> displayNameProvider;

		private readonly List<ChatLine> messages = new List<ChatLine>();

		private MonoBehaviour playerMoveScript;

		private string inputText = string.Empty;

		private string activeSceneName;

		private bool isOpen;

		private bool cursorStateCaptured;

		private bool cursorWasVisible;

		private CursorLockMode cursorLockState;

		private GUIStyle messageStyle;

		private GUIStyle inputStyle;

		public ChatController(RpcDispatcher dispatcher, NetworkManager networkManager, string localPlayerId, Func<string> displayNameProvider)
		{
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			this.dispatcher = dispatcher;
			this.networkManager = networkManager;
			this.localPlayerId = localPlayerId ?? string.Empty;
			this.displayNameProvider = displayNameProvider;
			Scene activeScene = SceneManager.GetActiveScene();
			activeSceneName = ((Scene)(ref activeScene)).name;
			if (dispatcher != null)
			{
				dispatcher.ChatMessageReceived += OnChatMessageReceived;
				dispatcher.ChatSystemMessageReceived += OnChatSystemMessageReceived;
				dispatcher.ServerResponseReceived += OnServerResponseReceived;
			}
		}

		public void Tick()
		{
			//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)
			Scene activeScene = SceneManager.GetActiveScene();
			string name = ((Scene)(ref activeScene)).name;
			if (name != activeSceneName)
			{
				activeSceneName = name;
				if (string.Equals(name, "SceneMenu", StringComparison.OrdinalIgnoreCase))
				{
					ClearHistory();
					CloseChat(restoreCursor: false);
				}
			}
			if (!IsChatAvailable())
			{
				if (isOpen)
				{
					CloseChat(restoreCursor: false);
				}
				return;
			}
			if (isOpen && Input.GetKeyDown((KeyCode)27))
			{
				Input.ResetInputAxes();
			}
			if (Input.GetKeyDown((KeyCode)13) || Input.GetKeyDown((KeyCode)271))
			{
				if (isOpen)
				{
					SubmitInput();
				}
				else
				{
					OpenChat();
				}
			}
			if (isOpen)
			{
				SetChatCursor();
				FreezeLocalMovement();
			}
		}

		public void OnGui()
		{
			if (Event.current != null && !((Object)(object)GUI.skin == (Object)null) && IsChatAvailable())
			{
				EnsureStyles();
				HandleKeyboard(Event.current);
				if (isOpen)
				{
					DrawOpenChat();
				}
				else
				{
					DrawClosedChat();
				}
			}
		}

		public void ShowSystemMessage(string text, string color = "yellow")
		{
			if (!string.IsNullOrWhiteSpace(text))
			{
				AddLine(text, string.IsNullOrWhiteSpace(color) ? "yellow" : color);
			}
		}

		public void Dispose()
		{
			if (dispatcher != null)
			{
				dispatcher.ChatMessageReceived -= OnChatMessageReceived;
				dispatcher.ChatSystemMessageReceived -= OnChatSystemMessageReceived;
				dispatcher.ServerResponseReceived -= OnServerResponseReceived;
			}
			if (isOpen)
			{
				CloseChat();
			}
			RestoreLocalMovement();
		}

		private void HandleKeyboard(Event currentEvent)
		{
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Invalid comparison between Unknown and I4
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Invalid comparison between Unknown and I4
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Invalid comparison between Unknown and I4
			if (!isOpen || (int)currentEvent.type != 4)
			{
				return;
			}
			if ((int)currentEvent.keyCode == 27)
			{
				CloseChat();
				currentEvent.Use();
			}
			else if ((int)currentEvent.keyCode == 8)
			{
				if (inputText.Length > 0)
				{
					inputText = inputText.Substring(0, inputText.Length - 1);
				}
				currentEvent.Use();
			}
			else if (currentEvent.character != 0 && !char.IsControl(currentEvent.character) && inputText.Length < 512)
			{
				inputText += currentEvent.character;
				currentEvent.Use();
			}
		}

		private void OpenChat()
		{
			isOpen = true;
			inputText = string.Empty;
			CaptureGameCursorState();
			SetChatCursor();
			FreezeLocalMovement();
		}

		private void CloseChat(bool restoreCursor = true)
		{
			isOpen = false;
			inputText = string.Empty;
			if (restoreCursor)
			{
				RestoreGameCursor();
			}
			else
			{
				ReleaseCursorState();
			}
			RestoreLocalMovement();
		}

		private void CaptureGameCursorState()
		{
			//IL_0015: 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)
			if (!cursorStateCaptured)
			{
				cursorWasVisible = Cursor.visible;
				cursorLockState = Cursor.lockState;
				cursorStateCaptured = true;
			}
		}

		private static void SetChatCursor()
		{
			Cursor.visible = true;
			Cursor.lockState = (CursorLockMode)0;
		}

		private void RestoreGameCursor()
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			if (cursorStateCaptured)
			{
				Cursor.visible = cursorWasVisible;
				Cursor.lockState = cursorLockState;
				ReleaseCursorState();
			}
		}

		private void ReleaseCursorState()
		{
			cursorStateCaptured = false;
		}

		private void SubmitInput()
		{
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			string text = (inputText ?? string.Empty).Trim();
			CloseChat();
			if (text.Length != 0 && dispatcher != null)
			{
				if (text.Length > 512)
				{
					text = text.Substring(0, 512);
				}
				ChatMessagePayload chatMessagePayload = new ChatMessagePayload();
				chatMessagePayload.senderId = localPlayerId;
				chatMessagePayload.displayName = GetDisplayName();
				Scene activeScene = SceneManager.GetActiveScene();
				chatMessagePayload.sceneName = ((Scene)(ref activeScene)).name;
				chatMessagePayload.position = GetLocalPosition();
				chatMessagePayload.text = text;
				if (!text.StartsWith("/"))
				{
					AddLine("You: " + text, "white");
				}
				dispatcher.SendChatMessage(chatMessagePayload);
			}
		}

		private void OnChatMessageReceived(ChatMessagePayload message)
		{
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			if (message == null || string.IsNullOrWhiteSpace(message.text) || (!string.IsNullOrEmpty(message.senderId) && message.senderId == localPlayerId))
			{
				return;
			}
			if (!string.IsNullOrEmpty(message.sceneName))
			{
				string sceneName = message.sceneName;
				Scene activeScene = SceneManager.GetActiveScene();
				if (!string.Equals(sceneName, ((Scene)(ref activeScene)).name, StringComparison.OrdinalIgnoreCase))
				{
					return;
				}
			}
			if (IsSenderNear(message.position))
			{
				string text = (string.IsNullOrWhiteSpace(message.displayName) ? message.senderId : message.displayName);
				if (string.IsNullOrWhiteSpace(text))
				{
					text = "Player";
				}
				AddLine(text + ": " + message.text, "white");
			}
		}

		private void OnChatSystemMessageReceived(ChatSystemMessagePayload message)
		{
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			if (message == null || string.IsNullOrWhiteSpace(message.text))
			{
				return;
			}
			if (!string.IsNullOrEmpty(message.sceneName))
			{
				string sceneName = message.sceneName;
				Scene activeScene = SceneManager.GetActiveScene();
				if (!string.Equals(sceneName, ((Scene)(ref activeScene)).name, StringComparison.OrdinalIgnoreCase))
				{
					return;
				}
			}
			AddLine(message.text, string.IsNullOrEmpty(message.color) ? "yellow" : message.color);
		}

		private void OnServerResponseReceived(ServerResponsePayload response)
		{
			if (response != null && !string.IsNullOrWhiteSpace(response.text))
			{
				AddLine(response.text, string.IsNullOrEmpty(response.color) ? "white" : response.color);
			}
		}

		private bool IsSenderNear(NetVector3 senderPosition)
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			if (networkManager == null || (Object)(object)networkManager.LocalPlayerRoot == (Object)null)
			{
				return false;
			}
			return Vector3.Distance(networkManager.LocalPlayerRoot.position, senderPosition.ToUnity()) <= 30f;
		}

		private NetVector3 GetLocalPosition()
		{
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			if (networkManager != null && (Object)(object)networkManager.LocalPlayerRoot != (Object)null)
			{
				return NetVector3.FromUnity(networkManager.LocalPlayerRoot.position);
			}
			return NetVector3.FromUnity(Vector3.zero);
		}

		private string GetDisplayName()
		{
			string text = ((displayNameProvider != null) ? displayNameProvider() : null);
			if (!string.IsNullOrWhiteSpace(text))
			{
				return text.Trim();
			}
			return "Player";
		}

		private void AddLine(string text, string color)
		{
			messages.Add(new ChatLine
			{
				Text = text,
				Color = color,
				ReceivedAt = Time.unscaledTime
			});
		}

		private void ClearHistory()
		{
			messages.Clear();
		}

		private static bool IsChatAvailable()
		{
			//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)
			Scene activeScene = SceneManager.GetActiveScene();
			string name = ((Scene)(ref activeScene)).name;
			if (string.IsNullOrEmpty(name) || name.IndexOf("menu", StringComparison.OrdinalIgnoreCase) >= 0)
			{
				return false;
			}
			return (Object)(object)LocalPlayerLocator.FindHardcodedPlayerPath() != (Object)null;
		}

		private void FreezeLocalMovement()
		{
			if ((Object)(object)playerMoveScript != (Object)null)
			{
				((Behaviour)playerMoveScript).enabled = false;
				return;
			}
			Transform val = LocalPlayerLocator.FindHardcodedPlayerPath();
			if (!((Object)(object)val == (Object)null))
			{
				Component component = ((Component)val).GetComponent("PlayerMove");
				playerMoveScript = (MonoBehaviour)(object)((component is MonoBehaviour) ? component : null);
				if ((Object)(object)playerMoveScript != (Object)null)
				{
					((Behaviour)playerMoveScript).enabled = false;
				}
			}
		}

		private void RestoreLocalMovement()
		{
			if ((Object)(object)playerMoveScript != (Object)null)
			{
				((Behaviour)playerMoveScript).enabled = true;
				playerMoveScript = null;
			}
		}

		private void EnsureStyles()
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Expected O, but got Unknown
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Expected O, but got Unknown
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Expected O, but got Unknown
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: 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_00c5: Expected O, but got Unknown
			if (messageStyle == null || inputStyle == null)
			{
				messageStyle = new GUIStyle(GUI.skin.label);
				messageStyle.fontSize = 16;
				messageStyle.wordWrap = true;
				messageStyle.padding = new RectOffset
				{
					left = 8,
					right = 8,
					top = 1,
					bottom = 1
				};
				inputStyle = new GUIStyle(GUI.skin.label);
				inputStyle.fontSize = 16;
				inputStyle.wordWrap = false;
				inputStyle.padding = new RectOffset
				{
					left = 8,
					right = 8,
					top = 1,
					bottom = 1
				};
			}
		}

		private void DrawOpenChat()
		{
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_0109: Unknown result type (might be due to invalid IL or missing references)
			//IL_0113: Expected O, but got Unknown
			//IL_010e: Unknown result type (might be due to invalid IL or missing references)
			//IL_018a: Unknown result type (might be due to invalid IL or missing references)
			//IL_01eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0213: Unknown result type (might be due to invalid IL or missing references)
			//IL_0222: Unknown result type (might be due to invalid IL or missing references)
			//IL_0151: Unknown result type (might be due to invalid IL or missing references)
			//IL_015b: Expected O, but got Unknown
			//IL_0156: Unknown result type (might be due to invalid IL or missing references)
			float num = Mathf.Min(640f, (float)Screen.width);
			float num2 = Mathf.Min(1280f, (float)Screen.width);
			float num3 = (float)Screen.height - 80f;
			float num4 = Mathf.Min(360f, num3);
			Rect historyRect = default(Rect);
			((Rect)(ref historyRect))..ctor(0f, num3 - num4, num, num4);
			DrawOpenHistory(historyRect);
			Rect rect = default(Rect);
			((Rect)(ref rect))..ctor(0f, (float)Screen.height - 20f, num2, 20f);
			DrawBackground(rect, 0.55f);
			Rect val = default(Rect);
			((Rect)(ref val))..ctor(((Rect)(ref rect)).x + 12f, ((Rect)(ref rect)).y + 1f, ((Rect)(ref rect)).width - 24f, ((Rect)(ref rect)).height - 2f);
			string text = inputText ?? string.Empty;
			float num5 = Mathf.Max(0f, ((Rect)(ref val)).width - (float)inputStyle.padding.horizontal - 8f);
			int num6 = 0;
			string text2 = text;
			float num7 = Mathf.Max(0f, inputStyle.CalcSize(new GUIContent(text2)).x - (float)inputStyle.padding.horizontal);
			while (num7 > num5 && num6 < text.Length)
			{
				num6++;
				text2 = text.Substring(num6);
				num7 = Mathf.Max(0f, inputStyle.CalcSize(new GUIContent(text2)).x - (float)inputStyle.padding.horizontal);
			}
			GUI.Label(val, text2, inputStyle);
			if (Time.unscaledTime % 1f < 0.5f)
			{
				float num8 = Mathf.Min(((Rect)(ref val)).xMax - (float)inputStyle.padding.right - 8f, ((Rect)(ref val)).x + (float)inputStyle.padding.left + num7);
				Color color = GUI.color;
				GUI.color = Color.white;
				GUI.DrawTexture(new Rect(num8, ((Rect)(ref rect)).yMax - 6f, 8f, 2f), (Texture)(object)Texture2D.whiteTexture);
				GUI.color = color;
			}
		}

		private void DrawOpenHistory(Rect historyRect)
		{
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			if (messages.Count == 0)
			{
				return;
			}
			float num = ((Rect)(ref historyRect)).width - 16f;
			float num2 = ((Rect)(ref historyRect)).yMax;
			int num3 = messages.Count - 1;
			while (num3 >= 0)
			{
				ChatLine chatLine = messages[num3];
				float lineHeight = GetLineHeight(chatLine, num);
				num2 -= lineHeight;
				if (!(num2 < ((Rect)(ref historyRect)).y))
				{
					DrawBackground(new Rect(((Rect)(ref historyRect)).x, num2, ((Rect)(ref historyRect)).width, lineHeight), 0.55f);
					Color color = GUI.color;
					GUI.color = GetColor(chatLine.Color, 1f);
					GUI.Label(new Rect(((Rect)(ref historyRect)).x + 8f, num2, num, lineHeight), chatLine.Text, messageStyle);
					GUI.color = color;
					num3--;
					continue;
				}
				break;
			}
		}

		private void DrawClosedChat()
		{
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			float num = Mathf.Min(640f, (float)Screen.width);
			float num2 = (float)Screen.height - 80f;
			int num3 = 0;
			int num4 = messages.Count - 1;
			Rect val = default(Rect);
			while (num4 >= 0 && num3 < 4)
			{
				ChatLine chatLine = messages[num4];
				float num5 = Time.unscaledTime - chatLine.ReceivedAt;
				if (!(num5 >= 10f))
				{
					float num6 = Mathf.Clamp01(1f - num5 / 10f);
					float lineHeight = GetLineHeight(chatLine, num);
					num2 -= lineHeight;
					((Rect)(ref val))..ctor(0f, num2, num, lineHeight);
					DrawBackground(val, 0.55f * num6);
					Color color = GUI.color;
					GUI.color = GetColor(chatLine.Color, num6);
					GUI.Label(val, chatLine.Text, messageStyle);
					GUI.color = color;
					num3++;
				}
				num4--;
			}
		}

		private float GetLineHeight(ChatLine line, float width)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Expected O, but got Unknown
			return Mathf.Max(18f, messageStyle.CalcHeight(new GUIContent(line.Text), width));
		}

		private static void DrawBackground(Rect rect, float alpha)
		{
			//IL_0000: 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_001f: Unknown result type (might be due to invalid IL or missing references)
			Color color = GUI.color;
			GUI.color = new Color(0f, 0f, 0f, alpha);
			GUI.DrawTexture(rect, (Texture)(object)Texture2D.whiteTexture);
			GUI.color = color;
		}

		private static Color GetColor(string color, float alpha)
		{
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			Color result = default(Color);
			switch ((color ?? string.Empty).ToLowerInvariant())
			{
			case "yellow":
				result = Color.yellow;
				break;
			case "red":
				result = Color.red;
				break;
			case "darkred":
				((Color)(ref result))..ctor(0.65f, 0.05f, 0.05f);
				break;
			case "green":
				result = Color.green;
				break;
			default:
				result = Color.white;
				break;
			}
			result.a = alpha;
			return result;
		}
	}
	[Serializable]
	public sealed class ChatMessagePayload
	{
		public string senderId;

		public string displayName;

		public string sceneName;

		public NetVector3 position;

		public string text;
	}
	[Serializable]
	public sealed class ChatSystemMessagePayload
	{
		public string text;

		public string color;

		public string sceneName;
	}
	[Serializable]
	public sealed class ServerResponsePayload
	{
		public string text;

		public string color;
	}
	internal static class DiagnosticLog
	{
		private const string Prefix = "[MiSideMultiplayer] ";

		private const string VerbosePrefix = "[MiSideMultiplayer][V] ";

		public static bool Enabled = true;

		public static bool VerboseEnabled = false;

		public static void Info(string message)
		{
			if (Enabled)
			{
				Debug.Log(Object.op_Implicit("[MiSideMultiplayer] " + message));
			}
		}

		public static void Verbose(string message)
		{
			if (Enabled && VerboseEnabled)
			{
				Debug.Log(Object.op_Implicit("[MiSideMultiplayer][V] " + message));
			}
		}

		public static void Warning(string message)
		{
			Debug.LogWarning(Object.op_Implicit("[MiSideMultiplayer] " + message));
		}

		public static void Error(string message)
		{
			Debug.LogError(Object.op_Implicit("[MiSideMultiplayer] " + message));
		}
	}
	[BepInPlugin("MS_Multiplayer", "MiSide Multiplayer Beta", "1.0.0-beta.1")]
	public sealed class MiSideMultiplayerPlugin : BasePlugin
	{
		public const string PluginGuid = "MS_Multiplayer";

		public const string PluginName = "MiSide Multiplayer Beta";

		public const string PluginVersion = "1.0.0-beta.1";

		internal static MiSideMultiplayerRuntime RuntimeDriver;

		internal static MiSideMultiplayerPlugin activeSettingsPlugin;

		private ConfigEntry<string> localPlayerId;

		private ConfigEntry<string> displayName;

		private ConfigEntry<string> visualRootCandidates;

		private ConfigEntry<string> playerRootCandidates;

		private ConfigEntry<bool> enableNetworking;

		private ConfigEntry<string> serverHost;

		private ConfigEntry<int> serverPort;

		private ConfigEntry<string> roomName;

		private ConfigEntry<float> snapshotSendRate;

		private ConfigEntry<float> snapshotHeartbeatSeconds;

		private ConfigEntry<bool> emitLocalSnapshots;

		private ConfigEntry<bool> debugLogging;

		private ConfigEntry<bool> verboseLogging;

		private ConfigEntry<bool> hideNativeLoadingUI;

		private static RuntimeServices runtimeServices;

		public override void Load()
		{
			//IL_0286: Unknown result type (might be due to invalid IL or missing references)
			//IL_028b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0291: Expected O, but got Unknown
			//IL_0291: Unknown result type (might be due to invalid IL or missing references)
			//IL_038a: Unknown result type (might be due to invalid IL or missing references)
			//IL_03af: Unknown result type (might be due to invalid IL or missing references)
			//IL_03b5: Expected O, but got Unknown
			//IL_03b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_03bb: Expected O, but got Unknown
			//IL_03bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_03c1: Expected O, but got Unknown
			//IL_03c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_03c7: Expected O, but got Unknown
			//IL_03c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_03cd: Expected O, but got Unknown
			//IL_03cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_03d3: Expected O, but got Unknown
			//IL_03d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_03d9: Expected O, but got Unknown
			//IL_03de: Expected O, but got Unknown
			//IL_0524: Unknown result type (might be due to invalid IL or missing references)
			//IL_0587: Unknown result type (might be due to invalid IL or missing references)
			//IL_05ea: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				((BasePlugin)this).Log.LogInfo((object)"[MITA-DIAG] Load() entered.");
			}
			catch
			{
			}
			localPlayerId = ((BasePlugin)this).Config.Bind<string>("Identity", "LocalPlayerId", CreateDefaultPlayerId(), "Stable ID sent with local snapshots. Use your server/account ID if you already have one.");
			displayName = ((BasePlugin)this).Config.Bind<string>("Identity", "DisplayName", "Player", "Name sent with local snapshots, chat messages, and join/leave notices.");
			visualRootCandidates = ((BasePlugin)this).Config.Bind<string>("Puppets", "VisualRootCandidates", "CameraMita/Mita,Mita/MitaPerson Mita,MitaPerson Mita,MitaPerson Mita/Body,Mita,Model,Visuals,PlayerModel,Character,Body,Armature,Mesh,Avatar", "Comma-separated child names or paths to search for the visual-only player hierarchy.");
			playerRootCandidates = ((BasePlugin)this).Config.Bind<string>("Puppets", "PlayerRootCandidates", "Tamagotchi,World/Tamagotchi,CameraMita,Player,LocalPlayer,PlayerController,FirstPersonController,MainPlayer,PlayerMove,Player_move,PlayerRoot,Character,Personage", "Comma-separated root names used by the automatic local-player locator.");
			enableNetworking = ((BasePlugin)this).Config.Bind<bool>("Networking", "EnableNetworking", true, "If true, connect to the MiSide multiplayer relay and exchange puppet snapshots.");
			serverHost = ((BasePlugin)this).Config.Bind<string>("Networking", "ServerHost", RuntimeServices.CentralRelayHost, "Deprecated: mod now uses the central relay by default. Ignored at runtime.");
			serverPort = ((BasePlugin)this).Config.Bind<int>("Networking", "ServerPort", RuntimeServices.CentralRelayPort, "Deprecated: mod now uses the central relay by default. Ignored at runtime.");
			roomName = ((BasePlugin)this).Config.Bind<string>("Networking", "RoomName", "default", "Simple client-side room filter. Players only consume packets from the same room.");
			snapshotSendRate = ((BasePlugin)this).Config.Bind<float>("Networking", "SnapshotSendRate", 40f, "Local state snapshots per second.");
			snapshotHeartbeatSeconds = ((BasePlugin)this).Config.Bind<float>("Networking", "SnapshotHeartbeatSeconds", 1f, "Maximum seconds between state snapshots even when the local player is idle.");
			emitLocalSnapshots = ((BasePlugin)this).Config.Bind<bool>("Networking", "EmitLocalSnapshots", true, "If true, LocalPlayerSampler raises RpcDispatcher.OutgoingRpc events.");
			debugLogging = ((BasePlugin)this).Config.Bind<bool>("Diagnostics", "DebugLogging", true, "If true, logs first snapshot send/receive and puppet creation diagnostics.");
			verboseLogging = ((BasePlugin)this).Config.Bind<bool>("Diagnostics", "VerboseLogging", false, "If true, logs HIGH-FREQUENCY session traffic (hello broadcasts, roster updates). Off by default because it can flood the BepInEx console and crash the game.");
			hideNativeLoadingUI = ((BasePlugin)this).Config.Bind<bool>("Diagnostics", "HideNativeLoadingUI", true, "If true, hides the game's native loading bar and text during the SceneLoading interstitial. The gradient background stays, so the multiplayer ring draws over it. Set false to debug the native UI.");
			DiagnosticLog.Enabled = debugLogging.Value;
			DiagnosticLog.VerboseEnabled = verboseLogging.Value;
			TryWriteVersionFile();
			RegisterIl2CppTypes();
			try
			{
				SceneLoadedEvent.RegisterEvent();
				((BasePlugin)this).Log.LogInfo((object)"[MITA-DIAG] SceneLoadedEvent registered.");
			}
			catch (Exception ex)
			{
				((BasePlugin)this).Log.LogError((object)("[MITA-DIAG] SceneLoadedEvent.RegisterEvent THREW: " + ex));
			}
			activeSettingsPlugin = this;
			MiSideMultiplayerRuntime miSideMultiplayerRuntime = (RuntimeDriver = ((BasePlugin)this).AddComponent<MiSideMultiplayerRuntime>());
			try
			{
				GameObject val = new GameObject("MiSideMultiplayer.LoadingManager");
				Object.DontDestroyOnLoad((Object)val);
				((Object)val).hideFlags = (HideFlags)61;
				val.AddComponent<LoadingStageManager>();
				((BasePlugin)this).Log.LogInfo((object)"[MITA-DIAG] LoadingStageManager spawned.");
			}
			catch (Exception ex2)
			{
				((BasePlugin)this).Log.LogError((object)("[MITA-DIAG] LoadingStageManager spawn THREW: " + ex2));
			}
			runtimeServices = new RuntimeServices(((Component)miSideMultiplayerRuntime).transform, localPlayerId.Value, displayName.Value, SplitCsv(visualRootCandidates.Value), SplitCsv(playerRootCandidates.Value), snapshotSendRate.Value, snapshotHeartbeatSeconds.Value, emitLocalSnapshots.Value, enableNetworking.Value, serverHost.Value, serverPort.Value, roomName.Value, () => displayName.Value);
			try
			{
				((BasePlugin)this).Log.LogInfo((object)"[MITA-DIAG] About to construct Harmony and call MitaPlayerOverride.Initialize().");
				Harmony val2 = new Harmony("com.miside.multiplayer.mita");
				((BasePlugin)this).Log.LogInfo((object)"[MITA-DIAG] Harmony instance created, calling Initialize()...");
				MitaPlayerOverride.Configure(localPlayerId.Value);
				MitaPlayerOverride.Initialize(val2);
				MitaEventSync.Initialize(val2);
				WorldTransitionSync.Initialize(val2);
				RuntimeServices.InitializeWorldGuard(val2);
				HostDepartureHook.Initialize(val2);
				SceneLoadingHook.Initialize(val2);
				SceneManagerHook.Initialize(val2);
				StorySceneTransitionSync.Initialize(val2);
				((BasePlugin)this).Log.LogInfo((object)"[MITA-DIAG] MitaPlayerOverride.Initialize() returned normally.");
				((BasePlugin)this).Log.LogInfo((object)"[MITA-DIAG] Probing MitaWalkTarget type...");
				Type type = FindTypeAcrossAssemblies("MitaWalkTarget");
				if (type == null)
				{
					((BasePlugin)this).Log.LogInfo((object)"[MITA-DIAG] Optional MitaWalkTarget reflection probe did not resolve. This type is not required by host-authoritative BoneSync or quest-object sync.");
				}
				else
				{
					((BasePlugin)this).Log.LogInfo((object)("[MITA-DIAG] MitaWalkTarget found: " + type.FullName + " in '" + type.Assembly.GetName().Name + "'"));
					MethodInfo method = type.GetMethod("Start", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
					MethodInfo method2 = type.GetMethod("Update", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
					((BasePlugin)this).Log.LogInfo((object)("[MITA-DIAG]   Start()  = " + ((method != null) ? "FOUND" : "MISSING")));
					((BasePlugin)this).Log.LogInfo((object)("[MITA-DIAG]   Update() = " + ((method2 != null) ? "FOUND" : "MISSING")));
				}
			}
			catch (Exception ex3)
			{
				((BasePlugin)this).Log.LogError((object)("[MITA-DIAG] Mita init/probe THREW: " + ex3));
			}
			try
			{
				((BasePlugin)this).Log.LogInfo((object)"[MITA-DIAG] Installing MenuPatch Harmony.");
				new Harmony("com.misidemultiplayer.qblib.menu").PatchAll(typeof(MenuPatch));
				((BasePlugin)this).Log.LogInfo((object)"[MITA-DIAG] MenuPatch installed.");
			}
			catch (Exception ex4)
			{
				((BasePlugin)this).Log.LogError((object)("[MITA-DIAG] MenuPatch install THREW: " + ex4));
			}
			try
			{
				((BasePlugin)this).Log.LogInfo((object)"[MITA-DIAG] Installing MenuLocationActivePatch Harmony.");
				new Harmony("com.misidemultiplayer.menumusic").PatchAll(typeof(MenuLocationActivePatch));
				((BasePlugin)this).Log.LogInfo((object)"[MITA-DIAG] MenuLocationActivePatch installed.");
			}
			catch (Exception ex5)
			{
				((BasePlugin)this).Log.LogError((object)("[MITA-DIAG] MenuLocationActivePatch install THREW: " + ex5));
			}
			try
			{
				((BasePlugin)this).Log.LogInfo((object)"[MITA-DIAG] Installing MenuLocationActiveFadePatch Harmony.");
				new Harmony("com.misidemultiplayer.menufade").PatchAll(typeof(MenuLocationActiveFadePatch));
				((BasePlugin)this).Log.LogInfo((object)"[MITA-DIAG] MenuLocationActiveFadePatch installed.");
			}
			catch (Exception ex6)
			{
				((BasePlugin)this).Log.LogError((object)("[MITA-DIAG] MenuLocationActiveFadePatch install THREW: " + ex6));
			}
			((BasePlugin)this).Log.LogInfo((object)"MiSide multiplayer Runtime initialized for BepInEx 6 IL2CPP.");
		}

		public static string GetDisplayName()
		{
			return activeSettingsPlugin?.displayName?.Value ?? "Player";
		}

		public static string GetLocalPlayerId()
		{
			return activeSettingsPlugin?.localPlayerId?.Value ?? "";
		}

		public static bool GetDebugLogging()
		{
			return activeSettingsPlugin?.debugLogging?.Value ?? true;
		}

		public static bool GetVerboseLogging()
		{
			return activeSettingsPlugin?.verboseLogging?.Value == true;
		}

		public static bool GetHideNativeLoadingUI()
		{
			return activeSettingsPlugin?.hideNativeLoadingUI?.Value ?? true;
		}

		public static string GetRelayHost()
		{
			return RuntimeServices.CentralRelayHost;
		}

		public static int GetRelayPort()
		{
			return RuntimeServices.CentralRelayPort;
		}

		public static void SetDisplayName(string v)
		{
			MiSideMultiplayerPlugin miSideMultiplayerPlugin = activeSettingsPlugin;
			if (miSideMultiplayerPlugin == null)
			{
				return;
			}
			miSideMultiplayerPlugin.displayName.Value = v ?? "";
			try
			{
				((BasePlugin)miSideMultiplayerPlugin).Config.Save();
			}
			catch
			{
			}
		}

		public static void SetDebugLogging(bool v)
		{
			MiSideMultiplayerPlugin miSideMultiplayerPlugin = activeSettingsPlugin;
			if (miSideMultiplayerPlugin == null)
			{
				return;
			}
			miSideMultiplayerPlugin.debugLogging.Value = v;
			DiagnosticLog.Enabled = v;
			try
			{
				((BasePlugin)miSideMultiplayerPlugin).Config.Save();
			}
			catch
			{
			}
		}

		public static void SetVerboseLogging(bool v)
		{
			MiSideMultiplayerPlugin miSideMultiplayerPlugin = activeSettingsPlugin;
			if (miSideMultiplayerPlugin == null)
			{
				return;
			}
			miSideMultiplayerPlugin.verboseLogging.Value = v;
			DiagnosticLog.VerboseEnabled = v;
			try
			{
				((BasePlugin)miSideMultiplayerPlugin).Config.Save();
			}
			catch
			{
			}
		}

		public static RpcDispatcher GetRpcDispatcher()
		{
			return runtimeServices?.Dispatcher;
		}

		public static bool IsRelayConnected()
		{
			return runtimeServices?.IsRelayConnected ?? false;
		}

		public static bool IsRelayReady()
		{
			return runtimeServices?.IsRelayReady ?? false;
		}

		public static bool IsLowConnection()
		{
			return runtimeServices?.IsLowConnection ?? false;
		}

		public static bool ConnectTo(string host, int port, string roomName = null)
		{
			RuntimeServices runtimeServices = MiSideMultiplayerPlugin.runtimeServices;
			if (runtimeServices == null)
			{
				DiagnosticLog.Warning("ConnectTo: runtimeServices is null.");
				return false;
			}
			return runtimeServices.ConnectTo(host, port, roomName);
		}

		public static bool ConnectToCentral(string roomName = null)
		{
			RuntimeServices runtimeServices = MiSideMultiplayerPlugin.runtimeServices;
			if (runtimeServices == null)
			{
				DiagnosticLog.Warning("ConnectToCentral: runtimeServices is null.");
				return false;
			}
			return runtimeServices.ConnectToCentral(roomName);
		}

		public static bool ConnectToCentralAt(int endpointIndex, string roomName = null, bool tryOtherEndpoints = true)
		{
			RuntimeServices runtimeServices = MiSideMultiplayerPlugin.runtimeServices;
			if (runtimeServices == null)
			{
				DiagnosticLog.Warning("ConnectToCentralAt: runtimeServices is null.");
				return false;
			}
			return runtimeServices.ConnectToCentralAt(endpointIndex, roomName, enable: true, tryOtherEndpoints);
		}

		public static int GetCentralEndpointCount()
		{
			return QBRelayFailover.EndpointCount;
		}

		public static string GetCentralEndpointName(int endpointIndex)
		{
			return QBRelayFailover.GetName(endpointIndex);
		}

		public static int GetCurrentCentralEndpointIndex()
		{
			return runtimeServices?.CurrentCentralEndpointIndex ?? (-1);
		}

		public static void DisconnectRelay()
		{
			runtimeServices?.Disconnect();
		}

		private static Type FindTypeAcrossAssemblies(string name)
		{
			Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
			foreach (Assembly assembly in assemblies)
			{
				try
				{
					Type type = assembly.GetType(name, throwOnError: false);
					if (type != null)
					{
						return type;
					}
				}
				catch
				{
				}
			}
			return null;
		}

		internal static void TickRuntime()
		{
			NotificationManager.Tick();
			MultiplayerMenuBranding.Tick();
			SceneLoadingHook.Tick();
			if (runtimeServices != null)
			{
				runtimeServices.Tick();
			}
			MultiplayerConnectionErrorHandler.Tick();
			MultiplayerErrorWindowMenu.Tick();
			MultiplayerSettingsMenu.Tick();
			MultiplayerLobbyMenu.Tick();
			MultiplayerHostSetupMenu.Tick();
			MultiplayerDirectConnectMenu.Tick();
			MultiplayerPrivateLobbyMenu.Tick();
			MultiplayerPublicLobbyMenu.Tick();
			MultiplayerConnectingOverlay.Tick();
		}

		internal static void LateTickRuntime()
		{
			if (runtimeServices != null)
			{
				runtimeServices.LateTick();
			}
			MitaController.LateTick();
			MultiplayerMenuMusic.LateTick();
		}

		internal static void DrawRuntimeGui()
		{
			if (runtimeServices != null)
			{
				runtimeServices.OnGui();
			}
			MultiplayerSettingsMenu.DrawGui();
			MultiplayerHostSetupMenu.DrawGui();
			MultiplayerDirectConnectMenu.DrawGui();
			MultiplayerPrivateLobbyMenu.DrawGui();
			MultiplayerPublicLobbyMenu.DrawGui();
			MultiplayerConnectingOverlay.Draw();
		}

		internal static void DisposeRuntime()
		{
			if (runtimeServices != null)
			{
				runtimeServices.Dispose();
				runtimeServices = null;
			}
		}

		private static void RegisterIl2CppTypes()
		{
			ClassInjector.RegisterTypeInIl2Cpp<MiSideMultiplayerRuntime>();
			ClassInjector.RegisterTypeInIl2Cpp<TextFader>();
			ClassInjector.RegisterTypeInIl2Cpp<MultiplayerMenuEnterHandler>();
			ClassInjector.RegisterTypeInIl2Cpp<MusicRunner>();
			ClassInjector.RegisterTypeInIl2Cpp<CreditsGradient>();
			ClassInjector.RegisterTypeInIl2Cpp<CanvasGroupFader>();
			ClassInjector.RegisterTypeInIl2Cpp<TextColorLock>();
			ClassInjector.RegisterTypeInIl2Cpp<LoadingStageManager>();
			ClassInjector.RegisterTypeInIl2Cpp<MultiplayerLoadingScene>();
		}

		private void TryWriteVersionFile()
		{
			try
			{
				VersionFileWriter.Write("MiSide Multiplayer Beta", "1.0.0-beta.1", enableNetworking.Value, RuntimeServices.CentralRelayHost, RuntimeServices.CentralRelayPort, roomName.Value);
			}
			catch (Exception ex)
			{
				((BasePlugin)this).Log.LogWarning((object)("Could not write Data\\Version.txt: " + ex.Message));
			}
		}

		private static string CreateDefaultPlayerId()
		{
			return Guid.NewGuid().ToString("N").Substring(0, 8);
		}

		private static string[] SplitCsv(string value)
		{
			if (string.IsNullOrEmpty(value))
			{
				return new string[0];
			}
			string[] array = value.Split(',');
			for (int i = 0; i < array.Length; i++)
			{
				array[i] = array[i].Trim();
			}
			return array;
		}
	}
	public sealed class MiSideMultiplayerRuntime : MonoBehaviour
	{
		public MiSideMultiplayerRuntime(IntPtr pointer)
			: base(pointer)
		{
		}

		private void Awake()
		{
			((Object)((Component)this).gameObject).name = "MiSideMultiplayer.Runtime";
			Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject);
		}

		private void Update()
		{
			MiSideMultiplayerPlugin.TickRuntime();
		}

		private void LateUpdate()
		{
			MiSideMultiplayerPlugin.LateTickRuntime();
		}

		private void OnGUI()
		{
			MiSideMultiplayerPlugin.DrawRuntimeGui();
		}

		private void OnDestroy()
		{
			MiSideMultiplayerPlugin.DisposeRuntime();
		}
	}
	internal static class QBRelayFailover
	{
		private sealed class Endpoint
		{
			public readonly string Host;

			public readonly int Port;

			public readonly string Name;

			public Endpoint(string host, int port, string name)
			{
				Host = host;
				Port = port;
				Name = name;
			}
		}

		private static readonly Endpoint[] Endpoints = new Endpoint[5]
		{
			new Endpoint("78.154.103.17", 16289, "Server 1"),
			new Endpoint("78.154.103.41", 12434, "Server 2"),
			new Endpoint("78.154.103.15", 16585, "Server 3"),
			new Endpoint("78.154.103.17", 16295, "Server 4"),
			new Endpoint("78.154.103.14", 16715, "Server 5")
		};

		public const float HandshakeTimeoutSeconds = 6f;

		public static string InitialHost => GetHost(0);

		public static int InitialPort => GetPort(0);

		public static int EndpointCount => Endpoints.Length;

		public static string GetHost(int index)
		{
			return Endpoints[NormalizeIndex(index)].Host;
		}

		public static int GetPort(int index)
		{
			return Endpoints[NormalizeIndex(index)].Port;
		}

		public static string GetName(int index)
		{
			return Endpoints[NormalizeIndex(index)].Name;
		}

		public static int GetStableStartIndex(string playerId)
		{
			if (string.IsNullOrEmpty(playerId))
			{
				return 0;
			}
			uint num = 2166136261u;
			for (int i = 0; i < playerId.Length; i++)
			{
				num ^= char.ToUpperInvariant(playerId[i]);
				num *= 16777619;
			}
			return (int)(num % (uint)EndpointCount);
		}

		public static int GetIndex(string host, int port)
		{
			for (int i = 0; i < Endpoints.Length; i++)
			{
				if (string.Equals(Endpoints[i].Host, host, StringComparison.OrdinalIgnoreCase) && Endpoints[i].Port == port)
				{
					return i;
				}
			}
			return -1;
		}

		private static int NormalizeIndex(int index)
		{
			if (EndpointCount == 0)
			{
				return 0;
			}
			int num = index % EndpointCount;
			if (num >= 0)
			{
				return num;
			}
			return num + EndpointCount;
		}
	}
	internal sealed class RuntimeServices : IDisposable
	{
		public static readonly string CentralRelayHost = QBRelayFailover.InitialHost;

		public static readonly int CentralRelayPort = QBRelayFailover.InitialPort;

		private RpcDispatcher rpcDispatcher;

		private NetworkManager networkManager;

		private LocalPlayerSampler localPlayerSampler;

		private TcpRelayTransport relayTransport;

		private MitaSampler mitaSampler;

		private MitaController mitaController;

		private WorldDoorSync worldDoorSync;

		private WorldStoryObjectSync worldStoryObjectSync;

		private MinigameSync minigameSync;

		private BoneSync boneSync;

		private ChatController chatController;

		private SharedLifeController sharedLifeController;

		private readonly string localPlayerId;

		private readonly string roomName;

		private readonly string displayName;

		private PlayerDisplayName playerDisplayName;

		private InventorySharingController inventorySharingController;

		private PuppetProximityFade puppetProximityFade;

		private HeldItemSync heldItemSync;

		private bool wasLowConnection;

		private float nextLowConnectionRefreshTime;

		private string _currentHost = "";

		private int _currentPort;

		private string _currentRoom = "";

		private bool _currentIsCentral;

		private bool _centralFailoverPending;

		private int _centralCandidateIndex = -1;

		private int _centralNextCandidateIndex = -1;

		private int _preferredCentralCandidateIndex = -1;

		private float _centralHandshakeDeadline;

		private string _centralRoom = "";

		private bool _centralEnable;

		private bool _centralRememberSuccess;

		private readonly List<int> _centralRemainingCandidates = new List<int>();

		private RelayFailureKind _centralLastFailure;

		private readonly UnityAction<Scene, LoadSceneMode> _sceneLoadedHandler;

		public RpcDispatcher Dispatcher => rpcDispatcher;

		public bool IsRelayConnected
		{
			get
			{
				if (relayTransport != null)
				{
					return relayTransport.IsConnected;
				}
				return false;
			}
		}

		public bool IsRelayReady
		{
			get
			{
				if (relayTransport != null)
				{
					return relayTransport.IsServerReady;
				}
				return false;
			}
		}

		public bool IsLowConnection
		{
			get
			{
				if (relayTransport != null)
				{
					return relayTransport.IsLowConnection;
				}
				return false;
			}
		}

		public int CurrentCentralEndpointIndex
		{
			get
			{
				if (!_currentIsCentral)
				{
					return -1;
				}
				return QBRelayFailover.GetIndex(_currentHost, _currentPort);
			}
		}

		public RuntimeServices(Transform runtimeRoot, string localPlayerId, string displayName, string[] visualRootCandidates, string[] playerRootCandidates, float snapshotSendRate, float snapshotHeartbeatSeconds, bool emitLocalSnapshots, bool enableNetworking, string serverHost, int serverPort, string roomName, Func<string> displayNameProvider)
		{
			this.localPlayerId = localPlayerId;
			this.displayName = displayName;
			this.roomName = roomName;
			CustomModelBridge.TryInitialize();
			PlayerFallGuard.Initialize();
			rpcDispatcher = new RpcDispatcher();
			networkManager = new NetworkManager(runtimeRoot);
			localPlayerSampler = new LocalPlayerSampler();
			mitaSampler = new MitaSampler();
			mitaController = new MitaController();
			worldDoorSync = new WorldDoorSync();
			worldStoryObjectSync = new WorldStoryObjectSync();
			minigameSync = new MinigameSync();
			boneSync = new BoneSync();
			chatController = new ChatController(rpcDispatcher, networkManager, localPlayerId, displayNameProvider);
			sharedLifeController = new SharedLifeController(rpcDispatcher, localPlayerId);
			inventorySharingController = new InventorySharingController(rpcDispatcher, localPlayerId);
			playerDisplayName = new PlayerDisplayName();
			puppetProximityFade = new PuppetProximityFade();
			heldItemSync = new HeldItemSync();
			mitaController.Configure(localPlayerId);
			mitaSampler.Configure(rpcDispatcher, localPlayerId, snapshotSendRate * 0.5f);
			worldDoorSync.Configure(rpcDispatcher, localPlayerId);
			worldStoryObjectSync.Configure(rpcDispatcher, localPlayerId);
			StorySceneTransitionSync.Configure(rpcDispatcher, localPlayerId);
			minigameSync.Configure(rpcDispatcher, localPlayerId);
			boneSync.Configure(rpcDispatcher, localPlayerId);
			networkManager.Configure(rpcDispatcher, visualRootCandidates, playerRootCandidates, localPlayerId, mitaController);
			localPlayerSampler.Configure(rpcDispatcher, networkManager, localPlayerId, displayName, snapshotSendRate, snapshotHeartbeatSeconds, emitLocalSnapshots);
			try
			{
				_sceneLoadedHandler = DelegateSupport.ConvertDelegate<UnityAction<Scene, LoadSceneMode>>((Delegate)new Action<Scene, LoadSceneMode>(OnSceneLoaded));
				SceneManager.sceneLoaded += _sceneLoadedHandler;
			}
			catch (Exception ex)
			{
				DiagnosticLog.Warning("RuntimeServices: failed to subscribe sceneLoaded: " + ex.GetType().Name + ": " + ex.Message);
				_sceneLoadedHandler = null;
			}
			DiagnosticLog.Info("RuntimeServices started. LocalPlayerId='" + localPlayerId + "'  DisplayName='" + displayName + "'  Relay idle (use ConnectTo to open a connection).");
		}

		public bool ConnectTo(string host, int port, string roomName = null, bool enable = true)
		{
			try
			{
				string text = (string.IsNullOrWhiteSpace(host) ? "127.0.0.1" : host);
				int num = ((port <= 0) ? 7777 : port);
				string text2 = (string.IsNullOrWhiteSpace(roomName) ? this.roomName : roomName);
				if (relayTransport != null && relayTransport.IsConnected && !_centralFailoverPending && string.Equals(_currentHost, text, StringComparison.OrdinalIgnoreCase) && _currentPort == num && string.Equals(_currentRoom, text2, StringComparison.OrdinalIgnoreCase))
				{
					DiagnosticLog.Info("RuntimeServices: ConnectTo skipped — already connected to " + text + ":" + num + "  room='" + text2 + "'.");
					return true;
				}
				Disconnect();
				return StartTransport(text, num, text2, enable, isCentral: false);
			}
			catch (Exception ex)
			{
				DiagnosticLog.Warning("RuntimeServices.ConnectTo failed: " + ex.Message);
				return false;
			}
		}

		public bool ConnectToCentral(string roomName = null, bool enable = true)
		{
			string text = (string.IsNullOrWhiteSpace(roomName) ? this.roomName : roomName);
			if (_centralFailoverPending && string.Equals(_centralRoom, text, StringComparison.OrdinalIgnoreCase))
			{
				return true;
			}
			if (relayTransport != null && _currentIsCentral && relayTransport.IsServerReady && string.Equals(_currentRoom, text, StringComparison.OrdinalIgnoreCase))
			{
				DiagnosticLog.Info("RuntimeServices: central relay already ready at " + _currentHost + ":" + _currentPort + " room='" + text + "'.");
				return true;
			}
			int endpointIndex = ((_preferredCentralCandidateIndex >= 0) ? _preferredCentralCandidateIndex : QBRelayFailover.GetStableStartIndex(localPlayerId));
			return ConnectToCentralAt(endpointIndex, text, enable, tryOtherEndpoints: true, rememberSuccess: true);
		}

		public bool ConnectToCentralAt(int endpointIndex, string roomName = null, bool enable = true, bool tryOtherEndpoints = true, bool rememberSuccess = false)
		{
			if (endpointIndex < 0 || endpointIndex >= QBRelayFailover.EndpointCount)
			{
				return false;
			}
			string text = (string.IsNullOrWhiteSpace(roomName) ? this.roomName : roomName);
			if (_centralFailoverPending && _centralCandidateIndex == endpointIndex && string.Equals(_centralRoom, text, StringComparison.OrdinalIgnoreCase))
			{
				return true;
			}
			if (relayTransport != null && _currentIsCentral && relayTransport.IsServerReady && QBRelayFailover.GetIndex(_currentHost, _currentPort) == endpointIndex && string.Equals(_currentRoom, text, StringComparison.OrdinalIgnoreCase))
			{
				return true;
			}
			List<int> list = new List<int>();
			if (tryOtherEndpoints)
			{
				for (int i = 1; i < QBRelayFailover.EndpointCount; i++)
				{
					list.Add((endpointIndex + i) % QBRelayFailover.EndpointCount);
				}
			}
			return StartCentralCandidate(text, enable, endpointIndex, list, rememberSuccess);
		}

		private bool StartCentralCandidate(string effectiveRoom, bool enable, int candidateIndex, List<int> remainingCandidates, bool rememberSuccess)
		{
			string host = QBRelayFailover.GetHost(candidateIndex);
			int port = QBRelayFailover.GetPort(candidateIndex);
			Disconnect();
			_centralFailoverPending = true;
			_centralCandidateIndex = candidateIndex;
			_centralNextCandidateIndex = ((remainingCandidates.Count > 0) ? remainingCandidates[0] : (-1));
			_centralRemainingCandidates.Clear();
			for (int i = 1; i < remainingCandidates.Count; i++)
			{
				_centralRemainingCandidates.Add(remainingCandidates[i]);
			}
			_centralHandshakeDeadline = Time.realtimeSinceStartup + 6f;
			_centralRoom = effectiveRoom;
			_centralEnable = enable;
			_centralRememberSuccess = rememberSuccess;
			_centralLastFailure = RelayFailureKind.ConnectionError;
			try
			{
				if (!StartTransport(host, port, effectiveRoom, enable, isCentral: true))
				{
					TryNextCentralCandidate(RelayFailureKind.ConnectionError);
					return true;
				}
				DiagnosticLog.Info("RuntimeServices: connecting to central relay " + QBRelayFailover.GetName(candidateIndex) + " at " + host + ":" + port + "; waiting for server heartbeat.");
				return true;
			}
			catch (Exception ex)
			{
				DiagnosticLog.Warning("RuntimeServices: central relay candidate start failed: " + ex.Message);
				TryNextCentralCandidate(RelayFailureKind.ConnectionError);
				return true;
			}
		}

		private bool StartTransport(string host, int port, string effectiveRoom, bool enable, bool isCentral)
		{
			relayTransport = new TcpRelayTransport(rpcDispatcher, localPlayerId, effectiveRoom, host, port, enable, () => displayName);
			int candidateIndex = _centralCandidateIndex;
			relayTransport.ConnectionFailed += delegate(RelayFailureKind failure)
			{
				if (isCentral && _centralFailoverPending && candidateIndex == _centralCandidateIndex)
				{
					TryNextCentralCandidate(failure);
				}
				else
				{
					MultiplayerConnectionErrorHandler.HandleRelayFailure(failure, !isCentral);
				}
			};
			_currentHost = host;
			_currentPort = port;
			_currentRoom = effectiveRoom;
			_currentIsCentral = isCentral;
			wasLowConnection = false;
			relayTransport.Start();
			DiagnosticLog.Info("RuntimeServices: ConnectTo " + host + ":" + port + "  room='" + effectiveRoom + "'  enabled=" + enable);
			return true;
		}

		private void TryNextCentralCandidate(RelayFailureKind failure)
		{
			if (_centralFailoverPending)
			{
				_centralLastFailure = failure;
				string centralRoom = _centralRoom;
				bool centralEnable = _centralEnable;
				int centralNextCandidateIndex = _centralNextCandidateIndex;
				bool centralRememberSuccess = _centralRememberSuccess;
				if (centralNextCandidateIndex >= 0)
				{
					List<int> remainingCandidates = new List<int>(_centralRemainingCandidates);
					DiagnosticLog.Info("RuntimeServices: central relay " + QBRelayFailover.GetName(_centralCandidateIndex) + " did not become healthy; silently trying " + QBRelayFailover.GetName(centralNextCandidateIndex) + ".");
					StartCentralCandidate(centralRoom, centralEnable, centralNextCandidateIndex, remainingCandidates, centralRememberSuccess);
				}
				else
				{
					_centralFailoverPending = false;
					_centralCandidateIndex = -1;
					_centralNextCandidateIndex = -1;
					_centralRemainingCandidates.Clear();
					_centralRoom = "";
					Disconnect();
					DiagnosticLog.Warning("RuntimeServices: all " + QBRelayFailover.EndpointCount + " central relay endpoints failed their connection health check.");
					MultiplayerConnectionErrorHandler.HandleRelayFailure(_centralLastFailure, isDirectConnection: false);
				}
			}
		}

		private void TickCentralRelayFailover()
		{
			if (!_centralFailoverPending)
			{
				return;
			}
			if (relayTransport != null && relayTransport.IsServerReady)
			{
				if (_centralRememberSuccess)
				{
					_preferredCentralCandidateIndex = _centralCandidateIndex;
				}
				_centralFailoverPending = false;
				_centralCandidateIndex = -1;
				_centralNextCandidateIndex = -1;
				_centralRemainingCandidates.Clear();
				_centralRoom = "";
				DiagnosticLog.Info("RuntimeServices: central relay heartbeat received; selected endpoint " + _currentHost + ":" + _currentPort + ".");
			}
			else if (Time.realtimeSinceStartup >= _centralHandshakeDeadline)
			{
				DiagnosticLog.Info("RuntimeServices: central relay " + QBRelayFailover.GetName(_centralCandidateIndex) + " did not return a heartbeat within " + 6f + "s.");
				TryNextCentralCandidate(RelayFailureKind.TimedOut);
			}
		}

		public void Disconnect()
		{
			_centralFailoverPending = false;
			_centralCandidateIndex = -1;
			_centralNextCandidateIndex = -1;
			_centralRemainingCandidates.Clear();
			_centralRoom = "";
			QBLib.Notifications.HideLowConnection();
			wasLowConnection = false;
			try
			{
				if (relayTransport != null)
				{
					relayTransport.Dispose();
					relayTransport = null;
					_currentHost = "";
					_currentPort = 0;
					_currentRoom = "";
					_currentIsCentral = false;
					DiagnosticLog.Info("RuntimeServices: relay disconnected.");
				}
			}
			catch (Exception ex)
			{
				DiagnosticLog.Warning("RuntimeServices.Disconnect failed: " + ex.Message);
			}
		}

		public static void InitializeWorldGuard(Harmony harmony)
		{
			WorldPlayerUpdateGuard.Initialize(harmony);
			WorldOccupancyGuard.Initialize(harmony);
			WorldStoryObjectSync.Initialize(harmony);
			MinigameSync.Initialize(harmony);
			FastMenuMultiplayerPatch.Initialize(harmony);
		}

		private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
		{
			try
			{
				MultiplayerMenuBranding.OnSceneLoaded(((Scene)(ref scene)).name);
			}
			catch
			{
			}
			try
			{
				MitaSceneCache.Invalidate();
			}
			catch
			{
			}
			try
			{
				WorldOccupancyGuard.Invalidate();
			}
			catch
			{
			}
			try
			{
				SceneManagerHook.OnSceneLoaded(((Scene)(ref scene)).name);
			}
			catch
			{
			}
			try
			{
				SceneLoadingHook.OnSceneLoaded(((Scene)(ref scene)).name);
			}
			catch
			{
			}
			try
			{
				WorldStoryObjectSync.OnDestinationSceneLoaded(((Scene)(ref scene)).name);
			}
			catch
			{
			}
			try
			{
				StorySceneTransitionSync.OnSceneLoaded(((Scene)(ref scene)).name);
			}
			catch
			{
			}
			try
			{
				minigameSync.OnSceneLoaded(((Scene)(ref scene)).name);
			}
			catch
			{
			}
			UpdateConnectionWarning(forceRefresh: true);
			if (string.Equals(((Scene)(ref scene)).name, "SceneMenu", StringComparison.Ordinal) && relayTransport != null && MultiplayerLobbyMenu.Session != null)
			{
				DiagnosticLog.Info("RuntimeServices: returned to SceneMenu; closing active multiplayer relay.");
				Disconnect();
			}
		}

		public void Tick()
		{
			PrologueLocalQuestSupport.Tick();
			if (relayTransport != null)
			{
				PerfProbe.Begin();
				relayTransport.Tick();
				PerfProbe.End("relay");
			}
			TickCentralRelayFailover();
			UpdateConnectionWarning();
			SceneManagerHook.Tick();
			PerfProbe.Begin();
			rpcDispatcher.Tick();
			PerfProbe.End("rpc");
			StorySceneTransitionSync.Tick();
			PerfProbe.Begin();
			networkManager.Tick();
			PerfProbe.End("network");
			PerfProbe.Begin();
			localPlayerSampler.Tick();
			PerfProbe.End("localPlayer");
			PerfProbe.Begin();
			mitaSampler.Tick();
			PerfProbe.End("mitaSampler");
			PerfProbe.Begin();
			mitaController.Tick();
			PerfProbe.End("mitaController");
			PerfProbe.Begin();
			worldDoorSync.Tick();
			PerfProbe.End("doors");
			PerfProbe.Begin();
			worldStoryObjectSync.Tick();
			PerfProbe.End("story");
			minigameSync.Tick();
			PerfProbe.Begin();
			boneSync.Tick();
			PerfProbe.End("boneSync");
			PerfProbe.Begin();
			chatController.Tick();
			PerfProbe.End("chat");
			PerfProbe.Begin();
			inventorySharingController.Tick();
			PerfProbe.End("inventory");
			PerfProbe.Begin();
			WorldOccupancyGuard.Tick();
			PerfProbe.End("occupancy");
			PerfProbe.Begin();
			PlayerFallGuard.Tick();
			PerfProbe.End("fallGuard");
			PerfProbe.Begin();
			playerDisplayName.Tick();
			PerfProbe.End("displayName");
			PerfProbe.Begin();
			puppetProximityFade.Tick();
			PerfProbe.End("proximityFade");
			PuppetShaderProbe.Tick();
			HeldItemProbe.Tick();
			PerfProbe.Begin();
			heldItemSync.Tick();
			PerfProbe.End("heldItem");
			PerfProbe.FrameDone();
		}

		public void LateTick()
		{
			PerfProbe.Begin();
			networkManager.LateTick();
			PerfProbe.End("networkLate");
			PerfProbe.Begin();
			boneSync.LateTick();
			PerfProbe.End("boneLate");
		}

		public void OnGui()
		{
			chatController.OnGui();
		}

		private void UpdateConnectionWarning(bool forceRefresh = false)
		{
			//IL_0023: 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)
			bool num = relayTransport != null && relayTransport.IsConnected && relayTransport.IsLowConnection;
			Scene activeScene = SceneManager.GetActiveScene();
			bool flag = IsGameplayScene(((Scene)(ref activeScene)).name);
			bool flag2 = num && flag;
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			if (flag2 && (!wasLowConnection || forceRefresh || realtimeSinceStartup >= nextLowConnectionRefreshTime))
			{
				QBLib.Notifications.ShowLowConnection();
				nextLowConnectionRefreshTime = realtimeSinceStartup + 1f;
			}
			else if (!flag2 && (wasLowConnection || forceRefresh))
			{
				QBLib.Notifications.HideLowConnection();
				nextLowConnectionRefreshTime = 0f;
			}
			wasLowConnection = flag2;
		}

		private static bool IsGameplayScene(string sceneName)
		{
			if (!string.IsNullOrEmpty(sceneName) && !string.Equals(sceneName, "SceneMenu", StringComparison.Ordinal))
			{
				return !string.Equals(sceneName, "SceneLoading", StringComparison.Ordinal);
			}
			return false;
		}

		public void Dispose()
		{
			if ((Delegate)(object)_sceneLoadedHandler != (Delegate)null)
			{
				try
				{
					SceneManager.sceneLoaded -= _sceneLoadedHandler;
				}
				catch (Exception ex)
				{
					DiagnosticLog.Warning("RuntimeServices: failed to unsubscribe sceneLoaded: " + ex.GetType().Name + ": " + ex.Message);
				}
			}
			if (rpcDispatcher != null)
			{
				rpcDispatcher.SendPlayerLeft(localPlayerId);
			}
			Disconnect();
			if (playerDisplayName != null)
			{
				playerDisplayName.Dispose();
			}
			if (puppetProximityFade != null)
			{
				puppetProximityFade.Dispose();
			}
			if (heldItemSync != null)
			{
				heldItemSync.Dispose();
			}
			if (minigameSync != null)
			{
				minigameSync.Dispose();
			}
			sharedLifeController.Dispose();
			chatController.Dispose();
			inventorySharingController.Dispose();
			networkManager.Dispose();
			boneSync.Dispose();
		}
	}
	internal static class VersionFileWriter
	{
		public static void Write(string pluginName, string pluginVersion, bool networkingEnabled, string serverHost, int serverPort, string roomName)
		{
			string gameRootPath = Paths.GameRootPath;
			if (!string.IsNullOrEmpty(gameRootPath))
			{
				string text = Path.Combine(gameRootPath, "Data");
				Directory.CreateDirectory(text);
				string path = Path.Combine(text, "Version.txt");
				string contents = "MiSide Multiplayer Beta" + Environment.NewLine + pluginName + Environment.NewLine + "Version: " + pluginVersion + Environment.NewLine + "Runtime: BepInEx 6 IL2CPP" + Environment.NewLine + "Networking: " + (networkingEnabled ? "Enabled" : "Disabled") + Environment.NewLine + "Relay: " + serverHost + ":" + serverPort + Environment.NewLine + "Room: " + roomName + Environment.NewLine;
				File.WriteAllText(path, contents);
			}
		}
	}
	public sealed class HeldItemSync
	{
		private sealed class PuppetItemState
		{
			public string CurrentItemName = "";

			public bool CurrentHidden;

			public bool HasBuiltClones;

			public bool LoggedLookupFailure;

			public readonly List<GameObject> Clones = new List<GameObject>();
		}

		private const string RightItemBonePath = "Person/Armature/Hips/Spine/Chest/Right shoulder/Right arm/Right elbow/Right wrist/Right item";

		private const string LocalHandRelativePath = "Person/Armature/Hips/Spine/Chest/Right shoulder/Right arm/Right elbow/Right wrist/Right item";

		private const string CloneNamePrefix = "MiSidePuppet_Item_";

		private readonly Dictionary<PuppetController, PuppetItemState> states = new Dictionary<PuppetController, PuppetItemState>();

		private readonly Dictionary<string, GameObject> sceneItemCache = new Dictionary<string, GameObject>(StringComparer.Ordinal);

		private string cachedSceneName = "";

		private static FieldInfo puppetLatestStateField;

		private static bool puppetLatestStateFieldResolved;

		private static PropertyInfo localPlayerMoveItemProp;

		private static bool localPlayerMoveItemResolved;

		private static PropertyInfo gameControllerPlayerMoveProp;

		private static bool gameControllerPlayerMoveResolved;

		private static Component cachedLocalPlayerMove;

		private static float nextLocalPlayerMoveLookupTime;

		private static float nextDiagLogTime;

		private const float DiagLogInterval = 5f;

		public void Tick()
		{
			//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)
			Scene activeScene = SceneManager.GetActiveScene();
			string name = ((Scene)(ref activeScene)).name;
			if (name != cachedSceneName)
			{
				cachedSceneName = name;
				sceneItemCache.Clear();
				cachedLocalPlayerMove = null;
				foreach (KeyValuePair<PuppetController, PuppetItemState> state in states)
				{
					if (state.Value != null)
					{
						state.Value.HasBuiltClones = false;
						state.Value.Clones.Clear();
					}
				}
				BuildSceneItemCache();
			}
			List<PuppetController> list = new List<PuppetController>();
			foreach (PuppetController allPuppet in PuppetRegistry.AllPuppets)
			{
				if (allPuppet != null && (Object)(object)allPuppet.GameObject != (Object)null)
				{
					list.Add(allPuppet);
				}
			}
			for (int i = 0; i < list.Count; i++)
			{
				PuppetController puppetController = list[i];
				if (puppetController != null && !((Object)(object)puppetController.GameObject == (Object)null))
				{
					try
					{
						ApplyToPuppet(puppetController);
					}
					catch (Exception ex)
					{
						DiagnosticLog.Warning("HeldItemSync: ApplyToPuppet threw: " + ex.GetType().Name + ": " + ex.Message);
					}
				}
			}
			PruneDestroyedPuppets(list);
		}

		public void Dispose()
		{
			foreach (KeyValuePair<PuppetController, PuppetItemState> state in states)
			{
				if (state.Value != null)
				{
					DestroyClones(state.Value);
				}
			}
			states.Clear();
			sceneItemCache.Clear();
			cachedLocalPlayerMove = null;
		}

		private void ApplyToPuppet(PuppetController puppet)
		{
			string itemName = null;
			bool hidden = false;
			if (!TryReadRemoteHeldItem(puppet, out itemName, out hidden))
			{
				return;
			}
			if (itemName == null)
			{
				itemName = "";
			}
			PuppetItemState orCreateState = GetOrCreateState(puppet);
			bool flag = !string.Equals(orCreateState.CurrentItemName, itemName, StringComparison.Ordinal);
			bool flag2 = orCreateState.CurrentHidden != hidden;
			bool flag3 = flag || (!orCreateState.HasBuiltClones && !string.IsNullOrEmpty(itemName));
			bool flag4 = flag2;
			if (!flag3 && !flag4)
			{
				return;
			}
			if (flag3)
			{
				if (flag)
				{
					DestroyClones(orCreateState);
					orCreateState.CurrentItemName = itemName;
					orCreateState.HasBuiltClones = false;
					orCreateState.LoggedLookupFailure = false;
				}
				if (!string.IsNullOrEmpty(itemName))
				{
					if (TryGetSceneItem(itemName, out var source))
					{
						if (!orCreateState.HasBuiltClones)
						{
							DestroyClones(orCreateState);
							BuildClones(puppet, source, orCreateState);
							if (orCreateState.Clones.Count > 0)
							{
								orCreateState.HasBuiltClones = true;
							}
						}
					}
					else if (flag && !orCreateState.LoggedLookupFailure)
					{
						orCreateState.LoggedLookupFailure = true;
						DiagnosticLog.Info("HeldItemSync: no scene item named '" + itemName + "' to clone for puppet '" + ((Object)puppet.GameObject).name + "' (will retry).");
					}
				}
			}
			orCreateState.CurrentHidden = hidden;
			SetClonesVisible(orCreateState, !hidden);
		}

		private bool TryReadRemoteHeldItem(PuppetController puppet, out string itemName, out bool hidden)
		{
			itemName = null;
			hidden = false;
			if (puppet == null)
			{
				return false;
			}
			if (!puppetLatestStateFieldResolved)
			{
				puppetLatestStateFieldResolved = true;
				try
				{
					puppetLatestStateField = typeof(PuppetController).GetField("latestState", BindingFlags.Instance | BindingFlags.NonPublic);
				}
				catch (Exception ex)
				{
					DiagnosticLog.Warning("HeldItemSync: reflection on PuppetController.latestState threw: " + ex.GetType().Name + ": " + ex.Message);
				}
				if (puppetLatestStateField == null)
				{
					DiagnosticLog.Warning("HeldItemSync: PuppetController.latestState field NOT FOUND. Held items will not sync.");
				}
				else
				{
					DiagnosticLog.Info("HeldItemSync: resolved PuppetController.latestState via reflection.");
				}
			}
			if (puppetLatestStateField == null)
			{
				return false;
			}
			RemotePlayerState remotePlayerState = null;
			try
			{
				remotePlayerState = puppetLatestStateField.GetValue(puppet) as RemotePlayerState;
			}
			catch (Exception ex2)
			{
				DiagnosticLog.Warning("HeldItemSync: reading latestState failed: " + ex2.GetType().Name + ": " + ex2.Message);
				return false;
			}
			if (remotePlayerState == null)
			{
				return false;
			}
			itemName = remotePlayerState.heldItemName;
			hidden = remotePlayerState.heldItemHidden;
			return true;
		}

		private PuppetItemState GetOrCreateState(PuppetController puppet)
		{
			if (states.TryGetValue(puppet, out var value) && value != null)
			{
				return value;
			}
			value = new PuppetItemState();
			states[puppet] = value;
			return value;
		}

		private void PruneDestroyedPuppets(List<PuppetController> live)
		{
			List<PuppetController> list = null;
			foreach (KeyValuePair<PuppetController, PuppetItemState> state in states)
			{
				PuppetController key = state.Key;
				bool flag = false;
				for (int i = 0; i < live.Count; i++)
				{
					if (live[i] == key)
					{
						flag = true;
						break;
					}
				}
				if (!flag)
				{
					if (list == null)
					{
						list = new List<PuppetController>();
					}
					list.Add(key);
				}
			}
			if (list == null)
			{
				return;
			}
			for (int j = 0; j < list.Count; j++)
			{
				if (states.TryGetValue(list[j], out var value) && value != null)
				{
					DestroyClones(value);
				}
				states.Remove(list[j]);
			}
		}

		private void BuildClones(PuppetController puppet, GameObject source, PuppetItemState state)
		{
			//IL_020e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0221: Unknown result type (might be due to invalid IL or missing references)
			//IL_0234: Unknown result type (might be due to invalid IL or missing references)
			Transform val = puppet.GameObject.transform.Find("Person/Armature/Hips/Spine/Chest/Right shoulder/Right arm/Right elbow/Right wrist/Right item");
			Transform transform = source.transform;
			int num = (((Object)(object)transform != (Object)null) ? transform.childCount : 0);
			DiagnosticLog.Info("HeldItemSync[DIAG]: puppet='" + ((Object)puppet.GameObject).name + "' boneFound=" + ((Object)(object)val != (Object)null) + " source='" + ((Object)source).name + "' sourceChildren=" + num + "' sourceActiveInHierarchy=" + source.activeInHierarchy);
			if ((Object)(object)val == (Object)null)
			{
				DiagnosticLog.Warning("HeldItemSync: puppet '" + ((Object)puppet.GameObject).name + "' has no 'Right item' bone. Dumping puppet root children:");
				Transform transform2 = puppet.GameObject.transform;
				for (int i = 0; i < transform2.childCount; i++)
				{
					Transform child = transform2.GetChild(i);
					DiagnosticLog.Info("  child[" + i + "] = '" + (((Object)(object)child != (Object)null) ? ((Object)child).name : "<null>") + "'");
				}
				return;
			}
			if ((Object)(object)transform == (Object)null)
			{
				DiagnosticLog.Warning("HeldItemSync: source '" + ((Object)source).name + "' has no transform; cannot clone.");
				return;
			}
			int num2 = 0;
			for (int j = 0; j < transform.childCount; j++)
			{
				Transform child2 = transform.GetChild(j);
				if (!((Object)(object)child2 == (Object)null) && HasRenderableDescendant(child2))
				{
					GameObject val2;
					try
					{
						val2 = Object.Instantiate<GameObject>(((Component)child2).gameObject);
					}
					catch (Exception ex)
					{
						DiagnosticLog.Warning("HeldItemSync: instantiate failed for '" + ((Object)child2).name + "': " + ex.Message);
						continue;
					}
					((Object)val2).name = "MiSidePuppet_Item_" + ((Object)child2).name;
					val2.transform.SetParent(val, false);
					val2.transform.localPosition = child2.localPosition;
					val2.transform.localRotation = child2.localRotation;
					val2.transform.localScale = child2.localScale;
					val2.SetActive(true);
					StripHostileComponents(val2);
					state.Clones.Add(val2);
					num2++;
				}
			}
			DiagnosticLog.Info("HeldItemSync: built " + num2 + " clone(s) for puppet '" + ((Object)puppet.GameObject).name + "' from item '" + ((Object)source).name + "'.");
		}

		private static bool HasRenderableDescendant(Transform t)
		{
			if ((Object)(object)t == (Object)null)
			{
				return false;
			}
			if ((Object)(object)((Component)t).GetComponent<Renderer>() != (Object)null)
			{
				return true;
			}
			for (int i = 0; i < t.childCount; i++)
			{
				if (HasRenderableDescendant(t.GetChild(i)))
				{
					return true;
				}
			}
			return false;
		}

		private static void StripHostileComponents(GameObject go)
		{
			try
			{
				Collider[] array = Il2CppArrayBase<Collider>.op_Implicit(go.GetComponentsInChildren<Collider>(true));
				for (int i = 0; i < array.Length; i++)
				{
					if ((Object)(object)array[i] != (Object)null)
					{
						Object.Destroy((Object)(object)array[i]);
					}
				}
			}
			catch (Exception)
			{
			}
			try
			{
				Rigidbody[] array2 = Il2CppArrayBase<Rigidbody>.op_Implicit(go.GetComponentsInChildren<Rigidbody>(true));
				for (int j = 0; j < array2.Length; j++)
				{
					if ((Object)(object)array2[j] != (Object)null)
					{
						Object.Destroy((Object)(object)array2[j]);
					}
				}
			}
			catch (Exception)
			{
			}
			try
			{
				Camera[] array3 = Il2CppArrayBase<Camera>.op_Implicit(go.GetComponentsInChildren<Camera>(true));
				for (int k = 0; k < array3.Length; k++)
				{
					if ((Object)(object)array3[k] != (Object)null)
					{
						Object.Destroy((Object)(object)array3[k]);
					}
				}
			}
			catch (Exception)
			{
			}
			try
			{
				AudioListener[] array4 = Il2CppArrayBase<AudioListener>.op_Implicit(go.GetComponentsInChildren<AudioListener>(true));
				for (int l = 0; l < array4.Length; l++)
				{
					if ((Object)(object)array4[l] != (Object)null)
					{
						Object.Destroy((Object)(object)array4[l]);
					}
				}
			}
			catch (Exception)
			{
			}
		}

		private static void DestroyClones(PuppetItemState state)
		{
			if (state == null)
			{
				return;
			}
			for (int i = 0; i < state.Clones.Count; i++)
			{
				GameObject val = state.Clones[i];
				if (!((Object)(object)val == (Object)null))
				{
					try
					{
						Object.Destroy((Object)(object)val);
					}
					catch (Exception)
					{
					}
				}
			}
			state.Clones.Clear();
		}

		private static void SetClonesVisible(PuppetItemState state, bool visible)
		{
			if (state == null)
			{
				return;
			}
			for (int i = 0; i < state.Clones.Count; i++)
			{
				GameObject val = state.Clones[i];
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				try
				{
					if (val.activeSelf != visible)
					{
						val.SetActive(visible);
					}
				}
				catch (Exception)
				{
				}
			}
		}

		private void BuildSceneItemCache()
		{
			DiagnosticLog.Info("HeldItemSync: scene changed to '" + cachedSceneName + "'; item cache will be resolved lazily.");
		}

		private static PropertyInfo ResolveLocalPlayerMoveItem()
		{
			if (localPlayerMoveItemResolved)
			{
				return localPlayerMoveItemProp;
			}
			localPlayerMoveItemResolved = true;
			try
			{
				Type type = null;
				Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
				for (int i = 0; i < assemblies.Length; i++)
				{
					Type type2 = assemblies[i].GetType("PlayerMove", throwOnError: false);
					if (type2 != null)
					{
						type = type2;
						break;
					}
				}
				if (type == null)
				{
					DiagnosticLog.Warning("HeldItemSync: PlayerMove type not found in any loaded assembly.");
					return null;
				}
				localPlayerMoveItemProp = type.GetProperty("item", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				if (localPlayerMoveItemProp == null)
				{
					DiagnosticLog.Warning("HeldItemSync: PlayerMove.item property not found via reflection.");
				}
				else
				{
					DiagnosticLog.Info("HeldItemSync: resolved PlayerMove.item via reflection.");
				}
			}
			catch (Exception ex)
			{
				DiagnosticLog.Warning("HeldItemSync: reflecting PlayerMove.item failed: " + ex.GetType().Name + ": " + ex.Message);
			}
			return localPlayerMoveItemProp;
		}

		private static Component GetLocalPlayerMove()
		{
			if ((Object)(object)cachedLocalPlayerMove != (Object)null)
			{
				return cachedLocalPlayerMove;
			}
			if (Time.unscaledTime < nextLocalPlayerMoveLookupTime)
			{
				return null;
			}
			nextLocalPlayerMoveLookupTime = Time.unscaledTime + 1f;
			try
			{
				if (!gameControllerPlayerMoveResolved)
				{
					gameControllerPlayerMoveResolved = true;
					Type type = null;
					Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
					for (int i = 0; i < assemblies.Length; i++)
					{
						Type type2 = assemblies[i].GetType("GameController", throwOnError: false);
						if (type2 != null)
						{
							type = type2;
							break;
						}
					}
					if (type != null)
					{
						gameControllerPlayerMoveProp = type.GetProperty("scrpm", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
					}
					if (gameControllerPlayerMoveProp == null)
					{
						DiagnosticLog.Info("HeldItemSync: GameController.scrpm property not found.");
					}
				}
				if (gameControllerPlayerMoveProp != null)
				{
					GameObject val = GameObject.Find("GameController");
					if ((Object)(object)val != (Object)null)
					{
						Component[] array = Il2CppArrayBase<Component>.op_Implicit(val.GetComponents<Component>());
						if (array != null)
						{
							for (int j = 0; j < array.Length; j++)
							{
								Component val2 = array[j];
								if (!((Object)(object)val2 == (Object)null))
								{
									object obj = null;
									try
									{
										obj = gameControllerPlayerMoveProp.GetValue(val2);
									}
									catch (Exception)
									{
										continue;
									}
									Component val3 = (Component)((obj is Component) ? obj : null);
									if ((Object)(object)val3 != (Object)null)
									{
										cachedLocalPlayerMove = val3;
										DiagnosticLog.Info("HeldItemSync: resolved PlayerMove via GameController.scrpm on component #" + j + " of '" + ((Object)val).name + "' -> '" + ((Object)val3.gameObject).name + "'.");
										return val3;
									}
								}
							}
						}
					}
				}
			}
			catch (Exception ex2)
			{
				DiagnosticLog.Warning("HeldItemSync: GameController.scrpm path failed: " + ex2.GetType().Name + ": " + ex2.Message);
			}
			try
			{
				Object[] array2 = Il2CppArrayBase<Object>.op_Implicit((Il2CppArrayBase<Object>)(object)Resources.FindObjectsOfTypeAll(Il2CppType.From(typeof(Component))));
				if (array2 != null)
				{
					foreach (Object obj2 in array2)
					{
						Component val4 = (Component)(object)((obj2 is Component) ? obj2 : null);
						if (!((Object)(object)val4 == (Object)null) && ((object)val4).GetType().Name == "PlayerMove")
						{
							cachedLocalPlayerMove = val4;
							DiagnosticLog.Info("HeldItemSync: resolved PlayerMove via FindObjectsOfTypeAll on '" + ((Object)val4.gameObject).name + "'.");
							return val4;
						}
					}
				}
			}
			catch (Exception)
			{
			}
			if (Time.unscaledTime >= nextDiagLogTime)
			{
				nextDiagLogTime = Time.unscaledTime + 5f;
				DiagnosticLog.Info("HeldItemSync: PlayerMove instance not located yet (will retry).");
			}
			return null;
		}

		private bool TryGetSceneItem(string name, out GameObject source)
		{
			//IL_0352: Unknown result type (might be due to invalid IL or missing references)
			//IL_0357: Unknown result type (might be due to invalid IL or missing references)
			//IL_029d: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_02af: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b4: Unknown result type (might be due to invalid IL or missing references)
			source = null;
			if (string.IsNullOrEmpty(name))
			{
				return false;
			}
			if (sceneItemCache.TryGetValue(name, out source) && (Object)(object)source != (Object)null)
			{
				return true;
			}
			try
			{
				string text = TryGetLocalHeldItemName();
				if (!string.IsNullOrEmpty(text) && string.Equals(text, name, StringComparison.Ordinal))
				{
					GameObject val = FindLocalHandBone();
					if ((Object)(object)val != (Object)null)
					{
						Transform transform = val.transform;
						if ((Object)(object)transform != (Object)null && transform.childCount > 0)
						{
							sceneItemCache[name] = val;
							source = val;
							DiagnosticLog.Info("HeldItemSync: resolved '" + name + "' via local hand clone (children=" + transform.childCount + ").");
							return true;
						}
					}
				}
			}
			catch (Exception ex)
			{
				DiagnosticLog.Warning("HeldItemSync: local hand-clone path failed for '" + name + "': " + ex.GetType().Name + ": " + ex.Message);
			}
			try
			{
				PropertyInfo propertyInfo = ResolveLocalPlayerMoveItem();
				if (propertyInfo != null)
				{
					Component localPlayerMove = GetLocalPlayerMove();
					if ((Object)(object)localPlayerMove != (Object)null)
					{
						object? value = propertyInfo.GetValue(localPlayerMove);
						GameObject val2 = (GameObject)((value is GameObject) ? value : null);
						if ((Object)(object)val2 != (Object)null && string.Equals(((Object)val2).name, name, StringComparison.Ordinal))
						{
							sceneItemCache[name] = val2;
							source = val2;
							DiagnosticLog.Info("HeldItemSync: resolved '" + name + "' via local PlayerMove.item (activeInHierarchy=" + val2.activeInHierarchy + ").");
							return true;
						}
					}
				}
			}
			catch (Exception ex2)
			{
				DiagnosticLog.Warning("HeldItemSync: local PlayerMove.item path failed for '" + name + "': " + ex2.GetType().Name + ": " + ex2.Message);
			}
			try
			{
				GameObject val3 = GameObject.Find(name);
				if ((Object)(object)val3 != (Object)null)
				{
					sceneItemCache[name] = val3;
					source = val3;
					DiagnosticLog.Info("HeldItemSync: resolved '" + name + "' via GameObject.Find (active).");
					return true;
				}
			}
			catch (Exception)
			{
			}
			try
			{
				Object[] array = Il2CppArrayBase<Object>.op_Implicit((Il2CppArrayBase<Object>)(object)Resources.FindObjectsOfTypeAll(Il2CppType.From(typeof(GameObject))));
				if (array != null)
				{
					GameObject val4 = null;
					Scene scene;
					foreach (Object obj in array)
					{
						GameObject val5 = (GameObject)(object)((obj is GameObject) ? obj : null);
						if ((Object)(object)val5 == (Object)null || !string.Equals(((Object)val5).name, name, StringComparison.Ordinal))
						{
							continue;
						}
						scene = val5.scene;
						if (!((Scene)(ref scene)).IsValid())
						{
							continue;
						}
						scene = val5.scene;
						if (!string.IsNullOrEmpty(((Scene)(ref scene)).name))
						{
							if ((Object)(object)val5.transform.parent == (Object)null)
							{
								val4 = val5;
								break;
							}
							if ((Object)(object)val4 == (Object)null)
							{
								val4 = val5;
							}
						}
					}
					if ((Object)(object)val4 != (Object)null)
					{
						sceneItemCache[name] = val4;
						source = val4;
						string[] obj2 = new string[7]
						{
							"HeldItemSync: resolved '",
							name,
							"' via Resources.FindObjectsOfTypeAll (activeInHierarchy=",
							val4.activeInHierarchy.ToString(),
							", scene='",
							null,
							null
						};
						scene = val4.scene;
						obj2[5] = ((Scene)(ref scene)).name;
						obj2[6] = "').";
						DiagnosticLog.Info(string.Concat(obj2));
						return true;
					}
				}
			}
			catch (Exception ex4)
			{
				DiagnosticLog.Warning("HeldItemSync: FindObjectsOfTypeAll scan for '" + name + "' failed: " + ex4.GetType().Name + ": " + ex4.Message);
			}
			return false;
		}

		private static GameObject FindLocalHandBone()
		{
			try
			{
				GameObject val = GameObject.Find("GameController");
				if ((Object)(object)val == (Object)null)
				{
					return null;
				}
				Transform val2 = val.transform.Find("Player");
				if ((Object)(object)val2 == (Object)null)
				{
					return null;
				}
				Transform val3 = val2.Find("Person/Armature/Hips/Spine/Chest/Right shoulder/Right arm/Right elbow/Right wrist/Right item");
				if ((Object)(object)val3 == (Object)null)
				{
					return null;
				}
				return ((Component)val3).gameObject;
			}
			catch (Exception)
			{
				return null;
			}
		}

		private static string TryGetLocalHeldItemName()
		{
			try
			{
				PropertyInfo propertyInfo = ResolveLocalPlayerMoveItem();
				if (propertyInfo == null)
				{
					return null;
				}
				Component localPlayerMove = GetLocalPlayerMove();
				if ((Object)(object)localPlayerMove == (Object)null)
				{
					return null;
				}
				object value = propertyInfo.GetValue(localPlayerMove);
				if (value == null)
				{
					return null;
				}
				GameObject val = (GameObject)((value is GameObject) ? value : null);
				if ((Object)(object)val == (Object)null)
				{
					return null;
				}
				return ((Object)val).name;
			}
			catch (Exception)
			{
				return null;
			}
		}
	}
	public static class HeldItemState
	{
		private static readonly Dictionary<PuppetController, RemotePlayerState> latest = new Dictionary<PuppetController, RemotePlayerState>();

		public static void Record(PuppetController puppet, RemotePlayerState state)
		{
			if (puppet != null && state != null)
			{
				latest[puppet] = state;
			}
		}

		public static RemotePlayerState GetLatestForPuppet(PuppetController puppet)
		{
			if (puppet == null)
			{
				return null;
			}
			if (!latest.TryGetValue(puppet, out var value))
			{
				return null;
			}
			return value;
		}

		public static void Remove(PuppetController puppet)
		{
			if (puppet != null)
			{
				latest.Remove(puppet);
			}
		}
	}
	internal sealed class InventorySharingController : IDisposable
	{
		private const float PendingTimeoutSeconds = 4f;

		private const string Scene5BasementKeyPickupPath = "World/Quests/Quest 4 KeyTake/ItemTake Key";

		private static InventorySharingController activeController;

		public static bool BulkClearingInventory;

		private readonly RpcDispatcher dispatcher;

		private readonly string localPlayerId;

		private readonly Harmony harmony;

		private readonly Dictionary<string, Component> pendingPickups = new Dictionary<string, Component>();

		private readonly Dictionary<string, float> pendingPickupExpiry = new Dictionary<string, float>();

		private readonly HashSet<string> claimedPickups = new HashSet<string>();

		private readonly Dictionary<string, string> pickupOwners = new Dictionary<string, string>();

		private readonly HashSet<string> hostPickupClicks = new HashSet<string>();

		private readonly HashSet<string> knownKeyItems = new HashSet<string>();

		private readonly HashSet<string> pendingConsumes = new HashSet<string>();

		private readonly HashSet<string> consumedKeyItems = new HashSet<string>();

		private bool localBasementKeyAdded;

		private string lastSceneName;

		private string allowedPickupPath;

		private bool applyingRemoteKeyChange;

		private bool disposed;

		internal static string Scene5BasementKeyOwnerId
		{
			get
			{
				InventorySharingController inventorySharingController = activeController;
				if (inventorySharingController == null)
				{
					return string.Empty;
				}
				foreach (KeyValuePair<string, string> pickupOwner in inventorySharingController.pickupOwners)
				{
					if (IsScene5BasementKeyPath(pickupOwner.Key) && !string.IsNullOrWhiteSpace(pickupOwner.Value))
					{
						return pickupOwner.Value;
					}
				}
				if (!inventorySharingController.localBasementKeyAdded)
				{
					return string.Empty;
				}
				return inventorySharingController.localPlayerId;
			}
		}

		public InventorySharingController(RpcDispatcher dispatcher, string localPlayerId)
		{
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: Expected O, but got Unknown
			this.dispatcher = dispatcher;
			this.localPlayerId = localPlayerId ?? string.Empty;
			harmony = new Harmony("com.miside.multiplayer.inventorysharing");
			if (dispatcher != null)
			{
				dispatcher.InventoryClaimResultReceived += OnClaimResult;
				dispatcher.InventoryKeyAddedReceived += OnKeyAddedReceived;
				dispatcher.InventoryKeyRemovedReceived += OnKeyRemovedReceived;
				dispatcher.InventoryConsumeResultReceived += OnConsumeResult;
				dispatcher.InventorySnapshotReceived += OnSnapshotReceived;
				dispatcher.CustomMessageReceived += OnCustomMessage;
			}
			activeController = this;
			InstallHooks();
		}

		public void Tick()
		{
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			if (disposed)
			{
				return;
			}
			Scene activeScene = SceneManager.GetActiveScene();
			string name = ((Scene)(ref activeScene)).name;
			if (!string.Equals(name, lastSceneName, StringComparison.Ordinal))
			{
				lastSceneName = name;
				pendingPickups.Clear();
				pendingPickupExpiry.Clear();
				claimedPickups.Clear();
				pickupOwners.Clear();
				hostPickupClicks.Clear();
				knownKeyItems.Clear();
				pendingConsumes.Clear();
				consumedKeyItems.Clear();
				localBasementKeyAdded = false;
			}
			if (pendingPickupExpiry.Count == 0)
			{
				return;
			}
			List<string> list = null;
			foreach (KeyValuePair<string, float> item in pendingPickupExpiry)
			{
				if (!(Time.unscaledTime < item.Value))
				{
					if (list == null)
					{
						list = new List<string>();
					}
					list.Add(item.Key);
				}
			}
			if (list != null)
			{
				for (int i = 0; i < list.Count; i++)
				{
					pendingPickups.Remove(list[i]);
					pendingPickupExpiry.Remove(list[i]);
				}
			}
		}

		public void Dispose()
		{
			if (disposed)
			{
				return;
			}
			disposed = true;
			if (dispatcher != null)
			{
				dispatcher.InventoryClaimResultReceived -= OnClaimResult;
				dispatcher.InventoryKeyAddedReceived -= OnKeyAddedReceived;
				dispatcher.InventoryKeyRemovedReceived -= OnKeyRemovedReceived;
				dispatcher.InventoryConsumeResultReceived -= OnConsumeResult;
				dispatcher.InventorySnapshotReceived -= OnSnapshotReceived;
				dispatcher.CustomMessageReceived -= OnCustomMessage;
			}
			if (activeController == this)
			{
				activeController = null;
			}
			try
			{
				harmony.UnpatchSelf();
			}
			catch (Exception ex)
			{
				DiagnosticLog.Warning("Inventory sharing patch cleanup failed: " + ex.Message);
			}
		}

		public static void ForceCacheClear()
		{
			InventorySharingController inventorySharingController = activeController;
			if (inventorySharingController != null)
			{
				inventorySharingController.pendingPickups.Clear();
				inventorySharingController.pendingPickupExpiry.Clear();
				inventorySharingController.claimedPickups.Clear();
				inventorySharingController.pickupOwners.Clear();
				inventorySharingController.hostPickupClicks.Clear();
				inventorySharingController.knownKeyItems.Clear();
				inventorySharingController.pendingConsumes.Clear();
				inventorySharingController.consumedKeyItems.Clear();
				inventorySharingController.localBasementKeyAdded = false;
				inventorySharingController.allowedPickupPath = null;
				inventorySharingController.applyingRemoteKeyChange = false;
				DiagnosticLog.Info("InventorySharingController: caches cleared for world transition.");
			}
		}

		private void InstallHooks()
		{
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: Expected O, but got Unknown
			//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d0: Expected O, but got Unknown
			//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0103: Expected O, but got Unknown
			//IL_0128: Unknown result type (might be due to invalid IL or missing references)
			//IL_0136: Expected O, but got Unknown
			//IL_0141: Unknown result type (might be due to invalid IL or missing references)
			//IL_014e: Expected O, but got Unknown
			MethodInfo methodInfo = FindMethod("ObjectInteractive", "Click", Type.EmptyTypes);
			MethodInfo methodInfo2 = FindMethod("GameController", "AddKeyItem", new Type[1] { typeof(GameObject) });
			MethodInfo methodInfo3 = FindMethod("GameController", "RemoveKeyItem", new Type[1] { typeof(GameObject) });
			MethodInfo patchMethod = GetPatchMethod("ClickPrefix");
			MethodInfo patchMethod2 = GetPatchMethod("KeyAddPostfix");
			MethodInfo patchMethod3 = GetPatchMethod("KeyRemovePrefix");
			MethodInfo patchMethod4 = GetPatchMethod("KeyRemovePostfix");
			MethodInfo patchMethod5 = GetPatchMethod("ClickPostfix");
			int num = 0;
			try
			{
				if (methodInfo != null)
				{
					harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(patchMethod), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(patchMethod5), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					num++;
				}
				else
				{
					DiagnosticLog.Warning("Inventory sharing could not find ObjectInteractive.Click.");
				}
				if (methodInfo2 != null)
				{
					harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(patchMethod2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					num++;
				}
				else
				{
					DiagnosticLog.Warning("Inventory sharing could not find GameController.AddKeyItem.");
				}
				if (methodInfo3 != null)
				{
					harmony.Patch((MethodBase)methodInfo3, new HarmonyMethod(patchMethod3), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					harmony.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, new HarmonyMethod(patchMethod4), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					num++;
				}
				else
				{
					DiagnosticLog.Warning("Inventory sharing could not find GameController.RemoveKeyItem.");
				}
			}
			catch (Exception ex)
			{
				DiagnosticLog.Warning("Inventory sharing could not install a hook: " + ex.Message);
			}
			DiagnosticLog.Info("Inventory sharing prepared " + num + " hook(s).");
		}

		private static bool ClickPrefix(Component __instance, out string __state)
		{
			__state = GetPickupPath(__instance);
			return activeController?.ShouldRunPickupClick(__instance) ?? true;
		}

		private static void ClickPostfix(Component __instance, string __state)
		{
			activeController?.OnPickupClickCompleted(__instance, __state);
		}

		private static void KeyAddPostfix(Component __instance, GameObject _item)
		{
			activeController?.OnLocalKeyAdded(_item);
		}

		private static void KeyRemovePrefix(GameObject _item, out string __state)
		{
			__state = GetPath(_item);
		}

		private static void KeyRemovePostfix(GameObject _item, string __state)
		{
			activeController?.OnLocalKeyRemoved(_item, __state);
		}

		private bool ShouldRunPickupClick(Component interactive)
		{
			//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
			if (disposed || applyingRemoteKeyChange || !IsGameplayScene() || !IsPickup(interactive))
			{
				return true;
			}
			string pickupPath = GetPickupPath(interactive);
			if (string.IsNullOrEmpty(pickupPath))
			{
				return true;
			}
			if (IsExcludedInventoryPath(pickupPath))
			{
				return true;
			}
			if (string.Equals(allowedPickupPath, pickupPath, StringComparison.Ordinal))
			{
				return true;
			}
			if (claimedPickups.Contains(pickupPath))
			{
				DeactivateRemotePickup(interactive.gameObject);
				return false;
			}
			if (pendingPickups.ContainsKey(pickupPath))
			{
				return false;
			}
			SessionController active = SessionController.Active;
			if (active == null || dispatcher == null)
			{
				return true;
			}
			if (active.IsHost)
			{
				claimedPickups.Add(pickupPath);
				pickupOwners[pickupPath] = localPlayerId;
				hostPickupClicks.Add(pickupPath);
				return true;
			}
			pendingPickups[pickupPath] = interactive;
			pendingPickupExpiry[pickupPath] = Time.unscaledTime + 4f;
			RpcDispatcher rpcDispatcher = dispatcher;
			InventoryClaimRequest inventoryClaimRequest = new InventoryClaimRequest();
			Scene activeScene = SceneManager.GetActiveScene();
			inventoryClaimRequest.sceneName = ((Scene)(ref activeScene)).name;
			inventoryClaimRequest.itemPath = pickupPath;
			inventoryClaimRequest.senderId = localPlayerId;
			rpcDispatcher.SendInventoryClaimRequest(inventoryClaimRequest);
			return false;
		}

		private void OnPickupClickCompleted(Component interactive, string capturedPath)
		{
			if (!disposed && IsGameplayScene())
			{
				string text = (string.IsNullOrEmpty(capturedPath) ? GetPickupPath(interactive) : capturedPath);
				if (!string.IsNullOrEmpty(text) && hostPickupClicks.Remove(text) && (!IsScene5BasementKeyPath(text) || !localBasementKeyAdded) && !knownKeyItems.Contains(text))
				{
					claimedPickups.Remove(text);
					pickupOwners.Remove(text);
				}
			}
		}

		private void OnCustomMessage(string eventName, string json)
		{
			if (disposed || dispatcher == null || !SessionIsHost() || !string.Equals(eventName, "miside.inventory.claim.request", StringComparison.Ordinal) || string.IsNullOrWhiteSpace(json))
			{
				return;
			}
			try
			{
				InventoryClaimRequest inventoryClaimRequest = JsonSerializer.Deserialize<InventoryClaimRequest>(json, new JsonSerializerOptions
				{
					IncludeFields = true,
					PropertyNameCaseInsensitive = true
				});
				if (inventoryClaimRequest == null || !IsCurrentScene(inventoryClaimRequest.sceneName) || string.IsNullOrWhiteSpace(inventoryClaimRequest.itemPath) || string.IsNullOrWhiteSpace(inventoryClaimRequest.senderId) || string.Equals(inventoryClaimRequest.senderId, localPlayerId, StringComparison.Ordinal) || IsExcludedInventoryPath(inventoryClaimRequest.itemPath) || !IsKnownParticipant(inventoryClaimRequest.senderId))
				{
					return;
				}
				if ((Object)(object)FindPickupByPath(inventoryClaimRequest.itemPath) == (Object)null)
				{
					dispatcher.SendInventoryClaimResult(new InventoryClaimResult
					{
						sceneName = inventoryClaimRequest.sceneName,
						itemPath = inventoryClaimRequest.itemPath,
						ownerId = string.Empty,
						approved = false
					});
					return;