using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using Bifrost.Cooked; using HarmonyLib; using JoinAnytime; using JoinAnytime.Enums; using JoinAnytime.Helpers; using MelonLoader; using MelonLoader.Preferences; using Microsoft.CodeAnalysis; using ReluProtocol; using ReluProtocol.C2S; using ReluProtocol.Enum; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: MelonInfo(typeof(JoinAnytimeMod), "JoinAnytime", "0.1.0", "Shlygly", null)] [assembly: MelonGame("ReLUGames", "MIMESIS")] [assembly: MelonColor(255, 0, 128, 255)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("Shlygly")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("JoinAnytime")] [assembly: AssemblyTitle("JoinAnytime")] [assembly: NeutralResourcesLanguage("en-US")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.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 JoinAnytime { public class JoinAnytimeMod : MelonMod { public static LateJoinRedirectState RedirectState = LateJoinRedirectState.None; public static string PendingSceneName = ""; public static JoinAnytimeMod Instance { get; private set; } public override void OnInitializeMelon() { Instance = this; ((MelonBase)this).LoggerInstance.Msg("Reading settings..."); Preferences.Init(); (bool, string)[] array = new(bool, string)[4] { (Preferences.AllowPreGame.Value, "pre-game tramway"), (Preferences.AllowOnPlaying.Value, "dungeon"), (Preferences.AllowEndGame.Value, "end game tramway"), (Preferences.AllowDeathMatch.Value, "final deathmatch") }; for (int i = 0; i < array.Length; i++) { var (flag, text) = array[i]; if (flag) { ((MelonBase)this).LoggerInstance.Msg("Players are allowed to join during " + text + "."); } else { ((MelonBase)this).LoggerInstance.Msg("Players won't be able to join during " + text + "."); } } ((MelonBase)this).LoggerInstance.Msg("Initialized !"); } public static void Log(string message) { if (Preferences.Verbose.Value) { ((MelonBase)Instance).LoggerInstance.Msg(message); } } } internal static class LateJoinManager { [CompilerGenerated] private sealed class <g__connectDelayed|8_0>d : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public VoiceManager voice; private PersistentData 5__1; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <g__connectDelayed|8_0>d(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { 5__1 = null; <>1__state = -2; } private bool MoveNext() { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Expected O, but got Unknown //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; voiceCheckRunning = true; <>2__current = (object)new WaitForSeconds(0.5f); <>1__state = 1; return true; case 1: <>1__state = -1; 5__1 = ReflectionTools.GetFieldOrProperty(Hub.s, "pdata"); if (5__1 != null && (Object)(object)voice != (Object)null && !voice.IsConnected()) { voice.TryRecoverMicrophone(); voice.ConnectVoiceChat(5__1.GameServerAddressOrSteamId, 5__1.WithRelay); } <>2__current = (object)new WaitForSeconds(2f); <>1__state = 2; return true; case 2: <>1__state = -1; voiceCheckRunning = false; return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private static bool voiceCheckRunning; public static bool IsStateAllowed(VGameSessionState state) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Invalid comparison between Unknown and I4 //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Invalid comparison between Unknown and I4 //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Invalid comparison between Unknown and I4 //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Invalid comparison between Unknown and I4 return ((int)state == 3 && Preferences.AllowPreGame.Value) || ((int)state == 4 && Preferences.AllowOnPlaying.Value) || ((int)state == 6 && Preferences.AllowEndGame.Value) || ((int)state == 7 && Preferences.AllowDeathMatch.Value); } public static void OnServerLogin(SessionContext context) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Invalid comparison between Unknown and I4 PersistentData fieldOrProperty = ReflectionTools.GetFieldOrProperty(Hub.s, "pdata"); if (fieldOrProperty != null && (int)fieldOrProperty.ClientMode <= 0) { GameMainBase main = fieldOrProperty.main; GamePlayScene val = (GamePlayScene)(object)((main is GamePlayScene) ? main : null); if (val != null && IsStateAllowed((VGameSessionState)4)) { JoinAnytimeMod.Log($"Login while OnPlaying: uid={context.GetPlayerUID()} dungeon={val.DungeonMasterID} seed={val.RandDungeonSeed}"); NetworkTools.SendOnPlayingStateToClient(context); } } } public static void OnServerPlayerCreated(VPlayer player) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Invalid comparison between Unknown and I4 //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Expected O, but got Unknown //IL_00c6: Unknown result type (might be due to invalid IL or missing references) PersistentData fieldOrProperty = ReflectionTools.GetFieldOrProperty(Hub.s, "pdata"); if (fieldOrProperty != null && (int)fieldOrProperty.ClientMode <= 0 && !player.IsHost) { JoinAnytimeMod.Log($"VPlayer ctor: UID={((VActor)player).UID} room={((object)((VActor)player).VRoom)?.GetType().Name} main={((object)fieldOrProperty.main)?.GetType().Name}"); if (fieldOrProperty.main is InTramWaitingScene && ((VActor)player).VRoom is MaintenanceRoom && IsStateAllowed((VGameSessionState)3)) { JoinAnytimeMod.Log($"Join PreGame detected, sending EnableWaitingRoomSig to UID={((VActor)player).UID}"); ((VActor)player).SendToMe((IMsg)new EnableWaitingRoomSig()); } else if (fieldOrProperty.main is GamePlayScene && ((VActor)player).VRoom is MaintenanceRoom && IsStateAllowed((VGameSessionState)4)) { NetworkTools.SendOnPlayingStateToClient(player); } } } public static bool OnClientPacket(IMsg msg) { //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Invalid comparison between Unknown and I4 //IL_01c2: Unknown result type (might be due to invalid IL or missing references) //IL_01c8: Invalid comparison between Unknown and I4 PersistentData fieldOrProperty = ReflectionTools.GetFieldOrProperty(Hub.s, "pdata"); AllMemberEnterRoomSig val = (AllMemberEnterRoomSig)(object)((msg is AllMemberEnterRoomSig) ? msg : null); if (val == null) { if (!(msg is EnableWaitingRoomSig)) { StartGameSig val2 = (StartGameSig)(object)((msg is StartGameSig) ? msg : null); if (val2 == null) { CompleteMakingRoomSig val3 = (CompleteMakingRoomSig)(object)((msg is CompleteMakingRoomSig) ? msg : null); if (val3 != null) { fieldOrProperty.completeMakingRoomSig = val3; if (val3.nextRoomInfo.roomMasterID != 0) { fieldOrProperty.dungeonMasterID = val3.nextRoomInfo.roomMasterID; } string sceneNameFromDungeon = RoomTools.GetSceneNameFromDungeon(fieldOrProperty.dungeonMasterID); JoinAnytimeMod.Log(string.Format("CompleteMakingRoomSig: roomUID={0}, scene={1}, main={2}", val3.nextRoomInfo.roomUID, sceneNameFromDungeon, ((object)fieldOrProperty.main)?.GetType().Name ?? "null")); JoinAnytimeMod.PendingSceneName = sceneNameFromDungeon; if ((int)fieldOrProperty.ClientMode == 1) { if (fieldOrProperty.main is MaintenanceScene && !string.IsNullOrEmpty(sceneNameFromDungeon)) { JoinAnytimeMod.Log("Redirect now -> " + sceneNameFromDungeon); JoinAnytimeMod.RedirectState = LateJoinRedirectState.EnteringDungeon; Hub.LoadScene(sceneNameFromDungeon); } else { JoinAnytimeMod.RedirectState = LateJoinRedirectState.PendingDungeonRedirect; } } } } else { fieldOrProperty.dungeonMasterID = val2.selectedDungeonMasterID; fieldOrProperty.randDungeonSeed = val2.randDungeonSeed; JoinAnytimeMod.Log($"StartGameSig: dungeon={fieldOrProperty.dungeonMasterID} seed={fieldOrProperty.randDungeonSeed}"); } } else { if (fieldOrProperty == null || (int)fieldOrProperty.ClientMode != 1) { return true; } JoinAnytimeMod.Log("PreGame signal received. main=" + (((object)fieldOrProperty.main)?.GetType().Name ?? "null")); JoinAnytimeMod.RedirectState = LateJoinRedirectState.PendingWaitingRoomRedirect; JoinAnytimeMod.PendingSceneName = "InTramWaitingScene"; if (fieldOrProperty.main is MaintenanceScene) { JoinAnytimeMod.RedirectState = LateJoinRedirectState.None; Hub.LoadScene("InTramWaitingScene"); } } } else if (ShouldIgnoreEarlyGameAllMemberEnterRoomSig(fieldOrProperty, val)) { return false; } return true; } private static bool ShouldIgnoreEarlyGameAllMemberEnterRoomSig(PersistentData pdata, AllMemberEnterRoomSig sig) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Invalid comparison between Unknown and I4 if (pdata == null || (int)pdata.ClientMode != 1) { return false; } if (!(pdata.main is GamePlayScene)) { return false; } if (ReflectionTools.GetFieldOrProperty(pdata.main, "EnteringCompleteAll")) { return false; } if (JoinAnytimeMod.RedirectState != LateJoinRedirectState.EnteringDungeon) { return false; } if (sig.enterCutsceneNames != null && sig.enterCutsceneNames.Contains("ArriveCutScene")) { JoinAnytimeMod.RedirectState = LateJoinRedirectState.None; JoinAnytimeMod.PendingSceneName = ""; return false; } JoinAnytimeMod.Log("Ignoring early AllMemberEnterRoomSig names=" + string.Join(",", sig.enterCutsceneNames ?? new List())); return true; } public static void OnLoadScene(string sceneName) { if (JoinAnytimeMod.RedirectState != 0 && (JoinAnytimeMod.RedirectState != LateJoinRedirectState.EnteringDungeon || !(sceneName == JoinAnytimeMod.PendingSceneName)) && sceneName != JoinAnytimeMod.PendingSceneName) { JoinAnytimeMod.RedirectState = LateJoinRedirectState.None; JoinAnytimeMod.PendingSceneName = ""; } } public static void OnMaintenanceSceneStart() { PersistentData fieldOrProperty = ReflectionTools.GetFieldOrProperty(Hub.s, "pdata"); if (fieldOrProperty != null) { if (JoinAnytimeMod.RedirectState == LateJoinRedirectState.PendingWaitingRoomRedirect) { JoinAnytimeMod.Log("Pending PreGame redirect -> InTramWaitingScene"); JoinAnytimeMod.RedirectState = LateJoinRedirectState.None; Hub.LoadScene("InTramWaitingScene"); } else if (JoinAnytimeMod.RedirectState == LateJoinRedirectState.PendingDungeonRedirect && !string.IsNullOrEmpty(JoinAnytimeMod.PendingSceneName)) { JoinAnytimeMod.Log("Pending in-game redirect -> " + JoinAnytimeMod.PendingSceneName); JoinAnytimeMod.RedirectState = LateJoinRedirectState.EnteringDungeon; Hub.LoadScene(JoinAnytimeMod.PendingSceneName); } } } public static void EnsureVoiceConnected(VoiceManager voiceManager, VoiceMode mode) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Invalid comparison between Unknown and I4 //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Invalid comparison between Unknown and I4 //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Invalid comparison between Unknown and I4 if (voiceCheckRunning) { return; } PersistentData fieldOrProperty = ReflectionTools.GetFieldOrProperty(Hub.s, "pdata"); if (fieldOrProperty == null) { return; } if (1 == 0) { } bool flag = (((int)mode == 1) ? (Preferences.AllowPreGame.Value || Preferences.AllowEndGame.Value) : ((int)mode == 2 && (Preferences.AllowOnPlaying.Value || Preferences.AllowDeathMatch.Value))); if (1 == 0) { } if (flag && (int)fieldOrProperty.ClientMode == 1) { GameMainBase main = fieldOrProperty.main; if (1 == 0) { } flag = ((main is InTramWaitingScene) ? (Preferences.AllowPreGame.Value || Preferences.AllowEndGame.Value) : ((main is GamePlayScene) ? Preferences.AllowOnPlaying.Value : (main is DeathMatchScene && Preferences.AllowDeathMatch.Value))); if (1 == 0) { } if (flag && !voiceManager.IsConnected()) { JoinAnytimeMod.Log($"Voice connect fix: addr={fieldOrProperty.GameServerAddressOrSteamId} relay={fieldOrProperty.WithRelay}"); ((MonoBehaviour)voiceManager).StartCoroutine(connectDelayed(voiceManager)); } } [IteratorStateMachine(typeof(<g__connectDelayed|8_0>d))] static IEnumerator connectDelayed(VoiceManager voice) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <g__connectDelayed|8_0>d(0) { voice = voice }; } } public static void OnServerEnterWaitingRoom(SessionContext context) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Invalid comparison between Unknown and I4 if (context != null && context.ExistPlayer()) { PersistentData fieldOrProperty = ReflectionTools.GetFieldOrProperty(Hub.s, "pdata"); if ((int)context.GetVRoomType() == 3 && fieldOrProperty?.main is InTramWaitingScene) { JoinAnytimeMod.Log("Moving player snapshot Maintenance -> Waiting"); RoomTools.MoveCurrentPlayerToSnapshot(context); } } } public static void OnServerEnterDungeon(SessionContext context, long roomUID) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Invalid comparison between Unknown and I4 if (context != null && context.ExistPlayer()) { PersistentData fieldOrProperty = ReflectionTools.GetFieldOrProperty(Hub.s, "pdata"); if ((int)context.GetVRoomType() == 3 && fieldOrProperty?.main is GamePlayScene) { JoinAnytimeMod.Log($"Moving player snapshot Maintenance -> Dungeon, roomUID={roomUID}"); RoomTools.MoveCurrentPlayerToSnapshot(context); } } } } public static class Preferences { public static MelonPreferences_Entry Verbose { get; private set; } public static MelonPreferences_Entry AllowPreGame { get; private set; } public static MelonPreferences_Entry AllowOnPlaying { get; private set; } public static MelonPreferences_Entry AllowEndGame { get; private set; } public static MelonPreferences_Entry AllowDeathMatch { get; private set; } public static void Init() { MelonPreferences_Category val = MelonPreferences.CreateCategory("Join Anytime"); Verbose = val.CreateEntry("Verbose logging", false, "Verbose logging", "Activate verbose logging.", false, false, (ValueValidator)null, (string)null); AllowPreGame = val.CreateEntry("Join during pre-game", true, "Join during pre-game", "Activate to allow players to join during pre-game.", false, false, (ValueValidator)null, (string)null); AllowOnPlaying = val.CreateEntry("Join when playing", true, "Join when playing", "Activate to allow players to join during a dungeon.", false, false, (ValueValidator)null, (string)null); AllowEndGame = val.CreateEntry("Join during end game", true, "Join during end game", "Activate to allow players to join for the end game.", false, false, (ValueValidator)null, (string)null); AllowDeathMatch = val.CreateEntry("Join during deathmatch", false, "Join during deathmatch", "Activate to allow players to join for the final deathmatch.", false, false, (ValueValidator)null, (string)null); } } } namespace JoinAnytime.Patches { [HarmonyPatch(typeof(Hub), "LoadScene", new Type[] { typeof(string) })] internal static class Hub_LoadScene { private static void Prefix(string sceneName) { LateJoinManager.OnLoadScene(sceneName); } } [HarmonyPatch(typeof(SessionContext), "Login")] internal static class SessionContext_Login { private static void Postfix(SessionContext __instance) { LateJoinManager.OnServerLogin(__instance); } } [HarmonyPatch(typeof(GameSessionInfo), "CanEnterSession")] internal static class GameSessionInfo_CanEnterSession { private static bool Prefix(ref GameSessionInfo __instance, ref bool __result) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) VGameSessionState gameSessionState = __instance.GameSessionState; if (LateJoinManager.IsStateAllowed(gameSessionState)) { __result = true; return false; } return true; } } [HarmonyPatch(typeof(MaintenanceScene), "Start")] internal static class MaintenanceScene_Start { private static void Postfix() { LateJoinManager.OnMaintenanceSceneStart(); } } [HarmonyPatch(typeof(NetworkManagerV2), "OnRecvPacket", new Type[] { typeof(IMsg) })] internal static class NetworkManagerV2_OnRecvPacket { private static bool Prefix(IMsg msg) { return LateJoinManager.OnClientPacket(msg); } } [HarmonyPatch(typeof(VoiceManager), "SetVoiceMode")] internal static class VoiceManager_SetVoiceMode { private static void Postfix(VoiceManager __instance, VoiceMode voiceMode) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) LateJoinManager.EnsureVoiceConnected(__instance, voiceMode); } } [HarmonyPatch] internal static class VPlayer_Ctor { private static MethodBase TargetMethod() { return AccessTools.Constructor(typeof(VPlayer), new Type[10] { typeof(SessionContext), typeof(int), typeof(int), typeof(bool), typeof(string), typeof(string), typeof(PosWithRot), typeof(bool), typeof(IVroom), typeof(ReasonOfSpawn) }, false); } private static void Postfix(VPlayer __instance) { LateJoinManager.OnServerPlayerCreated(__instance); } } [HarmonyPatch(typeof(VRoomManager), "EnterWaitingRoom")] internal static class VRoomManager_EnterWaitingRoom { private static void Prefix(SessionContext context, int hashCode) { LateJoinManager.OnServerEnterWaitingRoom(context); } } [HarmonyPatch(typeof(VRoomManager), "EnterDungeon")] internal static class VRoomManager_EnterDungeon { private static void Prefix(SessionContext context, int hashCode, long roomUID) { LateJoinManager.OnServerEnterDungeon(context, roomUID); } } } namespace JoinAnytime.Helpers { internal static class NetworkTools { public static void SendOnPlayingStateToClient(VPlayer player) { SendOnPlayingState(((VActor)player).UID, delegate(IMsg msg) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) ((VActor)player).SendToMe(msg); }); } public static void SendOnPlayingStateToClient(SessionContext context) { SendOnPlayingState(context.GetPlayerUID(), delegate(IMsg msg) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) context.Send(msg); }); } private static void SendOnPlayingState(long uid, Action send) { //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Expected O, but got Unknown //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: 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_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Expected O, but got Unknown //IL_00fc: Expected O, but got Unknown GameMainBase obj = ReflectionTools.GetFieldOrProperty(Hub.s, "pdata")?.main; GamePlayScene val = (GamePlayScene)(object)((obj is GamePlayScene) ? obj : null); if (val != null) { IVroom activeDungeonRoom = RoomTools.GetActiveDungeonRoom(); if (activeDungeonRoom == null) { JoinAnytimeMod.Log("SendOnPlayingStateToClient failed: no active DungeonRoom."); return; } JoinAnytimeMod.Log($"Sending OnPlaying state to UID={uid}: dungeon={val.DungeonMasterID}, seed={val.RandDungeonSeed}, roomUID={activeDungeonRoom.RoomID}"); send((IMsg)new StartGameSig { selectedDungeonMasterID = val.DungeonMasterID, randDungeonSeed = val.RandDungeonSeed }); send((IMsg)new CompleteMakingRoomSig { nextRoomInfo = new RoomInfo { roomType = (VRoomType)2, roomMasterID = val.DungeonMasterID, roomUID = activeDungeonRoom.RoomID } }); } } } internal static class RoomTools { public static void MoveCurrentPlayerToSnapshot(SessionContext context) { VPlayer fieldOrProperty = ReflectionTools.GetFieldOrProperty(context, "_vPlayer"); if (fieldOrProperty != null) { IVroom vRoom = ((VActor)fieldOrProperty).VRoom; int objectID = ((VActor)fieldOrProperty).ObjectID; JoinAnytimeMod.Log($"Removing old player actor={objectID} room={((object)vRoom)?.GetType().Name}"); if (vRoom != null) { vRoom.PendRemovePlayer(objectID, false, false); } context.CreatePlayerSnapshot(true); } } public static string GetSceneNameFromDungeon(int dungeonMasterID) { DataManager fieldOrProperty = ReflectionTools.GetFieldOrProperty(Hub.s, "dataman"); DungeonMasterInfo dungeonInfo = fieldOrProperty.ExcelDataManager.GetDungeonInfo(dungeonMasterID); if (dungeonInfo == null) { return ""; } MapMasterInfo mapInfo = fieldOrProperty.ExcelDataManager.GetMapInfo(dungeonInfo.MapID); return ((mapInfo != null) ? mapInfo.SceneName : null) ?? ""; } public static IVroom GetActiveDungeonRoom() { VRoomManager vRoomManager = ReflectionTools.GetFieldOrProperty(Hub.s, "vworld").VRoomManager; return ((IEnumerable)ReflectionTools.GetFieldOrProperty>(vRoomManager, "_vrooms").Values).FirstOrDefault((Func)((IVroom room) => room is DungeonRoom)); } } internal static class ReflectionTools { public static object GetFieldOrProperty(object instance, string name) { Type type = instance.GetType(); FieldInfo field = type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { return field.GetValue(instance); } PropertyInfo property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null) { return property.GetValue(instance); } throw new Exception("Field/property '" + name + "' not found on " + type.FullName); } public static T GetFieldOrProperty(object instance, string name) { return (T)GetFieldOrProperty(instance, name); } public static void SetFieldOrProperty(object instance, string name, object value) { Type type = instance.GetType(); FieldInfo field = type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { field.SetValue(instance, value); return; } PropertyInfo property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null) { property.SetValue(instance, value); return; } throw new Exception("Field/property '" + name + "' not found on " + type.FullName); } } } namespace JoinAnytime.Enums { public enum LateJoinRedirectState { None, PendingWaitingRoomRedirect, PendingDungeonRedirect, EnteringDungeon } }