Decompiled source of BetterPeakVoiceFix v1.1.4
BetterPeakVoiceFix.dll
Decompiled 6 days ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Threading; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using ExitGames.Client.Photon; using HarmonyLib; using Peak.Network; using PeakVoiceFix.Patches; using Photon.Pun; using Photon.Realtime; using Photon.Voice; using Photon.Voice.PUN; using Photon.Voice.Unity; using Steamworks; using TMPro; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("BetterPeakVoiceFix")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.1.3.0")] [assembly: AssemblyInformationalVersion("1.1.3+cb9e9fef3439b35ff41e3965e32d4c347c234b90")] [assembly: AssemblyProduct("BetterPeakVoiceFix")] [assembly: AssemblyTitle("BetterPeakVoiceFix")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.1.3.0")] [module: UnverifiableCode] namespace PeakVoiceFix { internal static class InviteHandshake { private const float TIMEOUT = 8f; private const int MAX_ATTEMPTS = 4; private static readonly FieldInfo fRequesting = AccessTools.Field(typeof(SteamLobbyHandler), "m_currentlyRequestingRoomID"); private static readonly FieldInfo fWaiting = AccessTools.Field(typeof(SteamLobbyHandler), "m_currentlyWaitingForRoomID"); private static float sentAt = -1f; private static int attempts = 0; public static bool Available { get { if (fRequesting != null) { return fWaiting != null; } return false; } } private static SteamLobbyHandler Handler { get { try { return GameHandler.GetService<SteamLobbyHandler>(); } catch (Exception) { return null; } } } public static void NoteRequestSent() { sentAt = Time.unscaledTime; if (attempts == 0) { attempts = 1; } NetworkManager.DiagLog(L.Get("invite_requested", attempts)); } public static void Update() { if (VoiceFix.EnableInviteRetry == null || !VoiceFix.EnableInviteRetry.Value || sentAt < 0f) { return; } if (PhotonNetwork.InRoom || !IsWaiting()) { Reset(); } else { if (Time.unscaledTime - sentAt < 8f) { return; } attempts++; if (attempts > 4) { Fail(); return; } sentAt = Time.unscaledTime; if (ClearRequestingFlag()) { NetworkManager.DiagLog(L.Get("invite_retry", attempts, 4)); } else { Fail(); } } } private static void Fail() { string text = L.Get("invite_failed", 4); if (VoiceFix.logger != null) { VoiceFix.logger.LogWarning((object)text); } Debug.LogWarning((object)("[PVF] " + text)); if ((Object)(object)VoiceUIManager.Instance != (Object)null) { VoiceUIManager.Instance.SetRegionWarning(text); VoiceUIManager.Instance.AddLog("System", text, isLocal: true); } Reset(); } public static void Reset() { sentAt = -1f; attempts = 0; } private static bool IsWaiting() { try { SteamLobbyHandler handler = Handler; if (handler == null || fWaiting == null) { return false; } object value = fWaiting.GetValue(handler); if (value == null) { return false; } PropertyInfo property = value.GetType().GetProperty("IsSome", BindingFlags.Instance | BindingFlags.Public); return property != null && (bool)property.GetValue(value); } catch (Exception) { return false; } } private static bool ClearRequestingFlag() { try { SteamLobbyHandler handler = Handler; if (handler == null || fRequesting == null) { return false; } Type fieldType = fRequesting.FieldType; object obj = null; PropertyInfo property = fieldType.GetProperty("None", BindingFlags.Static | BindingFlags.Public); if (property != null) { obj = property.GetValue(null); } if (obj == null) { FieldInfo field = fieldType.GetField("None", BindingFlags.Static | BindingFlags.Public); if (field != null) { obj = field.GetValue(null); } } if (obj == null) { return false; } fRequesting.SetValue(handler, obj); return true; } catch (Exception) { return false; } } } [HarmonyPatch(typeof(SteamLobbyHandler), "RequestPhotonRoomID")] internal static class RequestRoomIDPatch { private static void Postfix() { InviteHandshake.NoteRequestSent(); } } [HarmonyPatch] internal static class InviteRoomNameEncodingPatch { private const string DeserializerType = "Zorro.Core.Serizalization.BinaryDeserializer"; private static int warned; private static readonly FieldInfo LabelsField = typeof(CodeInstruction).GetField("labels"); private static MethodBase TargetMethod() { MethodInfo[] array = (from m in typeof(SteamLobbyHandler).GetMethods(BindingFlags.Instance | BindingFlags.NonPublic) where m.Name == "HandleMessage" select m).ToArray(); if (array == null || array.Length != 1) { throw new MissingMethodException("SteamLobbyHandler.HandleMessage is not unique"); } MethodInfo obj = array[0]; ParameterInfo[] parameters = obj.GetParameters(); if (obj.ReturnType != typeof(void) || parameters.Length != 3 || parameters[0].ParameterType.FullName != "SteamLobbyHandler+MessageType" || parameters[1].ParameterType.FullName != "Zorro.Core.Serizalization.BinaryDeserializer" || parameters[2].ParameterType.FullName != "Steamworks.CSteamID") { throw new MissingMethodException("SteamLobbyHandler.HandleMessage signature changed"); } return obj; } [HarmonyPriority(0)] [HarmonyAfter(new string[] { "com.github.LengSword.BetterRoomShare" })] internal static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { //IL_034a: Unknown result type (might be due to invalid IL or missing references) //IL_0351: Expected O, but got Unknown List<CodeInstruction> list = instructions.ToList(); try { int num = -1; int num2 = -1; for (int i = 0; i < list.Count; i++) { if (list[i].operand is MethodInfo { Name: "ReadString" } methodInfo && methodInfo.DeclaringType?.FullName == "Zorro.Core.Serizalization.BinaryDeserializer") { if (num >= 0 || methodInfo.IsStatic || methodInfo.ReturnType != typeof(string) || methodInfo.GetParameters().Length != 1 || methodInfo.GetParameters()[0].ParameterType != typeof(Encoding)) { return Skip(list, "room-name ReadString is ambiguous or changed"); } num = i; } if (list[i].opcode == OpCodes.Stfld && list[i].operand is FieldInfo fieldInfo && fieldInfo.DeclaringType?.FullName == "JoinSpecificRoomState" && fieldInfo.Name == "RoomName") { if (num2 >= 0 || fieldInfo.FieldType != typeof(string)) { return Skip(list, "RoomName destination is ambiguous or changed"); } num2 = i; } } if (num < 2 || num + 3 >= list.Count || num2 <= num + 3 || list[num - 2].opcode != OpCodes.Ldarg_2 || list[num - 1].opcode != OpCodes.Call || list[num].opcode != OpCodes.Callvirt || list[num + 2].opcode != OpCodes.Ldarg_0 || !IsField(list[num + 3], OpCodes.Ldflda, "m_currentlyWaitingForRoomID") || !list.Take(num - 2).Any((CodeInstruction c) => IsField(c, OpCodes.Ldflda, "m_currentlyRequestingRoomID")) || LocalIndex(list[num + 1], store: true) < 0 || LocalIndex(list[num + 1], store: true) != LocalIndex(list[num2 - 1], store: false) || HasIncomingLabel(list[num - 1]) || HasIncomingLabel(list[num]) || list[num - 1].blocks.Count != 0 || list[num].blocks.Count != 0) { return Skip(list, "room-name read/destination structure changed"); } MethodInfo method = typeof(Encoding).GetMethod("get_ASCII"); MethodInfo method2 = typeof(Encoding).GetMethod("get_UTF8"); if (object.Equals(list[num - 1].operand, method2)) { return list; } if (!object.Equals(list[num - 1].operand, method)) { return Skip(list, "unknown room-name encoding; left unchanged"); } CodeInstruction val = new CodeInstruction(list[num - 1]); val.operand = method2; list[num - 1] = val; return list; } catch (Exception ex) { return Skip(list, "inspection failed: " + ex); } } private static bool HasIncomingLabel(CodeInstruction c) { if (LabelsField?.GetValue(c) is ICollection collection) { return collection.Count != 0; } return true; } private static bool IsField(CodeInstruction c, OpCode opcode, string name) { if (c.opcode == opcode && c.operand is FieldInfo fieldInfo && fieldInfo.DeclaringType?.FullName == "SteamLobbyHandler") { return fieldInfo.Name == name; } return false; } private static int LocalIndex(CodeInstruction c, bool store) { if (c.opcode == (store ? OpCodes.Stloc_0 : OpCodes.Ldloc_0)) { return 0; } if (c.opcode == (store ? OpCodes.Stloc_1 : OpCodes.Ldloc_1)) { return 1; } if (c.opcode == (store ? OpCodes.Stloc_2 : OpCodes.Ldloc_2)) { return 2; } if (c.opcode == (store ? OpCodes.Stloc_3 : OpCodes.Ldloc_3)) { return 3; } if (c.opcode == (store ? OpCodes.Stloc : OpCodes.Ldloc) || c.opcode == (store ? OpCodes.Stloc_S : OpCodes.Ldloc_S)) { if (c.operand is LocalBuilder localBuilder) { return localBuilder.LocalIndex; } object operand = c.operand; if (operand is int) { return (int)operand; } operand = c.operand; if (operand is byte) { return (byte)operand; } } return -1; } private static IEnumerable<CodeInstruction> Skip(List<CodeInstruction> code, string reason) { if (Interlocked.Exchange(ref warned, 1) == 0) { ManualLogSource logger = VoiceFix.logger; if (logger != null) { logger.LogWarning((object)("[邀请编码] " + reason + "; UTF-8 adaptation skipped, BVF core remains active.")); } } return code; } } public static class L { private static string _lang = "中文"; private static Dictionary<string, string> _current; private static readonly Dictionary<string, string> ZH = new Dictionary<string, string> { { "ui_title", "语音详细状态" }, { "ui_debug_title", "语音修复调试控制台 (Alt+J) | EvCode:186" }, { "btn_copy_all", "复制全部" }, { "btn_export", "导出文件" }, { "btn_clear", "清空" }, { "btn_dump", "打印语音底层名单" }, { "filter_all", "全部" }, { "filter_local", "本机" }, { "client_not_connected", "客户端未连接" }, { "voice_player_list", "语音底层名单" }, { "ghost_tag", "[幽灵]" }, { "exported_to", "已导出" }, { "export_failed", "失败" }, { "copied", "已复制" }, { "label_local", "本机" }, { "label_sync", "同步:" }, { "label_abnormal", "异常:" }, { "warn_majority", "⚠ [警告] 多数玩家({0}人)在另一频道!" }, { "virtual_player", "虚拟玩家1" }, { "sos_snapshot", "[SOS快照]" }, { "sos_majority", "多数派" }, { "sos_person", "人" }, { "sos_detected", "检测到 {0} 掉线" }, { "sos_target", "目标" }, { "sos_last", "上次" }, { "unknown", "未知" }, { "region_not_reported", "区服未上报" }, { "cache_snapshot", "[缓存快照]" }, { "seconds_ago", "秒前" }, { "not_connected", "未连接" }, { "history", "[历史]" }, { "notification_disconnected", "连接断开" }, { "manual_operation", "[系统] 手动操作..." }, { "state_synced", "同步" }, { "state_connecting", "连接中" }, { "state_disconnected", "断开" }, { "state_connected", "已连接" }, { "state_mismatch", "错位" }, { "state_abnormal", "跨区" }, { "state_unknown", "未知" }, { "cs_initializing", "初始化中..." }, { "cs_authenticating", "验证中..." }, { "cs_authenticated", "已验证" }, { "cs_joining", "加入中..." }, { "cs_joined", "已连接" }, { "cs_disconnecting", "断开中..." }, { "cs_disconnected", "断开" }, { "cs_connecting_game", "连接到游戏服务器中..." }, { "cs_connecting_master", "连接到主服务器中..." }, { "cs_connecting_name", "连接到名称服务器中..." }, { "detail_connecting_local", "连接中..." }, { "voice_offline", "离线模式(语音未启用)" }, { "log_state_change", "状态变更" }, { "log_host_ip_change", "房主连接服务器变动" }, { "log_host_disconnected", "[Host] 房主意外断开,正在自动恢复..." }, { "log_loop_blind", "[循环] 已失败{0}次,暂时切换为盲连..." }, { "log_wrong_freq", "[异频] 当前:{0} 目标:{1} | 纠正({2}/2)" }, { "log_compromise", "[妥协] 纠正失败,驻留当前IP: {0}" }, { "log_reconnect_go", "[重连] {0} | 区服:{1}" }, { "log_reconnect_timeout", "[重连] 等待断开完成超时,本轮放弃" }, { "log_decision", "[决策] 目标变更" }, { "log_majority", "多数派({0}人)" }, { "log_host", "房主" }, { "log_auto_blind", "自动(盲连)" }, { "log_sos_send", "[SOS] 发送求救" }, { "log_sos_target", "目标" }, { "log_sos_local", "本机" }, { "log_sos_manual", "手动断开 (Manual)" }, { "log_sos_received", "收到 {0} SOS (目标:{1})" }, { "log_alt_k_disconnect", "[系统] Alt+K 手动断开" }, { "log_alt_k_reconnect", "[系统] Alt+K 强制重连" }, { "log_cache_snapshot", "[缓存快照] 记录数" }, { "cfg_cat_ui", "UI设置" }, { "cfg_cat_adv", "高级与调试" }, { "cfg_ui_position", "选择UI面板显示在屏幕的哪一侧。" }, { "cfg_show_pro", "是否在面板中显示具体的已连接语音服务器IP地址和调试信息。" }, { "cfg_offset_x_r", "距离屏幕右边缘的水平距离。" }, { "cfg_offset_y_r", "距离屏幕上边缘的垂直距离。" }, { "cfg_offset_x_l", "距离屏幕左边缘的水平距离。" }, { "cfg_offset_y_l", "距离屏幕上边缘的垂直距离。" }, { "cfg_font_size", "面板文字的基础大小。" }, { "cfg_host_symbol", "显示在房主名字前的特殊符号。" }, { "cfg_timeout", "如果连接卡住,超过多少秒判定为断开。" }, { "cfg_retry_interval", "每次自动重连之间的冷却时间。" }, { "cfg_manual_reconnect", "允许按 Alt+K 强制断开或重连语音。" }, { "cfgn_voice_recovery", "自动恢复语音状态" }, { "cfg_voice_recovery", "自动恢复本机远端玩家失效的语音状态引用。\n原理解析:游戏为每个玩家保存一份“语音状态”(静音/屏蔽等),新旧角色交接时登记表可能丢项,而语音组件只在初始化时取一次状态——引用失效后对方在你这里被当作静音,即使语音连接正常也听不到声音。\n开启后每 0.5 秒检测:发现已初始化的远端语音组件引用失效时,先从状态登记表取回同一玩家的有效状态,登记表缺失再用该角色自身数据兜底,交还原版组件处理;不修改静音、屏蔽或通信权限。\n面板隐藏时照常检测,异常玩家行显示“语音状态异常”或“已恢复”提示。不依赖 CrossplayStutterFix(它预防登记丢失,本项负责事后修复引用),不要求主机或队友安装。关闭后仍保留异常检测与提示。恢复引用只修这一类失声原因,不保证一定听到声音。" }, { "line2_voice_state_missing", "语音状态异常·本机播放受阻" }, { "line2_voice_state_recovered", "语音状态已恢复" }, { "cfg_max_name_len", "显示名字的最大字符数。" }, { "cfg_latency_offset", "Ping值显示的水平像素偏移。" }, { "cfg_auto_hide", "在机场常驻显示简易UI。关闭后简易UI只在出现异常或有通知时才冒出来;非机场场景一律只在异常时显示。" }, { "cfg_show_ping", "在简易模式下方显示本机延迟。" }, { "cfg_hide_menu", "打开ESC菜单时隐藏UI。" }, { "cfg_virtual_player", "添加一个假玩家用于测试UI布局。" }, { "cfg_virtual_name", "假玩家的名字。" }, { "cfg_language", "语言(重启游戏生效) | Language(Need restart)" }, { "cfgn_ui_position", "UI位置" }, { "cfgn_toggle_key", "详细UI切换键" }, { "cfgn_show_pro", "显示连接到的语音服务器IP和详细信息" }, { "cfgn_offset_x_r", "右侧边距" }, { "cfgn_offset_y_r", "顶部边距(右)" }, { "cfgn_offset_x_l", "左侧边距" }, { "cfgn_offset_y_l", "顶部边距(左)" }, { "cfgn_font_size", "字体大小" }, { "cfgn_host_symbol", "房主标记符号" }, { "cfgn_timeout", "重连超时时间 (s)" }, { "cfgn_retry_interval", "重试间隔 (s)" }, { "cfgn_manual_reconnect", "启用手动重置 (Alt+K)" }, { "cfgn_max_name_len", "最大名字长度" }, { "cfgn_latency_offset", "延迟对齐偏移量" }, { "cfgn_auto_hide", "机场常驻简易UI" }, { "cfgn_show_ping", "简易模式显示Ping" }, { "cfgn_hide_menu", "ESC菜单界面时隐藏" }, { "cfgn_virtual_player", "启用虚拟玩家" }, { "cfgn_virtual_name", "虚拟玩家名字" }, { "cfgn_forced_region", "强制区服-重启生效" }, { "cfg_forced_region", "强制游戏连接到指定 Photon 区服。auto = 由游戏自己测速选择。\n⚠ 注意:强制区服不等于更快,地理上更近的区服路由未必更优——无加速器直连时强制 hk 甚至延迟可能翻一倍(200ms→400ms+);该功能仅作为与区服延迟测试(Alt+J 面板中找到)功能联合调试使用。\n只影响你自己开房和主菜单连接;加入别人的房时游戏会自动切到房主所在区服,这是游戏本身的行为。可选 auto / asia / au / eu / hk / jp / sa / us / ussc / usw。改完需重启游戏。" }, { "region_worse", "[区服] 强制 {0} 实测 {1}ms,而自动选区({2}) 只有 {3}ms —— 强制反而更慢,建议改回 auto。" }, { "region_high", "[区服] 强制 {0} 实测 {1}ms,延迟偏高。没有加速器时强制区服通常不会更快。" }, { "region_ping_partial", "已测到的部分结果:" }, { "region_timeout", "[区服] 已强制 {0},但 20 秒仍未连上主服务器。检查加速器,或把「强制区服」改回 auto。" }, { "region_status_title", "区服状态" }, { "region_game", "游戏区服" }, { "region_voice", "语音区服" }, { "region_room_code", "房间码" }, { "region_from_code", "房间码解出" }, { "region_forced", "强制设置" }, { "region_auto", "auto(自动测速)" }, { "region_cache_pun", "游戏测速缓存" }, { "region_cache_voice", "语音测速缓存" }, { "region_unlisted", "未收录(多为港服)" }, { "region_ping_title", "各区延迟" }, { "region_ping_started", "[区服] 开始测速,约需 3-10 秒…" }, { "region_ping_busy", "[区服] 正在测速中,请稍候。" }, { "region_ping_failed", "[区服] 测速客户端连接失败。" }, { "region_ping_nosettings", "[区服] 读不到 PhotonServerSettings,无法测速。" }, { "region_ping_timeout", "[区服] 测速超时(30 秒)。" }, { "region_ping_error", "[区服] 测速出错: {0}" }, { "region_ping_empty", " 没有拿到任何区服,可能是网络不通。" }, { "region_ping_hint", " ★ = Photon 判定的最佳区;— = 未测到;>3s = 不可达" }, { "btn_region_ping", "测各区延迟" }, { "btn_region_status", "区服状态" }, { "mod_outdated", "{0}" }, { "majority_region", "多数派语音区服: {0} ({1},{2}人)" }, { "cfg_toggle_key_v2", "切换语音面板的按键,循环顺序:关闭 → 简易 → 详细。" }, { "hdr_room_region", "房间区服:" }, { "hdr_room_voice", "房间语音服:" }, { "hdr_local_state", "房间/语音地区:" }, { "hdr_room_status", "房间情况:" }, { "hdr_local_ping", "本机延迟:" }, { "col_status", "[状态]" }, { "col_name", "玩家名" }, { "col_ping", "房间 - 语音" }, { "line2_voice_server", "连接的语音服:" }, { "line2_cannot_judge", "未知:本机断开语音服且对方未安装本模组 1.2 以上版本" }, { "line2_not_in_voice", "未连入语音服" }, { "src_local_guess", "本机推定" }, { "src_host", "房主" }, { "src_single", "1人上报" }, { "rv_unknown", "未知(无人上报)" }, { "local_isolated", "本机孤立" }, { "voice_only_local", "语音房内只有本机" }, { "err_voice_connect", "语音服连接错误" }, { "snap_decision", "决策:" }, { "snap_retry", "重连:" }, { "snap_retry_fmt", "{0} 次 | 失败 {1} | IP不符 {2}" }, { "snap_voiceroom", "语音房:" }, { "snap_voiceroom_fmt", "{0} 人在线 | 判定 {1}/{2} | 幽灵 {3}" }, { "snap_roomcode", "房间码:" }, { "snap_voicename", "语音房名:" }, { "room_name_mismatch", "语音房名与游戏房名 + _voice_ 不一致,请检查语音连接。" }, { "room_name_pending", "房名信息缺失,暂时无法确认是否匹配。" }, { "snap_forced", "强制区服:" }, { "snap_no_decision", "尚未介入(语音正常,无需决策)" }, { "snap_blind", "盲连" }, { "newjoin", "新加入" }, { "newjoin_connecting", "正在连接…" }, { "newjoin_failed", "连接失败" }, { "diag_vregion_change", "[诊断] 本机语音区服: {0} → {1}" }, { "diag_vroom_change", "[诊断] 本机语音房: {0} → {1} (AppId {2})" }, { "diag_appid_snapshot", "[诊断] Photon AppId 快照: realtime {0} / voice {1}" }, { "diag_appid_restored", "[AppId守卫] 检测到 AppId 被改写: realtime {0} / voice {1} → 已还原真值" }, { "diag_isolated", "[诊断] 本机孤立:语音房内只有自己,游戏房有 {0} 人" }, { "diag_isolated_clear", "[诊断] 孤立解除,语音房内已有 {0} 人" }, { "diag_roomvoice", "[诊断] 房间语音服判定为 {0}(来源: {1})" }, { "diag_roomvoice_lost", "[诊断] 房间语音服失去可信来源,转为未知" }, { "diag_cross", "[诊断] {0} 跨区: 语音在 {1},房间在 {2}" }, { "diag_cross_clear", "[诊断] {0} 已回到房间区服 {1}" }, { "diag_newjoin_ok", "[诊断] {0} 入房后 {1} 秒接入语音" }, { "diag_newjoin_fail", "[诊断] {0} 入房 {1} 秒仍未接入语音" }, { "diag_mode_change", "[诊断] 面板模式: {0} → {1}" }, { "diag_cfg_change", "[诊断] 配置变更: {0} = {1}" }, { "mode_off", "关闭" }, { "mode_simple", "简易" }, { "mode_detail", "详细" }, { "cfgn_unknown_region", "未收录区服按此解析" }, { "cfg_unknown_region", "游戏那张区服编码表漏收了 hk(香港),导致港服房的房间码首字符是 '-',而本体解码时会把它夹成表里第一项 us(美国)——结果客机被送去美国区找一个在香港的房间,必然失败。这里指定 '-' 应该解析成哪个区。留空 = 不猜(保持本体行为,房间码进不去港服房)。与 BetterRoomShare 的同类修复兼容:结果已经正确时本 mod 不会再插手。" }, { "cfgn_wait_master", "切区服后等主服务器就绪" }, { "cfg_wait_master", "本体切换区服时是「断开 → 连接新区 → 立刻加房」,此时还没连上主服务器,加房必然失败(本体自己写了等待协程却没接上)。开启后会先等连上再加房,最多等 20 秒。" }, { "cfgn_appid_guard", "Photon AppId 守卫" }, { "cfg_appid_guard", "本机 Photon AppId 防改写:插件加载时快照真 AppIdRealtime/AppIdVoice,之后发现被改写即还原,语音每次连接前也会兜底拨回。\n已知改写来源:LocalMultiplayer 会在每次 NetworkConnector.Start 把全局 AppId 换成它配置里的自建 Photon 应用——语音首连撞上就连进另一个应用的同名房,同区同房名却互不可见、双向静音。\n仅当你有意使用自建 Photon 应用时才需要关闭。" }, { "joinwait_timeout", "[房间码] 等待主服务器超时(状态: {0},区服: {1}),加房已取消。" }, { "joinwait_ready", "[房间码] 主服务器已就绪(区服 {0}),继续加房" }, { "cfgn_invite_retry", "邀请握手超时重试" }, { "cfg_invite_retry", "接受 Steam 邀请后,客机要向大厅索要房间号,而房主只在自己确实待在房间里时才回应。本体这个握手没有超时、不重发、也不弹任何提示,房主当时在菜单/加载中/刚掉线,你就会永久静默卡在主菜单。开启后每 8 秒重试一次、最多 4 次,全部失败会明确告知原因。" }, { "invite_requested", "[邀请] 已向大厅索要房间号(第 {0} 次)" }, { "invite_retry", "[邀请] 8 秒未收到房间号,重新索要({0}/{1})" }, { "invite_failed", "[邀请] 重试 {0} 次仍未收到房间号。房主可能不在房间里(在菜单、加载中或已掉线),也可能你连不上房主所在区服。让房主先进入房间再邀请,或改用房间码。" } }; private static readonly Dictionary<string, string> EN = new Dictionary<string, string> { { "ui_title", "Voice Detail Status" }, { "ui_debug_title", "Voice Fix Debug Console (Alt+J) | EvCode:186" }, { "btn_copy_all", "Copy All" }, { "btn_export", "Export" }, { "btn_clear", "Clear" }, { "btn_dump", "Dump Photon Player List" }, { "filter_all", "All" }, { "filter_local", "Local" }, { "client_not_connected", "Client not connected" }, { "voice_player_list", "Photon Player List" }, { "ghost_tag", "[Ghost]" }, { "exported_to", "Exported to" }, { "export_failed", "Failed" }, { "copied", "Copied" }, { "label_local", "Local" }, { "label_sync", "Sync:" }, { "label_abnormal", "Abnormal:" }, { "warn_majority", "⚠ [WARN] Majority ({0}) on another channel!" }, { "virtual_player", "Virtual Player 1" }, { "sos_snapshot", "[SOS Snapshot]" }, { "sos_majority", "Majority" }, { "sos_person", "" }, { "sos_detected", "Detected {0} disconnected" }, { "sos_target", "Target" }, { "sos_last", "Last" }, { "unknown", "Unknown" }, { "region_not_reported", "Region not reported" }, { "cache_snapshot", "[Cache Snapshot]" }, { "seconds_ago", "sec ago" }, { "not_connected", "Not Connected" }, { "history", "[History]" }, { "notification_disconnected", "Disconnected" }, { "manual_operation", "[System] Manual operation..." }, { "state_synced", "Synced" }, { "state_connecting", "Connecting" }, { "state_disconnected", "Disconnected" }, { "state_connected", "Connected" }, { "state_mismatch", "Mismatched" }, { "state_abnormal", "Cross-region" }, { "state_unknown", "Unknown" }, { "cs_initializing", "Initializing..." }, { "cs_authenticating", "Authenticating..." }, { "cs_authenticated", "Authenticated" }, { "cs_joining", "Joining..." }, { "cs_joined", "Joined" }, { "cs_disconnecting", "Disconnecting..." }, { "cs_disconnected", "Disconnected" }, { "cs_connecting_game", "Connecting to game server..." }, { "cs_connecting_master", "Connecting to master server..." }, { "cs_connecting_name", "Connecting to name server..." }, { "detail_connecting_local", "Connecting..." }, { "voice_offline", "Offline mode (voice disabled)" }, { "log_state_change", "State change" }, { "log_host_ip_change", "Host server connection change" }, { "log_host_disconnected", "[Host] Unexpected disconnect, recovering..." }, { "log_loop_blind", "[Loop] Failed {0} times, switching to blind..." }, { "log_wrong_freq", "[WrongIP] Current:{0} Target:{1} | Fix({2}/2)" }, { "log_compromise", "[Compromise] Fix failed, staying on: {0}" }, { "log_reconnect_go", "[Reconnect] {0} | Region:{1}" }, { "log_reconnect_timeout", "[Reconnect] Timed out waiting for disconnect, skipping this round" }, { "log_decision", "[Decision] Target changed" }, { "log_majority", "Majority ({0})" }, { "log_host", "Host" }, { "log_auto_blind", "Auto (Blind)" }, { "log_sos_send", "[SOS] Sending SOS" }, { "log_sos_target", "Target" }, { "log_sos_local", "Local" }, { "log_sos_manual", "Manual Disconnect" }, { "log_sos_received", "Received {0} SOS (Target:{1})" }, { "log_alt_k_disconnect", "[System] Alt+K Manual disconnect" }, { "log_alt_k_reconnect", "[System] Alt+K Force reconnect" }, { "log_cache_snapshot", "[Cache Snapshot] Count" }, { "cfg_cat_ui", "UI Settings" }, { "cfg_cat_adv", "Advanced & Debug" }, { "cfg_ui_position", "Which side of the screen to display the UI panel." }, { "cfg_show_pro", "Show connected voice server IP and debug info in the panel." }, { "cfg_offset_x_r", "Horizontal offset from right edge." }, { "cfg_offset_y_r", "Vertical offset from top edge." }, { "cfg_offset_x_l", "Horizontal offset from left edge." }, { "cfg_offset_y_l", "Vertical offset from top edge." }, { "cfg_font_size", "Base font size of the panel." }, { "cfg_host_symbol", "Symbol displayed before host's name." }, { "cfg_timeout", "Seconds before connection is deemed disconnected." }, { "cfg_retry_interval", "Cooldown between auto-reconnect attempts." }, { "cfg_manual_reconnect", "Allow Alt+K to force disconnect/reconnect voice." }, { "cfgn_voice_recovery", "Auto Recover Voice State" }, { "cfg_voice_recovery", "Automatically restores a remote player's invalid voice-state reference on your machine.\nHow it happens: the game stores one voice state (mute/block etc.) per player; during an old/new character handover the registry entry can be lost, and the voice component reads the state only once at init - afterwards that player is treated as muted even when the voice connection is fine.\nWhen enabled it scans every 0.5s: if an initialized remote voice component's state reference is dead, a valid CharacterData belonging to the same player is taken from the registry first (falling back to that character's own data) and handed back to the stock component; mute, block and permission flags are never modified.\nDetection keeps running while the panel is hidden, and affected players show an error/restored note. Does not require CrossplayStutterFix (it prevents the loss; this repairs the reference afterwards) nor host/teammate installs. Turning it off keeps detection and the warning visible. Restoring the reference fixes one known cause of silence, not all of them." }, { "line2_voice_state_missing", "Voice state error · local playback blocked" }, { "line2_voice_state_recovered", "Voice state restored" }, { "cfg_max_name_len", "Max display characters for names." }, { "cfg_latency_offset", "Horizontal pixel offset for ping display." }, { "cfg_auto_hide", "Keep the simple overlay visible while in the airport. When off, it only appears on a problem or a notification. Outside the airport it always only appears on a problem." }, { "cfg_show_ping", "Show local ping below simple mode UI." }, { "cfg_hide_menu", "Hide UI when ESC menu is open." }, { "cfg_virtual_player", "Add a fake player for UI layout testing." }, { "cfg_virtual_name", "Name of the fake player." }, { "cfg_language", "语言(重启游戏生效) | Language(Need restart)" }, { "cfgn_ui_position", "UI Position" }, { "cfgn_toggle_key", "Toggle Key" }, { "cfgn_show_pro", "Show Server IP & Details" }, { "cfgn_offset_x_r", "Right Margin" }, { "cfgn_offset_y_r", "Top Margin (Right)" }, { "cfgn_offset_x_l", "Left Margin" }, { "cfgn_offset_y_l", "Top Margin (Left)" }, { "cfgn_font_size", "Font Size" }, { "cfgn_host_symbol", "Host Symbol" }, { "cfgn_timeout", "Reconnect Timeout (s)" }, { "cfgn_retry_interval", "Retry Interval (s)" }, { "cfgn_manual_reconnect", "Enable Manual Reset (Alt+K)" }, { "cfgn_max_name_len", "Max Name Length" }, { "cfgn_latency_offset", "Latency Alignment Offset" }, { "cfgn_auto_hide", "Persistent Simple UI in Airport" }, { "cfgn_show_ping", "Show Ping in Simple Mode" }, { "cfgn_hide_menu", "Hide on ESC Menu" }, { "cfgn_virtual_player", "Enable Virtual Player" }, { "cfgn_virtual_name", "Virtual Player Name" }, { "cfgn_forced_region", "Forced Region - needs restart" }, { "cfg_forced_region", "Force the game to connect to a specific Photon region. auto = let the game pick by ping.\nWARNING: forcing a region does not mean lower latency; a geographically closer region may still route worse - connecting to hk without a VPN/proxy can even double latency (200ms -> 400ms+). Use this only together with the region latency test (found in the Alt+J panel).\nOnly affects hosting your own room and the main-menu connection; joining someone else's room follows the host's region, which is the game's own behaviour. Accepts auto / asia / au / eu / hk / jp / sa / us / ussc / usw. Restart required." }, { "region_worse", "[Region] Forced {0} measures {1}ms, but auto ({2}) was only {3}ms - forcing made it worse. Consider setting it back to auto." }, { "region_high", "[Region] Forced {0} measures {1}ms, which is high. Without a proxy, forcing a region usually does not help." }, { "region_ping_partial", "Partial results measured so far:" }, { "region_timeout", "[Region] Forced {0}, but still not connected to a master server after 20s. Check your connection, or set Forced Region back to auto." }, { "region_status_title", "Region Status" }, { "region_game", "Game region" }, { "region_voice", "Voice region" }, { "region_room_code", "Room code" }, { "region_from_code", "decoded" }, { "region_forced", "Forced setting" }, { "region_auto", "auto (ping-based)" }, { "region_cache_pun", "Game ping cache" }, { "region_cache_voice", "Voice ping cache" }, { "region_unlisted", "unlisted (usually hk)" }, { "region_ping_title", "Region Latency" }, { "region_ping_started", "[Region] Pinging regions, takes 3-10s..." }, { "region_ping_busy", "[Region] A ping run is already in progress." }, { "region_ping_failed", "[Region] Ping client failed to connect." }, { "region_ping_nosettings", "[Region] PhotonServerSettings unavailable, cannot ping." }, { "region_ping_timeout", "[Region] Ping run timed out (30s)." }, { "region_ping_error", "[Region] Ping error: {0}" }, { "region_ping_empty", " No regions returned - network may be unreachable." }, { "region_ping_hint", " * = Photon's best region; - = not measured; >3s = unreachable" }, { "btn_region_ping", "Ping Regions" }, { "btn_region_status", "Region Status" }, { "mod_outdated", "{0}" }, { "majority_region", "Majority voice region: {0} ({1}, {2} people)" }, { "cfg_toggle_key_v2", "Key that cycles the voice panel: off -> simple -> detailed." }, { "hdr_room_region", "Room region:" }, { "hdr_room_voice", "Room voice server:" }, { "hdr_local_state", "Local game/voice:" }, { "hdr_room_status", "Room:" }, { "hdr_local_ping", "Local ping:" }, { "col_status", "[Status]" }, { "col_name", "Player" }, { "col_ping", "room - voice" }, { "line2_voice_server", "voice server:" }, { "line2_cannot_judge", "Unknown: local voice is disconnected and this player has no BetterPeakVoiceFix 1.2+" }, { "line2_not_in_voice", "not in the voice room" }, { "src_local_guess", "local guess" }, { "src_host", "host" }, { "src_single", "1 report" }, { "rv_unknown", "unknown (nobody reported)" }, { "local_isolated", "local isolated" }, { "voice_only_local", "only this client in the voice room" }, { "err_voice_connect", "wrong voice server" }, { "snap_decision", "Decision:" }, { "snap_retry", "Retries:" }, { "snap_retry_fmt", "{0} | failed {1} | wrong IP {2}" }, { "snap_voiceroom", "Voice room:" }, { "snap_voiceroom_fmt", "{0} online | classified {1}/{2} | ghosts {3}" }, { "snap_roomcode", "Room code:" }, { "snap_voicename", "Voice room name:" }, { "room_name_mismatch", "Voice room name does not equal the game room name + _voice_. Check the voice connection." }, { "room_name_pending", "Room name information is missing; matching cannot be confirmed yet." }, { "snap_forced", "Forced region:" }, { "snap_no_decision", "not engaged (voice is fine, nothing to decide)" }, { "snap_blind", "blind connect" }, { "newjoin", "Joined" }, { "newjoin_connecting", "connecting..." }, { "newjoin_failed", "connection failed" }, { "diag_vregion_change", "[Diag] Local voice region: {0} -> {1}" }, { "diag_vroom_change", "[Diag] Local voice room: {0} -> {1} (AppId {2})" }, { "diag_appid_snapshot", "[Diag] Photon AppId snapshot: realtime {0} / voice {1}" }, { "diag_appid_restored", "[AppIdGuard] AppId rewritten (realtime {0} / voice {1}), restored real values" }, { "diag_isolated", "[Diag] Local isolated: alone in the voice room while {0} players are in the game room" }, { "diag_isolated_clear", "[Diag] Isolation cleared, {0} clients in the voice room" }, { "diag_roomvoice", "[Diag] Room voice server resolved to {0} (source: {1})" }, { "diag_roomvoice_lost", "[Diag] Room voice server lost every trusted source, now unknown" }, { "diag_cross", "[Diag] {0} is cross-region: voice on {1}, room on {2}" }, { "diag_cross_clear", "[Diag] {0} returned to the room region {1}" }, { "diag_newjoin_ok", "[Diag] {0} reached voice {1}s after joining" }, { "diag_newjoin_fail", "[Diag] {0} still not in voice {1}s after joining" }, { "diag_mode_change", "[Diag] Panel mode: {0} -> {1}" }, { "diag_cfg_change", "[Diag] Config changed: {0} = {1}" }, { "mode_off", "off" }, { "mode_simple", "simple" }, { "mode_detail", "detailed" }, { "cfgn_unknown_region", "Unlisted region resolves to" }, { "cfg_unknown_region", "The game's region-code table is missing hk (Hong Kong), so a Hong Kong room's code starts with '-' and the game's decoder clamps that to the first table entry, us. The result is that the client is sent to the US region to look for a room that lives in Hong Kong, which always fails. This setting decides what '-' should resolve to. Leave empty to not guess (vanilla behaviour). Compatible with BetterRoomShare's equivalent fix: if the result is already correct, this mod stays out of it." }, { "cfgn_wait_master", "Wait for master server after region swap" }, { "cfg_wait_master", "When the game switches region it does disconnect -> connect to new region -> join room immediately, while still connecting to the master server, so the join always fails (the game has a wait coroutine for this but never calls it). With this on, the join waits for the master server first, up to 20 seconds." }, { "cfgn_appid_guard", "Photon AppId Guard" }, { "cfg_appid_guard", "Protects this machine's Photon AppIds: snapshots the real AppIdRealtime/AppIdVoice at plugin load, restores them whenever the global settings get rewritten, and re-checks right before every voice connect.\nKnown source: LocalMultiplayer swaps both AppIds for its own Photon app on every NetworkConnector.Start - a voice first-connect that hits the rewrite lands in the other app's same-named room: same region and name, invisible to everyone, muted both ways.\nDisable only if you intentionally run a custom Photon app." }, { "joinwait_timeout", "[RoomCode] Timed out waiting for the master server (state: {0}, region: {1}); join cancelled." }, { "joinwait_ready", "[RoomCode] Master server ready (region {0}), continuing to join" }, { "cfgn_invite_retry", "Retry the invite handshake on timeout" }, { "cfg_invite_retry", "After accepting a Steam invite the client asks the lobby for the room ID, and the host only answers while actually sitting in a room. The game's handshake has no timeout, never retries and shows nothing at all, so if the host is in a menu, still loading or has just dropped, you stay silently stuck on the main menu forever. With this on the request is retried every 8 seconds, up to 4 times, and the failure message names both likely causes." }, { "invite_requested", "[Invite] Asked the lobby for the room ID (attempt {0})" }, { "invite_retry", "[Invite] No room ID after 8s, asking again ({0}/{1})" }, { "invite_failed", "[Invite] Still no room ID after {0} attempts. The host may not be in a room (in a menu, loading, or just dropped), or you may not be able to reach the host's region. Ask the host to enter the room before inviting you, or use the room code instead." } }; public static bool IsChinese => _lang == "中文"; public static string DetectDefault() { if (!CultureInfo.CurrentUICulture.Name.StartsWith("zh")) { return "English"; } return "中文"; } public static void Init(string lang) { _lang = lang; _current = ((_lang == "中文") ? ZH : EN); } public static string Get(string key) { if (_current != null && _current.TryGetValue(key, out var value)) { return value; } if (ZH.TryGetValue(key, out var value2)) { return value2; } return key; } public static string Get(string key, params object[] args) { return string.Format(Get(key), args); } } public class CacheEntry { public string IP; public float LastSeenTime; public string PlayerName; public byte RemoteState; public string ModVersion; public string VoiceRegion; public int VoicePing = -1; public int GamePing = -1; } public class SOSData { public int ActorNumber; public string PlayerName; public string TargetIP; public string OriginIP; public float ReceiveTime; } public static class NetworkManager { public enum RoomVoiceSource { Majority, Host, SingleReport, LocalGuess, Unknown } public static Dictionary<int, CacheEntry> PlayerCache = new Dictionary<int, CacheEntry>(); public static List<SOSData> ActiveSOSList = new List<SOSData>(); public static List<string> HostHistory = new List<string>(); private static string LastKnownHostIP = ""; private static string LastDecisionLog = ""; private static ClientState lastClientState = (ClientState)14; public static PunVoiceClient punVoice; private static float nextRetryTime = 0f; private static bool reconnectPending = false; private static string reconnectPendingMode = ""; private static float reconnectPendingDeadline = 0f; private const float RECONNECT_WAIT_TIMEOUT = 5f; private static MethodInfo initNetworkVoiceMethod; private static bool initNetworkVoiceResolved = false; private static float lastPingPublishTime = 0f; private static float lastSOSTime = 0f; private static float nextSummaryLogTime = 0f; private static bool wasInRoom = false; private static float nextVoiceClientFindTime = 0f; private static float nextSOSManageTime = 0f; private static int lastPlayerCount = 0; private static readonly Dictionary<int, float> scavengeFailTime = new Dictionary<int, float>(); private static readonly Dictionary<int, float> firstSeenTime = new Dictionary<int, float>(); private static readonly Dictionary<int, int> voiceToGameActor = new Dictionary<int, int>(); private static readonly Dictionary<int, int> gameToVoiceActor = new Dictionary<int, int>(); private static float nextVoiceMapRefreshTime = 0f; private const float VOICE_MAP_REFRESH_INTERVAL = 1f; private const float SCAN_INTERVAL = 30f; private const float CACHE_TTL = 180f; private const float PING_PUBLISH_INTERVAL = 30f; private const float VOICE_CLIENT_FIND_INTERVAL = 1f; private const float SOS_MANAGE_INTERVAL = 0.5f; private const float SCAVENGE_RETRY_INTERVAL = 10f; private const int MAX_FAIL_BEFORE_BACKOFF = 12; private const string PROP_IP = "PVF_IP"; private const string PROP_PING = "PVF_Ping"; public const string PROP_VREGION = "PVF_VR"; public const string PROP_VPING = "PVF_VP"; private const byte TYPE_SOS = 0; private const byte TYPE_LOG = 1; private const byte TYPE_STATE = 2; private const byte TYPE_PING = 3; private const float PING_EVENT_INTERVAL = 3f; private static float nextPingEventTime = 0f; private static float nextDiagTime = 0f; private static string lastDiagVoiceRegion = null; private static string lastDiagVoiceRoom = null; private static bool lastDiagIsolated = false; private static string lastDiagRoomVoice = null; private static RoomVoiceSource lastDiagRoomVoiceSource = RoomVoiceSource.Unknown; private static readonly Dictionary<int, string> lastDiagCross = new Dictionary<int, string>(); public static string TargetGameServer { get; private set; } public static bool ConnectedUsingHost { get; private set; } = true; public static bool IsBlindConnect { get; private set; } = false; public static int WrongIPCount { get; private set; } = 0; public static int ConnectionFailCount { get; private set; } = 0; public static int TotalRetryCount { get; private set; } = 0; public static string LastErrorMessage { get; private set; } = ""; public static float LastScanTime { get; private set; } = 0f; public static float LastHostUpdateTime { get; private set; } = 0f; public static string LocalVoiceRegion { get { if ((Object)(object)punVoice == (Object)null || ((VoiceConnection)punVoice).Client == null) { return null; } string cloudRegion = ((LoadBalancingClient)((VoiceConnection)punVoice).Client).CloudRegion; if (!string.IsNullOrEmpty(cloudRegion)) { return cloudRegion; } return null; } } public static int LocalVoicePing { get { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Invalid comparison between Unknown and I4 try { if ((Object)(object)punVoice == (Object)null || ((VoiceConnection)punVoice).Client == null) { return -1; } if ((int)((LoadBalancingClient)((VoiceConnection)punVoice).Client).State != 9) { return -1; } LoadBalancingPeer loadBalancingPeer = ((LoadBalancingClient)((VoiceConnection)punVoice).Client).LoadBalancingPeer; return (loadBalancingPeer != null) ? ((PhotonPeer)loadBalancingPeer).RoundTripTime : (-1); } catch (Exception) { return -1; } } } public static string LocalVoiceRoomName { get { if ((Object)(object)punVoice == (Object)null || ((VoiceConnection)punVoice).Client == null || ((LoadBalancingClient)((VoiceConnection)punVoice).Client).CurrentRoom == null) { return null; } return ((LoadBalancingClient)((VoiceConnection)punVoice).Client).CurrentRoom.Name; } } public static string CurrentDecisionMode { get { if (IsBlindConnect) { return L.Get("log_auto_blind"); } if (ConnectedUsingHost) { return L.Get("log_host"); } GetRoomVoiceServer(out var _, out var reporters); return L.Get("log_majority", reporters); } } public static string GetPlayerName(int actorNumber) { //IL_00b5: Unknown result type (might be due to invalid IL or missing references) string text = "Unknown"; Player val = null; if (PhotonNetwork.CurrentRoom != null) { val = PhotonNetwork.CurrentRoom.GetPlayer(actorNumber, false); } if (val != null && !string.IsNullOrEmpty(val.NickName)) { text = val.NickName; UpdatePlayerCache(actorNumber, text); return text; } if (PlayerCache.ContainsKey(actorNumber)) { string playerName = PlayerCache[actorNumber].PlayerName; if (!string.IsNullOrEmpty(playerName) && playerName != "Unknown" && !playerName.StartsWith("Player ")) { return playerName; } } if ((text == "Unknown" || string.IsNullOrEmpty(text)) && val != null && !string.IsNullOrEmpty(val.UserId)) { try { if (ulong.TryParse(val.UserId, out var result)) { string friendPersonaName = SteamFriends.GetFriendPersonaName(new CSteamID(result)); if (!string.IsNullOrEmpty(friendPersonaName) && friendPersonaName != "[unknown]") { text = friendPersonaName; UpdatePlayerCache(actorNumber, text); return text; } } } catch (Exception) { } } if ((text == "Unknown" || string.IsNullOrEmpty(text)) && (!scavengeFailTime.TryGetValue(actorNumber, out var value) || Time.unscaledTime - value > 10f)) { string text2 = ScavengeNameFromScene(actorNumber); if (!string.IsNullOrEmpty(text2)) { scavengeFailTime.Remove(actorNumber); UpdatePlayerCache(actorNumber, text2); return text2; } scavengeFailTime[actorNumber] = Time.unscaledTime; } if (text == "Unknown") { return $"Player {actorNumber}"; } return text; } public static string ScavengeNameFromScene(int actorNumber) { try { PhotonView[] array = Object.FindObjectsOfType<PhotonView>(); foreach (PhotonView val in array) { if (!((Object)(object)val == (Object)null) && val.OwnerActorNr == actorNumber) { if (val.Owner != null && !string.IsNullOrEmpty(val.Owner.NickName)) { return val.Owner.NickName; } TextMeshProUGUI componentInChildren = ((Component)val).GetComponentInChildren<TextMeshProUGUI>(true); if ((Object)(object)componentInChildren != (Object)null && !string.IsNullOrEmpty(((TMP_Text)componentInChildren).text)) { return ((TMP_Text)componentInChildren).text; } TextMeshPro componentInChildren2 = ((Component)val).GetComponentInChildren<TextMeshPro>(true); if ((Object)(object)componentInChildren2 != (Object)null && !string.IsNullOrEmpty(((TMP_Text)componentInChildren2).text)) { return ((TMP_Text)componentInChildren2).text; } Text componentInChildren3 = ((Component)val).GetComponentInChildren<Text>(true); if ((Object)(object)componentInChildren3 != (Object)null && !string.IsNullOrEmpty(componentInChildren3.text)) { return componentInChildren3.text; } } } } catch (Exception) { } return null; } public static void UpdatePlayerCache(int actorNumber, string name, string ip = null, string version = null) { if (!PlayerCache.ContainsKey(actorNumber)) { PlayerCache[actorNumber] = new CacheEntry(); } if (!string.IsNullOrEmpty(name) && name != "Unknown") { PlayerCache[actorNumber].PlayerName = name; } if (!string.IsNullOrEmpty(ip)) { PlayerCache[actorNumber].IP = ip; } if (!string.IsNullOrEmpty(version)) { PlayerCache[actorNumber].ModVersion = version; } PlayerCache[actorNumber].LastSeenTime = Time.unscaledTime; } private static void RefreshVoiceActorMap() { if (Time.unscaledTime < nextVoiceMapRefreshTime) { return; } nextVoiceMapRefreshTime = Time.unscaledTime + 1f; voiceToGameActor.Clear(); gameToVoiceActor.Clear(); try { PhotonVoiceView[] array = Object.FindObjectsOfType<PhotonVoiceView>(); foreach (PhotonVoiceView val in array) { if ((Object)(object)val == (Object)null || (Object)(object)val.SpeakerInUse == (Object)null || !val.SpeakerInUse.IsLinked) { continue; } RemoteVoiceLink remoteVoice = val.SpeakerInUse.RemoteVoice; if (remoteVoice != null) { PhotonView component = ((Component)val).GetComponent<PhotonView>(); int num = (((Object)(object)component != (Object)null) ? component.OwnerActorNr : 0); if (num > 0) { voiceToGameActor[remoteVoice.PlayerId] = num; gameToVoiceActor[num] = remoteVoice.PlayerId; } } } } catch (Exception) { } } public static bool IsGhost(int voiceActorNumber) { if (PhotonNetwork.CurrentRoom == null) { return true; } if ((Object)(object)punVoice == (Object)null || ((VoiceConnection)punVoice).Client == null || ((LoadBalancingClient)((VoiceConnection)punVoice).Client).CurrentRoom == null) { return true; } if (!((LoadBalancingClient)((VoiceConnection)punVoice).Client).CurrentRoom.Players.TryGetValue(voiceActorNumber, out var value)) { return true; } if (value.IsLocal) { return false; } RefreshVoiceActorMap(); if (voiceToGameActor.TryGetValue(voiceActorNumber, out var value2)) { return PhotonNetwork.CurrentRoom.GetPlayer(value2, false) == null; } string userId = value.UserId; if (!string.IsNullOrEmpty(userId)) { Player[] playerList = PhotonNetwork.PlayerList; foreach (Player val in playerList) { if (!string.IsNullOrEmpty(val.UserId) && val.UserId == userId) { return false; } } return true; } if (PhotonNetwork.CurrentRoom.GetPlayer(voiceActorNumber, false) != null) { if (gameToVoiceActor.TryGetValue(voiceActorNumber, out var value3) && value3 != voiceActorNumber) { return true; } return false; } return true; } public static bool IsPlayerInVoiceRoom(int gameActorNumber) { //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Invalid comparison between Unknown and I4 if ((Object)(object)punVoice == (Object)null || ((VoiceConnection)punVoice).Client == null || ((LoadBalancingClient)((VoiceConnection)punVoice).Client).CurrentRoom == null) { return false; } if (PhotonNetwork.CurrentRoom == null) { return false; } Player player = PhotonNetwork.CurrentRoom.GetPlayer(gameActorNumber, false); if (player == null) { return false; } if (player.IsLocal) { return (int)((LoadBalancingClient)((VoiceConnection)punVoice).Client).State == 9; } RefreshVoiceActorMap(); if (gameToVoiceActor.TryGetValue(gameActorNumber, out var value)) { return ((LoadBalancingClient)((VoiceConnection)punVoice).Client).CurrentRoom.Players.ContainsKey(value); } string userId = player.UserId; if (!string.IsNullOrEmpty(userId)) { foreach (KeyValuePair<int, Player> player2 in ((LoadBalancingClient)((VoiceConnection)punVoice).Client).CurrentRoom.Players) { if (!string.IsNullOrEmpty(player2.Value.UserId) && player2.Value.UserId == userId) { return true; } } } if (((LoadBalancingClient)((VoiceConnection)punVoice).Client).CurrentRoom.Players.TryGetValue(gameActorNumber, out var value2)) { if (voiceToGameActor.TryGetValue(gameActorNumber, out var value3) && value3 != gameActorNumber) { return false; } if (!string.IsNullOrEmpty(value2.UserId) && value2.UserId != userId) { return false; } return true; } return false; } public static int GetGhostCount() { if ((Object)(object)punVoice == (Object)null || ((VoiceConnection)punVoice).Client == null || ((LoadBalancingClient)((VoiceConnection)punVoice).Client).CurrentRoom == null) { return 0; } if (PhotonNetwork.CurrentRoom != null && PhotonNetwork.CurrentRoom.PlayerCount <= 1) { return 0; } int num = 0; foreach (int key in ((LoadBalancingClient)((VoiceConnection)punVoice).Client).CurrentRoom.Players.Keys) { if (IsGhost(key)) { num++; } } return num; } public static bool IsModUser(Player p) { if (p == null) { return false; } if (p.IsLocal) { return true; } if (p.CustomProperties != null && ((Dictionary<object, object>)(object)p.CustomProperties).ContainsKey((object)"PVF_Ping")) { return true; } if (PlayerCache.TryGetValue(p.ActorNumber, out var value) && !string.IsNullOrEmpty(value.ModVersion)) { return true; } return false; } public static bool IsModUser(int actorNumber) { Player val = ((PhotonNetwork.CurrentRoom != null) ? PhotonNetwork.CurrentRoom.GetPlayer(actorNumber, false) : null); if (val != null) { return IsModUser(val); } if (PlayerCache.TryGetValue(actorNumber, out var value) && !string.IsNullOrEmpty(value.ModVersion)) { return true; } return false; } private static void PurgeDepartedActors() { if (PhotonNetwork.CurrentRoom == null) { return; } List<int> list = new List<int>(); foreach (int key in PlayerCache.Keys) { if (PhotonNetwork.CurrentRoom.GetPlayer(key, false) == null) { list.Add(key); } } foreach (int item in list) { PlayerCache.Remove(item); scavengeFailTime.Remove(item); firstSeenTime.Remove(item); } ActiveSOSList.RemoveAll((SOSData s) => PhotonNetwork.CurrentRoom.GetPlayer(s.ActorNumber, false) == null); } private static void TrackFirstSeen() { if (PhotonNetwork.PlayerList == null) { return; } Player[] playerList = PhotonNetwork.PlayerList; foreach (Player val in playerList) { if (val != null && !val.IsLocal && !firstSeenTime.ContainsKey(val.ActorNumber)) { firstSeenTime[val.ActorNumber] = Time.unscaledTime; } } } public static void SystemUpdate() { //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Invalid comparison between Unknown and I4 //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) if (!PhotonNetwork.InRoom) { if (wasInRoom) { ResetRoomScopedState(); } wasInRoom = false; return; } wasInRoom = true; if (PhotonNetwork.OfflineMode) { return; } int num = ((PhotonNetwork.PlayerList != null) ? PhotonNetwork.PlayerList.Length : 0); if (num < lastPlayerCount) { PurgeDepartedActors(); } lastPlayerCount = num; TrackFirstSeen(); if ((Object)(object)punVoice == (Object)null && Time.unscaledTime >= nextVoiceClientFindTime) { nextVoiceClientFindTime = Time.unscaledTime + 1f; try { punVoice = Object.FindFirstObjectByType<PunVoiceClient>(); } catch (Exception) { punVoice = null; } } if ((Object)(object)punVoice != (Object)null && ((VoiceConnection)punVoice).Client != null) { ClientState state = ((LoadBalancingClient)((VoiceConnection)punVoice).Client).State; if (state != lastClientState) { BroadcastLog(string.Format("{0}: {1} -> {2}", L.Get("log_state_change"), lastClientState, state)); SendStateSync(state); if ((int)state == 9) { ConnectionFailCount = 0; TryInitNetworkVoice(); } UpdateDataLayer(force: true); lastClientState = state; } } ProcessPendingReconnect(); UpdateDataLayer(); PublishPingEvent(); TrackDiagnostics(); HandleInputAndState(); ManageSOSList(); if ((Object)(object)punVoice != (Object)null && ((VoiceConnection)punVoice).Client != null && !reconnectPending && Time.unscaledTime >= nextRetryTime) { if (PhotonNetwork.IsMasterClient) { HandleHostLogic(); } else { HandleClientLogic(); } } } private static void PublishPingEvent() { //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Expected O, but got Unknown //IL_0089: Unknown result type (might be due to invalid IL or missing references) if (!(Time.unscaledTime < nextPingEventTime)) { nextPingEventTime = Time.unscaledTime + 3f; if (PhotonNetwork.IsConnectedAndReady && PhotonNetwork.CurrentRoom != null && PhotonNetwork.CurrentRoom.PlayerCount > 1) { object[] array = new object[4] { (byte)3, PhotonNetwork.GetPing(), LocalVoicePing, LocalVoiceRegion ?? "" }; RaiseEventOptions val = new RaiseEventOptions { Receivers = (ReceiverGroup)0 }; PhotonNetwork.RaiseEvent((byte)186, (object)array, val, SendOptions.SendUnreliable); } } } public static void SendStateSync(ClientState state) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Expected O, but got Unknown //IL_0038: Unknown result type (might be due to invalid IL or missing references) byte b = (byte)state; object[] array = new object[3] { (byte)2, b, "v1.1.3" }; RaiseEventOptions val = new RaiseEventOptions { Receivers = (ReceiverGroup)0 }; PhotonNetwork.RaiseEvent((byte)186, (object)array, val, SendOptions.SendReliable); } public static void BroadcastLog(string message) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Expected O, but got Unknown //IL_0054: Unknown result type (might be due to invalid IL or missing references) string playerName = GetPlayerName(PhotonNetwork.LocalPlayer.ActorNumber); if ((Object)(object)VoiceUIManager.Instance != (Object)null) { VoiceUIManager.Instance.AddLog(playerName, message, isLocal: true); } byte b = 186; object[] array = new object[2] { (byte)1, message }; RaiseEventOptions val = new RaiseEventOptions { Receivers = (ReceiverGroup)0 }; PhotonNetwork.RaiseEvent(b, (object)array, val, SendOptions.SendReliable); } private static void UpdateDataLayer(bool force = false) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Expected O, but got Unknown //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Invalid comparison between Unknown and I4 bool flag = Time.unscaledTime - lastPingPublishTime > 30f; if (force || flag) { lastPingPublishTime = Time.unscaledTime; Hashtable val = new Hashtable(); int actorNumber = PhotonNetwork.LocalPlayer.ActorNumber; int ping = PhotonNetwork.GetPing(); string localVoiceRegion = LocalVoiceRegion; int localVoicePing = LocalVoicePing; val[(object)"PVF_Ping"] = ping; string ip = (string)(val[(object)"PVF_IP"] = (((Object)(object)punVoice != (Object)null && ((VoiceConnection)punVoice).Client != null && (int)((LoadBalancingClient)((VoiceConnection)punVoice).Client).State == 9) ? ((LoadBalancingClient)((VoiceConnection)punVoice).Client).GameServerAddress : "")); val[(object)"PVF_VR"] = localVoiceRegion ?? ""; val[(object)"PVF_VP"] = localVoicePing; PhotonNetwork.LocalPlayer.SetCustomProperties(val, (Hashtable)null, (WebFlags)null); UpdatePlayerCache(actorNumber, PhotonNetwork.LocalPlayer.NickName, ip, "v1.1.3"); CacheEntry cacheEntry = PlayerCache[actorNumber]; cacheEntry.VoiceRegion = localVoiceRegion; cacheEntry.VoicePing = localVoicePing; cacheEntry.GamePing = ping; } if (!force) { if (Time.unscaledTime - LastScanTime > 30f) { LastScanTime = Time.unscaledTime; ScanPlayers(); } if (Time.unscaledTime > nextSummaryLogTime) { PrintSummaryLog(); nextSummaryLogTime = Time.unscaledTime + 60f; } } } private static void PrintSummaryLog() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine(string.Format("{0}:{1}", L.Get("log_cache_snapshot"), PlayerCache.Count)); foreach (KeyValuePair<int, CacheEntry> item in PlayerCache) { string arg = (string.IsNullOrEmpty(item.Value.IP) ? "N/A" : item.Value.IP); stringBuilder.AppendLine($" - {item.Value.PlayerName}: {arg} (St:{item.Value.RemoteState})"); } if ((Object)(object)VoiceUIManager.Instance != (Object)null) { VoiceUIManager.Instance.AddLog("System", stringBuilder.ToString(), isLocal: true); } } public static void CacheVoiceInfo(Player p) { if (p != null && p.CustomProperties != null) { if (!PlayerCache.TryGetValue(p.ActorNumber, out var value)) { value = new CacheEntry { LastSeenTime = Time.unscaledTime }; PlayerCache[p.ActorNumber] = value; } if (((Dictionary<object, object>)(object)p.CustomProperties).TryGetValue((object)"PVF_VR", out object value2) && value2 is string text) { value.VoiceRegion = (string.IsNullOrEmpty(text) ? null : text); } if (((Dictionary<object, object>)(object)p.CustomProperties).TryGetValue((object)"PVF_VP", out value2) && value2 is int voicePing) { value.VoicePing = voicePing; } } } private static void ScanPlayers() { Player[] playerListOthers = PhotonNetwork.PlayerListOthers; foreach (Player val in playerListOthers) { CacheVoiceInfo(val); object value = null; if (!((Dictionary<object, object>)(object)val.CustomProperties).TryGetValue((object)"PVF_IP", out value) || !(value is string text)) { continue; } string playerName = GetPlayerName(val.ActorNumber); UpdatePlayerCache(val.ActorNumber, playerName, text); if (!val.IsMasterClient) { continue; } if (!string.IsNullOrEmpty(text)) { LastHostUpdateTime = Time.unscaledTime; } if (!string.IsNullOrEmpty(LastKnownHostIP) && LastKnownHostIP != text && !string.IsNullOrEmpty(text)) { BroadcastLog(L.Get("log_host_ip_change") + ": " + LastKnownHostIP + " -> " + text); if (HostHistory.Count > 5) { HostHistory.RemoveAt(0); } HostHistory.Add($"[{DateTime.Now:HH:mm:ss}] {text}"); } LastKnownHostIP = text; } foreach (int item in (from x in PlayerCache where Time.unscaledTime - x.Value.LastSeenTime > 180f select x.Key).ToList()) { PlayerCache.Remove(item); } } private static void ManageSOSList() { if (ActiveSOSList.Count == 0 || Time.unscaledTime < nextSOSManageTime) { return; } nextSOSManageTime = Time.unscaledTime + 0.5f; for (int num = ActiveSOSList.Count - 1; num >= 0; num--) { SOSData sOSData = ActiveSOSList[num]; if (Time.unscaledTime - sOSData.ReceiveTime > 60f) { ActiveSOSList.RemoveAt(num); } else { string playerName = GetPlayerName(sOSData.ActorNumber); if ((string.IsNullOrEmpty(playerName) || playerName == "Unknown" || playerName.StartsWith("Player ")) && (PhotonNetwork.CurrentRoom == null || PhotonNetwork.CurrentRoom.GetPlayer(sOSData.ActorNumber, false) == null)) { ActiveSOSList.RemoveAt(num); } else if (PhotonNetwork.CurrentRoom != null) { Player player = PhotonNetwork.CurrentRoom.GetPlayer(sOSData.ActorNumber, false); object value = null; if (player != null && ((Dictionary<object, object>)(object)player.CustomProperties).TryGetValue((object)"PVF_IP", out value) && value is string value2 && !string.IsNullOrEmpty(value2)) { ActiveSOSList.RemoveAt(num); } } } } } private static void HandleHostLogic() { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Invalid comparison between Unknown and I4 if ((int)((LoadBalancingClient)((VoiceConnection)punVoice).Client).State == 14) { BroadcastLog(L.Get("log_host_disconnected")); ConnectVoiceNow(L.Get("log_host")); nextRetryTime = Time.unscaledTime + 5f; } } private static void HandleClientLogic() { //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Invalid comparison between Unknown and I4 //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Invalid comparison between Unknown and I4 //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Invalid comparison between Unknown and I4 string mode; string text = DecideTargetIP(out mode); if (!string.IsNullOrEmpty(text) && ConnectionFailCount > 0 && ConnectionFailCount % 6 >= 3) { BroadcastLog(L.Get("log_loop_blind", ConnectionFailCount)); text = null; mode += "->BlindLoop"; } if (string.IsNullOrEmpty(text)) { IsBlindConnect = true; TargetGameServer = null; if ((int)((LoadBalancingClient)((VoiceConnection)punVoice).Client).State == 14) { PerformReconnect("Blind"); } return; } IsBlindConnect = false; TargetGameServer = text; string gameServerAddress = ((LoadBalancingClient)((VoiceConnection)punVoice).Client).GameServerAddress; ClientState state = ((LoadBalancingClient)((VoiceConnection)punVoice).Client).State; if ((int)state == 9) { if (gameServerAddress == TargetGameServer) { WrongIPCount = 0; ConnectionFailCount = 0; TotalRetryCount = 0; return; } WrongIPCount++; if (WrongIPCount <= 2) { BroadcastLog(L.Get("log_wrong_freq", gameServerAddress, TargetGameServer, WrongIPCount) ?? ""); PerformReconnect(mode); } else if (WrongIPCount == 3) { BroadcastLog(L.Get("log_compromise", gameServerAddress)); } } else if ((int)state == 14) { ConnectionFailCount++; if (ConnectionFailCount > 12) { nextRetryTime = Time.unscaledTime + Mathf.Min(VoiceFix.RetryInterval.Value * 5f, 60f); } else { PerformReconnect(mode); } } } private static bool ConnectVoiceNow(string mode) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Expected O, but got Unknown //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Expected O, but got Unknown if ((Object)(object)punVoice == (Object)null || ((VoiceConnection)punVoice).Client == null) { return false; } try { LoadBalancingTransport client = ((VoiceConnection)punVoice).Client; AppSettings val = PhotonNetwork.PhotonServerSettings.AppSettings.CopyTo(new AppSettings()); string cloudRegion = PhotonNetwork.CloudRegion; if (!string.IsNullOrEmpty(cloudRegion)) { val.FixedRegion = cloudRegion; } ((LoadBalancingClient)client).SerializationProtocol = PhotonNetwork.NetworkingClient.SerializationProtocol; if (PhotonNetwork.AuthValues != null) { if (((LoadBalancingClient)client).AuthValues == null) { ((LoadBalancingClient)client).AuthValues = new AuthenticationValues(); } ((LoadBalancingClient)client).AuthValues = PhotonNetwork.AuthValues.CopyTo(((LoadBalancingClient)client).AuthValues); } ((LoadBalancingClient)client).AuthMode = PhotonNetwork.NetworkingClient.AuthMode; ((LoadBalancingClient)client).EncryptionMode = PhotonNetwork.NetworkingClient.EncryptionMode; BroadcastLog(L.Get("log_reconnect_go", string.IsNullOrEmpty(mode) ? "Auto" : mode, string.IsNullOrEmpty(cloudRegion) ? "?" : cloudRegion)); return ((VoiceConnection)punVoice).ConnectUsingSettings(val); } catch (Exception ex) { LastErrorMessage = ex.Message; if (VoiceFix.logger != null) { VoiceFix.logger.LogError((object)$"[PVF] ConnectVoiceNow: {ex}"); } return false; } } private static void TryInitNetworkVoice() { try { if (!initNetworkVoiceResolved) { initNetworkVoiceResolved = true; Type type = AccessTools.TypeByName("VoiceClientHandler"); if (type != null) { initNetworkVoiceMethod = AccessTools.Method(type, "InitNetworkVoice", (Type[])null, (Type[])null); } } initNetworkVoiceMethod?.Invoke(null, null); } catch (Exception) { } } private static void ProcessPendingReconnect() { //IL_003c: Unknown result type (might be due to invalid IL or missing references) if (!reconnectPending) { return; } if ((Object)(object)punVoice == (Object)null || ((VoiceConnection)punVoice).Client == null) { reconnectPending = false; return; } LoadBalancingPeer loadBalancingPeer = ((LoadBalancingClient)((VoiceConnection)punVoice).Client).LoadBalancingPeer; if (loadBalancingPeer != null && (int)((PhotonPeer)loadBalancingPeer).PeerState != 0) { if (Time.unscaledTime > reconnectPendingDeadline) { reconnectPending = false; BroadcastLog(L.Get("log_reconnect_timeout")); } } else { reconnectPending = false; ConnectVoiceNow(reconnectPendingMode); } } private static string DecideTargetIP(out string mode) { RoomVoiceSource source; int reporters; string roomVoiceServer = GetRoomVoiceServer(out source, out reporters); if (source == RoomVoiceSource.Majority && !string.IsNullOrEmpty(roomVoiceServer)) { mode = L.Get("log_majority", reporters); ConnectedUsingHost = false; LogDecision(mode, roomVoiceServer); return roomVoiceServer; } if (source == RoomVoiceSource.Host && !string.IsNullOrEmpty(roomVoiceServer)) { mode = L.Get("log_host"); ConnectedUsingHost = true; LogDecision(mode, roomVoiceServer); return roomVoiceServer; } mode = L.Get("log_auto_blind"); ConnectedUsingHost = false; LogDecision(mode, "Auto"); return null; } private static void LogDecision(string mode, string target) { string text = mode + "->" + target; if (text != LastDecisionLog) { BroadcastLog(L.Get("log_decision") + ": " + text); LastDecisionLog = text; } } public static string GetMajorityIP(out int maxCount) { maxCount = 0; if (PlayerCache.Count == 0) { return null; } Dictionary<string, int> dictionary = new Dictionary<string, int>(); foreach (KeyValuePair<int, CacheEntry> item in PlayerCache) { if (!string.IsNullOrEmpty(item.Value.IP)) { if (!dictionary.ContainsKey(item.Value.IP)) { dictionary[item.Value.IP] = 0; } dictionary[item.Value.IP]++; } } string result = null; foreach (KeyValuePair<string, int> item2 in dictionary) { if (item2.Value > maxCount) { maxCount = item2.Value; result = item2.Key; } } return result; } public static string GetMajorityRegion(out int maxCount) { maxCount = 0; if (PlayerCache.Count == 0) { return null; } Dictionary<string, int> dictionary = new Dictionary<string, int>(); foreach (KeyValuePair<int, CacheEntry> item in PlayerCache) { string voiceRegion = item.Value.VoiceRegion; if (!string.IsNullOrEmpty(voiceRegion)) { dictionary.TryGetValue(voiceRegion, out var value); dictionary[voiceRegion] = value + 1; } } string result = null; foreach (KeyValuePair<string, int> item2 in dictionary) { if (item2.Value > maxCount) { maxCount = item2.Value; result = item2.Key; } } return result; } public static string GetRoomVoiceServer(out RoomVoiceSource source, out int reporters) { //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Invalid comparison between Unknown and I4 source = RoomVoiceSource.Unknown; reporters = 0; if (PhotonNetwork.CurrentRoom == null) { return null; } Dictionary<string, int> dictionary = new Dictionary<string, int>(); string text = null; string text2 = null; Player[] playerListOthers = PhotonNetwork.PlayerListOthers; foreach (Player val in playerListOthers) { if (val == null || val.CustomProperties == null || !((Dictionary<object, object>)(object)val.CustomProperties).TryGetValue((object)"PVF_IP", out object value)) { continue; } string text3 = value as string; if (!string.IsNullOrEmpty(text3)) { dictionary.TryGetValue(text3, out var value2); dictionary[text3] = value2 + 1; if (val.IsMasterClient) { text = text3; } if (text2 == null) { text2 = text3; } } } string result = null; int num = 0; foreach (KeyValuePair<string, int> item in dictionary) { if (item.Value > num) { num = item.Value; result = item.Key; } } if (num >= 2) { source = RoomVoiceSource.Majority; reporters = num; return result; } if (!string.IsNullOrEmpty(text)) { source = RoomVoiceSource.Host; reporters = 1; return text; } if (!string.IsNullOrEmpty(text2)) { source = RoomVoiceSource.SingleReport; reporters = 1; return text2; } if ((Object)(object)punVoice != (Object)null && ((VoiceConnection)punVoice).Client != null && (int)((LoadBalancingClient)((VoiceConnection)punVoice).Client).State == 9) { string gameServerAddress = ((LoadBalancingClient)((VoiceConnection)punVoice).Client).GameServerAddress; if (!string.IsNullOrEmpty(gameServerAddress)) { source = RoomVoiceSource.LocalGuess; return gameServerAddress; } } return null; } public static bool IsLocalIsolated() { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Invalid comparison between Unknown and I4 if ((Object)(object)punVoice == (Object)null || ((VoiceConnection)punVoice).Client == null) { return false; } if ((int)((LoadBalancingClient)((VoiceConnection)punVoice).Client).State != 9) { return false; } if (((LoadBalancingClient)((VoiceConnection)punVoice).Client).CurrentRoom == null) { return false; } if (PhotonNetwork.CurrentRoom == null) { return false; } if (PhotonNetwork.CurrentRoom.PlayerCount <= 1) { return false; } if (((LoadBalancingClient)((VoiceConnection)punVoice).Client).CurrentRoom.Players.Count > 1) { return false; } float num = ((VoiceFix.ConnectTimeout != null) ? VoiceFix.ConnectTimeout.Value : 25f); Player[] playerListOthers = PhotonNetwork.PlayerListOthers; foreach (Player val in playerListOthers) { if (val != null) { if (val.CustomProperties != null && ((Dictionary<object, object>)(object)val.CustomProperties).TryGetValue((object)"PVF_IP", out object value) && value is string value2 && !string.IsNullOrEmpty(value2)) { return true; } if (firstSeenTime.TryGetValue(val.ActorNumber, out var value3) && Time.unscaledTime - value3 > num) { return true; } } } return false; } public static void GetRetryStats(out int total, out int fail, out int wrongIP) { total = TotalRetryCount; fail = ConnectionFailCount; wrongIP = WrongIPCount; } private static void TrackDiagnostics() { if (Time.unscaledTime < nextDiagTime) { return; } nextDiagTime = Time.unscaledTime + 1f; string localVoiceRegion = LocalVoiceRegion; if (localVoiceRegion != lastDiagVoiceRegion) { DiagLog(L.Get("diag_vregion_change", lastDiagVoiceRegion ?? "—", localVoiceRegion ?? "—")); lastDiagVoiceRegion = localVoiceRegion; } string localVoiceRoomName = LocalVoiceRoomName; if (localVoiceRoomName != lastDiagVoiceRoom) { string text = "—"; try { if ((Object)(object)punVoice != (Object)null && ((VoiceConnection)punVoice).Client != null && !string.IsNullOrEmpty(((LoadBalancingClient)((VoiceConnection)punVoice).Client).AppId)) { text = ((LoadBalancingClient)((VoiceConnection)punVoice).Client).AppId; } } catch (Exception) { } DiagLog(L.Get("diag_vroom_change", lastDiagVoiceRoom ?? "—", localVoiceRoomName ?? "—", text)); lastDiagVoiceRoom = localVoiceRoomName; } bool flag = IsLocalIsolated(); if (flag != lastDiagIsolated) { int num = ((PhotonNetwork.CurrentRoom != null) ? PhotonNetwork.CurrentRoom.PlayerCount : 0); int num2 = (((Object)(object)punVoice != (Object)null && ((VoiceConnection)punVoice).Client != null && ((LoadBalancingClient)((VoiceConnection)punVoice).Client).CurrentRoom != null) ? ((LoadBalancingClient)((VoiceConnection)punVoice).Client).CurrentRoom.Players.Count : 0); DiagLog(flag ? L.Get("diag_isolated", num) : L.Get("diag_isolated_clear", num2)); lastDiagIsolated = flag; } RoomVoiceSource source; int reporters; string roomVoiceServer = GetRoomVoiceServer(out source, out reporters); if (roomVoiceServer != lastDiagRoomVoice || source != lastDiagRoomVoiceSource) { if (string.IsNullOrEmpty(roomVoiceServer)) { DiagLog(L.Get("diag_roomvoice_lost")); } else { DiagLog(L.Get("diag_roomvoice", roomVoiceServer, source.ToString())); } lastDiagRoomVoice = roomVoiceServer; lastDiagRoomVoiceSource = source; } string cloudRegion = PhotonNetwork.CloudRegion; if (PhotonNetwork.CurrentRoom == null || string.IsNullOrEmpty(cloudRegion)) { return; } Player[] playerListOthers = PhotonNetwork.PlayerListOthers; foreach (Player val in playerListOthers) { if (val == null) { continue; } CacheEntry value; string text2 = (PlayerCache.TryGetValue(val.ActorNumber, out value) ? value.VoiceRegion : null); lastDiagCross.TryGetValue(val.ActorNumber, out var value2); bool flag2 = !string.IsNullOrEmpty(text2) && text2 != cloudRegion; string text3 = (flag2 ? text2 : null); if (!(text3 == value2)) { if (flag2) { DiagLog(L.Get("diag_cross", GetPlayerName(val.ActorNumber), text2, cloudRegion)); } else if (!string.IsNullOrEmpty(value2)) { DiagLog(L.Get("diag_cross_clear", GetPlayerName(val.ActorNumber), cloudRegion)); } lastDiagCross[val.ActorNumber] = text3; } } } public static void DiagLog(string msg) { if (!string.IsNullOrEmpty(msg)) { if (VoiceFix.logger != null) { VoiceFix.logger.LogInfo((object)msg); } if ((Object)(object)VoiceUIManager.Instance != (Object)null) { VoiceUIManager.Instance.AddLog("System", msg, isLocal: true); } } } private static void PerformReconnect(string mode) { //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Invalid comparison between Unknown and I4 if (!((Object)(object)punVoice == (Object)null) && ((VoiceConnection)punVoice).Client != null) { TotalRetryCount++; nextRetryTime = Time.unscaledTime + VoiceFix.RetryInterval.Value; if (Time.unscaledTime - lastSOSTime > 20f && PhotonNetwork.IsConnectedAndReady) { lastSOSTime = Time.unscaledTime; SendSOS(string.IsNullOrEmpty(TargetGameServer) ? "Unknown" : TargetGameServer); } reconnectPending = true; reconnectPendingMode = mode; reconnectPendingDeadline = Time.unscaledTime + 5f; if ((int)((LoadBalancingClient)((VoiceConnection)punVoice).Client).State != 14) { ((LoadBalancingClient)((VoiceConnection)punVoice).Client).Disconnect(); } } } private static void SendSOS(string targetInfo) { //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Expected O, but got Unknown //IL_00c9: Unknown result type (might be due to invalid IL or missing references) string text = "Unknown"; if ((Object)(object)punVoice != (Object)null && ((VoiceConnection)punVoice).Client != null) { text = ((LoadBalancingClient)((VoiceConnection)punVoice).Client).GameServerAddress; } if (string.IsNullOrEmpty(text)) { text = "Disconnected"; } BroadcastLog(L.Get("log_sos_send") + " -> " + L.Get("log_sos_target") + ":" + targetInfo + " | " + L.Get("log_sos_local") + ":" + text); object[] array = new object[3] { (byte)0, targetInfo, text }; RaiseEventOptions val = new RaiseEventOptions { Receivers = (ReceiverGroup)0 }; PhotonNetwork.RaiseEvent((byte)186, (object)array, val, SendOptions.SendReliable); } public static void OnEvent(EventData photonEvent) { if (photonEvent.Code != 186) { return; } int senderActor = photonEvent.Sender; string playerName = GetPlayerName(senderActor); if (!(photonEvent.CustomData is object[] array) || array.Length < 2) { return; } byte b = 0; if (array[0] is byte b2) { b = b2; } else if (array[0] is int num) { b = (byte)num; } switch (b) { case 1: { string msg = array[1] as string; if ((Object)(object)VoiceUIManager.Instance != (Object)null) { VoiceUIManager.Instance.AddLog(playerName, msg, isLocal: false); } break; } case 0: { string text4 = array[1] as string; string originIP = "Unknown(old)"; if (array.Length >= 3 && array[2] is string text5) { originIP = text5; } else if (PlayerCache.ContainsKey(senderActor)) { originIP = PlayerCache[senderActor].IP; } ActiveSOSList.RemoveAll((SOSData x) => x.ActorNumber == senderActor); ActiveSOSList.Add(new SOSData { ActorNumber = senderActor, PlayerName = playerName, TargetIP = text4, OriginIP = originIP, ReceiveTime = Time.unscaledTime }); if ((Object)(object)VoiceUIManager.Instance != (Object)null) { VoiceUIManager.Instance.AddLog("System", L.Get("log_sos_received", playerName, text4), isLocal: true); VoiceUIManager.Instance.TriggerNotification(playerName); } break; } case 2: { byte remoteState = 0; if (array[1] is byte b3) { remoteState = b3; } else if (array[1] is int num2) { remoteState = (byte)num2; } string text2 = ""; if (array.Length >= 3 && array[2] is string text3) { text2 = text3; } if (PlayerCache.ContainsKey(senderActor)) { PlayerCache[senderActor].RemoteState = remoteState; if (!string.IsNullOrEmpty(text2)) { PlayerCache[senderActor].ModVersion = text2; } PlayerCache[senderActor].LastSeenTime = Time.unscaledTime; } else { CacheEntry value2 = new CacheEntry { PlayerName = playerName, LastSeenTime = Time.unscaledTime, RemoteState = remoteState, ModVersion = text2 }; PlayerCache[senderActor] = value2; } break; } case 3: { if (!PlayerCache.TryGetValue(senderActor, out var value)) { value = new CacheEntry { PlayerName = playerName }; PlayerCache[senderActor] = value; } if (array.Length >= 2 && array[1] is int gamePing) { value.GamePing = gamePing; } if (array.Length >= 3 && array[2] is int voicePing) { value.VoicePing = voicePing; } if (array.Length >= 4 && array[3] is string text) { value.VoiceRegion = (string.IsNullOrEmpty(text) ? null : text); } value.LastSeenTime = Time.unscaledTime; break; } } } private static void HandleInputAndState() { //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Invalid comparison between Unknown and I4 //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Invalid comparison between Unknown and I4 //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Invalid comparison between Unknown and I4 if (VoiceFix.EnableManualReconnect == null || !VoiceFix.EnableManualReconnect.Value || (!Input.GetKey((KeyCode)308) && !Input.GetKey((KeyCode)307)) || !Input.GetKeyDown((KeyCode)107)) { return; } if ((Object)(object)punVoice == (Object)null || ((VoiceConnection)punVoice).Client == null) { BroadcastLog("[System] Alt+K ignored: Voice client not ready."); return; } if ((int)((LoadBalancingClient)((VoiceConnection)punVoice).Client).State == 9 || (int)((LoadBalancingClient)((VoiceConnection)punVoice).Client).State == 6 || (int)((LoadBalancingClient)((VoiceConnection)punVoice).Client).State == 1) { BroadcastLog(L.Get("log_alt_k_disconnect")); if (PhotonNetwork.IsConnectedAndReady) { SendSOS(L.Get("log_sos_manual")); } ((LoadBalancingClient)((VoiceConnection)punVoice).Client).Disconnect(); if ((Object)(object)VoiceUIManager.Instance != (Object)null) { VoiceUIManager.Instance.ShowStatsTemporary(); } } else { BroadcastLog(L.Get("log_alt_k_reconnect")); TargetGameServer = DecideTargetIP(out var _); ConnectionFailCount = 0; PerformReconnect(L.Get("log_sos_manual")); } WrongIPCount = 0; TotalRetryCount = 0; } private static void ResetRoomScopedState() { //IL_0076: Unknown result type (might be due to invalid IL or missing references) PlayerCache.Clear(); ActiveSOSList.Clear(); HostHistory.Clear(); TargetGameServer = null; ConnectedUsingHost = true; IsBlindConnect = false; WrongIPCount = 0; ConnectionFailCount = 0; TotalRetryCount = 0; LastErrorMessage = ""; LastKnownHostIP = ""; LastHostUpdateTime = 0f; LastScanTime = 0f; LastDecisionLog = ""; lastClientState = (ClientState)14; nextRetryTime = 0f; lastPingPublishTime = 0f; lastSOSTime = 0f; nextSummaryLogTime = 0f; nextVoiceClientFindTime = 0f; nextSOSManageTime = 0f; lastPlayerCount = 0; scavengeFailTime.Clear(); firstSeenTime.Clear(); voiceToGameActor.Clear(); gameToVoiceActor.Clear(); nextVoiceMapRefreshTime = 0f; punVoice = null; reconnectPending = false; reconnectPendingMode = ""; reconnectPendingDeadline = 0f; nextDiagTime = 0f; lastDiagVoiceRegion = null; lastDiagVoiceRoom = null; lastDiagIsolated = false; lastDiagRoomVoice = null; lastDiagRoomVoiceSource = RoomVoiceSource.Unknown; lastDiagCross.Clear(); nextPingEventTime = 0f; } } internal static class PanelText { internal static string Literal(string value) { return (value ?? "").Replace("<", "<noparse><</noparse>"); } internal static bool? RoomNamesMatch(string gameRoom, string voiceRoom) { if (!string.IsNullOrEmpty(gameRoom) && !string.IsNullOrEmpty(voiceRoom)) { return string.Equals(voiceRoom, gameRoom + "_voice_", StringComparison.Ordinal); } return null; } } [HarmonyPatch] public static class PhotonRPCFix { [HarmonyPatch(typeof(PhotonNetwork), "RPC", new Type[] { typeof(PhotonView), typeof(string), typeof(RpcTarget), typeof(Player), typeof(bool), typeof(object[]) })] [HarmonyPrefix] public static void PrePhotonNetworkRPC(PhotonView view, string methodName, ref RpcTarget target) { if (methodName == "RemoveSkeletonRPC" && (int)target == 3) { target = (RpcTarget)0; } } } internal static class PhotonSettingsGuard { private static string realRealtime; private static string realVoice; private static bool captured; private static bool deviatedLogged; private static float nextCheckTime; private const float CHECK_INTERVAL = 1f; public static bool Enabled { get { if (VoiceFix.EnableAppIdGuard != null) { return VoiceFix.EnableAppIdGuard.Value; } return true; } } public static void Init() { TryCapture(); } public static void Update() { if (Enabled && !(Time.unscaledTime < nextCheckTime)) { nextCheckTime = Time.unscaledTime + 1f; if (!captured) { TryCapture(); } else { CheckGlobal(); } } } private static AppSettings Global() { ServerSettings photonServerSettings = PhotonNetwork.PhotonServerSettings; if ((Object)(object)photonServerSettings == (Object)null) { return null; } return photonServerSettings.AppSettings; } private static void TryCapture() { try { AppSettings val = Global(); if (val != null) { if (realRealtime == null && !string.IsNullOrEmpty(val.AppIdRealtime)) { realRealtime = val.AppIdRealtime; } if (realVoice == null && !string.IsNullOrEmpty(val.AppIdVoice)) { realVoice = val.AppIdVoice; } if (!captured && realRealtime != null && realVoice != null) { captured = true; NetworkManager.DiagLog(L.Get("diag_appid_snapshot", realRealtime, realVoice)); } } } catch (Exception) { } } private static void CheckGlobal() { try { AppSettings val = Global(); if (val == null) { return; } bool flag = realRealtime != null && val.AppIdRealtime != realRealtime; bool flag2 = realVoice != null && val.AppIdVoice != realVoice; if (!flag && !flag2) { deviatedLogged = false; return; } if (!deviatedLogged) { deviatedLogged = true; string text = L.Get("diag_appid_restored", flag ? val.AppIdRealtime : "—", flag2 ? val.AppIdVoice : "—"); NetworkManager.DiagLog(text); if (VoiceFix.logger != null) { VoiceFix.logger.LogWarning((object)text); } } if (flag) { val.AppIdRealtime = realRealtime; } if (flag2) { val.AppIdVoice = realVoice; } } catch (Exception) { } } public static void SanitizeForVoiceConnect(VoiceConnection conn, AppSettings overwrite) { if (!Enabled || !captured) { return; } try { AppSettings val = overwrite ?? (((Object)(object)conn != (Object)null) ? conn.Settings : null); if (val != null) { if (realVoice != null && val.AppIdVoice != realVoice) { val.AppIdVoice = realVoice; } if (realRealtime != null && val.AppIdRealtime != realRealtime) { val.AppIdRealtime = realRealtime; } } CheckGlobal(); } catch (Exception) { } } } [HarmonyPatch(typeof(VoiceConnection), "ConnectUsingSettings")] internal static class VoiceConnectSettingsPatch { private static void Prefix(VoiceConnection __instance, AppSettings overwriteSettings) { PhotonSettingsGuard.SanitizeForVoiceConnect(__instance, overwriteSettings); } } internal static class RegionControl { public const string AUTO = "auto"; public static readonly string[] KNOWN_REGIONS = new string[9] { "asia", "au", "eu", "hk", "jp", "sa", "us", "ussc", "usw" }; private static readonly Dictionary<string, string> ZH_NAME = new Dictionary<string, string> { { "asia", "亚洲/新加坡" }, { "au", "澳洲" }, { "eu", "欧洲" }, { "hk", "香港" }, { "jp", "日本" }, { "sa", "南美" }, { "us", "美东" }, { "ussc", "美中南" }, { "usw", "美西" } }; private static string appliedRegion = null; private static float connectAttemptTime = -1f; private static bool warnedThisAttempt = false; private static LoadBalancingClient pingClient; private static RegionHandler borrowedHandler; private static bool pingBusy; private static bool pingRequested; private static bool pingDisconnecting; private static float pingStartTime; private static volatile bool pingDone; private static volatile string pingPending; private static bool qualityChecked = false; private static float qualityCheckTime = -1f; public static bool IsPinging => pingBusy; public static string Configured { get { string text = ((VoiceFix.ForcedRegion != null) ? VoiceFix.ForcedRegion.Value : "auto"); if (string.IsNullOrEmpty(text)) { return "auto"; } text = text.Trim().ToLowerInvariant(); if (text == "auto") { return "auto"; } string[] kNOWN_REGIONS = KNOWN_REGIONS; for (int i = 0; i < kNOWN_REGIONS.Length; i++) { if (kNOWN_REGIONS[i] == text) { return text; } } return "auto"; } } public static bool IsAuto => Configured == "auto"; public static string Describe(string code) { if (string.IsNullOrEmpty(code)) { return "—"; } if (L.IsChinese && ZH_NAME.TryGetValue(code, out var value)) { return code + " " + value; } return code; } public static void ApplyFixedRegion() { try { AppSettings val = (((Object)(object)PhotonNetwork.PhotonServerSettings != (Object)null) ? PhotonNetwork.PhotonServerSettings.AppSettings : null); if (val == null) { return; } connectAttemptTime = Time.unscaledTime; warnedThisAttempt = false; qualityChecked = false; qualityCheckTime = -1f; if (IsAuto) { if (appliedRegion != null && val.FixedRegion == appliedRegion) { val.FixedRegion = ""; if (VoiceFix.logger != null) { VoiceFix.logger.LogInfo((object)"[区服] 已恢复自动选区"); } } appliedRegion = null; return; } string configured = Configured; if (val.FixedRegion != configured) { val.FixedRegion = configured; if (VoiceFix.logger != null) { VoiceFix.logger.LogInfo((object)("[区服] 强制连接到 " + Describe(configured) + "(原设置: '" + appliedRegion + "')")); } } appliedRegion = configured; } catch (Exception ex) { if (VoiceFix.logger != null) { VoiceFix.logger.LogWarning((object)("[区服] 应用强制区服失败: " + ex.Message)); } } } public static void Update() { CheckConnectTimeout(); CheckForcedRegionQuality(); PumpPing(); } private static void CheckForcedRegionQuality() { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Invalid comparison between Unknown and I4 //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Invalid comparison between Unknown and I4 //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Invalid comparison between Unknown and I4 if (qualityChecked || IsAuto) { return; } ClientState networkClientState = PhotonNetwork.NetworkClientState; if ((int)networkClientState != 15 && (int)networkClientState != 4 && (int)networkClientState != 9 && !PhotonNetwork.InRoom) { qualityCheckTime = -1f; } else if (qualityCheckTime < 0f) { qualityCheckTime = Time.unscaledTime; } else { if (Time.unscaledTime - qualityCheckTime < 8f) { return; } qualityChecked = true; int ping = PhotonNetwork.GetPing(); if (ping <= 0) { return; } string code; int num = ParseCachedBestPing(out code); string text = null; if (num > 0 && ping > num + 60) { text = L.Get("region_worse", Describe(Configured), ping, Describe(code), num); } else if (ping >= 300) { text = L.Get("region_high", Describe(Configured), ping); } if (text != null) { if (VoiceFix.logger != null) { VoiceFix.logger.LogWarning((object)text); } if ((Object)(object)VoiceUIManager.Instance != (Object)null) { VoiceUIManager.Instance.SetRegionWarning(text); VoiceUIManager.Instance.AddLog("System", text, isLocal: true); } } } } private static int ParseCachedBestPing(out string code) { code = null; try { string bestRegionSummaryInPreferences = PhotonNetwork.BestRegionSummaryInPreferences; if (string.IsNullOrEmpty(bestRegionSummaryInPreferences)) { return -1; } string[] array = bestRegionSummaryInPreferences.Split(';'); if (array.Length < 2) { return -1; } code = array[0]; int result; return int.TryParse(array[1], out result) ? result : (-1); } catch (Exception) { return -1; } } private static void CheckConnectTimeout() { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Invalid comparison between Unknown and I4 //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Invalid comparison between Unknown and I4 //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Invalid comparison between Unknown and I4 if (connectAttemptTime < 0f || warnedThisAttempt) { return; } if (IsAuto) { connectAttemptTime = -1f; return; } ClientState networkClient