using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Configuration; using ExitGames.Client.Photon; using HarmonyLib; using LateJoin.Patches; 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("LateJoin")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Allows players to join lobbies that are already in-progress.")] [assembly: AssemblyFileVersion("1.1.1.0")] [assembly: AssemblyInformationalVersion("1.1.1+66f6370566b62a8b3f3b94e484e4c6ebf9d8e9f9")] [assembly: AssemblyProduct("LateJoin")] [assembly: AssemblyTitle("LateJoin")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.1.1.0")] [module: UnverifiableCode] [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 LateJoin { [BepInPlugin("semimodder-latejoin", "Late Join", "1.1.1")] public class Plugin : BaseUnityPlugin { public static ConfigEntry CfgEnabled; public static Plugin Instance { get; private set; } private void Awake() { //IL_002b: Unknown result type (might be due to invalid IL or missing references) Instance = this; CfgEnabled = ((BaseUnityPlugin)this).Config.Bind("General", "Enabled", true, "Enable mid-game join support (Host keeps lobby open and syncs basic state)."); new Harmony("semimodder-latejoin").PatchAll(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Late Join Mod loaded successfully."); } private void Update() { if (CfgEnabled.Value) { LobbyPatches.Update(); LevelPatches.Update(); ItemPatches.Update(); EnemyPatches.Update(); } } } } namespace LateJoin.Patches { public static class EnemyPatches { private static readonly FieldInfo EnemyOnScreenCoroutineOwner = AccessTools.Field(AccessTools.TypeByName("EnemyOnScreen+d__19"), "4__this") ?? AccessTools.Field(AccessTools.TypeByName("EnemyOnScreen+d__19"), "<>4__this"); private static readonly FieldInfo EnemyVisionCoroutineOwner = AccessTools.Field(AccessTools.TypeByName("EnemyVision+d__34"), "4__this") ?? AccessTools.Field(AccessTools.TypeByName("EnemyVision+d__34"), "<>4__this"); private static int lastPlayerSignature = 0; public static void Update() { if (Plugin.CfgEnabled.Value) { int playerSignature = GetPlayerSignature(); if (playerSignature != lastPlayerSignature) { lastPlayerSignature = playerSignature; SyncAll(); } } } public static void SyncAll() { if (!Plugin.CfgEnabled.Value) { return; } List playerViewIds = GetPlayerViewIds(); if (playerViewIds.Count == 0) { return; } try { EnemyOnScreen[] array = Object.FindObjectsOfType(); foreach (EnemyOnScreen val in array) { if ((Object)(object)val != (Object)null) { Sync(val, playerViewIds); } } EnemyVision[] array2 = Object.FindObjectsOfType(); foreach (EnemyVision val2 in array2) { if ((Object)(object)val2 != (Object)null) { Sync(val2, playerViewIds); } } } catch (Exception ex) { Debug.LogWarning((object)("[LateJoin] SyncAll enemies failed: " + ex.Message)); } } public static void Sync(EnemyOnScreen enemyOnScreen) { if ((Object)(object)enemyOnScreen != (Object)null) { Sync(enemyOnScreen, GetPlayerViewIds()); } } public static void Sync(EnemyVision enemyVision) { if ((Object)(object)enemyVision != (Object)null) { Sync(enemyVision, GetPlayerViewIds()); } } public static void SyncEnemyOnScreenCoroutine(object stateMachine) { if (EnemyOnScreenCoroutineOwner != null && stateMachine != null) { object? value = EnemyOnScreenCoroutineOwner.GetValue(stateMachine); Sync((EnemyOnScreen)((value is EnemyOnScreen) ? value : null)); } } public static void SyncEnemyVisionCoroutine(object stateMachine) { if (EnemyVisionCoroutineOwner != null && stateMachine != null) { object? value = EnemyVisionCoroutineOwner.GetValue(stateMachine); Sync((EnemyVision)((value is EnemyVision) ? value : null)); } } private static void Sync(EnemyOnScreen enemyOnScreen, List playerIds) { if ((Object)(object)enemyOnScreen == (Object)null || playerIds.Count == 0) { return; } try { foreach (int playerId in playerIds) { enemyOnScreen.PlayerAdded(playerId); } } catch (Exception ex) { Debug.LogWarning((object)("[LateJoin] Error syncing PlayerAdded on EnemyOnScreen: " + ex.Message)); } } private static void Sync(EnemyVision enemyVision, List playerIds) { if ((Object)(object)enemyVision == (Object)null || playerIds.Count == 0) { return; } try { foreach (int playerId in playerIds) { enemyVision.PlayerAdded(playerId); } } catch (Exception ex) { Debug.LogWarning((object)("[LateJoin] Error syncing PlayerAdded on EnemyVision: " + ex.Message)); } } private static int GetPlayerSignature() { List playerViewIds = GetPlayerViewIds(); if (playerViewIds.Count == 0) { return 0; } int num = 17; foreach (int item in playerViewIds) { num = num * 31 + item; } return num; } private static List GetPlayerViewIds() { List list = new List(); if ((Object)(object)GameDirector.instance == (Object)null || GameDirector.instance.PlayerList == null) { return list; } foreach (PlayerAvatar player in GameDirector.instance.PlayerList) { if ((Object)(object)player != (Object)null && (Object)(object)player.photonView != (Object)null && player.photonView.ViewID != 0) { list.Add(player.photonView.ViewID); } } return list; } } [HarmonyPatch(typeof(EnemyOnScreen), "OnEnable")] public static class EnemyOnScreenOnEnablePatch { [HarmonyPrefix] public static void Prefix(EnemyOnScreen __instance) { if (Plugin.CfgEnabled.Value) { EnemyPatches.Sync(__instance); } } } [HarmonyPatch(typeof(EnemyVision), "OnEnable")] public static class EnemyVisionOnEnablePatch { [HarmonyPrefix] public static void Prefix(EnemyVision __instance) { if (Plugin.CfgEnabled.Value) { EnemyPatches.Sync(__instance); } } } [HarmonyPatch(typeof(PlayerAvatar), "Start")] public static class PlayerAvatarStartEnemyPatch { [HarmonyPostfix] public static void Postfix() { if (Plugin.CfgEnabled.Value) { EnemyPatches.SyncAll(); } } } [HarmonyPatch] public static class EnemyOnScreenLogicMoveNextPatch { public static MethodBase TargetMethod() { Type type = AccessTools.TypeByName("EnemyOnScreen+d__19"); if (!(type == null)) { return AccessTools.Method(type, "MoveNext", (Type[])null, (Type[])null); } return null; } [HarmonyPrefix] public static void Prefix(object __instance) { if (Plugin.CfgEnabled.Value) { EnemyPatches.SyncEnemyOnScreenCoroutine(__instance); } } } [HarmonyPatch] public static class EnemyVisionMoveNextPatch { public static MethodBase TargetMethod() { Type type = AccessTools.TypeByName("EnemyVision+d__34"); if (!(type == null)) { return AccessTools.Method(type, "MoveNext", (Type[])null, (Type[])null); } return null; } [HarmonyPrefix] public static void Prefix(object __instance) { if (Plugin.CfgEnabled.Value) { EnemyPatches.SyncEnemyVisionCoroutine(__instance); } } } public static class InteractivePatches { private sealed class StateSyncEntry { public string TypeName; public string RpcName; public string FieldName; public bool CastToInt; public Type ResolvedType; public FieldInfo ResolvedField; } private static readonly List entries = new List { S("ExtractionPoint", "StateSetRPC", castToInt: false), S("ShopKeycardDoor", "StateSetRPC"), S("UpgradeStand", "StateSetRPC"), S("ItemValuableBox", "StateSetRPC"), S("ItemMine", "StateSetRPC", castToInt: true, "state"), S("ItemMelee", "StateSetRPC"), S("ItemGun", "StateSetRPC", castToInt: true, "stateCurrent"), S("ItemDrone", "StateSetRPC"), S("ItemVehicle", "SetStateRPC"), S("ItemCartCannonMain", "StateSetRPC", castToInt: true, "stateCurrent"), S("FanTrap", "SetStateRPC", castToInt: false), S("CrystalBallValuable", "SetStateRPC", castToInt: false), S("BlenderValuable", "SetStateRPC", castToInt: false), S("FlamethrowerValuable", "SetStateRPC", castToInt: false), S("IceSawValuable", "SetStateRPC", castToInt: false), S("FireExtinguisherValuable", "SetStateRPC", castToInt: false), S("ScreamDollValuable", "SetStateRPC", castToInt: false), S("JackhammerValuable", "SetStateRPC", castToInt: false) }; private static StateSyncEntry S(string type, string rpc, bool castToInt = true, string field = "currentState") { return new StateSyncEntry { TypeName = type, RpcName = rpc, FieldName = field, CastToInt = castToInt }; } public static void SyncInteractiveObjectsToPlayer(Player target) { if (Plugin.CfgEnabled.Value && SemiFunc.IsMultiplayer() && PhotonNetwork.IsMasterClient && target != null) { ((MonoBehaviour)Plugin.Instance).StartCoroutine(SyncCoroutine(target)); } } private static IEnumerator SyncCoroutine(Player target) { yield return (object)new WaitForSeconds(3.5f); if (target == null || PhotonNetwork.CurrentRoom == null) { yield break; } int count = 0; foreach (StateSyncEntry entry in entries) { if (entry.ResolvedType == null) { entry.ResolvedType = AccessTools.TypeByName(entry.TypeName); if (entry.ResolvedType != null) { entry.ResolvedField = AccessTools.Field(entry.ResolvedType, entry.FieldName); } } if (entry.ResolvedType == null || entry.ResolvedField == null) { continue; } Object[] array = Object.FindObjectsOfType(entry.ResolvedType); foreach (Object val in array) { if (val == (Object)null) { continue; } Object obj = ((val is Component) ? val : null); PhotonView val2 = ((obj != null) ? ((Component)obj).GetComponent() : null); if (!((Object)(object)val2 != (Object)null) || val2.ViewID == 0) { continue; } try { object value = entry.ResolvedField.GetValue(val); if (value != null) { object obj2 = (entry.CastToInt ? ((object)Convert.ToInt32(value)) : value); val2.RPC(entry.RpcName, target, new object[1] { obj2 }); count++; } } catch (Exception ex) { Debug.LogWarning((object)$"[LateJoin] Failed to sync state for {entry.TypeName} (ID: {val2.ViewID}): {ex.Message}"); } } yield return (object)new WaitForSeconds(0.05f); } Debug.Log((object)$"[LateJoin] Finished syncing {count} interactive object states to {target.NickName}."); } } [HarmonyPatch(typeof(NetworkManager), "OnPlayerEnteredRoom")] public static class NetworkManagerOnPlayerEnteredRoomInteractivePatch { [HarmonyPostfix] public static void Postfix(Player newPlayer) { if (Plugin.CfgEnabled.Value) { InteractivePatches.SyncInteractiveObjectsToPlayer(newPlayer); } } } public static class ItemPatches { private sealed class PendingSync { public Player Target; public float NextTime; public int Attempts; } private static readonly FieldInfo BatteryPhotonViewField = AccessTools.Field(typeof(ItemBattery), "photonView"); private static readonly FieldInfo BatteryLifeIntField = AccessTools.Field(typeof(ItemBattery), "batteryLifeInt"); private static readonly FieldInfo BatteryCurrentBarsField = AccessTools.Field(typeof(ItemBattery), "currentBars"); private static readonly FieldInfo InventorySpotBatteryVisualLogicField = AccessTools.Field(typeof(InventorySpot), "batteryVisualLogic"); private static readonly List Pending = new List(); public static void ClearBatteryPending() { Pending.Clear(); } public static void QueueBatterySync(PlayerAvatar playerAvatar) { if (Plugin.CfgEnabled.Value && SemiFunc.IsMultiplayer() && PhotonNetwork.IsMasterClient && (Object)(object)playerAvatar != (Object)null && (Object)(object)playerAvatar.photonView != (Object)null) { Player owner = playerAvatar.photonView.Owner; if (owner != null && PhotonNetwork.LocalPlayer != null && owner.ActorNumber != PhotonNetwork.LocalPlayer.ActorNumber) { QueueBatterySync(owner, 2f); } } } public static void QueueAllBatterySync(float delay) { if (Plugin.CfgEnabled.Value && SemiFunc.IsMultiplayer() && PhotonNetwork.IsMasterClient && PhotonNetwork.PlayerListOthers != null) { Player[] playerListOthers = PhotonNetwork.PlayerListOthers; for (int i = 0; i < playerListOthers.Length; i++) { QueueBatterySync(playerListOthers[i], delay); } } } private static void QueueBatterySync(Player target, float delay) { if (target == null || PhotonNetwork.LocalPlayer == null || target.ActorNumber == PhotonNetwork.LocalPlayer.ActorNumber) { return; } for (int num = Pending.Count - 1; num >= 0; num--) { if (Pending[num].Target != null && Pending[num].Target.ActorNumber == target.ActorNumber) { Pending.RemoveAt(num); } } Pending.Add(new PendingSync { Target = target, NextTime = Time.unscaledTime + Mathf.Max(0.1f, delay), Attempts = 0 }); } public static void Update() { if (Pending.Count == 0) { return; } for (int num = Pending.Count - 1; num >= 0; num--) { PendingSync pendingSync = Pending[num]; if (pendingSync != null && pendingSync.Target != null && !(Time.unscaledTime < pendingSync.NextTime)) { int num2 = SendCurrentBatteryState(pendingSync.Target); pendingSync.Attempts++; if (pendingSync.Attempts >= 12) { Pending.RemoveAt(num); Debug.Log((object)$"[LateJoin] Battery state sync completed for {pendingSync.Target.NickName}. Synced {num2} batteries."); } else { pendingSync.NextTime = Time.unscaledTime + 5f; } } } } private static int SendCurrentBatteryState(Player target) { int num = 0; ItemBattery[] array = FindBatteries(); foreach (ItemBattery val in array) { if ((Object)(object)val == (Object)null) { continue; } PhotonView photonView = GetPhotonView(val); if ((Object)(object)photonView != (Object)null && photonView.ViewID != 0 && CanReceiveBatteryRpc(photonView, val)) { try { int batteryLevel = GetBatteryLevel(val); bool batteryActive = val.batteryActive; photonView.RPC("BatteryToggleRPC", target, new object[1] { false }); photonView.RPC("BatteryFullPercentChangeRPC", target, new object[2] { batteryLevel, false }); photonView.RPC("BatteryToggleRPC", target, new object[1] { batteryActive }); num++; } catch (Exception ex) { Debug.LogWarning((object)("[LateJoin] Could not sync battery: " + ex.Message)); } } } return num; } private static ItemBattery[] FindBatteries() { try { return Object.FindObjectsOfType(true); } catch { return Object.FindObjectsOfType(); } } private static PhotonView GetPhotonView(ItemBattery battery) { if (BatteryPhotonViewField != null) { object? value = BatteryPhotonViewField.GetValue(battery); PhotonView val = (PhotonView)((value is PhotonView) ? value : null); if ((Object)(object)val != (Object)null) { return val; } } return ((Component)battery).GetComponent(); } private static bool CanReceiveBatteryRpc(PhotonView pv, ItemBattery battery) { if ((Object)(object)pv == (Object)null || (Object)(object)battery == (Object)null) { return false; } ItemBattery component = ((Component)pv).GetComponent(); if ((Object)(object)component != (Object)null) { return component == battery; } return false; } private static int GetBatteryLevel(ItemBattery battery) { int num = Mathf.Max(1, battery.batteryBars); int num2 = 0; if (BatteryLifeIntField != null && BatteryLifeIntField.GetValue(battery) is int num3) { num2 = num3; } if (num2 < 0 || num2 > num || (num2 == 0 && battery.batteryLife > 0f)) { num2 = Mathf.RoundToInt(battery.batteryLife / (100f / (float)num)); } return Mathf.Clamp(num2, 0, num); } public static void RepairInventorySpot(InventorySpot spot) { if (!Plugin.CfgEnabled.Value || (Object)(object)spot == (Object)null || !spot.IsOccupied()) { return; } ItemBattery val = FindBatteryInComponent((Component)(object)spot.CurrentItem); if ((Object)(object)val == (Object)null) { return; } int num = RepairBatteryCounters(val); if (!(InventorySpotBatteryVisualLogicField != null)) { return; } object? value = InventorySpotBatteryVisualLogicField.GetValue(spot); BatteryVisualLogic val2 = (BatteryVisualLogic)((value is BatteryVisualLogic) ? value : null); if (!((Object)(object)val2 != (Object)null)) { return; } try { if (!((Component)val2).gameObject.activeSelf) { ((Component)val2).gameObject.SetActive(true); } if ((Object)(object)val2.itemBattery != (Object)(object)val) { val2.itemBattery = val; val2.BatteryBarsSet(); } else if (val2.batteryBars != val.batteryBars) { val2.BatteryBarsSet(); } val2.BatteryBarsUpdate(num, true); } catch (Exception ex) { Debug.LogWarning((object)("[LateJoin] Could not repair inventory battery UI: " + ex.Message)); } } public static void RepairInventoryBatteryResult(Inventory inventory, int index, ref int result) { if (!Plugin.CfgEnabled.Value || (Object)(object)inventory == (Object)null) { return; } InventorySpot spotByIndex = inventory.GetSpotByIndex(index); if ((Object)(object)spotByIndex == (Object)null || !spotByIndex.IsOccupied()) { return; } ItemBattery val = FindBatteryInComponent((Component)(object)spotByIndex.CurrentItem); if ((Object)(object)val != (Object)null) { int num = RepairBatteryCounters(val); if (result < 0 || (result == 0 && val.batteryLife > 0f) || result > Mathf.Max(1, val.batteryBars)) { result = num; } } } private static ItemBattery FindBatteryInComponent(Component c) { if ((Object)(object)c == (Object)null) { return null; } return c.GetComponent() ?? c.GetComponentInChildren(true) ?? c.GetComponentInParent(true); } private static int RepairBatteryCounters(ItemBattery battery) { int batteryLevel = GetBatteryLevel(battery); if (BatteryLifeIntField != null) { BatteryLifeIntField.SetValue(battery, batteryLevel); } if (BatteryCurrentBarsField != null) { BatteryCurrentBarsField.SetValue(battery, batteryLevel); } return batteryLevel; } private static int GetBatteryBars(ItemBattery battery) { if (BatteryCurrentBarsField != null) { object value = BatteryCurrentBarsField.GetValue(battery); if (value is int) { return (int)value; } } return 0; } public static void TakeoverOrphanedPhysGrabObjects(int leftActor) { if (!SemiFunc.IsMasterClient()) { return; } PhysGrabObject[] array = Object.FindObjectsOfType(); if (array == null) { return; } int num = 0; PhysGrabObject[] array2 = array; foreach (PhysGrabObject val in array2) { if ((Object)(object)val == (Object)null) { continue; } PhotonView component = ((Component)val).GetComponent(); if ((Object)(object)component != (Object)null && component.ViewID != 0 && (component.Owner == null || component.Owner.ActorNumber == leftActor)) { try { component.TransferOwnership(PhotonNetwork.LocalPlayer); num++; } catch (Exception ex) { Debug.LogWarning((object)("[LateJoin] Ownership transfer failed: " + ex.Message)); } } } if (num > 0) { Debug.Log((object)$"[LateJoin] Took over {num} orphaned grabbed objects from leaving player {leftActor}."); } } } [HarmonyPatch(typeof(NetworkManager), "OnPlayerEnteredRoom")] public static class NetworkManagerOnPlayerEnteredRoomPatch { [HarmonyPostfix] public static void Postfix(Player newPlayer) { //IL_01df: Unknown result type (might be due to invalid IL or missing references) //IL_01ee: Unknown result type (might be due to invalid IL or missing references) //IL_01fd: Unknown result type (might be due to invalid IL or missing references) if (!Plugin.CfgEnabled.Value || (Object)(object)LevelGenerator.Instance == (Object)null || !LevelGenerator.Instance.Generated) { return; } if (SemiFunc.IsMasterClient()) { Debug.Log((object)("[LateJoin] Mid-game join detected: " + newPlayer.NickName + ". Syncing stats...")); PunManager.instance.SyncAllDictionaries(); PhysGrabObject[] array = Object.FindObjectsOfType(); foreach (PhysGrabObject val in array) { if (!((Object)(object)val != (Object)null) || val.playerGrabbing == null || val.playerGrabbing.Count <= 0) { continue; } PhotonView component = ((Component)val).GetComponent(); if (!((Object)(object)component != (Object)null)) { continue; } foreach (PhysGrabber item in val.playerGrabbing) { if ((Object)(object)item != (Object)null && (Object)(object)item.photonView != (Object)null) { component.RPC("GrabPlayerAddRPC", newPlayer, new object[1] { item.photonView.ViewID }); } } } ItemPatches.QueueAllBatterySync(2f); } if (!((Object)(object)PhysGrabber.instance != (Object)null) || !PhysGrabber.instance.grabbed) { return; } PhysGrabber instance = PhysGrabber.instance; PhysGrabObject fieldValue = instance.GetFieldValue("grabbedPhysGrabObject"); if ((Object)(object)fieldValue != (Object)null && (Object)(object)instance.photonView != (Object)null && instance.photonView.IsMine) { PhotonView component2 = ((Component)fieldValue).GetComponent(); if ((Object)(object)component2 != (Object)null) { int fieldValue2 = instance.GetFieldValue("grabbedPhysGrabObjectColliderID"); component2.RPC("GrabLinkRPC", newPlayer, new object[5] { instance.photonView.ViewID, fieldValue2, instance.physGrabPoint.position, instance.cameraRelativeGrabbedForward, instance.cameraRelativeGrabbedUp }); } } } } [HarmonyPatch(typeof(NetworkManager), "OnPlayerLeftRoom")] public static class NetworkManagerOnPlayerLeftRoomPatch { [HarmonyPostfix] public static void Postfix(Player otherPlayer) { if (Plugin.CfgEnabled.Value && otherPlayer != null) { ItemPatches.TakeoverOrphanedPhysGrabObjects(otherPlayer.ActorNumber); } } } [HarmonyPatch(typeof(PlayerAvatar), "Start")] public static class PlayerAvatarStartBatteryPatch { [HarmonyPostfix] public static void Postfix(PlayerAvatar __instance) { if (Plugin.CfgEnabled.Value) { ItemPatches.QueueBatterySync(__instance); } } } [HarmonyPatch(typeof(InventorySpot), "StateOccupied")] public static class InventorySpotStateOccupiedPatch { [HarmonyPostfix] public static void Postfix(InventorySpot __instance) { if (Plugin.CfgEnabled.Value) { ItemPatches.RepairInventorySpot(__instance); } } } [HarmonyPatch(typeof(Inventory), "GetBatteryStateFromInventorySpot")] public static class InventoryGetBatteryStateFromInventorySpotPatch { [HarmonyPostfix] public static void Postfix(Inventory __instance, int index, ref int __result) { if (Plugin.CfgEnabled.Value) { ItemPatches.RepairInventoryBatteryResult(__instance, index, ref __result); } } } public static class LevelPatches { private sealed class ModuleConnectionState { public PhotonView PhotonView; public int ViewId; public bool Top; public bool Bottom; public bool Right; public bool Left; public bool First; } private sealed class PendingReplay { public Player Target; public float NextTime; public int Attempts; } private static readonly FieldInfo ModulePhotonViewField = AccessTools.Field(typeof(Module), "photonView"); private static readonly FieldInfo ModuleConnectingTopField = AccessTools.Field(typeof(Module), "ConnectingTop"); private static readonly FieldInfo ModuleConnectingBottomField = AccessTools.Field(typeof(Module), "ConnectingBottom"); private static readonly FieldInfo ModuleConnectingRightField = AccessTools.Field(typeof(Module), "ConnectingRight"); private static readonly FieldInfo ModuleConnectingLeftField = AccessTools.Field(typeof(Module), "ConnectingLeft"); private static readonly FieldInfo ModuleSetupDoneField = AccessTools.Field(typeof(Module), "SetupDone"); private static readonly FieldInfo ModuleFirstField = AccessTools.Field(typeof(Module), "First"); private static readonly FieldInfo ModuleGridXField = AccessTools.Field(typeof(Module), "GridX"); private static readonly FieldInfo ModuleGridYField = AccessTools.Field(typeof(Module), "GridY"); private static readonly FieldInfo ModuleStartRoomField = AccessTools.Field(typeof(Module), "StartRoom"); private static readonly FieldInfo ModulePropSwitchModuleField = AccessTools.Field(typeof(ModulePropSwitch), "Module"); private static readonly FieldInfo LevelGeneratorModulesSpawnedField = AccessTools.Field(typeof(LevelGenerator), "ModulesSpawned"); private static readonly Dictionary States = new Dictionary(); private static readonly List Pending = new List(); public static void ClearStates() { States.Clear(); Pending.Clear(); } public static bool TrySendBuffered(Module module, bool top, bool bottom, bool right, bool left, bool first) { if (!Plugin.CfgEnabled.Value) { return false; } if ((Object)(object)module == (Object)null || !SemiFunc.IsMultiplayer() || !PhotonNetwork.IsMasterClient) { return false; } PhotonView photonView = GetPhotonView(module); if ((Object)(object)photonView == (Object)null || photonView.ViewID == 0) { return false; } try { States[photonView.ViewID] = new ModuleConnectionState { PhotonView = photonView, ViewId = photonView.ViewID, Top = top, Bottom = bottom, Right = right, Left = left, First = first }; } catch (Exception ex) { Debug.LogWarning((object)("[LateJoin] Could not store module connection state: " + ex.Message)); } return false; } public static void Queue(PlayerAvatar playerAvatar) { if (Plugin.CfgEnabled.Value && SemiFunc.IsMultiplayer() && PhotonNetwork.IsMasterClient && (Object)(object)playerAvatar != (Object)null && (Object)(object)playerAvatar.photonView != (Object)null) { Queue(playerAvatar.photonView.Owner, 3f); } } public static void QueueAllRemote(float delay) { if (Plugin.CfgEnabled.Value && SemiFunc.IsMultiplayer() && PhotonNetwork.IsMasterClient && PhotonNetwork.PlayerListOthers != null) { Player[] playerListOthers = PhotonNetwork.PlayerListOthers; for (int i = 0; i < playerListOthers.Length; i++) { Queue(playerListOthers[i], delay); } } } private static void Queue(Player target, float delay) { if (target == null || PhotonNetwork.LocalPlayer == null || target.ActorNumber == PhotonNetwork.LocalPlayer.ActorNumber) { return; } for (int num = Pending.Count - 1; num >= 0; num--) { if (Pending[num].Target != null && Pending[num].Target.ActorNumber == target.ActorNumber) { Pending.RemoveAt(num); } } Pending.Add(new PendingReplay { Target = target, NextTime = Time.unscaledTime + Mathf.Max(0.1f, delay), Attempts = 0 }); } public static void Update() { if (Pending.Count == 0) { return; } for (int num = Pending.Count - 1; num >= 0; num--) { PendingReplay pendingReplay = Pending[num]; if (pendingReplay != null && pendingReplay.Target != null && !(Time.unscaledTime < pendingReplay.NextTime)) { int num2 = SendCurrentState(pendingReplay.Target); pendingReplay.Attempts++; if (pendingReplay.Attempts >= 16) { Pending.RemoveAt(num); Debug.Log((object)$"[LateJoin] Sent {num2} module connection states to player {pendingReplay.Target.NickName}."); } else { pendingReplay.NextTime = Time.unscaledTime + 5f; } } } } private static int SendCurrentState(Player target) { if (States.Count == 0) { return 0; } int num = 0; foreach (ModuleConnectionState value in States.Values) { if (value == null) { continue; } PhotonView val = (((Object)(object)value.PhotonView != (Object)null) ? value.PhotonView : PhotonNetwork.GetPhotonView(value.ViewId)); if ((Object)(object)val != (Object)null && val.ViewID != 0) { try { val.RPC("ModuleConnectionSetRPC", target, new object[5] { value.Top, value.Bottom, value.Right, value.Left, value.First }); num++; } catch (Exception ex) { Debug.LogWarning((object)("[LateJoin] Could not replay module connection: " + ex.Message)); } } } return num; } public static void ApplyLocalClientConnections(LevelGenerator levelGenerator) { if (!Plugin.CfgEnabled.Value || !SemiFunc.IsMultiplayer() || PhotonNetwork.IsMasterClient || (Object)(object)levelGenerator == (Object)null) { return; } Module[] array = FindModules(); if (array == null || array.Length == 0) { return; } Dictionary dictionary = new Dictionary(); Module[] array2 = array; foreach (Module val in array2) { if ((Object)(object)val != (Object)null && !IsStartRoom(val)) { int num = GetInt(ModuleGridXField, val); int num2 = GetInt(ModuleGridYField, val); string key = $"{num}:{num2}"; if (!dictionary.ContainsKey(key)) { dictionary.Add(key, val); } } } int num3 = 0; array2 = array; foreach (Module val2 in array2) { if ((Object)(object)val2 != (Object)null && !IsStartRoom(val2)) { int num4 = GetInt(ModuleGridXField, val2); int num5 = GetInt(ModuleGridYField, val2); bool flag = IsFirstModule(val2); bool top = dictionary.ContainsKey($"{num4}:{num5 + 1}"); bool bottom = dictionary.ContainsKey($"{num4}:{num5 - 1}") || flag; bool right = dictionary.ContainsKey($"{num4 + 1}:{num5}"); bool left = dictionary.ContainsKey($"{num4 - 1}:{num5}"); if (ApplyState(val2, top, bottom, right, left, flag, countModuleReady: true)) { num3++; } } } } public static bool ApplyIncomingRpc(Module module, bool top, bool bottom, bool right, bool left, bool first) { if ((Object)(object)module == (Object)null || !SemiFunc.IsMultiplayer() || PhotonNetwork.IsMasterClient) { return true; } bool flag = GetBool(ModuleSetupDoneField, module); if (flag && GetBool(ModuleConnectingTopField, module) == top && GetBool(ModuleConnectingBottomField, module) == bottom && GetBool(ModuleConnectingRightField, module) == right && GetBool(ModuleConnectingLeftField, module) == left && GetBool(ModuleFirstField, module) == first) { return false; } ApplyState(module, top, bottom, right, left, first, !flag); return false; } private static PhotonView GetPhotonView(Module module) { if (ModulePhotonViewField != null) { object? value = ModulePhotonViewField.GetValue(module); PhotonView val = (PhotonView)((value is PhotonView) ? value : null); if ((Object)(object)val != (Object)null) { return val; } } return ((Component)module).GetComponent(); } private static bool ApplyState(Module module, bool top, bool bottom, bool right, bool left, bool first, bool countModuleReady) { if ((Object)(object)module == (Object)null) { return false; } bool flag = GetBool(ModuleSetupDoneField, module); SetBool(ModuleConnectingTopField, module, top); SetBool(ModuleConnectingBottomField, module, bottom); SetBool(ModuleConnectingRightField, module, right); SetBool(ModuleConnectingLeftField, module, left); SetBool(ModuleFirstField, module, first); SetBool(ModuleSetupDoneField, module, value: true); try { ModulePropSwitch[] componentsInChildren = ((Component)module).GetComponentsInChildren(true); foreach (ModulePropSwitch val in componentsInChildren) { if ((Object)(object)val != (Object)null) { if (ModulePropSwitchModuleField != null) { ModulePropSwitchModuleField.SetValue(val, module); } val.Setup(); } } } catch (Exception ex) { Debug.LogWarning((object)("[LateJoin] ModulePropSwitch setup failed: " + ex.Message)); } if (countModuleReady && !flag && (Object)(object)LevelGenerator.Instance != (Object)null && LevelGeneratorModulesSpawnedField != null) { int num = GetInt(LevelGeneratorModulesSpawnedField, LevelGenerator.Instance); LevelGeneratorModulesSpawnedField.SetValue(LevelGenerator.Instance, num + 1); } return !flag; } private static bool IsFirstModule(Module module) { if ((Object)(object)module != (Object)null) { if (!GetBool(ModuleFirstField, module) && !IsStartRoom(module) && !((Object)(object)((Component)module).GetComponent() != (Object)null)) { return (Object)(object)((Component)module).GetComponentInChildren(true) != (Object)null; } return true; } return false; } private static bool IsStartRoom(Module module) { if ((Object)(object)module != (Object)null) { return GetBool(ModuleStartRoomField, module); } return false; } private static Module[] FindModules() { try { return Object.FindObjectsOfType(true); } catch { return Object.FindObjectsOfType(); } } private static bool GetBool(FieldInfo field, object instance) { if (field == null || instance == null) { return false; } object value = field.GetValue(instance); bool flag = default(bool); int num; if (value is bool) { flag = (bool)value; num = 1; } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } private static void SetBool(FieldInfo field, object instance, bool value) { if (field != null && instance != null) { field.SetValue(instance, value); } } private static int GetInt(FieldInfo field, object instance) { if (field == null || instance == null) { return 0; } object value = field.GetValue(instance); if (value is int) { return (int)value; } return 0; } } [HarmonyPatch(typeof(LevelGenerator), "Start")] public static class LevelGeneratorStartPatch { [HarmonyPostfix] public static void Postfix() { if (Plugin.CfgEnabled.Value) { LevelPatches.ClearStates(); } } } [HarmonyPatch(typeof(LevelGenerator), "GenerateDone")] public static class LevelGeneratorGenerateDonePatch { [HarmonyPostfix] public static void Postfix() { if (Plugin.CfgEnabled.Value) { LevelPatches.QueueAllRemote(2f); } } } [HarmonyPatch(typeof(LevelGenerator), "GenerateConnectObjects")] public static class LevelGeneratorGenerateConnectObjectsPatch { [HarmonyPrefix] public static void Prefix(LevelGenerator __instance) { if (Plugin.CfgEnabled.Value) { LevelPatches.ApplyLocalClientConnections(__instance); } } } [HarmonyPatch(typeof(LevelGenerator), "GenerateBlockObjects")] public static class LevelGeneratorGenerateBlockObjectsPatch { [HarmonyPrefix] public static void Prefix(LevelGenerator __instance) { if (Plugin.CfgEnabled.Value) { LevelPatches.ApplyLocalClientConnections(__instance); } } } [HarmonyPatch(typeof(Module), "ModuleConnectionSet")] public static class ModuleConnectionSetPatch { [HarmonyPrefix] public static bool Prefix(Module __instance, bool _top, bool _bottom, bool _right, bool _left, bool _first) { if (Plugin.CfgEnabled.Value) { return !LevelPatches.TrySendBuffered(__instance, _top, _bottom, _right, _left, _first); } return true; } } [HarmonyPatch(typeof(Module), "ModuleConnectionSetRPC")] public static class ModuleConnectionSetRpcPatch { [HarmonyPrefix] public static bool Prefix(Module __instance, bool _top, bool _bottom, bool _right, bool _left, bool _first) { if (Plugin.CfgEnabled.Value) { return LevelPatches.ApplyIncomingRpc(__instance, _top, _bottom, _right, _left, _first); } return true; } } public static class ReflectionHelper { public static T GetFieldValue(this object obj, string fieldName) { FieldInfo fieldInfo = AccessTools.Field(obj.GetType(), fieldName); if (fieldInfo != null) { return (T)fieldInfo.GetValue(obj); } return default(T); } public static void SetFieldValue(this object obj, string fieldName, object value) { FieldInfo fieldInfo = AccessTools.Field(obj.GetType(), fieldName); if (fieldInfo != null) { fieldInfo.SetValue(obj, value); } } } [HarmonyPatch(typeof(MenuPageLobby), "ButtonStart")] public static class MenuPageLobbyButtonStartPatch { [HarmonyPostfix] public static void Postfix() { if (!Plugin.CfgEnabled.Value || !PhotonNetwork.IsMasterClient) { return; } try { if ((Object)(object)SteamManager.instance != (Object)null) { bool fieldValue = SteamManager.instance.GetFieldValue("privateLobby"); SteamManager.instance.UnlockLobby(!fieldValue); } if (PhotonNetwork.CurrentRoom != null) { PhotonNetwork.CurrentRoom.IsOpen = true; PhotonNetwork.CurrentRoom.IsVisible = true; } } catch (Exception ex) { Debug.LogWarning((object)("[LateJoin] ButtonStart Postfix unlock failed: " + ex.Message)); } } } [HarmonyPatch(typeof(SteamManager), "LockLobby")] public static class SteamManagerLockLobbyPatch { [HarmonyPrefix] public static bool Prefix() { if (Plugin.CfgEnabled.Value) { Debug.Log((object)"[LateJoin] Suppressing SteamManager.LockLobby()"); return false; } return true; } } [HarmonyPatch(typeof(RunManager), "ChangeLevel")] public static class RunManagerChangeLevelPatch { private static readonly FieldInfo removeFilterFieldInfo = AccessTools.Field(typeof(PhotonNetwork), "removeFilter"); private static readonly FieldInfo keyByteSevenFieldInfo = AccessTools.Field(typeof(PhotonNetwork), "keyByteSeven"); private static readonly FieldInfo serverCleanOptionsFieldInfo = AccessTools.Field(typeof(PhotonNetwork), "ServerCleanOptions"); private static readonly MethodInfo raiseEventInternalMethodInfo = AccessTools.Method(typeof(PhotonNetwork), "RaiseEventInternal", (Type[])null, (Type[])null); [HarmonyPrefix] public static void Prefix(RunManager __instance, bool _completedLevel, bool _levelFailed, ChangeLevelType _changeLevelType) { //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) if (!Plugin.CfgEnabled.Value || _levelFailed || !PhotonNetwork.IsMasterClient) { return; } try { object value = AccessTools.Field(typeof(RunManager), "runManagerPUN").GetValue(__instance); object? value2 = AccessTools.Field(value.GetType(), "photonView").GetValue(value); PhotonView val = (PhotonView)((value2 is PhotonView) ? value2 : null); if (val != null) { PhotonNetwork.RemoveBufferedRPCs(val.ViewID, (string)null, (int[])null); } PhotonView[] array = Object.FindObjectsOfType(); foreach (PhotonView val2 in array) { if ((Object)(object)val2 != (Object)null) { Scene scene = ((Component)val2).gameObject.scene; if (((Scene)(ref scene)).buildIndex != -1) { ClearPhotonCache(val2); } } } Debug.Log((object)"[LateJoin] Cleared Photon RPC cache for level change."); } catch (Exception ex) { Debug.LogError((object)("[LateJoin] Error in ChangeLevel Prefix: " + ex.Message)); } } [HarmonyPostfix] public static void Postfix() { if (Plugin.CfgEnabled.Value && PhotonNetwork.IsMasterClient) { SteamManager.instance.UnlockLobby(true); if (PhotonNetwork.CurrentRoom != null) { PhotonNetwork.CurrentRoom.IsOpen = true; PhotonNetwork.CurrentRoom.IsVisible = true; } } } private static void ClearPhotonCache(PhotonView pv) { //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) try { object? value = removeFilterFieldInfo.GetValue(null); Hashtable val = (Hashtable)((value is Hashtable) ? value : null); object value2 = keyByteSevenFieldInfo.GetValue(null); object? value3 = serverCleanOptionsFieldInfo.GetValue(null); RaiseEventOptions val2 = (RaiseEventOptions)((value3 is RaiseEventOptions) ? value3 : null); if (val != null && val2 != null) { val[value2] = pv.InstantiationId; val2.CachingOption = (EventCaching)6; raiseEventInternalMethodInfo.Invoke(null, new object[4] { (byte)202, val, val2, SendOptions.SendReliable }); } } catch (Exception ex) { Debug.LogWarning((object)("[LateJoin] ClearPhotonCache failed: " + ex.Message)); } } } public static class LobbyPatches { private static float nextUnlockTime; public static void Update() { if (!Plugin.CfgEnabled.Value || !PhotonNetwork.IsMasterClient || !(Time.unscaledTime >= nextUnlockTime)) { return; } nextUnlockTime = Time.unscaledTime + 2f; try { if ((Object)(object)SteamManager.instance != (Object)null) { bool fieldValue = SteamManager.instance.GetFieldValue("privateLobby"); SteamManager.instance.UnlockLobby(!fieldValue); } if (PhotonNetwork.CurrentRoom != null) { PhotonNetwork.CurrentRoom.IsOpen = true; PhotonNetwork.CurrentRoom.IsVisible = true; } } catch (Exception ex) { Debug.LogWarning((object)("[LateJoin] Periodic unlock failed: " + ex.Message)); } } } [HarmonyPatch(typeof(PlayerAvatar), "Spawn")] public static class PlayerAvatarSpawnPatch { [HarmonyPrefix] public static bool Prefix(PlayerAvatar __instance) { if (!Plugin.CfgEnabled.Value) { return true; } return !__instance.GetFieldValue("spawned"); } } [HarmonyPatch(typeof(PlayerAvatar), "Start")] public static class PlayerAvatarStartPatch { private static bool originalGeneratedState; [HarmonyPrefix] public static void Prefix(PlayerAvatar __instance) { if (Plugin.CfgEnabled.Value && (Object)(object)LevelGenerator.Instance != (Object)null) { originalGeneratedState = LevelGenerator.Instance.Generated; if (originalGeneratedState) { LevelGenerator.Instance.Generated = false; } } } [HarmonyPostfix] public static void Postfix(PlayerAvatar __instance) { //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) if (!Plugin.CfgEnabled.Value || !((Object)(object)LevelGenerator.Instance != (Object)null)) { return; } if (originalGeneratedState) { LevelGenerator.Instance.Generated = true; if (SemiFunc.IsMasterClient() && (Object)(object)__instance.photonView != (Object)null && !__instance.photonView.IsMine) { Vector3 val = Vector3.zero; Quaternion val2 = Quaternion.identity; bool flag = false; if ((Object)(object)TruckSafetySpawnPoint.instance != (Object)null) { val = ((Component)TruckSafetySpawnPoint.instance).transform.position; val2 = ((Component)TruckSafetySpawnPoint.instance).transform.rotation; flag = true; } else { SpawnPoint[] array = Object.FindObjectsOfType(); if (array.Length != 0) { SpawnPoint obj = array[Random.Range(0, array.Length)]; val = ((Component)obj).transform.position; val2 = ((Component)obj).transform.rotation; flag = true; } } if (flag) { Debug.Log((object)$"[LateJoin] Teleporting late-joining player {__instance.photonView.Owner.NickName} to Safety Spawnpoint: {val}"); __instance.Spawn(val, val2); } } } if (PhotonNetwork.IsMasterClient && !SemiFunc.RunIsLobby() && (Object)(object)__instance.photonView != (Object)null) { __instance.photonView.RPC("LoadingLevelAnimationCompletedRPC", (RpcTarget)4, Array.Empty()); } ((MonoBehaviour)__instance).StartCoroutine(InitLateJoinPlayer(__instance)); } private static IEnumerator InitLateJoinPlayer(PlayerAvatar player) { while ((Object)(object)LevelGenerator.Instance == (Object)null || !LevelGenerator.Instance.Generated) { yield return null; } yield return (object)new WaitForSeconds(0.5f); if (!((Object)(object)player == (Object)null) && !((Object)(object)((Component)player).gameObject == (Object)null) && player.photonView.IsMine && SemiFunc.IsMultiplayer() && !SemiFunc.MenuLevel()) { PlayerDeathHead fieldValue = player.GetFieldValue("playerDeathHead"); Vector3 fieldValue2 = AssetManager.instance.GetFieldValue("physDisabledPosition"); if (!Object.op_Implicit((Object)(object)fieldValue)) { Debug.Log((object)"[LateJoin] Spawning local DeathHead for self."); PlayerDeathHead component = PhotonNetwork.Instantiate(((Object)LevelGenerator.Instance.PlayerDeathHeadPrefab).name, fieldValue2, Quaternion.identity, (byte)0, (object[])null).GetComponent(); component.playerAvatar = player; player.SetFieldValue("playerDeathHead", component); } if (!Object.op_Implicit((Object)(object)player.GetFieldValue("tumble"))) { Debug.Log((object)"[LateJoin] Spawning local Tumble for self."); PlayerTumble component2 = PhotonNetwork.Instantiate(((Object)LevelGenerator.Instance.PlayerTumblePrefab).name, fieldValue2, Quaternion.identity, (byte)0, (object[])null).GetComponent(); component2.playerAvatar = player; player.SetFieldValue("tumble", component2); } } } } }