Decompiled source of PEAK Pathfinder v0.6.10
plugins/PeakPathfinder/PeakPathfinder.dll
Decompiled a month ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using UnityEngine; using UnityEngine.SceneManagement; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: AssemblyVersion("0.0.0.0")] namespace PeakPathfinder; [BepInPlugin("peak.pathfinder", "PEAK Pathfinder", "0.6.10")] public sealed class Plugin : BaseUnityPlugin { public const string PluginGuid = "peak.pathfinder"; private const string LegacyPluginGuid = "peak.alternativephotonports"; public const string PluginName = "PEAK Pathfinder"; public const string PluginVersion = "0.6.10"; internal static ManualLogSource Log; private Harmony harmony; private ConfigEntry<MainConnectionMode> connectionMode; private void Awake() { //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; MigrateLegacyConfigIfNeeded(); connectionMode = ((BaseUnityPlugin)this).Config.Bind<MainConnectionMode>("Connection", "Mode", MainConnectionMode.UdpSelection, "PEAK Pathfinder connection mode: UdpSelection or EncryptedWss."); ConnectionModeManager.Initialize(connectionMode); try { AutoRetryTracker.Initialize(); harmony = new Harmony("peak.pathfinder"); int num = PatchPunConnectUsingSettings(); int num2 = PatchVoiceConnectionConnectUsingSettings(); int num3 = PatchVoiceTransportConstructors(); int num4 = PatchRealtimeConnectUsingSettings(); int num5 = PatchRealtimeOnStatusChanged(); int num6 = PatchNetworkConnectorHandle(); int num7 = PatchNetworkConnectorOnDisconnected(); int num8 = PatchCreateRoomDiagnostics(); int num9 = PatchOperationResponseDiagnostics(); int num10 = PatchPeakRoomFailureCallbacks(); int num11 = 0; int num12 = PatchPhotonPeerConnect(); PhotonHybridSetter.TryApplyPunMainStatic(); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Ready. PUN patches=" + num + ", VoiceConnection patches=" + num2 + ", Voice constructor patches=" + num3 + ", Realtime connect patches=" + num4 + ", status patches=" + num5 + ", NetworkConnector patches=" + num6 + ", controlled-disconnect patches=" + num7 + ", CreateRoom diagnostics=" + num8 + ", operation-response diagnostics=" + num9 + ", PEAK failure callbacks=" + num10 + ", auto-restart menu patches=" + num11 + ", state-driven UI scanner=enabled, peer patches=" + num12 + ".")); ((BaseUnityPlugin)this).Logger.LogInfo((object)("PEAK Pathfinder active mode: " + ConnectionModeManager.DisplayName + ". Photon Voice WSS/443 is prepared at three stages: VoiceConnection gets a private WSS settings clone, Voice transport constructors are requested as WebSocketSecure, and the actual Realtime Voice ConnectUsingSettings argument/peer are forced to WebSocketSecure immediately before Photon builds the NameServer address. Voice ServerPortOverrides and fallback remain untouched.")); ((BaseUnityPlugin)this).Logger.LogInfo((object)"UDP selection mode keeps MAIN Photon on UDP with GOOD/BAD GameServer learning while Photon Voice uses clean WSS/443. Encrypted mode uses WSS/443 for MAIN Photon as well and does not use the UDP GOOD/BAD database."); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Known bad Game Servers are rejected BEFORE the UDP GameServer connection and the mod requests PEAK/Photon to obtain a new assignment through the normal host flow. If an UNKNOWN GameServer times out after joining, PEAK handles the disconnect normally. The mod waits for the actual disconnect modal, closes it through Zorro.UI.Modal.Modal.CloseModal(), executes PEAK's exact OK callback (load Title), dismisses any follow-up stale modal through CloseModal(), then waits for the real Create Game button and clicks it."); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Simple auto-restart mode: no OnDisconnected/OnLeftRoom/UI suppression and no fixed delays. UI progression is state-driven: modal open -> Modal.CloseModal + exact OK callback -> follow-up modal dismissal if present -> Master + Create Game button ready -> click."); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Maximum automatic reassignments per room attempt: " + 30 + ".")); ((BaseUnityPlugin)this).Logger.LogInfo((object)("History: " + AutoRetryTracker.ResultsPath)); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Manual blacklist: " + BadServerDb.BlacklistPath)); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("Failed to initialize: " + ex)); } } private void MigrateLegacyConfigIfNeeded() { try { string text = Path.Combine(Paths.ConfigPath, "peak.alternativephotonports.cfg"); string text2 = Path.Combine(Paths.ConfigPath, "peak.pathfinder.cfg"); if (File.Exists(text) && !File.Exists(text2)) { File.Copy(text, text2, overwrite: false); ((BaseUnityPlugin)this).Config.Reload(); ((BaseUnityPlugin)this).Logger.LogInfo((object)("PEAK Pathfinder: migrated legacy config to " + text2)); } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("PEAK Pathfinder: could not migrate legacy config: " + ex.Message)); } } private void Update() { ConnectionModeManager.Tick(); AutoRetryTracker.Tick(); } private void OnGUI() { ModGui.Draw(connectionMode); } private int PatchPunConnectUsingSettings() { //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Expected O, but got Unknown Type type = AccessTools.TypeByName("Photon.Pun.PhotonNetwork"); if (type == null) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Photon.Pun.PhotonNetwork was not found."); return 0; } MethodInfo method = typeof(PunConnectPatch).GetMethod("Prefix", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (method == null) { return 0; } int num = 0; MethodInfo[] methods = type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (!(methodInfo.Name != "ConnectUsingSettings")) { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(method), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); num++; } } return num; } private int PatchVoiceConnectionConnectUsingSettings() { //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Expected O, but got Unknown Type type = AccessTools.TypeByName("Photon.Voice.Unity.VoiceConnection"); if (type == null) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Photon.Voice.Unity.VoiceConnection was not found."); return 0; } MethodInfo method = typeof(VoiceConnectionConnectPatch).GetMethod("Prefix", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (method == null) { return 0; } int num = 0; MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (!(methodInfo.Name != "ConnectUsingSettings")) { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(method), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); num++; } } return num; } private int PatchVoiceTransportConstructors() { //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Expected O, but got Unknown //IL_0116: Expected O, but got Unknown MethodInfo method = typeof(VoiceTransportConstructorPatch).GetMethod("Prefix", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); MethodInfo method2 = typeof(VoiceTransportConstructorPatch).GetMethod("Postfix", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (method == null || method2 == null) { return 0; } int num = 0; string[] array = new string[2] { "Photon.Voice.LoadBalancingTransport", "Photon.Voice.LoadBalancingTransport2" }; string[] array2 = array; foreach (string text in array2) { Type type = AccessTools.TypeByName(text); if (type == null) { continue; } ConstructorInfo[] constructors = type.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (ConstructorInfo constructorInfo in constructors) { bool flag = false; ParameterInfo[] parameters = constructorInfo.GetParameters(); foreach (ParameterInfo parameterInfo in parameters) { if (parameterInfo.ParameterType.IsEnum && parameterInfo.ParameterType.Name.IndexOf("ConnectionProtocol", StringComparison.OrdinalIgnoreCase) >= 0) { flag = true; break; } } if (flag) { harmony.Patch((MethodBase)constructorInfo, new HarmonyMethod(method), new HarmonyMethod(method2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); num++; } } } return num; } private int PatchRealtimeConnectUsingSettings() { //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Expected O, but got Unknown Type type = AccessTools.TypeByName("Photon.Realtime.LoadBalancingClient"); if (type == null) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Photon.Realtime.LoadBalancingClient was not found."); return 0; } MethodInfo method = typeof(RealtimeConnectPatch).GetMethod("Prefix", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); MethodInfo method2 = typeof(RealtimeConnectPatch).GetMethod("Postfix", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (method == null) { return 0; } int num = 0; MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (!(methodInfo.Name != "ConnectUsingSettings")) { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(method), (method2 == null) ? ((HarmonyMethod)null) : new HarmonyMethod(method2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); num++; } } return num; } private int PatchRealtimeOnStatusChanged() { //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Expected O, but got Unknown Type type = AccessTools.TypeByName("Photon.Realtime.LoadBalancingClient"); if (type == null) { return 0; } MethodInfo method = typeof(RealtimeStatusPatch).GetMethod("Prefix", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (method == null) { return 0; } int num = 0; MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (!(methodInfo.Name != "OnStatusChanged")) { ParameterInfo[] parameters = methodInfo.GetParameters(); if (parameters.Length == 1) { MethodInfo method2 = typeof(RealtimeStatusPatch).GetMethod("Postfix", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(method), (method2 == null) ? ((HarmonyMethod)null) : new HarmonyMethod(method2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); num++; } } } return num; } private int PatchNetworkConnectorHandle() { //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Expected O, but got Unknown Type type = AccessTools.TypeByName("NetworkConnector"); if (type == null) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"PEAK NetworkConnector type was not found."); return 0; } MethodInfo method = typeof(NetworkConnectorHandlePatch).GetMethod("Prefix", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (method == null) { return 0; } int num = 0; MethodInfo[] methods = type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (!(methodInfo.Name != "HandleConnectionState") && methodInfo.GetParameters().Length == 1) { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(method), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Patched PEAK method: NetworkConnector.HandleConnectionState(" + methodInfo.GetParameters()[0].ParameterType.FullName + ").")); num++; } } return num; } private int PatchNetworkConnectorOnDisconnected() { //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Expected O, but got Unknown Type type = AccessTools.TypeByName("NetworkConnector"); if (type == null) { return 0; } MethodInfo method = typeof(PeakOnDisconnectedPatch).GetMethod("Prefix", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (method == null) { return 0; } int num = 0; MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (!(methodInfo.Name != "OnDisconnected") && methodInfo.GetParameters().Length == 1) { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(method), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Patched PEAK controlled disconnect callback: NetworkConnector.OnDisconnected(" + methodInfo.GetParameters()[0].ParameterType.FullName + ").")); num++; } } return num; } private int PatchCreateRoomDiagnostics() { //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Expected O, but got Unknown //IL_00b6: Expected O, but got Unknown //IL_0172: Unknown result type (might be due to invalid IL or missing references) //IL_0179: Unknown result type (might be due to invalid IL or missing references) //IL_0186: Expected O, but got Unknown //IL_0186: Expected O, but got Unknown int num = 0; Type type = AccessTools.TypeByName("Photon.Pun.PhotonNetwork"); MethodInfo method = typeof(CreateRoomDiagnosticPatch).GetMethod("PunPrefix", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); MethodInfo method2 = typeof(CreateRoomDiagnosticPatch).GetMethod("PunPostfix", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (type != null && method != null && method2 != null) { MethodInfo[] methods = type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (!(methodInfo.Name != "CreateRoom") && !(methodInfo.ReturnType != typeof(bool))) { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(method), new HarmonyMethod(method2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); num++; } } } Type type2 = AccessTools.TypeByName("Photon.Realtime.LoadBalancingClient"); MethodInfo method3 = typeof(CreateRoomDiagnosticPatch).GetMethod("OpPrefix", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); MethodInfo method4 = typeof(CreateRoomDiagnosticPatch).GetMethod("OpPostfix", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (type2 != null && method3 != null && method4 != null) { MethodInfo[] methods2 = type2.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo2 in methods2) { if (!(methodInfo2.Name != "OpCreateRoom") && !(methodInfo2.ReturnType != typeof(bool))) { harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(method3), new HarmonyMethod(method4), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); num++; } } } return num; } private int PatchOperationResponseDiagnostics() { //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Expected O, but got Unknown Type type = AccessTools.TypeByName("Photon.Realtime.LoadBalancingClient"); if (type == null) { return 0; } MethodInfo method = typeof(OperationResponseDiagnosticPatch).GetMethod("Prefix", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (method == null) { return 0; } int num = 0; MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (!(methodInfo.Name != "OnOperationResponse") && methodInfo.GetParameters().Length == 1) { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(method), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); num++; } } return num; } private int PatchPeakRoomFailureCallbacks() { //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Expected O, but got Unknown Type type = AccessTools.TypeByName("NetworkConnector"); if (type == null) { return 0; } MethodInfo method = typeof(PeakRoomFailureDiagnosticPatch).GetMethod("Prefix", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (method == null) { return 0; } int num = 0; MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (!(methodInfo.Name != "OnCreateRoomFailed") || !(methodInfo.Name != "OnJoinRoomFailed") || !(methodInfo.Name != "OnJoinRandomFailed")) { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(method), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Patched PEAK failure callback: NetworkConnector." + methodInfo.Name + "(" + methodInfo.GetParameters().Length + " args).")); num++; } } return num; } private int PatchSimpleAutoRestartMenu() { //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Expected O, but got Unknown Type type = AccessTools.TypeByName("MainMenuPageHandler"); MethodInfo method = typeof(MainMenuAutoRestartPatch).GetMethod("Postfix", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (type == null || method == null) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"MainMenuPageHandler or Simple Auto-Restart postfix was not found."); return 0; } MethodInfo methodInfo = AccessTools.Method(type, "Update", (Type[])null, (Type[])null); if (methodInfo == null) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"MainMenuPageHandler.Update() was not found."); return 0; } harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(method), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Patched PEAK main menu for simple automatic Create Game restart: MainMenuPageHandler.Update()."); return 1; } private int PatchPhotonPeerConnect() { //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Expected O, but got Unknown Type type = AccessTools.TypeByName("ExitGames.Client.Photon.PhotonPeer"); if (type == null) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"ExitGames.Client.Photon.PhotonPeer was not found."); return 0; } MethodInfo method = typeof(PeerConnectPatch).GetMethod("Prefix", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (method == null) { return 0; } int num = 0; MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (methodInfo.Name != "Connect" || methodInfo.ReturnType != typeof(bool)) { continue; } bool flag = false; ParameterInfo[] parameters = methodInfo.GetParameters(); foreach (ParameterInfo parameterInfo in parameters) { if (parameterInfo.ParameterType == typeof(string)) { flag = true; break; } } if (flag) { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(method), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); num++; } } return num; } private void OnDestroy() { if (harmony != null) { harmony.UnpatchSelf(); } } } internal static class VoiceConnectionConnectPatch { public static void Prefix(object __instance, object[] __args) { PhotonHybridSetter.TryPrepareVoiceConnectionSettings(__instance, __args); } } internal static class VoiceTransportConstructorPatch { public static void Prefix(MethodBase __originalMethod, object[] __args) { PhotonHybridSetter.TryForceVoiceConstructorProtocol(__originalMethod, __args); } public static void Postfix(object __instance) { object clientPeer = PhotonHybridSetter.GetClientPeer(__instance); PhotonHybridSetter.RegisterVoicePeer(clientPeer); if (clientPeer != null) { Plugin.Log.LogInfo((object)("VOICE transport constructed; peer TransportProtocol=" + PhotonHybridSetter.ReadTransportProtocol(clientPeer) + ".")); } } } internal static class PunConnectPatch { public static void Prefix(object[] __args) { bool isEncryptedWss = ConnectionModeManager.IsEncryptedWss; PhotonHybridSetter.TryApplyPunMainStatic(); PhotonHybridSetter.TryApplyArguments(__args, "PUN argument", isEncryptedWss); } } internal sealed class VoiceSettingsRestoreState { public object Settings; public object Protocol; public object Port; public bool HasProtocol; public bool HasPort; } internal static class RealtimeConnectPatch { public static void Prefix(object __instance, object[] __args, ref VoiceSettingsRestoreState __state) { if (PhotonHybridSetter.IsVoiceClient(__instance)) { object clientPeer = PhotonHybridSetter.GetClientPeer(__instance); PhotonHybridSetter.RegisterVoicePeer(clientPeer); string text = PhotonHybridSetter.ReadTransportProtocol(clientPeer); bool flag = PhotonHybridSetter.TryPrepareVoiceWssPeer(__instance); bool flag2 = PhotonHybridSetter.TryPrepareVoiceWssArgumentsInPlace(__args, out __state); clientPeer = PhotonHybridSetter.GetClientPeer(__instance); string text2 = PhotonHybridSetter.ReadTransportProtocol(clientPeer); Plugin.Log.LogInfo((object)("Detected Photon Voice client. v0.6.10 WSS/443 preparation: peer=" + flag + ", exact-settings=" + flag2 + "; peer TransportProtocol " + text + " -> " + text2 + ". Waiting for actual VOICE PhotonPeer.Connect proof.")); } else { __state = null; AutoRetryTracker.SetMainClient(__instance); bool isEncryptedWss = ConnectionModeManager.IsEncryptedWss; PhotonHybridSetter.TryApplyClient(__instance, "MAIN Realtime client", isEncryptedWss); PhotonHybridSetter.TryApplyArguments(__args, "MAIN Realtime argument", isEncryptedWss); } } public static void Postfix(object __instance, VoiceSettingsRestoreState __state) { if (__instance != null && PhotonHybridSetter.IsVoiceClient(__instance)) { PhotonHybridSetter.RestoreVoiceSettings(__state); } } } internal static class RealtimeStatusPatch { public static void Prefix(object __instance, object[] __args) { if (__instance != null && !PhotonHybridSetter.IsVoiceClient(__instance)) { AutoRetryTracker.SetMainClient(__instance); string status = "<unknown>"; if (__args != null && __args.Length > 0 && __args[0] != null) { status = __args[0].ToString(); } AutoRetryTracker.OnMainStatus(__instance, status); } } public static void Postfix(object __instance, object[] __args) { if (__instance != null && !PhotonHybridSetter.IsVoiceClient(__instance)) { string status = "<unknown>"; if (__args != null && __args.Length > 0 && __args[0] != null) { status = __args[0].ToString(); } AutoRetryTracker.AfterMainStatus(__instance, status); } } } internal static class CreateRoomDiagnosticPatch { public static void PunPrefix(MethodBase __originalMethod, object[] __args) { AutoRetryTracker.ObserveHostCreateRoomAttempt(__args); AutoRetryTracker.TryReplaceRetryRoomName(__args); Plugin.Log.LogWarning((object)("DIAG PhotonNetwork.CreateRoom ENTER overload=" + ((__originalMethod == null) ? "?" : __originalMethod.ToString()) + "; NetworkClientState=" + PhotonHybridSetter.ReadStaticStringMember("Photon.Pun.PhotonNetwork", "NetworkClientState") + "; args=" + DescribeArgs(__args))); } public static void PunPostfix(MethodBase __originalMethod, ref bool __result) { Plugin.Log.LogWarning((object)("DIAG PhotonNetwork.CreateRoom EXIT result=" + __result + "; NetworkClientState=" + PhotonHybridSetter.ReadStaticStringMember("Photon.Pun.PhotonNetwork", "NetworkClientState"))); } public static void OpPrefix(object __instance, MethodBase __originalMethod, object[] __args) { AutoRetryTracker.TryReplaceRetryEnterRoomParams(__instance, __args); Plugin.Log.LogWarning((object)("DIAG LoadBalancingClient.OpCreateRoom ENTER; State=" + PhotonHybridSetter.ReadStringMember(__instance, "State") + "; Server=" + PhotonHybridSetter.ReadStringMember(__instance, "Server") + "; CurrentServerAddress=" + PhotonHybridSetter.ReadStringMember(__instance, "CurrentServerAddress") + "; lastJoinType=" + Convert.ToString(PhotonHybridSetter.ReadObjectMember(__instance, "lastJoinType")) + "; GameServerAddress=" + PhotonHybridSetter.ReadStringMember(__instance, "GameServerAddress") + "; args=" + DescribeArgs(__args))); } public static void OpPostfix(object __instance, ref bool __result) { Plugin.Log.LogWarning((object)("DIAG LoadBalancingClient.OpCreateRoom EXIT result=" + __result + "; State=" + PhotonHybridSetter.ReadStringMember(__instance, "State") + "; Server=" + PhotonHybridSetter.ReadStringMember(__instance, "Server") + "; lastJoinType=" + Convert.ToString(PhotonHybridSetter.ReadObjectMember(__instance, "lastJoinType")) + "; GameServerAddress=" + PhotonHybridSetter.ReadStringMember(__instance, "GameServerAddress"))); } private static string DescribeArgs(object[] args) { if (args == null) { return "<null>"; } List<string> list = new List<string>(); for (int i = 0; i < args.Length; i++) { object obj = args[i]; if (obj == null) { list.Add(i + ":<null>"); continue; } string text; try { text = obj.ToString(); string text2 = PhotonHybridSetter.ReadStringMember(obj, "RoomName"); if (!string.IsNullOrEmpty(text2)) { text = text + " RoomName=" + text2; } } catch { text = obj.GetType().FullName; } list.Add(i + ":" + text); } return string.Join(" | ", list.ToArray()); } } internal static class OperationResponseDiagnosticPatch { public static void Prefix(object __instance, object[] __args) { if (__instance != null && !PhotonHybridSetter.IsVoiceClient(__instance)) { object obj = ((__args != null && __args.Length > 0) ? __args[0] : null); if (obj != null) { string text = PhotonHybridSetter.ReadStringMember(obj, "OperationCode"); string text2 = PhotonHybridSetter.ReadStringMember(obj, "ReturnCode"); string text3 = PhotonHybridSetter.ReadStringMember(obj, "DebugMessage"); Plugin.Log.LogWarning((object)("DIAG Photon OnOperationResponse: op=" + text + "; returnCode=" + text2 + "; debugMessage=" + text3 + "; clientState=" + PhotonHybridSetter.ReadStringMember(__instance, "State") + "; server=" + PhotonHybridSetter.ReadStringMember(__instance, "Server") + "; address=" + PhotonHybridSetter.ReadStringMember(__instance, "CurrentServerAddress"))); } } } } internal static class PeakRoomFailureDiagnosticPatch { public static void Prefix(MethodBase __originalMethod, object[] __args) { string text = ((__originalMethod == null) ? "<unknown>" : __originalMethod.Name); List<string> list = new List<string>(); if (__args != null) { for (int i = 0; i < __args.Length; i++) { list.Add(i + "=" + ((__args[i] == null) ? "<null>" : __args[i].ToString())); } } Plugin.Log.LogError((object)("DIAG PEAK " + text + " called: " + string.Join("; ", list.ToArray()))); } } internal static class NetworkConnectorHandlePatch { public static bool Prefix(object[] __args) { object state = ((__args != null && __args.Length > 0) ? __args[0] : null); return AutoRetryTracker.OnPeakHandleConnectionState(state); } } internal static class PeakOnDisconnectedPatch { public static bool Prefix(object[] __args) { object cause = ((__args != null && __args.Length > 0) ? __args[0] : null); return AutoRetryTracker.ShouldRunPeakOnDisconnected(cause); } } internal static class PeakOnLeftRoomPatch { public static bool Prefix() { return AutoRetryTracker.ShouldRunPeakOnLeftRoom(); } } internal static class MainMenuAutoRestartPatch { public static void Postfix(object __instance) { AutoRetryTracker.TryAutoRestartFromMainMenu(__instance); } } internal static class PeerConnectPatch { public static bool Prefix(object __instance, MethodBase __originalMethod, object[] __args, ref bool __result) { string text = PhotonHybridSetter.ReadTransportProtocol(__instance); string text2 = ExtractAddress(__originalMethod, __args); bool flag = PhotonHybridSetter.IsRegisteredVoicePeer(__instance); if (!string.IsNullOrEmpty(text2)) { Plugin.Log.LogInfo((object)((flag ? "VOICE " : "MAIN/OTHER ") + "PhotonPeer.Connect serverAddress = " + text2 + "; TransportProtocol=" + text)); if (flag && !string.Equals(text, "WebSocketSecure", StringComparison.OrdinalIgnoreCase)) { Plugin.Log.LogError((object)("VOICE reached PhotonPeer.Connect without WebSocketSecure. WSS preparation failed before Photon built the server address; address=" + text2 + ", protocol=" + text + ".")); } } if (AutoRetryTracker.ShouldRejectPeerConnect(__instance, text2, text)) { __result = false; return false; } return true; } private static string ExtractAddress(MethodBase method, object[] args) { if (args == null) { return ""; } ParameterInfo[] array = ((method == null) ? null : method.GetParameters()); for (int i = 0; i < args.Length; i++) { string text = args[i] as string; if (!string.IsNullOrEmpty(text)) { string text2 = ((array != null && i < array.Length) ? array[i].Name : ""); if (!string.IsNullOrEmpty(text2) && text2.IndexOf("server", StringComparison.OrdinalIgnoreCase) >= 0) { return text; } if (text.IndexOf(":5056", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf(":5055", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf(":5058", StringComparison.OrdinalIgnoreCase) >= 0) { return text; } } } return ""; } } internal enum MainConnectionMode { UdpSelection, EncryptedWss } internal static class ConnectionModeManager { private static ConfigEntry<MainConnectionMode> modeEntry; private static bool switchPending; private static bool disconnectIssued; private static bool connectIssued; private static bool disconnectSuppressionLogged; private static bool modalCloseLogged; private static DateTime switchStartedUtc; private static string lastSwitchError = string.Empty; public static MainConnectionMode CurrentMode { get { if (modeEntry != null) { return modeEntry.Value; } return MainConnectionMode.UdpSelection; } } public static bool IsUdpSelection => CurrentMode == MainConnectionMode.UdpSelection; public static bool IsEncryptedWss => CurrentMode == MainConnectionMode.EncryptedWss; public static bool SwitchInProgress => switchPending; public static bool SuppressPeakDisconnectUi { get { if (switchPending) { return disconnectIssued; } return false; } } public static string LastSwitchError => lastSwitchError ?? string.Empty; public static string DisplayName { get { if (!IsEncryptedWss) { return "Подбор UDP-серверов"; } return "Защищённое соединение (WSS/443)"; } } public static void Initialize(ConfigEntry<MainConnectionMode> entry) { modeEntry = entry; } public static void SelectMode(MainConnectionMode mode) { if (modeEntry != null && modeEntry.Value != mode) { modeEntry.Value = mode; AutoRetryTracker.OnConnectionModeChanged(); switchPending = true; disconnectIssued = false; connectIssued = false; disconnectSuppressionLogged = false; modalCloseLogged = false; lastSwitchError = string.Empty; switchStartedUtc = DateTime.UtcNow; Plugin.Log.LogWarning((object)("PEAK Pathfinder: connection mode changed to " + DisplayName + ". Photon will reconnect in the Title menu so the selected transport is active before creating a lobby.")); } } public static void NotifySuppressedPeakDisconnect() { if (!disconnectSuppressionLogged) { disconnectSuppressionLogged = true; Plugin.Log.LogInfo((object)"PEAK Pathfinder: suppressed PEAK's normal disconnect popup during an intentional connection-mode switch."); } } public static void Tick() { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) if (!switchPending) { return; } try { Scene activeScene = SceneManager.GetActiveScene(); string a = ((Scene)(ref activeScene)).name ?? string.Empty; if (!string.Equals(a, "Title", StringComparison.OrdinalIgnoreCase)) { return; } string text = PhotonHybridSetter.ReadStaticStringMember("Photon.Pun.PhotonNetwork", "NetworkClientState"); if (!disconnectIssued) { if (!IsDisconnectedState(text)) { if (!InvokeStaticNoArg("Photon.Pun.PhotonNetwork", "Disconnect")) { FailSwitch("PhotonNetwork.Disconnect() was not found or failed. Restart PEAK to apply the selected mode."); return; } disconnectIssued = true; Plugin.Log.LogInfo((object)"PEAK Pathfinder: intentional Photon disconnect started for mode switch."); return; } disconnectIssued = true; } if (UiButtonAutomation.IsModalOpen() && UiButtonAutomation.TryCloseModal(out var closePath) && !modalCloseLogged) { modalCloseLogged = true; Plugin.Log.LogInfo((object)("PEAK Pathfinder: closed a transient disconnect modal during mode switch via " + closePath + ".")); } if (!connectIssued) { if (!IsDisconnectedState(text)) { if ((DateTime.UtcNow - switchStartedUtc).TotalSeconds > 8.0) { FailSwitch("Photon did not reach Disconnected state in time. Restart PEAK to apply the selected mode."); } return; } PhotonHybridSetter.TryApplyPunMainStatic(); SetStaticBool("Photon.Pun.PhotonNetwork", "OfflineMode", value: false); if (!TryConnectToNetwork()) { FailSwitch("Could not start Photon reconnect. Restart PEAK to apply the selected mode."); return; } connectIssued = true; Plugin.Log.LogInfo((object)("PEAK Pathfinder: Photon reconnect started with mode " + DisplayName + ".")); } else if (string.Equals(text, "ConnectedToMasterServer", StringComparison.OrdinalIgnoreCase)) { switchPending = false; Plugin.Log.LogWarning((object)("PEAK Pathfinder: mode switch completed. Active MAIN transport: " + DisplayName + ".")); } else if ((DateTime.UtcNow - switchStartedUtc).TotalSeconds > 15.0) { FailSwitch("Photon did not reconnect to Master in time. Restart PEAK to apply the selected mode."); } } catch (Exception ex) { FailSwitch("Mode switch failed: " + ex.Message + ". Restart PEAK to apply the selected mode."); } } private static bool IsDisconnectedState(string state) { if (string.IsNullOrEmpty(state)) { return false; } if (state.IndexOf("Disconnected", StringComparison.OrdinalIgnoreCase) < 0 && state.IndexOf("PeerCreated", StringComparison.OrdinalIgnoreCase) < 0) { return state.IndexOf("Uninitialized", StringComparison.OrdinalIgnoreCase) >= 0; } return true; } private static bool InvokeStaticNoArg(string typeName, string methodName) { try { Type type = AccessTools.TypeByName(typeName); if (type == null) { return false; } MethodInfo[] methods = type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (!(methodInfo.Name != methodName) && methodInfo.GetParameters().Length == 0) { methodInfo.Invoke(null, null); return true; } } } catch (Exception ex) { Plugin.Log.LogWarning((object)("PEAK Pathfinder: " + typeName + "." + methodName + " failed: " + ex.Message)); } return false; } private static void SetStaticBool(string typeName, string memberName, bool value) { try { Type type = AccessTools.TypeByName(typeName); if (type == null) { return; } PropertyInfo property = type.GetProperty(memberName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.CanWrite && property.PropertyType == typeof(bool)) { property.SetValue(null, value, null); return; } FieldInfo field = type.GetField(memberName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (field != null && field.FieldType == typeof(bool)) { field.SetValue(null, value); } } catch { } } private static bool TryConnectToNetwork() { if (InvokeStaticNoArg("Peak.Network.NetworkingUtilities", "ConnectToNetwork")) { return true; } return InvokeStaticNoArg("Photon.Pun.PhotonNetwork", "ConnectUsingSettings"); } private static void FailSwitch(string message) { lastSwitchError = message; switchPending = false; Plugin.Log.LogError((object)("PEAK Pathfinder: " + message)); } } internal static class AutoRetryTracker { public const int MaxRetries = 30; public const double GoodSeconds = 60.0; private static object mainClient; private static string trackedGameAddress; private static string trackedPrefix; private static DateTime trackedSinceUtc; private static bool trackedRecorded; private static DateTime lastTickUtc; private static bool reconnectPending; private static DateTime nextActionUtc; private static int reconnectCallAttempts; private static int retryCount; private static string rejectedAddress; private static bool awaitingPeakHostRetry; private static DateTime masterReadySinceUtc; private static bool freshRoomNameRequired; private static string pendingFreshRoomName; private static string activeRootRoomName; private static object savedHostState; private static bool timeoutHostRecoveryActive; private static bool simpleAutoRestartPending; private static bool simpleAutoRestartTriggered; private static bool simpleDisconnectModalSeen; private static bool simpleDisconnectOkActionIssued; private static int simpleFollowupUiActions; private static bool simpleModalApiDumped; private static bool simpleWaitingMasterLogged; private static bool simpleWaitingCreateLogged; private static int simpleTimeoutRestartCount; private static int currentSearchAttempt = 1; private static readonly object fileLock = new object(); public static string ResultsPath { get; private set; } public static string CurrentGameServerAddress { get { if (mainClient != null) { string text = PhotonHybridSetter.ReadStringMember(mainClient, "CurrentServerAddress"); string server = PhotonHybridSetter.ReadStringMember(mainClient, "Server"); if (IsGameServer(server, text) && !string.IsNullOrEmpty(text)) { return text; } } return trackedGameAddress ?? string.Empty; } } public static int CurrentSearchAttempt => Math.Max(1, currentSearchAttempt); public static double CurrentStabilitySeconds { get { if (string.IsNullOrEmpty(trackedGameAddress) || trackedSinceUtc == default(DateTime)) { return 0.0; } return Math.Max(0.0, (DateTime.UtcNow - trackedSinceUtc).TotalSeconds); } } public static string CurrentMainProtocol { get { if (mainClient == null) { return string.Empty; } object clientPeer = PhotonHybridSetter.GetClientPeer(mainClient); return PhotonHybridSetter.ReadTransportProtocol(clientPeer) ?? string.Empty; } } public static string CurrentMainState { get { if (mainClient != null) { return PhotonHybridSetter.ReadStringMember(mainClient, "State"); } return string.Empty; } } public static bool CurrentServerPassedStability { get { if (!ConnectionModeManager.IsUdpSelection) { return false; } string currentGameServerAddress = CurrentGameServerAddress; if (!string.IsNullOrEmpty(currentGameServerAddress) && BadServerDb.IsGood(currentGameServerAddress)) { return CurrentStabilitySeconds >= 60.0; } return false; } } public static void Initialize() { string text = Path.Combine(Paths.PluginPath, "PeakPathfinder"); string text2 = Path.Combine(text, "data"); string path = Path.Combine(Paths.PluginPath, "PeakAlternativePhotonPorts"); string path2 = Path.Combine(path, "data"); Directory.CreateDirectory(text); Directory.CreateDirectory(text2); MigrateLegacyDataFile(Path.Combine(path2, "udp_server_results.csv"), Path.Combine(text2, "udp_server_results.csv"), csvWithHeader: true); MigrateLegacyDataFile(Path.Combine(path2, "bad_servers.txt"), Path.Combine(text2, "bad_servers.txt"), csvWithHeader: false); MigrateLegacyDataFile(Path.Combine(path, "udp_server_results.csv"), Path.Combine(text2, "udp_server_results.csv"), csvWithHeader: true); MigrateLegacyDataFile(Path.Combine(path, "bad_servers.txt"), Path.Combine(text2, "bad_servers.txt"), csvWithHeader: false); ResultsPath = Path.Combine(text2, "udp_server_results.csv"); if (!File.Exists(ResultsPath)) { File.WriteAllText(ResultsPath, "utc_time,result,reason,address,prefix,duration_seconds,state,server\r\n"); } BadServerDb.Initialize(text2, ResultsPath); } private static void MigrateLegacyDataFile(string legacyPath, string newPath, bool csvWithHeader) { try { if (!File.Exists(legacyPath)) { return; } string directoryName = Path.GetDirectoryName(newPath); if (!string.IsNullOrEmpty(directoryName)) { Directory.CreateDirectory(directoryName); } if (!File.Exists(newPath)) { File.Copy(legacyPath, newPath, overwrite: false); Plugin.Log.LogInfo((object)("PEAK Pathfinder: copied legacy data file to " + newPath + ". The old copy was left untouched.")); return; } List<string> merged = new List<string>(); HashSet<string> seen = new HashSet<string>(StringComparer.Ordinal); Action<string[], bool> action = delegate(string[] lines, bool skipFirstHeader) { for (int i = 0; i < lines.Length; i++) { string text = lines[i]; if ((!skipFirstHeader || i != 0 || !text.StartsWith("utc_time,", StringComparison.OrdinalIgnoreCase)) && seen.Add(text)) { merged.Add(text); } } }; string[] arg = File.ReadAllLines(newPath); action(arg, arg2: false); string[] arg2 = File.ReadAllLines(legacyPath); action(arg2, csvWithHeader); if (csvWithHeader && (merged.Count == 0 || !merged[0].StartsWith("utc_time,", StringComparison.OrdinalIgnoreCase))) { merged.Insert(0, "utc_time,result,reason,address,prefix,duration_seconds,state,server"); } File.WriteAllLines(newPath, merged.ToArray()); Plugin.Log.LogInfo((object)("PEAK Pathfinder: merged legacy data into " + newPath + ". The old copy was left untouched.")); } catch (Exception ex) { Plugin.Log.LogWarning((object)("PEAK Pathfinder: could not migrate legacy data file '" + legacyPath + "': " + ex.Message)); } } public static void SetMainClient(object client) { if (client != null) { mainClient = client; } } public static void OnConnectionModeChanged() { reconnectPending = false; reconnectCallAttempts = 0; retryCount = 0; rejectedAddress = null; awaitingPeakHostRetry = false; masterReadySinceUtc = DateTime.MinValue; freshRoomNameRequired = false; pendingFreshRoomName = null; activeRootRoomName = null; savedHostState = null; timeoutHostRecoveryActive = false; simpleAutoRestartPending = false; simpleAutoRestartTriggered = false; simpleDisconnectModalSeen = false; simpleDisconnectOkActionIssued = false; simpleFollowupUiActions = 0; simpleModalApiDumped = false; simpleWaitingMasterLogged = false; simpleWaitingCreateLogged = false; simpleTimeoutRestartCount = 0; currentSearchAttempt = 1; trackedGameAddress = null; trackedPrefix = null; trackedSinceUtc = default(DateTime); trackedRecorded = false; } public static bool ShouldRejectPeerConnect(object peer, string address, string protocol) { if (!ConnectionModeManager.IsUdpSelection) { return false; } if (mainClient == null || peer == null || string.IsNullOrEmpty(address)) { return false; } if (protocol == null || protocol.IndexOf("Udp", StringComparison.OrdinalIgnoreCase) < 0) { return false; } if (!IsGameAddress(address)) { return false; } object clientPeer = PhotonHybridSetter.GetClientPeer(mainClient); if (clientPeer == null || !object.ReferenceEquals(clientPeer, peer)) { return false; } if (!BadServerDb.IsBad(address)) { return false; } if (retryCount >= 30) { Plugin.Log.LogError((object)("NATIVE-RETRY: maximum retries reached. Allowing server " + address + " so PEAK can fail normally instead of looping forever.")); return false; } object obj = PhotonHybridSetter.ReadObjectMember(mainClient, "lastJoinType"); string text = ((obj == null) ? "" : obj.ToString()); if (text.IndexOf("CreateRoom", StringComparison.OrdinalIgnoreCase) < 0) { Plugin.Log.LogWarning((object)("NATIVE-RETRY: " + address + " is known bad, but lastJoinType=" + text + ". Allowing it because automatic reassignment is HOST/CreateRoom-only.")); return false; } rejectedAddress = address; retryCount++; currentSearchAttempt++; freshRoomNameRequired = true; pendingFreshRoomName = null; reconnectPending = true; reconnectCallAttempts = 0; nextActionUtc = DateTime.UtcNow.AddMilliseconds(80.0); string text2 = Prefix24(address); Record("AUTO_REJECT", "known_bad_before_connect", address, text2, 0.0, PhotonHybridSetter.ReadStringMember(mainClient, "State"), PhotonHybridSetter.ReadStringMember(mainClient, "Server")); Plugin.Log.LogWarning((object)("NATIVE-RETRY " + retryCount + "/" + 30 + ": rejected known-bad GameServer " + address + " (" + text2 + ") before UDP connect. Returning to Master; PEAK will retry its HostState/CreateRoom natively.")); return true; } public static void ObserveHostCreateRoomAttempt(object[] args) { if (args == null || args.Length == 0) { return; } string text = null; for (int i = 0; i < args.Length; i++) { string text2 = args[i] as string; if (!string.IsNullOrEmpty(text2) && Guid.TryParse(text2, out var _)) { text = text2; break; } } if (string.IsNullOrEmpty(text)) { return; } bool flag = simpleAutoRestartTriggered; bool flag2 = simpleAutoRestartPending && !simpleAutoRestartTriggered; if ((flag || flag2) && !string.IsNullOrEmpty(activeRootRoomName) && !string.Equals(activeRootRoomName, text, StringComparison.OrdinalIgnoreCase)) { if (flag) { Plugin.Log.LogWarning((object)("SIMPLE AUTO-RESTART: fresh Create Game attempt started with room id=" + text + ". This follows the mod's own PlayButton.onClick; automatic menu restart completed.")); } else { Plugin.Log.LogWarning((object)("SIMPLE AUTO-RESTART: a fresh room id=" + text + " appeared while automation was still waiting, but the mod had NOT clicked Create Game. Treating this as MANUAL user recovery and cancelling the stale UI automation state.")); } simpleAutoRestartPending = false; simpleAutoRestartTriggered = false; simpleDisconnectModalSeen = false; simpleDisconnectOkActionIssued = false; simpleFollowupUiActions = 0; simpleModalApiDumped = false; simpleWaitingMasterLogged = false; simpleWaitingCreateLogged = false; } if (freshRoomNameRequired || reconnectPending || awaitingPeakHostRetry) { if (string.IsNullOrEmpty(activeRootRoomName)) { activeRootRoomName = text; } } else if (string.IsNullOrEmpty(activeRootRoomName)) { activeRootRoomName = text; timeoutHostRecoveryActive = false; retryCount = 0; if (!flag) { simpleTimeoutRestartCount = 0; currentSearchAttempt = 1; } Plugin.Log.LogInfo((object)("NATIVE-RETRY: new host room attempt detected; root room id=" + text + ". Retry counter starts at 0/" + 30 + ".")); } else if (!string.Equals(activeRootRoomName, text, StringComparison.OrdinalIgnoreCase)) { string text3 = activeRootRoomName; activeRootRoomName = text; timeoutHostRecoveryActive = false; retryCount = 0; if (!flag) { simpleTimeoutRestartCount = 0; currentSearchAttempt = 1; } rejectedAddress = null; reconnectPending = false; reconnectCallAttempts = 0; awaitingPeakHostRetry = false; masterReadySinceUtc = DateTime.MinValue; freshRoomNameRequired = false; pendingFreshRoomName = null; trackedGameAddress = null; trackedPrefix = null; trackedSinceUtc = default(DateTime); trackedRecorded = false; Plugin.Log.LogWarning((object)("NATIVE-RETRY: NEW USER HOST ROOM detected " + text3 + " -> " + text + ". Retry counter reset to 0/" + 30 + " for the new lobby.")); } } public static void TryReplaceRetryRoomName(object[] args) { if (!freshRoomNameRequired || args == null || args.Length == 0) { return; } for (int i = 0; i < args.Length; i++) { string text = args[i] as string; if (!string.IsNullOrEmpty(text) && Guid.TryParse(text, out var _)) { if (string.IsNullOrEmpty(pendingFreshRoomName)) { pendingFreshRoomName = Guid.NewGuid().ToString(); } args[i] = pendingFreshRoomName; Plugin.Log.LogWarning((object)("NATIVE-RETRY: staged fresh Photon room id " + text + " -> " + pendingFreshRoomName + " for PEAK's native CreateRoom retry. Waiting to apply it to EnterRoomParams.RoomName.")); return; } } Plugin.Log.LogError((object)"NATIVE-RETRY: a fresh room id was required after rejecting a GameServer, but no GUID room-name argument was found in PhotonNetwork.CreateRoom."); } public static void TryReplaceRetryEnterRoomParams(object client, object[] args) { if (!freshRoomNameRequired || string.IsNullOrEmpty(pendingFreshRoomName) || args == null || client == null || PhotonHybridSetter.IsVoiceClient(client)) { return; } string text = PhotonHybridSetter.ReadStringMember(client, "Server"); if (text.IndexOf("Master", StringComparison.OrdinalIgnoreCase) < 0) { return; } foreach (object obj in args) { if (obj == null) { continue; } string text2 = PhotonHybridSetter.ReadStringMember(obj, "RoomName"); if (!string.IsNullOrEmpty(text2) && Guid.TryParse(text2, out var _)) { if (!PhotonHybridSetter.TrySetStringMember(obj, "RoomName", pendingFreshRoomName)) { Plugin.Log.LogError((object)("NATIVE-RETRY: found EnterRoomParams.RoomName=" + text2 + " but could not write the fresh room id.")); return; } string text3 = pendingFreshRoomName; freshRoomNameRequired = false; pendingFreshRoomName = null; Plugin.Log.LogWarning((object)("NATIVE-RETRY: applied fresh Photon room id to low-level EnterRoomParams.RoomName " + text2 + " -> " + text3 + ". This is the value that will actually be sent to Master.")); return; } } Plugin.Log.LogError((object)"NATIVE-RETRY: fresh room id was staged, but no writable GUID EnterRoomParams.RoomName was found in OpCreateRoom."); } public static bool ShouldRunPeakOnDisconnected(object cause) { if (ConnectionModeManager.SuppressPeakDisconnectUi) { ConnectionModeManager.NotifySuppressedPeakDisconnect(); return false; } if (!timeoutHostRecoveryActive) { return true; } string text = ((cause == null) ? "" : cause.ToString()); if (text.IndexOf("Timeout", StringComparison.OrdinalIgnoreCase) < 0) { return true; } Plugin.Log.LogWarning((object)("NATIVE-RETRY UI GUARD: suppressing PEAK NetworkConnector.OnDisconnected(" + text + ") because this HOST timeout is being recovered automatically.")); return false; } public static bool ShouldRunPeakOnLeftRoom() { if (!timeoutHostRecoveryActive) { return true; } Plugin.Log.LogWarning((object)"NATIVE-RETRY SCENE GUARD: suppressing PEAK NetworkConnector.OnLeftRoom() during automatic HOST timeout recovery. PEAK would otherwise load the Title scene while Photon is already reconnecting in the background."); return false; } public static bool ShouldSuppressDisconnectedUi() { return timeoutHostRecoveryActive; } public static void Tick() { DateTime utcNow = DateTime.UtcNow; if (!((utcNow - lastTickUtc).TotalMilliseconds < 100.0)) { lastTickUtc = utcNow; TickRetry(utcNow); TickPeakFallback(utcNow); TickStability(utcNow); TickSimpleAutoRestartUi(); } } private static void TickRetry(DateTime now) { if (mainClient != null && reconnectPending && !(now < nextActionUtc)) { bool flag = PhotonHybridSetter.InvokeNoArgBool(mainClient, "ReconnectToMaster"); reconnectCallAttempts++; if (flag) { reconnectPending = false; awaitingPeakHostRetry = true; masterReadySinceUtc = DateTime.MinValue; trackedGameAddress = null; trackedRecorded = false; Plugin.Log.LogInfo((object)("NATIVE-RETRY: ReconnectToMaster accepted after rejecting " + rejectedAddress + ". No room request is replayed by the mod. Waiting for PEAK NetworkConnector.OnConnectedToMaster -> HandleConnectionState(HostState).")); } else if (reconnectCallAttempts >= 20) { Plugin.Log.LogError((object)"NATIVE-RETRY: ReconnectToMaster did not start after 20 attempts. Automatic retry stopped for this assignment."); reconnectPending = false; } else { nextActionUtc = now.AddMilliseconds(150.0); } } } public static bool OnPeakHandleConnectionState(object state) { string text = ((state == null) ? "<null>" : state.GetType().FullName); Plugin.Log.LogInfo((object)("PEAK NetworkConnector.HandleConnectionState called with " + text + ".")); bool flag = !string.IsNullOrEmpty(text) && text.EndsWith("HostState", StringComparison.OrdinalIgnoreCase); bool flag2 = !string.IsNullOrEmpty(text) && text.EndsWith("DefaultConnectionState", StringComparison.OrdinalIgnoreCase); if (flag && state != null) { savedHostState = state; } if (awaitingPeakHostRetry) { if (flag) { awaitingPeakHostRetry = false; masterReadySinceUtc = DateTime.MinValue; Plugin.Log.LogInfo((object)"NATIVE-RETRY: PEAK invoked HandleConnectionState(HostState) after Master reconnect. No fallback call needed."); return true; } if (flag2 && savedHostState != null) { Plugin.Log.LogWarning((object)"NATIVE-RETRY: PEAK attempted HandleConnectionState(DefaultConnectionState) while a HostState retry is pending. Suppressing this stale state; saved HostState will be used after Master reconnect."); return false; } } return true; } public static void AfterMainStatus(object client, string status) { } private static void TickPeakFallback(DateTime now) { if (!awaitingPeakHostRetry || mainClient == null) { masterReadySinceUtc = DateTime.MinValue; return; } string text = PhotonHybridSetter.ReadStringMember(mainClient, "State"); if (string.IsNullOrEmpty(text) || text.IndexOf("ConnectedToMasterServer", StringComparison.OrdinalIgnoreCase) < 0) { masterReadySinceUtc = DateTime.MinValue; } else if (masterReadySinceUtc == DateTime.MinValue) { masterReadySinceUtc = now; Plugin.Log.LogInfo((object)"NATIVE-RETRY: Photon reached ConnectedToMasterServer after reassignment. Waiting briefly for PEAK's native callback."); } else { if ((now - masterReadySinceUtc).TotalMilliseconds < 350.0 || !awaitingPeakHostRetry) { return; } try { Type type = AccessTools.TypeByName("NetworkConnector"); if (type == null) { Plugin.Log.LogError((object)"NATIVE-RETRY FALLBACK: NetworkConnector type not found."); awaitingPeakHostRetry = false; return; } MethodInfo methodInfo = AccessTools.Method(type, "get_CurrentConnectionState", (Type[])null, (Type[])null); MethodInfo methodInfo2 = null; MethodInfo[] methods = type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo3 in methods) { if (methodInfo3.Name == "HandleConnectionState" && methodInfo3.GetParameters().Length == 1) { methodInfo2 = methodInfo3; break; } } if (methodInfo2 == null) { Plugin.Log.LogError((object)"NATIVE-RETRY FALLBACK: PEAK HandleConnectionState(ConnectionState) was not found."); awaitingPeakHostRetry = false; return; } object obj = ((methodInfo == null) ? null : methodInfo.Invoke(null, null)); string text2 = ((obj == null) ? "<null>" : obj.GetType().FullName); object obj2 = ((savedHostState != null) ? savedHostState : obj); string text3 = ((obj2 == null) ? "<null>" : obj2.GetType().FullName); if (obj2 == null) { Plugin.Log.LogError((object)"NATIVE-RETRY FALLBACK: neither saved HostState nor CurrentConnectionState is available."); awaitingPeakHostRetry = false; masterReadySinceUtc = DateTime.MinValue; return; } awaitingPeakHostRetry = false; masterReadySinceUtc = DateTime.MinValue; if (savedHostState != null) { Plugin.Log.LogWarning((object)("NATIVE-RETRY FALLBACK: PEAK did not restore HostState after reaching ConnectedToMasterServer. CurrentConnectionState=" + text2 + "; invoking HandleConnectionState with SAVED " + text3 + " once.")); } else { Plugin.Log.LogWarning((object)("NATIVE-RETRY FALLBACK: no saved HostState was available; invoking HandleConnectionState(CurrentConnectionState) once; state=" + text3 + ".")); } methodInfo2.Invoke(null, new object[1] { obj2 }); } catch (Exception ex) { awaitingPeakHostRetry = false; masterReadySinceUtc = DateTime.MinValue; Plugin.Log.LogError((object)("NATIVE-RETRY FALLBACK: HandleConnectionState invocation failed: " + ex)); } } } private static void TickStability(DateTime now) { if (!ConnectionModeManager.IsUdpSelection || mainClient == null) { return; } try { string text = PhotonHybridSetter.ReadStringMember(mainClient, "CurrentServerAddress"); string server = PhotonHybridSetter.ReadStringMember(mainClient, "Server"); string text2 = PhotonHybridSetter.ReadStringMember(mainClient, "State"); object clientPeer = PhotonHybridSetter.GetClientPeer(mainClient); string text3 = PhotonHybridSetter.ReadTransportProtocol(clientPeer); bool flag = text3 != null && text3.IndexOf("Udp", StringComparison.OrdinalIgnoreCase) >= 0; if (IsGameServer(server, text) && !string.IsNullOrEmpty(text) && flag) { if (!string.Equals(text, trackedGameAddress, StringComparison.OrdinalIgnoreCase)) { trackedGameAddress = text; trackedPrefix = Prefix24(text); trackedSinceUtc = now; trackedRecorded = false; Plugin.Log.LogInfo((object)("NATIVE-RETRY MAIN GameServer candidate: " + text + " prefix=" + trackedPrefix + " state=" + text2 + ". Stability timer started.")); } if (timeoutHostRecoveryActive && !string.IsNullOrEmpty(text2) && text2.IndexOf("Joined", StringComparison.OrdinalIgnoreCase) >= 0) { timeoutHostRecoveryActive = false; Plugin.Log.LogInfo((object)"NATIVE-RETRY: replacement GameServer reached Joined. Temporary PEAK disconnect-UI suppression released; a later timeout will re-arm it automatically."); } double totalSeconds = (now - trackedSinceUtc).TotalSeconds; if (!trackedRecorded && totalSeconds >= 60.0) { Record("GOOD_STABLE", "stayed_connected_60s", trackedGameAddress, trackedPrefix, totalSeconds, text2, server); trackedRecorded = true; retryCount = 0; simpleTimeoutRestartCount = 0; timeoutHostRecoveryActive = false; freshRoomNameRequired = false; pendingFreshRoomName = null; BadServerDb.MarkGood(trackedGameAddress, trackedPrefix); Plugin.Log.LogInfo((object)("NATIVE-RETRY GOOD: " + trackedGameAddress + " (" + trackedPrefix + ") stayed connected for " + totalSeconds.ToString("F1", CultureInfo.InvariantCulture) + "s. Retry counter reset.")); } } } catch (Exception ex) { Plugin.Log.LogDebug((object)("NativeRetry stability reflection error: " + ex.Message)); } } public static void OnMainStatus(object client, string status) { try { string text = PhotonHybridSetter.ReadStringMember(client, "CurrentServerAddress"); string text2 = PhotonHybridSetter.ReadStringMember(client, "Server"); string text3 = PhotonHybridSetter.ReadStringMember(client, "State"); Plugin.Log.LogInfo((object)("NATIVE-RETRY MAIN status=" + status + "; server=" + text2 + "; state=" + text3 + "; address=" + text)); if (!IsFailureStatus(status) || !IsGameServer(text2, text)) { return; } object clientPeer = PhotonHybridSetter.GetClientPeer(client); string text4 = PhotonHybridSetter.ReadTransportProtocol(clientPeer); if (text4 == null || text4.IndexOf("Udp", StringComparison.OrdinalIgnoreCase) < 0) { Plugin.Log.LogInfo((object)("NATIVE-RETRY: ignoring GameServer failure for non-UDP MAIN transport=" + text4 + "; address=" + text + ".")); return; } string text5 = ((!string.IsNullOrEmpty(text)) ? text : trackedGameAddress); if (string.IsNullOrEmpty(text5)) { return; } string text6 = Prefix24(text5); double duration = ((trackedSinceUtc == default(DateTime)) ? 0.0 : Math.Max(0.0, (DateTime.UtcNow - trackedSinceUtc).TotalSeconds)); if (trackedRecorded) { Plugin.Log.LogWarning((object)("NATIVE-RETRY: GameServer " + text5 + " had already passed the 60s stability check before status=" + status + ". It will NOT be learned as bad from this later disconnect.")); return; } Record("BAD", status, text5, text6, duration, text3, text2); trackedRecorded = true; BadServerDb.MarkBad(text5, text6); Plugin.Log.LogWarning((object)("NATIVE-RETRY LEARNED BAD: " + text5 + " (" + text6 + ") status=" + status + " after " + duration.ToString("F1", CultureInfo.InvariantCulture) + "s. Future room attempts will reject this IP immediately.")); object obj = PhotonHybridSetter.ReadObjectMember(mainClient, "lastJoinType"); string text7 = ((obj == null) ? "" : obj.ToString()); if (text7.IndexOf("CreateRoom", StringComparison.OrdinalIgnoreCase) >= 0) { if (simpleTimeoutRestartCount >= 30) { Plugin.Log.LogError((object)("SIMPLE AUTO-RESTART: maximum automatic full-lobby restarts reached after " + text5 + ". PEAK will remain in its normal error/menu flow.")); return; } simpleTimeoutRestartCount++; currentSearchAttempt++; simpleAutoRestartPending = true; simpleAutoRestartTriggered = false; simpleDisconnectModalSeen = false; simpleDisconnectOkActionIssued = false; simpleFollowupUiActions = 0; simpleModalApiDumped = false; simpleWaitingMasterLogged = false; simpleWaitingCreateLogged = false; timeoutHostRecoveryActive = false; reconnectPending = false; reconnectCallAttempts = 0; awaitingPeakHostRetry = false; masterReadySinceUtc = DateTime.MinValue; freshRoomNameRequired = false; pendingFreshRoomName = null; savedHostState = null; rejectedAddress = text5; Plugin.Log.LogWarning((object)("SIMPLE AUTO-RESTART " + simpleTimeoutRestartCount + "/" + 30 + ": HOST GameServer " + text5 + " timed out. PEAK will handle the disconnect normally. Waiting for the real error-modal OK button; after the modal flow is finished, the real Create Game button will be clicked automatically.")); } else { timeoutHostRecoveryActive = false; Plugin.Log.LogInfo((object)("NATIVE-RETRY: learned bad GameServer during lastJoinType=" + text7 + ". It was saved for future attempts, but automatic restart is HOST/CreateRoom-only.")); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("NativeRetry status recorder failed: " + ex.Message)); } } public static void TryAutoRestartFromMainMenu(object menuInstance) { TickSimpleAutoRestartUi(); } private static void TickSimpleAutoRestartUi() { if (!simpleAutoRestartPending || simpleAutoRestartTriggered) { return; } try { bool flag = UiButtonAutomation.IsModalOpen(); string text = PhotonHybridSetter.ReadStaticStringMember("Photon.Pun.PhotonNetwork", "NetworkClientState"); bool flag2 = string.Equals(text, "ConnectedToMasterServer", StringComparison.OrdinalIgnoreCase); if (!simpleDisconnectOkActionIssued) { if (!flag) { return; } if (!simpleDisconnectModalSeen) { simpleDisconnectModalSeen = true; Plugin.Log.LogWarning((object)"SIMPLE AUTO-RESTART UI: disconnect modal is actually open. Calling Zorro Modal.CloseModal(), then PEAK's exact OK callback (LoadScene Title); no timer is used."); } if (!simpleModalApiDumped) { simpleModalApiDumped = true; UiButtonAutomation.DumpModalApiOnce(); } if (!UiButtonAutomation.TryCloseModal(out var closePath)) { if (!simpleWaitingCreateLogged) { simpleWaitingCreateLogged = true; Plugin.Log.LogInfo((object)"SIMPLE AUTO-RESTART UI: modal is open, but Modal.CloseModal() is not callable yet. Waiting for the real modal API state; no timer is used."); } } else { simpleWaitingCreateLogged = false; SceneManager.LoadScene("Title"); simpleDisconnectOkActionIssued = true; Plugin.Log.LogWarning((object)("SIMPLE AUTO-RESTART UI: closed the disconnect modal via " + closePath + " and executed PEAK's exact OK callback SceneManager.LoadScene(\"Title\"). Waiting for Title/error UI to settle by state.")); } return; } if (flag) { if (simpleFollowupUiActions < 6 && UiButtonAutomation.TryCloseModal(out var closePath2)) { simpleFollowupUiActions++; simpleWaitingCreateLogged = false; Plugin.Log.LogWarning((object)("SIMPLE AUTO-RESTART UI: follow-up modal dismissed via " + closePath2 + " (" + simpleFollowupUiActions + "/6). Waiting for UI to become clear.")); } else if (!simpleWaitingCreateLogged) { simpleWaitingCreateLogged = true; Plugin.Log.LogInfo((object)"SIMPLE AUTO-RESTART UI: a follow-up modal is still open and Modal.CloseModal() is not callable yet. Waiting for UI state."); } return; } if (UiButtonAutomation.TryClickVisibleButtonText(new string[4] { "OK", "ЕЩЕ РАЗ", "ЕЩЁ РАЗ", "TRY AGAIN" }, out var clickedText)) { simpleFollowupUiActions++; simpleWaitingCreateLogged = false; Plugin.Log.LogWarning((object)("SIMPLE AUTO-RESTART UI: clicked visible follow-up affirmative button '" + clickedText + "'. Waiting for the resulting UI state.")); return; } if (!flag2) { if (!simpleWaitingMasterLogged) { simpleWaitingMasterLogged = true; Plugin.Log.LogInfo((object)("SIMPLE AUTO-RESTART UI: error UI is clear but Photon is not ConnectedToMasterServer yet (state=" + text + "). Waiting before Create Game.")); } return; } simpleWaitingMasterLogged = false; if (!UiButtonAutomation.IsVisibleCreateGameButtonReady(out var text2)) { if (!simpleWaitingCreateLogged) { simpleWaitingCreateLogged = true; Plugin.Log.LogInfo((object)"SIMPLE AUTO-RESTART UI: no error modal is open and Master is ready, but MainMenuMainPage.PlayButton is not active/interactable yet. Waiting for the actual Create Game button state."); } return; } simpleWaitingCreateLogged = false; if (UiButtonAutomation.TryClickVisibleCreateGameButton(out text2)) { simpleAutoRestartTriggered = true; Plugin.Log.LogWarning((object)("SIMPLE AUTO-RESTART UI: real Create Game button '" + text2 + "' is active/interactable, no error UI is open, and Photon is ConnectedToMasterServer. Clicked its real Unity Button.onClick automatically.")); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("SIMPLE AUTO-RESTART UI state machine failed for this frame; it will keep waiting for valid UI state. " + ex.GetType().Name + ": " + ex.Message)); } } private static bool IsFailureStatus(string s) { if (string.IsNullOrEmpty(s)) { return false; } string text = s.ToLowerInvariant(); if (!text.Contains("timeout") && !text.Contains("exceptiononconnect") && !text.Contains("securityexception") && !text.Contains("serveraddressinvalid") && !text.Contains("dnsexception") && !text.Contains("encryptionfailed")) { return text.Contains("senderror"); } return true; } private static bool IsGameServer(string server, string address) { if (!string.IsNullOrEmpty(server) && server.IndexOf("Game", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } return IsGameAddress(address); } private static bool IsGameAddress(string address) { if (!string.IsNullOrEmpty(address)) { if (address.IndexOf(":5056", StringComparison.OrdinalIgnoreCase) < 0) { return address.IndexOf(":27002", StringComparison.OrdinalIgnoreCase) >= 0; } return true; } return false; } internal static string HostOnly(string address) { if (string.IsNullOrEmpty(address)) { return ""; } string text = address.Trim(); int num = text.IndexOf("://", StringComparison.Ordinal); if (num >= 0) { text = text.Substring(num + 3); } int num2 = text.IndexOf('/'); if (num2 >= 0) { text = text.Substring(0, num2); } int num3 = text.LastIndexOf(':'); if (num3 > 0) { text = text.Substring(0, num3); } return text; } internal static string Prefix24(string address) { string text = HostOnly(address); string[] array = text.Split(new char[1] { '.' }); if (array.Length == 4 && int.TryParse(array[0], out var result) && int.TryParse(array[1], out var result2) && int.TryParse(array[2], out var result3) && int.TryParse(array[3], out var _)) { return result + "." + result2 + "." + result3 + ".0/24"; } return text; } private static void Record(string result, string reason, string address, string prefix, double duration, string state, string server) { string contents = Csv(DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture)) + "," + Csv(result) + "," + Csv(reason) + "," + Csv(address) + "," + Csv(prefix) + "," + duration.ToString("F3", CultureInfo.InvariantCulture) + "," + Csv(state) + "," + Csv(server) + "\r\n"; lock (fileLock) { File.AppendAllText(ResultsPath, contents); } } private static string Csv(string s) { if (s == null) { s = ""; } return "\"" + s.Replace("\"", "\"\"") + "\""; } } internal static class ModGui { private const int MainWindowId = 1346715979; private const int LobbyWindowId = 1346916180; private const string UdpTooltip = "Основное игровое соединение Photon остаётся на UDP с минимальной дополнительной задержкой. PEAK Pathfinder проверяет назначенные Game Server IP и сохраняет результаты локально. При следующих запусках уже известные BAD-IP отбрасываются сразу, без повторного ожидания их таймаута. Photon Voice при этом подключается через WSS/443, чтобы не зависеть от отдельного UDP Voice GameServer. Первые подборы могут занять заметное время; по мере накопления истории этап повторных попыток может значительно ускориться."; private const string WssTooltip = "Режим для случаев, когда обычный UDP-трафик Photon блокируется, фильтруется или нестабильно проходит по сети провайдера. PEAK Pathfinder переводит основное Photon-соединение и Photon Voice в WebSocket Secure через TCP/TLS на стандартном порту 443. Перебор и база плохих UDP IP в этом режиме не используются.\n\n<color=#ff5555>Важно: WSS включается отдельно на каждом компьютере. Хост не может переключить всю Photon-комнату на WSS. Если другому игроку тоже нужен защищённый маршрут, он должен установить Pathfinder и включить WSS/443 у себя. Режим может увеличить пинг.</color>"; private const string BoostyDonateUrl = "https://boosty.to/peak_pathfinder/donate"; private static Rect mainRect = new Rect(20f, 55f, 465f, 405f); private static Rect lobbyRect = new Rect(20f, 55f, 430f, 315f); private static string previousScene = string.Empty; private static string previousLobbyServer = string.Empty; private static bool lobbyWindowClosed; private static bool airportOfflineMode; private static GUIStyle wrapLabel; private static GUIStyle centerLabel; private static GUIStyle sectionLabel; private static GUIStyle tooltipStyle; private static GUIStyle opaqueBackgroundStyle; private static GUIStyle boostyButtonStyle; private static Texture2D opaqueBackgroundTexture; private static Texture2D tooltipBackgroundTexture; private static Texture2D boostyButtonTexture; private static Texture2D boostyButtonHoverTexture; public static void Draw(ConfigEntry<MainConnectionMode> connectionMode) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: 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_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Expected O, but got Unknown //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Expected O, but got Unknown //IL_0151: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Unknown result type (might be due to invalid IL or missing references) WindowFunction val = null; try { Scene activeScene = SceneManager.GetActiveScene(); string a = ((Scene)(ref activeScene)).name ?? string.Empty; string currentGameServerAddress = AutoRetryTracker.CurrentGameServerAddress; if (!string.Equals(a, previousScene, StringComparison.OrdinalIgnoreCase)) { if (string.Equals(a, "Airport", StringComparison.OrdinalIgnoreCase)) { lobbyWindowClosed = false; ((Rect)(ref lobbyRect)).x = 20f; ((Rect)(ref lobbyRect)).y = 55f; airportOfflineMode = ReadPhotonOfflineModeOnce(); } if (string.Equals(a, "Title", StringComparison.OrdinalIgnoreCase)) { PositionMainWindowBottomCenter(); } previousScene = a; } if (!string.Equals(currentGameServerAddress, previousLobbyServer, StringComparison.OrdinalIgnoreCase)) { if (!string.IsNullOrEmpty(currentGameServerAddress)) { lobbyWindowClosed = false; } previousLobbyServer = currentGameServerAddress; } EnsureStyles(); if (string.Equals(a, "Title", StringComparison.OrdinalIgnoreCase)) { GUI.Box(mainRect, "", opaqueBackgroundStyle); Rect val2 = mainRect; if (val == null) { val = (WindowFunction)delegate(int id) { DrawMainWindow(id, connectionMode); }; } mainRect = GUI.Window(1346715979, val2, val, "PEAK Pathfinder v0.6.10"); } if (string.Equals(a, "Airport", StringComparison.OrdinalIgnoreCase) && !lobbyWindowClosed && !airportOfflineMode) { GUI.Box(lobbyRect, "", opaqueBackgroundStyle); lobbyRect = GUI.Window(1346916180, lobbyRect, new WindowFunction(DrawLobbyWindow), "PEAK Pathfinder — соединение"); } } catch (Exception ex) { Plugin.Log.LogDebug((object)("GUI draw error: " + ex.Message)); } } private static void EnsureStyles() { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Expected O, but got Unknown //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Expected O, but got Unknown //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Expected O, but got Unknown //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Expected O, but got Unknown //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Expected O, but got Unknown //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Expected O, but got Unknown //IL_01b7: Unknown result type (might be due to invalid IL or missing references) //IL_01c1: Expected O, but got Unknown //IL_01dc: Unknown result type (might be due to invalid IL or missing references) //IL_01f0: Unknown result type (might be due to invalid IL or missing references) //IL_01fa: Expected O, but got Unknown //IL_021a: Unknown result type (might be due to invalid IL or missing references) //IL_0224: Expected O, but got Unknown //IL_023f: Unknown result type (might be due to invalid IL or missing references) //IL_0255: Unknown result type (might be due to invalid IL or missing references) //IL_025f: Expected O, but got Unknown //IL_027a: Unknown result type (might be due to invalid IL or missing references) //IL_0298: Unknown result type (might be due to invalid IL or missing references) //IL_02a2: Expected O, but got Unknown //IL_031e: Unknown result type (might be due to invalid IL or missing references) //IL_0332: Unknown result type (might be due to invalid IL or missing references) //IL_0346: Unknown result type (might be due to invalid IL or missing references) //IL_035a: Unknown result type (might be due to invalid IL or missing references) if (wrapLabel == null) { wrapLabel = new GUIStyle(GUI.skin.label); wrapLabel.wordWrap = true; } if (centerLabel == null) { centerLabel = new GUIStyle(GUI.skin.label); centerLabel.alignment = (TextAnchor)4; centerLabel.wordWrap = true; } if (sectionLabel == null) { sectionLabel = new GUIStyle(GUI.skin.label); sectionLabel.fontStyle = (FontStyle)1; } if (tooltipStyle == null) { tooltipBackgroundTexture = new Texture2D(1, 1); tooltipBackgroundTexture.SetPixel(0, 0, new Color(0.06f, 0.06f, 0.06f, 1f)); tooltipBackgroundTexture.Apply(); tooltipStyle = new GUIStyle(GUI.skin.box); tooltipStyle.wordWrap = true; tooltipStyle.richText = true; tooltipStyle.alignment = (TextAnchor)3; tooltipStyle.padding = new RectOffset(12, 12, 10, 10); tooltipStyle.normal.background = tooltipBackgroundTexture; tooltipStyle.hover.background = tooltipBackgroundTexture; tooltipStyle.active.background = tooltipBackgroundTexture; tooltipStyle.focused.background = tooltipBackgroundTexture; tooltipStyle.onNormal.background = tooltipBackgroundTexture; tooltipStyle.onHover.background = tooltipBackgroundTexture; tooltipStyle.onActive.background = tooltipBackgroundTexture; tooltipStyle.onFocused.background = tooltipBackgroundTexture; } if (opaqueBackgroundStyle == null) { opaqueBackgroundTexture = new Texture2D(1, 1); opaqueBackgroundTexture.SetPixel(0, 0, new Color(0.1f, 0.1f, 0.1f, 1f)); opaqueBackgroundTexture.Apply(); opaqueBackgroundStyle = new GUIStyle(); opaqueBackgroundStyle.normal.background = opaqueBackgroundTexture; } if (boostyButtonStyle == null) { boostyButtonTexture = new Texture2D(1, 1); boostyButtonTexture.SetPixel(0, 0, new Color(1f, 0.32f, 0.1f, 1f)); boostyButtonTexture.Apply(); boostyButtonHoverTexture = new Texture2D(1, 1); boostyButtonHoverTexture.SetPixel(0, 0, new Color(1f, 0.46f, 0.16f, 1f)); boostyButtonHoverTexture.Apply(); boostyButtonStyle = new GUIStyle(GUI.skin.button); boostyButtonStyle.fontStyle = (FontStyle)1; boostyButtonStyle.fontSize = 15; boostyButtonStyle.alignment = (TextAnchor)4; boostyButtonStyle.normal.background = boostyButtonTexture; boostyButtonStyle.hover.background = boostyButtonHoverTexture; boostyButtonStyle.active.background = boostyButtonHoverTexture; boostyButtonStyle.focused.background = boostyButtonTexture; boostyButtonStyle.normal.textColor = Color.white; boostyButtonStyle.hover.textColor = Color.white; boostyButtonStyle.active.textColor = Color.white; boostyButtonStyle.focused.textColor = Color.white; } } private static void PositionMainWindowBottomCenter() { float val = ((float)Screen.width - ((Rect)(ref mainRect)).width) * 0.5f; float val2 = (float)Screen.height - ((Rect)(ref mainRect)).height - 20f; ((Rect)(ref mainRect)).x = Math.Max(10f, val); ((Rect)(ref mainRect)).y = Math.Max(10f, val2); } private static void OpenBoostyDonatePage() { try { Application.OpenURL("https://boosty.to/peak_pathfinder/donate"); Plugin.Log.LogInfo((object)"PEAK Pathfinder: opened Boosty donation page in the default browser."); } catch (Exception ex) { Plugin.Log.LogWarning((object)("PEAK Pathfinder: failed to open Boosty donation page: " + ex.Message)); } } private static void DrawMainWindow(int id, ConfigEntry<MainConnectionMode> connectionMode) { //IL_0253: Unknown result type (might be due to invalid IL or missing references) //IL_01cc: Unknown result type (might be due to invalid IL or missing references) //IL_01d7: Expected O, but got Unknown //IL_020a: Unknown result type (might be due to invalid IL or missing references) //IL_022f: Unknown result type (might be due to invalid IL or missing references) GUILayout.Space(3f); GUILayout.Label("Помощник подключения к Photon", centerLabel, (GUILayoutOption[])(object)new GUILayoutOption[0]); GUILayout.Space(5f); GUILayout.Label("Режим подключения", sectionLabel, (GUILayoutOption[])(object)new GUILayoutOption[0]); string hoveredTooltip = null; bool enabled = GUI.enabled; GUI.enabled = enabled && !ConnectionModeManager.SwitchInProgress; DrawModeRow(MainConnectionMode.UdpSelection, "Подбор UDP-серверов", "Основное игровое соединение Photon остаётся на UDP с минимальной дополнительной задержкой. PEAK Pathfinder проверяет назначенные Game Server IP и сохраняет результаты локально. При следующих запусках уже известные BAD-IP отбрасываются сразу, без повторного ожидания их таймаута. Photon Voice при этом подключается через WSS/443, чтобы не зависеть от отдельного UDP Voice GameServer. Первые подборы могут занять заметное время; по мере накопления истории этап повторных попыток может значительно ускориться.", ref hoveredTooltip); DrawModeRow(MainConnectionMode.EncryptedWss, "Защищённое соединение (WSS/443)", "Режим для случаев, когда обычный UDP-трафик Photon блокируется, фильтруется или нестабильно проходит по сети провайдера. PEAK Pathfinder переводит основное Photon-соединение и Photon Voice в WebSocket Secure через TCP/TLS на стандартном порту 443. Перебор и база плохих UDP IP в этом режиме не используются.\n\n<color=#ff5555>Важно: WSS включается отдельно на каждом компьютере. Хост не может переключить всю Photon-комнату на WSS. Если другому игроку тоже нужен защищённый маршрут, он должен установить Pathfinder и включить WSS/443 у себя. Режим может увеличить пинг.</color>", ref hoveredTooltip); GUI.enabled = enabled; GUILayout.Space(4f); GUILayout.Label("Выбран: " + ConnectionModeManager.DisplayName, wrapLabel, (GUILayoutOption[])(object)new GUILayoutOption[0]); if (ConnectionModeManager.SwitchInProgress) { GUILayout.Label("Применение режима: переподключение к Photon...", wrapLabel, (GUILayoutOption[])(object)new GUILayoutOption[0]); } else if (!string.IsNullOrEmpty(ConnectionModeManager.LastSwitchError)) { GUILayout.Label(ConnectionModeManager.LastSwitchError, wrapLabel, (GUILayoutOption[])(object)new GUILayoutOption[0]); } GUILayout.Space(8f); GUILayout.Label("Данные мода", sectionLabel, (GUILayoutOption[])(object)new GUILayoutOption[0]); GUILayout.Label("bad_servers.txt — ручной чёрный список\nudp_server_results.csv — история и база GOOD/BAD серверов", wrapLabel, (GUILayoutOption[])(object)new GUILayoutOption[0]); if (GUILayout.Button("Открыть папку с файлами", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(30f) })) { OpenDataFolder(); } GUILayout.Space(9f); GUILayout.Label("Если хочешь меня отблагодарить ❤\ufe0f", centerLabel, (GUILayoutOption[])(object)new GUILayoutOption[0]); if (GUILayout.Button("★ ПОДДЕРЖАТЬ АВТОРА НА BOOSTY ★", boostyButtonStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(42f) })) { OpenBoostyDonatePage(); } GUILayout.Label("Поддержка полностью добровольная и не влияет на работу мода.", centerLabel, (GUILayoutOption[])(object)new GUILayoutOption[0]); if (!string.IsNullOrEmpty(hoveredTooltip)) { float num = ((Rect)(ref mainRect)).width - 28f; float val = tooltipStyle.CalcHeight(new GUIContent(hoveredTooltip), num); val = Math.Min(190f, Math.Max(95f, val)); float num2 = Math.Min(((Rect)(ref mainRect)).height - val - 10f, Math.Max(78f, Event.current.mousePosition.y + 14f)); GUI.Box(new Rect(14f, num2, num, val), hoveredTooltip, tooltipStyle); } GUI.DragWindow(new Rect(0f, 0f, 10000f, 22f)); } private static void DrawModeRow(MainConnectionMode mode, string label, string tooltip, ref string hoveredTooltip) { //IL_007d: 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_008a: Unknown result type (might be due to invalid IL or missing references) GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[0]); string text = ((ConnectionModeManager.CurrentMode == mode) ? "● " : "○ "); if (GUILayout.Button(text + label, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(29f) })) { ConnectionModeManager.SelectMode(mode); } GUILayout.Box("?", (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(29f), GUILayout.Height(29f) }); Rect lastRect = GUILayoutUtility.GetLastRect(); if (((Rect)(ref lastRect)).Contains(Event.current.mousePosition)) { hoveredTooltip = tooltip; } GUILayout.EndHorizontal(); } private static void DrawLobbyWindow(int id) { //IL_0160: Unknown result type (might be due to invalid IL or missing references) string currentGameServerAddress = AutoRetryTracker.CurrentGameServerAddress; string currentMainProtocol = AutoRetryTracker.CurrentMainProtocol; string currentMainState = AutoRetryTracker.CurrentMainState; bool isUdpSelection = ConnectionModeManager.IsUdpSelection; GUILayout.Space(4f); GUILayout.Label("Активный режим: " + ConnectionModeManager.DisplayName, sectionLabel, (GUILayoutOption[])(object)new GUILayoutOption[0]); if (isUdpSelection) { GUILayout.Label("Попытка подбора: " + AutoRetryTracker.CurrentSearchAttempt + " / " + 31, wrapLabel, (GUILayoutOption[])(object)new GUILayoutOption[0]); } GUILayout.Space(5f); GUILayout.Label("Photon Game Server", sectionLabel, (GUILayoutOption[])(object)new GUILayoutOption[0]); string text = (isUdpSelection ? "IP" : "Адрес"); GUILayout.Label(string.IsNullOrEmpty(currentGameServerAddress) ? (text + ": ожидание назначения...") : (text + ": " + currentGameServerAddress), wrapLabel, (GUILayoutOption[])(object)new GUILayoutOption[0]); GUILayout.Label("Транспорт: " + (string.IsNullOrEmpty(currentMainProtocol) ? "—" : currentMainProtocol) + " Photon: " + (string.IsNullOrEmpty(currentMainState) ? "—" : currentMainState), wrapLabel, (GUILayoutOption[])(object)new GUILayoutOption[0]); GUILayout.Space(8f); if (isUdpSelection) { DrawUdpLobbyStatus(currentGameServerAddress); } else { DrawWssLobbyStatus(currentGameServerAddress, currentMainState); } GUI.DragWindow(new Rect(0f, 0f, 10000f, 22f)); } private static void DrawUdpLobbyStatus(string address) { double currentStabilitySeconds = AutoRetryTracker.CurrentStabilitySeconds; double num = 60.0; double num2 = Math.Min(num, Math.Max(0.0, currentStabilitySeconds)); double a = Math.Max(0.0, num - currentStabilitySeconds); bool flag = AutoRetryTracker.CurrentServerPassedStability || (!string.IsNullOrEmpty(address) && currentStabilitySeconds >= num && !BadServerDb.IsBad(address)); bool flag2 = !string.IsNullOrEmpty(address) && BadServerDb.IsGood(address); string text = ((!string.IsNullOrEmpty(address) && BadServerDb.IsBad(address)) ? "Статус: BAD — сервер уже отмечен как проблемный" : (flag ? "Статус: GOOD — сервер выдержал проверку" : ((!flag2) ? "Статус: проверка стабильности" : "Статус: ранее был GOOD; текущая сессия проверяется"))); GUILayout.Label(text, wrapLabel, (GUILayoutOption[])(object)new GUILayoutOption[0]); if (!string.IsNullOrEmpty(address)) { GUILayout.Label("Проверка: " + num2.ToString("F1", CultureInfo.InvariantCulture) + " / " + num.ToString("F0", CultureInfo.InvariantCulture) + " сек", centerLabel, (GUILayoutOption[])(object)new GUILayoutOption[0]); if (!flag) { GUILayout.Label("Осталось подождать примерно " + Math.Ceiling(a).ToString("F0", CultureInfo.InvariantCulture) + " сек", centerLabel, (GUILayoutOption[])(object)new GUILayoutOption[0]); } else { GUILayout.Label("Сервер считается стабильным. Можно закрыть это окно.", centerLabel, (GUILayoutOption[])(object)new GUILayoutOption[0]); } } else { GUILayout.Label("Таймер начнётся после подключения к UDP Game Server.", centerLabel, (GUILayoutOption[])(object)new GUILayoutOption[0]); } GUILayout.Space(8f); if (flag && GUILayout.Button("Закрыть", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(32f) })) { lobbyWindowClosed = true; } } private static void DrawWssLobbyStatus(string address, string state) { bool flag = !string.IsNullOrEmpty(address) && !string.IsNullOrEmpty(state) && state.IndexOf("Joined", StringComparison.OrdinalIgnoreCase) >= 0; GUILayout.Label("Статус: защищённое соединение WSS/443", wrapLabel, (GUILayoutOption[])(object)new GUILayoutOption[0]); GUILayout.Label("В этом режиме перебор IP и 60-секундная UDP-проверка не используются.", centerLabel, (GUILayoutOption[])(object)new GUILayoutOption[0]); GUILayout.Space(8f); if (flag && GUILayout.Button("Закрыть", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(32f) })) { lobbyWindowClosed = true; } } private static bool ReadPhotonOfflineModeOnce() { string a = PhotonHybridSetter.ReadStaticStringMember("Photon.Pun.PhotonNetwork", "OfflineMode"); return string.Equals(a, "True", StringComparison.OrdinalIgnoreCase); } private static void OpenDataFolder() { try { string text = Path.GetDirectoryName(AutoRetryTracker.ResultsPath); if (string.IsNullOrEmpty(text)) { text = Path.Combine(Paths.PluginPath, "PeakPathfinder", "data"); } if (!Directory.Exists(text)) { Plugin.Log.LogWarning((object)("Could not open mod data folder because it does not exist: " + text)); return; } string absoluteUri = new Uri(Path.GetFullPath(text) + Path.DirectorySeparatorChar).AbsoluteUri; Application.OpenURL(absoluteUri); Plugin.Log.LogInfo((object)"PEAK Pathfinder: requested Unity to open the local data folder."); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not open mod data folder: " + ex.Message)); } } } internal static class UiButtonAutomation { private static Type buttonType; private static Type tmpTextType; private static Type modalType; private static bool modalApiWasDumped; public static bool IsModalOpen() { try { if (modalType == null) { modalType = AccessTools.TypeByName("Zorro.UI.Modal.Modal"); } if (modalType == null) { return false; } PropertyInfo property = modalType.GetProperty("IsOpen", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (property == null || !property.CanRead) { return false; } object value = property.GetValue(null, null); return value is bool && (bool)value; } catch { return false; } } public static void DumpModalApiOnce() { if (modalApiWasDumped) { return; } modalApiWasDumped = true; try { if (modalType == null) { modalType = AccessTools.TypeByName("Zorro.UI.Modal.Modal"); } if (modalType == null) { Plugin.Log.LogWarning((object)"SIMPLE AUTO-RESTART UI DIAG: Zorro.UI.Modal.Modal type was not found."); return; } List<string> list = new List<string>(); MethodInfo[] methods = modalType.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { string name = methodInfo.Name; if (name.IndexOf("close", StringComparison.OrdinalIgnoreCase) < 0 && name.IndexOf("dismiss", StringComparison.OrdinalIgnoreCase) < 0 && name.IndexOf("hide", StringComparison.OrdinalIgnoreCase) < 0 && name.IndexOf("select", StringComparison.OrdinalIgnoreCase) < 0 && name.IndexOf("submit", StringComparison.OrdinalIgnoreCase) < 0 && name.IndexOf("option", StringComparison.OrdinalIgnoreCase) < 0) { continue; } ParameterInfo[] parameters = methodInfo.GetParameters(); string text = (methodInfo.IsStatic ? "static " : "instance ") + name + "("; for (int j = 0; j < parameters.Length; j++) { if (j > 0) { text += ","; } text += parameters[j].ParameterType.FullName; } text += ")"; list.Add(text); } Plugin.Log.LogInfo((object)("SIMPLE AUTO-RESTART UI DIAG: Modal API candidates: " + ((list.Count == 0) ? "<none>" : string.Join(" | ", list.ToArray())))); } catch (Exception ex) { Plugin.Log.LogWarning((object)("SIMPLE AUTO-RESTART UI DIAG: Modal API dump failed: " + ex.GetType().Name + ": " + ex.Message)); } } public static bool TryCloseModal(out string closePath) { closePath = "<none>"; try { if (modalType == null) { modalType = AccessTools.TypeByName("Zorro.UI.Modal.Modal"); } if (modalType == null) { return false; } MethodInfo method = modalType.GetMethod("CloseModal", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); if (method == null) { return false; } method.Invoke(null, null); closePath = "Zorro.UI.Modal.Modal.CloseModal()"; return true; } catch (Exception ex) { Plugin.Log.LogWarning((object)("SIMPLE AUTO-RESTART UI: Modal.CloseModal() failed: " + ex.GetType().Name + ": " + ex.Message)); return false; } } public static bool TrySubmitCurrentSelectedUi(out string selectedName, out string activationPath) { selectedName = "<unknown>"; activationPath = "<none>"; try { Type type = AccessTools.TypeByName("UnityEngine.EventSystems.EventSystem"); if (type == null) { return false; } PropertyInfo property = type.GetProperty("current", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (property == null) { return false; } object value = property.GetValue(null, null); if (value == null) { return false; } object obj = PhotonHybridSetter.ReadObjectMember(value, "currentSelectedGameObject"); if (obj == null) { return false; } if (!IsObjectActive(obj)) { return false; } string text = PhotonHybridSetter.ReadStringMember(obj, "name"); string objectText = GetObjectText(obj); selectedName = ((!string.IsNullOrWhiteSpace(objectText)) ? objectText.Trim() : ((!string.IsNullOrWhiteSpace(text)) ? text : "<selected UI>")); object obj2 = FindComponentOnGameObject(obj, "UnityEngine.UI.Button"); if (obj2 != null && IsButtonReady(obj2) && InvokeButton(obj2)) { activationPath = "Unity Button.onClick"; return true; } object[] allComponents = GetAllComponents(obj); foreach (object obj3 in allComponents) { if (obj3 == null) { continue; } MethodInfo[] methods = obj3.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (methodInfo.Name != "OnSubmit") { continue; } ParameterInfo[] parameters = methodInfo.GetParameters(); if (parameters.Length == 1) { object obj4 = CreateEventData(parameters[0].ParameterType, value, type); if (obj4 != null) { methodInfo.Invoke(obj3, new object[1] { obj4 }); activationPath = obj3.GetType().FullName + ".OnSubmit"; return true; } } } } string[] array = new string[5] { "Press", "Click", "Submit", "Confirm", "OnClick" }; foreach (object obj5 in allComponents) { if (obj5 == null) { continue; } Type type2 = obj5.GetType(); for (int l = 0; l < array.Length; l++) { MethodInfo method = type2.GetMethod(array[l], BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); if (!(method == null)) { method.Invoke(obj5, null); activationPath = type2.FullName + "." + method.Name + "()"; return true; } } } } catch (Exception ex) { Plugin.Log.LogWarning((object)("SIMPLE AUTO-RESTART UI: selected-control submit failed: " + ex.GetType().Name + ": " + ex.Message)); } return false; } private static object CreateEventData(Type requestedType, object eventSystem, Type eventSystemType) { try { if (requestedType == null) { return null; } ConstructorInfo constructor = requestedType.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { eventSystemType }, null); if (constructor != null) { return constructor.Invoke(new object[1] { eventSystem }); } Type type = AccessTools.TypeByName("UnityEngine.EventSystems.BaseEventData"); if (type != null && requestedType.IsAssignableFrom(type)) { constructor = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { eventSystemType }, null); if (constructor != null) { return constructor.Invoke(new object[1] { eventSystem }); } } } catch { } return null; } private static object FindComponentOnGameObject(object go, string typeName) { try { Type type = AccessTools.TypeByName(typeName); if (go == null || type == null) { return null; } MethodInfo method = go.GetType().GetMethod("GetComponent", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(Type) }, null); if (method == null) { return null; } return method.Invoke(go, new object[1] { type }); } catch { return null; } } private static object[] GetAllComponents(object go) { try { Type type = AccessTools.TypeByName("UnityEngine.Component"); if (go == null || type ==