Decompiled source of MiSideMultiplayer v1.0.1
MiSideMultiplayer.dll
Decompiled a day ago
The result has been truncated due to the large size, download it to view full contents!
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;