Decompiled source of PhotonJoinFix v1.3.0

PhotonJoinFix.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Logging;
using ExitGames.Client.Photon;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Photon.Pun;
using Photon.Realtime;
using UnityEngine;
using UnityEngine.SceneManagement;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("Rombusz")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Host and client protections against PEAK late-join failures caused by Photon ID conflicts and unloaded map bundles.")]
[assembly: AssemblyFileVersion("1.3.0.0")]
[assembly: AssemblyInformationalVersion("1.3.0")]
[assembly: AssemblyProduct("PhotonJoinFix")]
[assembly: AssemblyTitle("PhotonJoinFix")]
[assembly: AssemblyVersion("1.3.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[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 PhotonJoinFix
{
	[HarmonyPatch(typeof(PhotonNetwork), "LoadLevel", new Type[] { typeof(string) })]
	internal static class ClientBundleLoader
	{
		private sealed class PendingLoad
		{
			internal readonly Room Room;

			internal readonly string Scene;

			internal readonly object? RoomScene;

			internal readonly bool QueueWasRunning;

			internal PendingLoad(Room room, string scene)
			{
				Room = room;
				Scene = scene;
				RoomScene = ((RoomInfo)room).CustomProperties[(object)"curScn"];
				QueueWasRunning = PhotonNetwork.IsMessageQueueRunning;
			}
		}

		internal static MonoBehaviour? Runner;

		private static PendingLoad? _pending;

		private static bool _resuming;

		private static readonly FieldInfo? LoadedBundleField = AccessTools.Field(typeof(MapBundleData), "m_assetBundle");

		internal static void ValidateGameBindings()
		{
			if (LoadedBundleField == null || LoadedBundleField.FieldType != typeof(AssetBundle))
			{
				throw new MissingFieldException("MapBundleData.m_assetBundle is unavailable; cannot verify map loading safely.");
			}
		}

		private static bool IsJoiningOrJoinedGuest(Room? room)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Invalid comparison between Unknown and I4
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Invalid comparison between Unknown and I4
			if (room != null && !PhotonNetwork.IsMasterClient)
			{
				if ((int)PhotonNetwork.NetworkClientState != 8)
				{
					return (int)PhotonNetwork.NetworkClientState == 9;
				}
				return true;
			}
			return false;
		}

		[HarmonyPrefix]
		[HarmonyPriority(800)]
		private static bool BeforeLoad(string levelName)
		{
			if (_resuming)
			{
				return true;
			}
			Room currentRoom = PhotonNetwork.CurrentRoom;
			if (_pending != null && currentRoom == _pending.Room && levelName == _pending.Scene)
			{
				return false;
			}
			Cancel();
			if (!IsJoiningOrJoinedGuest(currentRoom) || currentRoom == null || !TryGetMapIndex(levelName, out var index))
			{
				return true;
			}
			if ((Object)(object)Runner == (Object)null)
			{
				Plugin.Log.LogError((object)("Cannot prepare '" + levelName + "': the bundle loader is unavailable. Rejoin after restarting the game."));
				return false;
			}
			PendingLoad pending = (_pending = new PendingLoad(currentRoom, levelName));
			PhotonNetwork.IsMessageQueueRunning = false;
			try
			{
				Runner.StartCoroutine(LoadBundleThenResume(pending, index));
			}
			catch (Exception ex)
			{
				Fail(pending, ex.GetBaseException().Message);
			}
			return false;
		}

		private static bool TryGetMapIndex(string scene, out int index)
		{
			index = -1;
			if (scene != null && scene.StartsWith("Level_", StringComparison.Ordinal) && int.TryParse(scene.Substring(6), NumberStyles.None, CultureInfo.InvariantCulture, out index))
			{
				return index >= 0;
			}
			return false;
		}

		private static bool IsCurrent(PendingLoad pending)
		{
			if (_pending == pending && IsJoiningOrJoinedGuest(PhotonNetwork.CurrentRoom) && PhotonNetwork.CurrentRoom == pending.Room)
			{
				return object.Equals(((RoomInfo)pending.Room).CustomProperties[(object)"curScn"], pending.RoomScene);
			}
			return false;
		}

		private static IEnumerator LoadBundleThenResume(PendingLoad pending, int index)
		{
			MapBundleData bundle;
			try
			{
				MapBundleService service = GameHandler.GetService<MapBundleService>();
				bundle = ((service != null) ? service.LoadBundlesNearestIndex(index, false) : null);
			}
			catch (Exception ex)
			{
				Fail(pending, ex.GetBaseException().Message);
				yield break;
			}
			if (bundle == null)
			{
				Fail(pending, "No installed map bundle contains this map index.");
				yield break;
			}
			Plugin.Log.LogInfo((object)("Preparing the map bundle for '" + pending.Scene + "' before Photon loads the scene."));
			float deadline = Time.realtimeSinceStartup + 120f;
			while (bundle.Loading)
			{
				if (!IsCurrent(pending))
				{
					Cancel(pending);
					yield break;
				}
				if (Time.realtimeSinceStartup >= deadline)
				{
					Fail(pending, "Timed out waiting for the map bundle.");
					yield break;
				}
				yield return null;
			}
			if (!IsCurrent(pending))
			{
				Cancel(pending);
				yield break;
			}
			try
			{
				if (!ContainsLoadedScene(bundle, pending.Scene))
				{
					Fail(pending, "The map bundle failed to load or does not contain the requested scene.");
					yield break;
				}
			}
			catch (Exception ex2)
			{
				Fail(pending, "Could not verify the loaded map bundle: " + ex2.GetBaseException().Message);
				yield break;
			}
			_pending = null;
			_resuming = true;
			try
			{
				Plugin.Log.LogInfo((object)("Map bundle ready for '" + pending.Scene + "'; resuming Photon scene loading."));
				PhotonNetwork.LoadLevel(pending.Scene);
			}
			catch (Exception ex3)
			{
				PhotonNetwork.IsMessageQueueRunning = pending.QueueWasRunning;
				Plugin.Log.LogError((object)("Photon could not load '" + pending.Scene + "': " + ex3.GetBaseException().Message));
			}
			finally
			{
				_resuming = false;
			}
		}

		private static bool ContainsLoadedScene(MapBundleData bundle, string scene)
		{
			ValidateGameBindings();
			object? value = LoadedBundleField.GetValue(bundle);
			AssetBundle val = (AssetBundle)((value is AssetBundle) ? value : null);
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			string[] allScenePaths = val.GetAllScenePaths();
			for (int i = 0; i < allScenePaths.Length; i++)
			{
				if (string.Equals(Path.GetFileNameWithoutExtension(allScenePaths[i]), scene, StringComparison.Ordinal))
				{
					return true;
				}
			}
			return false;
		}

		private static void Fail(PendingLoad pending, string reason)
		{
			Plugin.Log.LogError((object)("Cannot prepare '" + pending.Scene + "': " + reason + " Leave and rejoin after correcting the map-file problem."));
			Cancel(pending);
		}

		private static void Cancel(PendingLoad pending)
		{
			if (_pending == pending)
			{
				_pending = null;
				PhotonNetwork.IsMessageQueueRunning = pending.QueueWasRunning;
			}
		}

		internal static void Cancel()
		{
			if (_pending != null)
			{
				Cancel(_pending);
			}
		}
	}
	internal static class ClientSceneGuard
	{
		private sealed class PreservedView
		{
			internal readonly PhotonView View;

			internal readonly int SceneHandle;

			internal bool DestroyWarningLogged;

			internal PreservedView(PhotonView view)
			{
				//IL_0014: Unknown result type (might be due to invalid IL or missing references)
				//IL_0019: Unknown result type (might be due to invalid IL or missing references)
				//IL_001c: Unknown result type (might be due to invalid IL or missing references)
				View = view;
				Scene scene = ((Component)view).gameObject.scene;
				SceneHandle = SceneHandle.op_Implicit(((Scene)(ref scene)).handle);
			}
		}

		private static readonly Dictionary<int, PreservedView> Preserved = new Dictionary<int, PreservedView>();

		private static Room? _room;

		internal static void Install(Harmony harmony)
		{
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b1: Expected O, but got Unknown
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d2: Expected O, but got Unknown
			MethodInfo methodInfo = AccessTools.Method(typeof(PhotonNetwork), "NetworkInstantiate", new Type[3]
			{
				typeof(InstantiateParameters),
				typeof(bool),
				typeof(bool)
			}, (Type[])null);
			MethodInfo methodInfo2 = AccessTools.Method(typeof(PhotonNetwork), "OnEvent", new Type[1] { typeof(EventData) }, (Type[])null);
			if (methodInfo == null || methodInfo2 == null)
			{
				throw new MissingMethodException("The required Photon instantiation/event methods were not found.");
			}
			MethodInfo methodInfo3 = AccessTools.Method(typeof(ClientSceneGuard), "BeforeInstantiate", (Type[])null, (Type[])null);
			harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(methodInfo3), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			try
			{
				harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(typeof(ClientSceneGuard), "BeforeEvent", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
			catch
			{
				harmony.Unpatch((MethodBase)methodInfo, methodInfo3);
				throw;
			}
		}

		internal static void Reset()
		{
			Preserved.Clear();
			_room = null;
		}

		internal static void ResetScene(Scene scene)
		{
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			if (Preserved.Count == 0)
			{
				return;
			}
			List<int> list = new List<int>();
			foreach (KeyValuePair<int, PreservedView> item in Preserved)
			{
				if ((Object)(object)item.Value.View == (Object)null || SceneHandle.op_Implicit(item.Value.SceneHandle) == ((Scene)(ref scene)).handle)
				{
					list.Add(item.Key);
				}
			}
			foreach (int item2 in list)
			{
				Preserved.Remove(item2);
			}
		}

		private static void CheckRoom()
		{
			Room currentRoom = PhotonNetwork.CurrentRoom;
			if (currentRoom != _room)
			{
				Preserved.Clear();
				_room = currentRoom;
			}
		}

		private static bool BeforeInstantiate(InstantiateParameters parameters, bool instantiateEvent, ref GameObject? __result)
		{
			//IL_0003: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			if (!instantiateEvent || parameters.viewIDs == null)
			{
				return true;
			}
			CheckRoom();
			bool flag = false;
			int[] viewIDs = parameters.viewIDs;
			foreach (int num in viewIDs)
			{
				if (num <= 0)
				{
					continue;
				}
				PhotonView photonView = PhotonNetwork.GetPhotonView(num);
				if (!((Object)(object)photonView == (Object)null) && !photonView.isRuntimeInstantiated)
				{
					flag = true;
					if (!Preserved.TryGetValue(num, out PreservedView value) || value.View != photonView)
					{
						Preserved[num] = new PreservedView(photonView);
						Plugin.Log.LogWarning((object)$"Skipped incoming '{parameters.prefabName}': view ID {num} belongs to scene object '{((Object)photonView).name}'. Preserving the scene object.");
					}
				}
			}
			if (!flag)
			{
				return true;
			}
			__result = null;
			return false;
		}

		private static bool BeforeEvent(EventData photonEvent)
		{
			if (photonEvent.Code != 204)
			{
				return true;
			}
			CheckRoom();
			object customData = photonEvent.CustomData;
			Hashtable val = (Hashtable)((customData is Hashtable) ? customData : null);
			if (val == null || !(val[(byte)0] is int num) || !Preserved.TryGetValue(num, out PreservedView value))
			{
				return true;
			}
			PhotonView photonView = PhotonNetwork.GetPhotonView(num);
			if ((Object)(object)photonView == (Object)null || photonView.isRuntimeInstantiated || photonView != value.View)
			{
				Preserved.Remove(num);
				return true;
			}
			if (!value.DestroyWarningLogged)
			{
				value.DestroyWarningLogged = true;
				Plugin.Log.LogWarning((object)$"Ignored ambiguous destroy for colliding view ID {num}; preserved scene object '{((Object)photonView).name}'.");
			}
			return false;
		}
	}
	[HarmonyPatch(typeof(PhotonNetwork), "LoadLevel", new Type[] { typeof(string) })]
	internal static class HostCleanup
	{
		[HarmonyPrefix]
		[HarmonyPriority(0)]
		private static void BeforeLoad(string levelName, bool __runOriginal)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: 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 (!__runOriginal || !PhotonNetwork.InRoom || !PhotonNetwork.IsMasterClient)
			{
				return;
			}
			Scene activeScene = SceneManager.GetActiveScene();
			if (!IsAirportDeparture(((Scene)(ref activeScene)).name, levelName))
			{
				return;
			}
			try
			{
				RemoveAirportItems(activeScene);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Airport item cleanup could not finish: " + ex.Message));
			}
		}

		internal static bool IsAirportDeparture(string source, string destination)
		{
			if (source != null && source.IndexOf("Airport", StringComparison.Ordinal) >= 0 && destination != null)
			{
				if (destination.IndexOf("Island", StringComparison.Ordinal) < 0)
				{
					return destination.IndexOf("Level_", StringComparison.Ordinal) >= 0;
				}
				return true;
			}
			return false;
		}

		private static void RemoveAirportItems(Scene airport)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e6: 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)
			Room currentRoom = PhotonNetwork.CurrentRoom;
			List<PhotonView> list = new List<PhotonView>();
			HashSet<int> hashSet = new HashSet<int>();
			ValueIterator<int, PhotonView> enumerator = PhotonNetwork.PhotonViewCollection.GetEnumerator();
			try
			{
				while (enumerator.MoveNext())
				{
					PhotonView current = enumerator.Current;
					if (!((Object)(object)current == (Object)null) && current.isRuntimeInstantiated && current.IsRoomView && current.InstantiationId > 0 && hashSet.Add(current.InstantiationId))
					{
						PhotonView photonView = PhotonNetwork.GetPhotonView(current.InstantiationId);
						if (IsCleanupCandidate(photonView, airport))
						{
							list.Add(photonView);
						}
					}
				}
			}
			finally
			{
				((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose();
			}
			int num = 0;
			foreach (PhotonView item in list)
			{
				if (!PhotonNetwork.InRoom || !PhotonNetwork.IsMasterClient || PhotonNetwork.CurrentRoom != currentRoom || SceneManager.GetActiveScene() != airport)
				{
					break;
				}
				if (!IsCleanupCandidate(item, airport))
				{
					continue;
				}
				int viewID = item.ViewID;
				string name = ((Object)((Component)item).gameObject).name;
				try
				{
					PhotonNetwork.Destroy(((Component)item).gameObject);
					if ((Object)(object)PhotonNetwork.GetPhotonView(viewID) == (Object)null)
					{
						num++;
					}
					else
					{
						Plugin.Log.LogWarning((object)$"Airport item '{name}' (view {viewID}) was not removed.");
					}
				}
				catch (Exception ex)
				{
					Plugin.Log.LogWarning((object)$"Could not remove airport item '{name}' (view {viewID}): {ex.Message}");
				}
			}
			if (list.Count > 0 && PhotonNetwork.InRoom && PhotonNetwork.CurrentRoom == currentRoom)
			{
				PhotonNetwork.SendAllOutgoingCommands();
				Plugin.Log.LogInfo((object)$"Removed {num} airport room item(s) and their cached spawn events before expedition load.");
			}
		}

		private static bool IsCleanupCandidate(PhotonView root, Scene airport)
		{
			//IL_004c: 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_006c: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)root == (Object)null || !root.isRuntimeInstantiated || !root.IsRoomView || !root.IsMine || root.InstantiationId <= 0 || root.ViewID != root.InstantiationId || PhotonNetwork.GetPhotonView(root.ViewID) != root || ((Component)root).gameObject.scene != airport)
			{
				return false;
			}
			Item component = ((Component)root).GetComponent<Item>();
			if ((Object)(object)component != (Object)null && (int)component.itemState == 0 && (Object)(object)component.holderCharacter == (Object)null && (Object)(object)component.wearerCharacter == (Object)null && (Object)(object)((Component)root).GetComponentInParent<Character>() == (Object)null)
			{
				return (Object)(object)((Component)root).GetComponentInParent<Player>() == (Object)null;
			}
			return false;
		}
	}
	[BepInPlugin("Rombusz.PhotonJoinFix", "PhotonJoinFix", "1.3.0")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string Guid = "Rombusz.PhotonJoinFix";

		public const string Name = "PhotonJoinFix";

		public const string Version = "1.3.0";

		internal static ManualLogSource Log;

		private Harmony? _harmony;

		private void Awake()
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			ClientBundleLoader.Runner = (MonoBehaviour?)(object)this;
			_harmony = new Harmony("Rombusz.PhotonJoinFix");
			bool flag = true;
			try
			{
				_harmony.CreateClassProcessor(typeof(HostCleanup)).Patch();
			}
			catch (Exception ex)
			{
				flag = false;
				Log.LogError((object)("Host cleanup could not be installed: " + ex.GetBaseException().Message));
			}
			try
			{
				ClientSceneGuard.Install(_harmony);
			}
			catch (Exception ex2)
			{
				flag = false;
				Log.LogError((object)("Client scene protection could not be installed: " + ex2.GetBaseException().Message));
			}
			try
			{
				ClientBundleLoader.ValidateGameBindings();
				_harmony.CreateClassProcessor(typeof(ClientBundleLoader)).Patch();
				MethodInfo methodInfo = AccessTools.Method(typeof(PhotonNetwork), "LoadLevel", new Type[1] { typeof(string) }, (Type[])null);
				MethodInfo prefix = AccessTools.Method(typeof(ClientBundleLoader), "BeforeLoad", (Type[])null, (Type[])null);
				Patches patchInfo = Harmony.GetPatchInfo((MethodBase)methodInfo);
				if (patchInfo == null || !patchInfo.Prefixes.Any((Patch p) => p.owner == "Rombusz.PhotonJoinFix" && p.PatchMethod == prefix))
				{
					throw new InvalidOperationException("The bundle-loading prefix is absent from PhotonNetwork.LoadLevel(string).");
				}
			}
			catch (Exception ex3)
			{
				flag = false;
				Log.LogError((object)("Client map bundle loading protection could not be installed: " + ex3.GetBaseException().Message));
			}
			SceneManager.sceneUnloaded += ClientSceneGuard.ResetScene;
			if (flag)
			{
				Log.LogMessage((object)"Plugin PhotonJoinFix 1.3.0 is loaded!");
			}
			else
			{
				Log.LogWarning((object)"Plugin PhotonJoinFix 1.3.0 loaded with incomplete protections. See the installation errors above.");
			}
		}

		private void OnDestroy()
		{
			SceneManager.sceneUnloaded -= ClientSceneGuard.ResetScene;
			ClientBundleLoader.Cancel();
			ClientBundleLoader.Runner = null;
			Harmony? harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
			ClientSceneGuard.Reset();
		}
	}
}