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();
}
}
}