Decompiled source of BigWalk Public Lobbies v1.6.0

BepInEx/plugins/BigWalkPublicLobbies/BigWalkPublicLobbies.dll

Decompiled 15 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using BepInEx.Unity.IL2CPP;
using Epic.OnlineServices;
using Epic.OnlineServices.Lobby;
using Epic.OnlineServices.Platform;
using HarmonyLib;
using Il2CppInterop.Runtime;
using Il2CppInterop.Runtime.InteropTypes;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppSystem.Collections.Generic;
using Mirror;
using PlayEveryWare.EpicOnlineServices;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.SceneManagement;
using UnityEngine.TextCore.LowLevel;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("BigWalkPublicLobbies")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.6.0.0")]
[assembly: AssemblyInformationalVersion("1.6.0+bf61215e09d53a237e9315d4208c011ac4ab2bcb")]
[assembly: AssemblyProduct("BigWalkPublicLobbies")]
[assembly: AssemblyTitle("BigWalkPublicLobbies")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.6.0.0")]
[module: UnverifiableCode]
namespace BigWalkPublicLobbies;

internal static class DebugLog
{
	public static bool Verbose;

	public static void Info(string msg)
	{
		ManualLogSource log = Plugin.Log;
		if (log != null)
		{
			log.LogInfo((object)msg);
		}
	}

	public static void Warn(string msg)
	{
		ManualLogSource log = Plugin.Log;
		if (log != null)
		{
			log.LogWarning((object)msg);
		}
	}

	public static void Error(string msg)
	{
		ManualLogSource log = Plugin.Log;
		if (log != null)
		{
			log.LogError((object)msg);
		}
	}

	public static void Debug(string msg)
	{
		if (Verbose)
		{
			ManualLogSource log = Plugin.Log;
			if (log != null)
			{
				log.LogDebug((object)msg);
			}
		}
	}
}
public static class LobbyScanner
{
	public enum SearchOutcome
	{
		NotStarted,
		Success,
		NoSignal,
		TimedOut
	}

	private static readonly Dictionary<string, RoomInfo> Cache = new Dictionary<string, RoomInfo>();

	private const float BatchTimeoutSec = 30f;

	private const float NoSignalCapSec = 15f;

	private const uint MaxResults = 200u;

	private const int ResultBudget = 50;

	private static readonly List<LobbyDetails> Incoming = new List<LobbyDetails>();

	private static readonly List<LobbyDetails> _takenBuf = new List<LobbyDetails>();

	private static bool _searchStarted;

	private static bool _searchDone;

	private static float _searchStartAt;

	private static int _baseVersion;

	private static int _consumeCursor;

	private static int _taggedGot;

	private static bool _lastRoundAbandoned;

	private static readonly Stopwatch BatchWatch = new Stopwatch();

	private static EOSLobby _carrier;

	private static bool _readFailWarned;

	private static readonly uint _effectiveMaxResults = 200u;

	private static int _filterVerified;

	public static SearchOutcome Outcome { get; private set; } = SearchOutcome.NotStarted;

	public static bool LocalRoomMerged { get; private set; }

	public static bool FilterVerified => _filterVerified == 1;

	public static int FilterState => _filterVerified;

	public static void ReleaseCarrierOnUnload()
	{
		try
		{
			float value = Time.realtimeSinceStartup - _searchStartAt;
			DebugLog.Debug($"[LobbyScanner] 卸载清理:搜索载体保留给进程回收(_searchStarted={_searchStarted},距上次发起 {value:F1}s;未消费结果 {Incoming.Count - _consumeCursor} 条随进程结束一并回收)");
			_consumeCursor = 0;
			Incoming.Clear();
		}
		catch (Exception ex)
		{
			DebugLog.Debug("[LobbyScanner] 卸载清理失败(忽略): " + ex.Message);
		}
	}

	private static EOSLobby EnsureCarrier()
	{
		//IL_0018: Unknown result type (might be due to invalid IL or missing references)
		//IL_001e: Expected O, but got Unknown
		if ((Object)(object)_carrier != (Object)null)
		{
			return _carrier;
		}
		GameObject val = new GameObject("BWPL_LobbySearch");
		try
		{
			Object.DontDestroyOnLoad((Object)(object)val);
			val.SetActive(false);
			_carrier = val.AddComponent<EOSLobby>();
			return _carrier;
		}
		catch (Exception ex)
		{
			DebugLog.Warn("[Tag] 创建 EOSLobby 搜索载体失败: " + ex.Message);
			try
			{
				Object.Destroy((Object)(object)val);
			}
			catch (Exception ex2)
			{
				DebugLog.Debug("[Tag] 销毁临时搜索载体失败(忽略): " + ex2.Message);
			}
			return null;
		}
	}

	public static void ResetBatch(bool startSearches = true)
	{
		_readFailWarned = false;
		Abandon("重置");
		BatchWatch.Restart();
		if (startSearches)
		{
			Cache.Clear();
			_searchStarted = false;
			_searchDone = false;
			_taggedGot = 0;
			Outcome = SearchOutcome.NotStarted;
			LocalRoomMerged = false;
			StartTaggedSearches();
		}
	}

	private static void Abandon(string reason)
	{
		int num = Incoming.Count - _consumeCursor;
		if (num > 0)
		{
			DebugLog.Debug($"[LobbyScanner] {reason}:释放 {num} 条未消费结果的句柄");
		}
		for (int i = _consumeCursor; i < Incoming.Count; i++)
		{
			ReleaseDetails(Incoming[i]);
		}
		Incoming.Clear();
		_consumeCursor = 0;
		_searchStarted = false;
		_searchDone = false;
		_lastRoundAbandoned = false;
		DrainOrphans(reason);
	}

	private static void StartTaggedSearches()
	{
		try
		{
			if (ModState.TaggedRounds <= 0)
			{
				DebugLog.Debug("[LobbyScanner] TaggedRounds=0,本轮不发起搜索(立即空列表)");
				return;
			}
			EOSLobby val = EnsureCarrier();
			if ((Object)(object)val == (Object)null)
			{
				DebugLog.Warn("[LobbyScanner] EOSLobby 搜索载体不可用,本轮无搜索(显示空列表)");
				return;
			}
			List<LobbyDetails> foundLobbies = val.foundLobbies;
			if (foundLobbies == null)
			{
				try
				{
					val.foundLobbies = new List<LobbyDetails>();
					foundLobbies = val.foundLobbies;
					DebugLog.Debug("[Tag] EOSLobby.foundLobbies 为空,已显式初始化");
				}
				catch (Exception ex)
				{
					DebugLog.Warn("[Tag] 初始化 EOSLobby.foundLobbies 失败: " + ex.Message);
				}
			}
			if (foundLobbies == null)
			{
				DebugLog.Warn("[LobbyScanner] EOSLobby.foundLobbies 不可用,本轮无搜索(显示空列表)");
				return;
			}
			_baseVersion = ReadVersion(foundLobbies);
			LocalRoomMerged = false;
			AddLocalRoom();
			_searchStarted = true;
			_searchDone = false;
			_searchStartAt = Time.realtimeSinceStartup;
			if (_filterVerified == 0)
			{
				_filterVerified = (SelfCheckFilter() ? 1 : (-1));
			}
			Il2CppReferenceArray<LobbySearchSetParameterOptions> val2;
			if (_filterVerified == 1)
			{
				val2 = BuildFilteredArray();
				DebugLog.Info("[Tag] 自检通过 → 使用带 bwpl_open 过滤的参数数组(服务端过滤生效)");
			}
			else
			{
				val2 = new Il2CppReferenceArray<LobbySearchSetParameterOptions>(0L);
				DebugLog.Warn("[Tag] 自检未通过 → 退回空参数数组(无服务端过滤,只能客户端筛选)");
			}
			if (val2 == null)
			{
				DebugLog.Warn("[LobbyScanner] 构造搜索参数失败,本轮无搜索(显示空列表)");
			}
			else
			{
				val.FindLobbies(_effectiveMaxResults, val2);
			}
		}
		catch (Exception ex2)
		{
			DebugLog.Warn("[Tag] 定向搜索发起失败: " + ex2.Message);
		}
	}

	private static bool SelfCheckFilter()
	{
		//IL_0066: Unknown result type (might be due to invalid IL or missing references)
		//IL_0078: 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_0081: Unknown result type (might be due to invalid IL or missing references)
		//IL_0086: Unknown result type (might be due to invalid IL or missing references)
		//IL_0088: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
		//IL_017d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0182: 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_01df: Unknown result type (might be due to invalid IL or missing references)
		//IL_01e2: Invalid comparison between Unknown and I4
		bool result = false;
		try
		{
			EOSSingleton instance = EOSManager.Instance;
			if (instance == null)
			{
				DebugLog.Warn("[Tag] 自检:EOSManager.Instance 为空");
				return false;
			}
			PlatformInterface eOSPlatformInterface = instance.GetEOSPlatformInterface();
			if ((Handle)(object)eOSPlatformInterface == (Handle)null)
			{
				DebugLog.Warn("[Tag] 自检:platform 为空");
				return false;
			}
			LobbyInterface lobbyInterface = eOSPlatformInterface.GetLobbyInterface();
			if ((Handle)(object)lobbyInterface == (Handle)null)
			{
				DebugLog.Warn("[Tag] 自检:LobbyInterface 为空");
				return false;
			}
			LobbySearch val = null;
			try
			{
				CreateLobbySearchOptions val2 = default(CreateLobbySearchOptions);
				((CreateLobbySearchOptions)(ref val2)).MaxResults = 200u;
				CreateLobbySearchOptions val3 = val2;
				Result val4 = lobbyInterface.CreateLobbySearch(ref val3, ref val);
				if ((int)val4 != 0 || (Handle)(object)val == (Handle)null)
				{
					DebugLog.Warn($"[Tag] 自检 CreateLobbySearch → {val4},search={(((Handle)(object)val == (Handle)null) ? "null" : "ok")}");
					if ((Handle)(object)val != (Handle)null)
					{
						try
						{
							val.Release();
						}
						catch (Exception ex)
						{
							DebugLog.Warn("[Tag] 自检句柄释放失败: " + ex.Message);
						}
					}
					return false;
				}
			}
			catch (Exception ex2)
			{
				DebugLog.Warn("[Tag] 自检 CreateLobbySearch 异常: " + ex2.Message);
				if ((Handle)(object)val != (Handle)null)
				{
					try
					{
						val.Release();
					}
					catch (Exception ex3)
					{
						DebugLog.Warn("[Tag] 自检句柄释放失败: " + ex3.Message);
					}
				}
				return false;
			}
			try
			{
				LobbySearchSetParameterOptions val5 = BuildFilteredElementRaw();
				Result val6 = val.SetParameter(ref val5);
				DebugLog.Info($"[Tag] 自检 SetParameter({"bwpl_open"}={"1"}) → {val6}");
				result = (int)val6 == 0;
			}
			catch (Exception ex4)
			{
				DebugLog.Warn("[Tag] 自检 SetParameter 异常: " + ex4.Message);
			}
			try
			{
				val.Release();
			}
			catch (Exception ex5)
			{
				DebugLog.Warn("[Tag] 自检 search 句柄释放失败(EOS 句柄可能泄漏): " + ex5.Message);
			}
		}
		catch (Exception ex6)
		{
			DebugLog.Warn("[Tag] 自检异常: " + ex6.Message);
		}
		return result;
	}

	private static Il2CppReferenceArray<LobbySearchSetParameterOptions> BuildFilteredArray()
	{
		try
		{
			return new Il2CppReferenceArray<LobbySearchSetParameterOptions>(1L) { [0] = BuildFilteredElementRaw() };
		}
		catch (Exception ex)
		{
			DebugLog.Warn("[Tag] 构造过滤数组异常: " + ex.Message);
			return null;
		}
	}

	private static LobbySearchSetParameterOptions BuildFilteredElementRaw()
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0005: 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_0016: Unknown result type (might be due to invalid IL or missing references)
		//IL_001b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0030: Expected O, but got Unknown
		//IL_0030: Unknown result type (might be due to invalid IL or missing references)
		//IL_0036: Expected O, but got Unknown
		AttributeData val = new AttributeData
		{
			Key = Utf8String.op_Implicit("bwpl_open"),
			Value = new AttributeDataValue
			{
				AsUtf8 = Utf8String.op_Implicit("1")
			}
		};
		LobbySearchSetParameterOptions val2 = new LobbySearchSetParameterOptions();
		IntPtr intPtr = IL2CPP.il2cpp_object_unbox(((Il2CppObjectBase)val2).Pointer);
		IntPtr source = IL2CPP.il2cpp_object_unbox(((Il2CppObjectBase)val).Pointer);
		Marshal.WriteByte(intPtr, 0, 1);
		byte[] array = new byte[64];
		Marshal.Copy(source, array, 0, 64);
		Marshal.Copy(array, 0, intPtr + 8, 64);
		Marshal.WriteInt32(intPtr, 72, 0);
		return val2;
	}

	private static void AddLocalRoom()
	{
		try
		{
			if (!ModState.EnableRoomTag)
			{
				return;
			}
			EOSPrivateLobbyManager instance = EOSPrivateLobbyManager.Instance;
			LobbyInfo val = (((Object)(object)instance == (Object)null) ? null : instance.CurrentLobbyInfo);
			if (val == null)
			{
				return;
			}
			string joinCode = val.joinCode;
			if (!string.IsNullOrEmpty(joinCode))
			{
				RoomInfo roomInfo = new RoomInfo
				{
					JoinCode = joinCode,
					WorldName = val.worldName,
					HostName = val.WorldOwnerUserName,
					Tag = "1"
				};
				try
				{
					SaveData val2 = ((SaveManager.instance != null) ? SaveManager.instance.currentData : null);
					roomInfo.Password = ((val2 == null) ? null : val2.password);
					roomInfo.HasPasswordAttribute = roomInfo.Password != null;
				}
				catch (Exception ex)
				{
					roomInfo.HasPasswordAttribute = false;
					DebugLog.Warn("[Tag] 读取本机房间密码属性失败(按无属性处理): " + ex.Message);
				}
				AddOrMerge(roomInfo);
				LocalRoomMerged = true;
				DebugLog.Info("[Tag] 已并入本机房间(EOS 搜索会排除自己所在的房间): " + joinCode + " / " + (val.worldName ?? "<无世界名>"));
			}
		}
		catch (Exception ex2)
		{
			DebugLog.Warn("[Tag] 并入本机房间失败: " + ex2.Message);
		}
	}

	private static int ReadVersion(List<LobbyDetails> buffer)
	{
		try
		{
			return buffer._version;
		}
		catch (Exception ex)
		{
			if (!_readFailWarned)
			{
				_readFailWarned = true;
				DebugLog.Warn("[Tag] 读取 foundLobbies._version 失败(改用宽限兜底,本批次不再重复): " + ex.Message);
			}
			return -1;
		}
	}

	public static bool TryFinishBatch()
	{
		if (!_searchStarted)
		{
			return true;
		}
		if (!_searchDone)
		{
			float num = Time.realtimeSinceStartup - _searchStartAt;
			bool flag = BatchWatch.Elapsed.TotalSeconds > 30.0;
			bool flag2 = false;
			try
			{
				List<LobbyDetails> val = (((Object)(object)_carrier != (Object)null) ? _carrier.foundLobbies : null);
				if (val != null && _baseVersion >= 0)
				{
					flag2 = ReadVersion(val) != _baseVersion;
				}
				if (!flag2 && _baseVersion < 0 && val != null && val.Count > 0)
				{
					flag2 = true;
				}
			}
			catch (Exception ex)
			{
				if (!_readFailWarned)
				{
					_readFailWarned = true;
					DebugLog.Warn("[Tag] 检查搜索完成状态失败(本批次不再重复): " + ex.Message);
				}
			}
			if (!flag2 && !flag && num < 15f)
			{
				return false;
			}
			if (flag)
			{
				DebugLog.Warn($"[LobbyScanner] 批次超时 {30f:F0}s,强制结束(已并入 {_taggedGot} 条)");
			}
			else if (!flag2)
			{
				DebugLog.Warn($"[LobbyScanner] {15f:F0}s 内未收到搜索完成信号(搜索可能失败),按无结果处理");
			}
			Outcome = (flag2 ? SearchOutcome.Success : (flag ? SearchOutcome.TimedOut : SearchOutcome.NoSignal));
			if (flag2 && _lastRoundAbandoned && num < 1f)
			{
				DebugLog.Debug($"[LobbyScanner] 本轮完成信号在 {num:F2}s 内到达,且上一轮是无信号收工 → 疑似上一轮迟到回调(结果仍是真实快照)");
			}
			if (flag2 && _baseVersion >= 0)
			{
				try
				{
					int num2 = ReadVersion(((Object)(object)_carrier != (Object)null) ? _carrier.foundLobbies : null);
					int num3 = (((Object)(object)_carrier != (Object)null && _carrier.foundLobbies != null) ? _carrier.foundLobbies.Count : 0);
					if (num2 >= 0 && num2 - _baseVersion != 1 + num3)
					{
						DebugLog.Warn($"[LobbyScanner] 完成信号 delta 异常:{num2 - _baseVersion} ≠ 1 + {num3}(预期外写入者,仅留痕)");
					}
				}
				catch (Exception ex2)
				{
					DebugLog.Warn("[LobbyScanner] delta 自检读取失败(忽略): " + ex2.Message);
				}
			}
			_lastRoundAbandoned = !flag2;
			DebugLog.Info($"[LobbyScanner] 本轮结局={Outcome}(_version {(flag2 ? "已递增" : "未变")},本机房间并入={LocalRoomMerged},并入条数={_taggedGot})");
			CopyFoundLobbies();
			_searchDone = true;
		}
		int num4 = 0;
		while (_consumeCursor < Incoming.Count && num4 < 50)
		{
			LobbyDetails val2 = Incoming[_consumeCursor++];
			num4++;
			try
			{
				RoomInfo roomInfo = ToRoomInfo(val2);
				if (roomInfo != null)
				{
					AddOrMerge(roomInfo);
					_taggedGot++;
				}
			}
			catch (Exception ex3)
			{
				DebugLog.Warn("[Tag] 处理单条搜索结果失败: " + ex3.Message);
			}
			finally
			{
				ReleaseDetails(val2);
			}
		}
		if (_consumeCursor >= Incoming.Count)
		{
			Incoming.Clear();
			_consumeCursor = 0;
			return true;
		}
		return false;
	}

	private static void CopyFoundLobbies()
	{
		try
		{
			List<LobbyDetails> val = (((Object)(object)_carrier != (Object)null) ? _carrier.foundLobbies : null);
			if (val == null)
			{
				return;
			}
			int count = val.Count;
			_takenBuf.Clear();
			for (int i = 0; i < count; i++)
			{
				LobbyDetails val2 = val[i];
				if ((Handle)(object)val2 != (Handle)null)
				{
					_takenBuf.Add(val2);
				}
			}
			val.Clear();
			_baseVersion = ReadVersion(val);
			for (int j = 0; j < _takenBuf.Count; j++)
			{
				Incoming.Add(_takenBuf[j]);
			}
			_takenBuf.Clear();
		}
		catch (Exception ex)
		{
			DebugLog.Warn("[Tag] 读取搜索结果失败: " + ex.Message);
		}
	}

	private static void DrainOrphans(string reason)
	{
		try
		{
			List<LobbyDetails> val = (((Object)(object)_carrier != (Object)null) ? _carrier.foundLobbies : null);
			if (val == null)
			{
				return;
			}
			int count = val.Count;
			if (count <= 0)
			{
				return;
			}
			_takenBuf.Clear();
			for (int i = 0; i < count; i++)
			{
				LobbyDetails val2 = val[i];
				if ((Handle)(object)val2 != (Handle)null)
				{
					_takenBuf.Add(val2);
				}
			}
			val.Clear();
			_baseVersion = ReadVersion(val);
			for (int j = 0; j < _takenBuf.Count; j++)
			{
				ReleaseDetails(_takenBuf[j]);
			}
			DebugLog.Debug($"[LobbyScanner] {reason}:回收到达于收工之后的 {_takenBuf.Count} 条孤儿结果句柄");
			_takenBuf.Clear();
		}
		catch (Exception ex)
		{
			DebugLog.Debug("[LobbyScanner] 孤儿结果回收失败(忽略): " + ex.Message);
		}
	}

	private static void ReleaseDetails(LobbyDetails d)
	{
		if ((Handle)(object)d == (Handle)null)
		{
			return;
		}
		try
		{
			d.Release();
		}
		catch (Exception ex)
		{
			DebugLog.Warn("[Tag] 释放房间详情句柄失败(EOS 句柄可能泄漏): " + ex.Message);
		}
	}

	private unsafe static RoomInfo ToRoomInfo(LobbyDetails details)
	{
		//IL_0036: Unknown result type (might be due to invalid IL or missing references)
		//IL_0044: Unknown result type (might be due to invalid IL or missing references)
		//IL_0046: Unknown result type (might be due to invalid IL or missing references)
		//IL_0059: Unknown result type (might be due to invalid IL or missing references)
		//IL_005e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0060: Unknown result type (might be due to invalid IL or missing references)
		//IL_0074: Unknown result type (might be due to invalid IL or missing references)
		//IL_0079: Unknown result type (might be due to invalid IL or missing references)
		//IL_007b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0084: Unknown result type (might be due to invalid IL or missing references)
		//IL_008d: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
		string text = Attr(details, "bwpl_open");
		if (text != "1")
		{
			return null;
		}
		string text2 = Attr(details, "bwpl_password");
		bool hasPasswordAttribute = text2 != null;
		string text3 = null;
		int num = -1;
		int val = -1;
		try
		{
			LobbyDetailsCopyInfoOptionsInternal val2 = new LobbyDetailsCopyInfoOptionsInternal
			{
				m_ApiVersion = 1
			};
			IntPtr zero = IntPtr.Zero;
			Result val3 = Bindings.EOS_LobbyDetails_CopyInfo(((Handle)details).InnerHandle, ref val2, ref zero);
			if ((int)val3 == 0 && zero != IntPtr.Zero)
			{
				try
				{
					LobbyDetailsInfoInternal val4 = Marshal.PtrToStructure<LobbyDetailsInfoInternal>(zero);
					num = (int)val4.m_MaxMembers;
					val = (int)val4.m_AvailableSlots;
					if (val4.m_LobbyId != IntPtr.Zero)
					{
						text3 = Marshal.PtrToStringAnsi(val4.m_LobbyId);
					}
				}
				finally
				{
					try
					{
						Bindings.EOS_LobbyDetails_Info_Release(zero);
					}
					catch (Exception ex)
					{
						DebugLog.Warn("[Tag] LobbyDetailsInfo 释放失败(原生内存可能泄漏): " + ex.Message);
					}
				}
			}
			else
			{
				DebugLog.Debug("[Tag] CopyInfo 未成功: " + ((object)(*(Result*)(&val3))/*cast due to .constrained prefix*/).ToString());
			}
		}
		catch (Exception ex2)
		{
			DebugLog.Warn("[Tag] 读取房间详情失败: " + ex2.Message);
		}
		if (string.IsNullOrEmpty(text3))
		{
			return null;
		}
		RoomInfo roomInfo = new RoomInfo
		{
			JoinCode = text3,
			WorldName = Attr(details, "world_name"),
			HostName = Attr(details, "world_username"),
			Tag = text,
			Password = text2,
			HasPasswordAttribute = hasPasswordAttribute
		};
		if (num >= 0)
		{
			roomInfo.MaxPlayers = num;
			roomInfo.CurrentPlayers = Math.Max(0, num - Math.Max(0, val));
		}
		return roomInfo;
	}

	private static string Attr(LobbyDetails details, string key)
	{
		try
		{
			string text = LobbyDetailsExtensions.TryGetStringAttribute(details, key);
			return string.IsNullOrEmpty(text) ? null : text;
		}
		catch (Exception ex)
		{
			DebugLog.Debug("[Tag] 读取属性 " + key + " 失败: " + ex.Message);
			return null;
		}
	}

	public static List<RoomInfo> GetRoomsSnapshot()
	{
		return (from r in Cache.Values
			where !string.IsNullOrEmpty(r.JoinCode)
			select new RoomInfo
			{
				JoinCode = r.JoinCode,
				WorldName = r.WorldName,
				HostName = r.HostName,
				CurrentPlayers = r.CurrentPlayers,
				MaxPlayers = r.MaxPlayers,
				Tag = r.Tag,
				Password = r.Password,
				HasPasswordAttribute = r.HasPasswordAttribute
			}).OrderBy<RoomInfo, string>((RoomInfo r) => r.WorldName, StringComparer.OrdinalIgnoreCase).ThenBy<RoomInfo, string>((RoomInfo r) => r.JoinCode ?? "", StringComparer.OrdinalIgnoreCase).ToList();
	}

	private static void AddOrMerge(RoomInfo room)
	{
		if (string.IsNullOrEmpty(room.JoinCode))
		{
			return;
		}
		if (Cache.TryGetValue(room.JoinCode, out var value))
		{
			if (!string.IsNullOrEmpty(room.WorldName))
			{
				value.WorldName = room.WorldName;
			}
			if (!string.IsNullOrEmpty(room.HostName))
			{
				value.HostName = room.HostName;
			}
			value.Tag = room.Tag;
			value.Password = room.Password;
			value.HasPasswordAttribute = room.HasPasswordAttribute;
			if (value.MaxPlayers < 0 && room.MaxPlayers >= 0)
			{
				value.MaxPlayers = room.MaxPlayers;
				value.CurrentPlayers = room.CurrentPlayers;
			}
		}
		else
		{
			Cache[room.JoinCode] = room;
		}
		UiStyle.EnqueueRoomCharsForPrebake(RoomChars(room));
	}

	private static IEnumerable<uint> RoomChars(RoomInfo room)
	{
		if (!string.IsNullOrEmpty(room.WorldName))
		{
			foreach (uint item in UiStyle.CodePoints(room.WorldName))
			{
				yield return item;
			}
		}
		if (!string.IsNullOrEmpty(room.HostName))
		{
			foreach (uint item2 in UiStyle.CodePoints(room.HostName))
			{
				yield return item2;
			}
		}
		if (!room.HasPasswordAttribute || string.IsNullOrEmpty(room.Password))
		{
			yield break;
		}
		foreach (uint item3 in UiStyle.CodePoints(room.Password))
		{
			yield return item3;
		}
	}
}
internal static class ModState
{
	public static int TaggedRounds = 1;

	public static bool EnableRoomTag = true;

	public static bool AutoFillPassword = true;

	public static ConfigEntry<bool> EnableRoomTagEntry;

	public static bool DevMode = false;

	public static PublicLobbyPanel Panel;

	public static TitleMenu TitleMenu;

	public static GameObject StartButtons;
}
internal static class ModText
{
	public enum LangGroup
	{
		Latin,
		Chinese,
		Japanese,
		Korean,
		Russian
	}

	public static int LangIndex = 0;

	private static readonly Dictionary<string, string[]> Texts = new Dictionary<string, string[]>
	{
		{
			"title",
			new string[15]
			{
				"Public Lobbies", "Salons publics", "Stanze pubbliche", "Öffentliche Räume", "Salas públicas", "公开房间", "公開ルーム", "Публичные комнаты", "공개 방", "Salas públicas",
				"Publiczne pokoje", "Genel odalar", "Veřejné místnosti", "Salas públicas", "公開房間"
			}
		},
		{
			"search_ph",
			new string[15]
			{
				"Search: world / host", "Rechercher : monde / hôte", "Cerca: mondo / host", "Suche: Welt / Gastgeber", "Buscar: mundo / anfitrión", "搜索:世界名 / 房主名", "検索:ワールド名 / ホスト名", "Поиск: мир / хост", "검색: 월드명 / 호스트명", "Buscar: mundo / anfitrião",
				"Szukaj: świat / gospodarz", "Ara: dünya / sunucu", "Hledat: svět / hostitel", "Buscar: mundo / anfitrión", "搜尋:世界名稱 / 房主名稱"
			}
		},
		{
			"lang_toggle_format",
			new string[15]
			{
				"{0}: {1}", "{0} : {1}", "{0}: {1}", "{0}: {1}", "{0}: {1}", "{0}:{1}", "{0}:{1}", "{0}: {1}", "{0}: {1}", "{0}: {1}",
				"{0}: {1}", "{0}: {1}", "{0}: {1}", "{0}: {1}", "{0}:{1}"
			}
		},
		{
			"refresh",
			new string[15]
			{
				"Refresh", "Actualiser", "Aggiorna", "Aktualisieren", "Actualizar", "刷新", "更新", "Обновить", "새로고침", "Atualizar",
				"Odśwież", "Yenile", "Obnovit", "Actualizar", "重新整理"
			}
		},
		{
			"searching",
			new string[15]
			{
				"Searching…", "Recherche en cours…", "Ricerca in corso…", "Suche läuft…", "Buscando…", "搜索中…", "検索中…", "Поиск…", "검색 중…", "Procurando…",
				"Szukanie…", "Aranıyor…", "Hledání…", "Buscando…", "搜尋中…"
			}
		},
		{
			"searching_base",
			new string[15]
			{
				"Searching", "Recherche en cours", "Ricerca", "Suche", "Buscando", "搜索中", "検索中", "Поиск", "검색 중", "Procurando",
				"Szukanie", "Aranıyor", "Hledání", "Buscando", "搜尋中"
			}
		},
		{
			"loading",
			new string[15]
			{
				"Loading…", "Chargement…", "Caricamento…", "Wird geladen…", "Cargando…", "加载中…", "読み込み中…", "Загрузка…", "불러오는 중…", "Carregando…",
				"Ładowanie…", "Yükleniyor…", "Načítání…", "Cargando…", "載入中…"
			}
		},
		{
			"join",
			new string[15]
			{
				"Join Room", "Rejoindre la salle", "Entra nella stanza", "Raum beitreten", "Unirse a la sala", "加入房间", "ルームに参加", "Войти в комнату", "방 참가", "Entrar na sala",
				"Dołącz do pokoju", "Odaya katıl", "Připojit se", "Unirse a la sala", "加入房間"
			}
		},
		{
			"back",
			new string[15]
			{
				"Back", "Retour", "Indietro", "Zurück", "Volver", "返回", "戻る", "Назад", "뒤로", "Voltar",
				"Wstecz", "Geri", "Zpět", "Volver", "返回"
			}
		},
		{
			"empty_none",
			new string[15]
			{
				"No other public rooms right now", "Aucun autre salon public pour le moment", "Nessun'altra stanza pubblica al momento", "Derzeit keine weiteren öffentlichen Räume", "No hay otras salas públicas ahora mismo", "当前没有其他公开房间", "現在ほかの公開ルームはありません", "Сейчас других публичных комнат нет", "현재 다른 공개 방이 없습니다", "Nenhuma outra sala pública no momento",
				"Obecnie brak innych publicznych pokoi", "Şu anda başka genel oda yok", "Nyní nejsou žádné další veřejné místnosti", "No hay otras salas públicas ahora mismo", "目前沒有其他公開房間"
			}
		},
		{
			"empty_failed",
			new string[15]
			{
				"Search failed — EOS may be rate-limiting", "Recherche échouée : EOS limite peut-être les requêtes", "Ricerca non riuscita: EOS potrebbe limitare", "Suche fehlgeschlagen — EOS drosselt evtl.", "Búsqueda fallida: EOS puede estar limitando", "搜索失败,可能触发了限流", "検索に失敗しました(制限の可能性)", "Поиск не удался — возможно, EOS ограничивает", "검색 실패 — EOS 제한일 수 있습니다", "Falha na busca — o EOS pode estar limitando",
				"Wyszukiwanie nie powiodło się — możliwe ograniczenie EOS", "Arama başarısız — EOS sınırlıyor olabilir", "Hledání selhalo — EOS může omezovat", "Búsqueda fallida: EOS puede estar limitando", "搜尋失敗,可能觸發了限流"
			}
		},
		{
			"empty_self",
			new string[15]
			{
				"Only your own room is listed", "Seul votre propre salon est listé", "È elencata solo la tua stanza", "Nur dein eigener Raum wird gelistet", "Solo aparece tu propia sala", "当前只有你自己的房间", "表示されているのは自分のルームだけです", "Показана только ваша комната", "지금은 내 방만 표시됩니다", "Apenas a sua própria sala está listada",
				"Wymieniony jest tylko twój pokój", "Yalnızca kendi odanız listeleniyor", "Je uvedena jen vaše vlastní místnost", "Solo aparece tu propia sala", "目前只有你自己的房間"
			}
		},
		{
			"empty_filtered",
			new string[15]
			{
				"No rooms match the filter", "Aucun salon ne correspond au filtre", "Nessuna stanza corrisponde al filtro", "Kein Raum entspricht dem Filter", "Ninguna sala coincide con el filtro", "没有符合筛选条件的房间", "絞り込み条件に合うルームがありません", "Нет комнат по фильтру", "검색 조건에 맞는 방이 없습니다", "Nenhuma sala corresponde ao filtro",
				"Brak pokoi pasujących do filtra", "Filtreye uyan oda yok", "Filtru neodpovídá žádná místnost", "Ninguna sala coincide con el filtro", "沒有符合篩選條件的房間"
			}
		},
		{
			"empty_hint_refresh",
			new string[15]
			{
				"Other players' rooms appear here once they host and publish.", "Les salons des autres joueurs apparaîtront ici dès qu'ils hébergeront.", "Le stanze degli altri appariranno qui quando le ospiteranno.", "Räume anderer Spieler erscheinen hier, sobald sie hosten.", "Las salas de otros jugadores aparecerán aquí cuando alojen.", "其他玩家开房并公开后,会出现在这里;稍后可再点「刷新」。", "他のプレイヤーがルームを立てて公開すると、ここに表示されます。", "Комнаты других игроков появятся здесь, когда они создадут игру.", "다른 플레이어가 방을 열고 공개하면 여기에 표시됩니다.", "As salas de outros jogadores aparecem aqui quando eles hospedarem.",
				"Pokoje innych graczy pojawią się tutaj, gdy zaczną hostować.", "Diğer oyuncuların odaları açtıklarında burada görünür.", "Místnosti ostatních hráčů se objeví, jakmile začnou hostovat.", "Las salas de otros jugadores aparecerán aquí cuando alojen.", "其他玩家開房並公開後,會出現在這裡;稍後可再點「重新整理」。"
			}
		},
		{
			"empty_hint_filter",
			new string[15]
			{
				"Clear the search box or turn off the language filter.", "Effacez la recherche ou désactivez le filtre de langue.", "Cancella la ricerca o disattiva il filtro lingua.", "Suchfeld leeren oder Sprachfilter ausschalten.", "Borra la búsqueda o desactiva el filtro de idioma.", "清空搜索框,或关闭「仅本语言」再试。", "検索欄を空にするか「日本語のみ」をオフにしてください。", "Очистите поле поиска или отключите фильтр языка.", "검색창을 비우거나 언어 필터를 끄세요.", "Limpe a busca ou desative o filtro de idioma.",
				"Wyczyść pole wyszukiwania lub wyłącz filtr języka.", "Arama kutusunu temizleyin veya dil filtresini kapatın.", "Vymažte hledání nebo vypněte filtr jazyka.", "Borra la búsqueda o desactiva el filtro de idioma.", "清空搜尋框,或關閉「僅本語言」再試。"
			}
		},
		{
			"join_short",
			new string[15]
			{
				"Join", "Entrer", "Entra", "Beitreten", "Unirse", "加入", "参加", "Войти", "참가", "Entrar",
				"Dołącz", "Katıl", "Připojit", "Unirse", "加入"
			}
		},
		{
			"empty_title",
			new string[15]
			{
				"No public rooms found", "Aucun salon public trouvé", "Nessuna stanza pubblica trovata", "Keine öffentlichen Räume gefunden", "No se encontraron salas públicas", "没有找到公开房间", "公開ルームが見つかりません", "Публичные комнаты не найдены", "공개 방을 찾지 못했습니다", "Nenhuma sala pública encontrada",
				"Nie znaleziono publicznych pokoi", "Genel oda bulunamadı", "Nebyly nalezeny veřejné místnosti", "No se encontraron salas públicas", "沒有找到公開房間"
			}
		},
		{
			"no_rooms",
			new string[15]
			{
				"No rooms found: EOS search may be rate-limited. Please wait a moment and refresh again.", "Aucune salle trouvée : la recherche EOS est peut-être limitée. Attendez un instant puis actualisez.", "Nessuna stanza trovata: la ricerca EOS potrebbe essere limitata. Attendi e aggiorna.", "Keine Räume gefunden: die EOS-Suche ist evtl. limitiert. Bitte warten und aktualisieren.", "No se encontraron salas: la búsqueda de EOS puede estar limitada. Espere y actualice.", "未搜索到房间:可能触发了 EOS 搜索限流,请等待片刻后再刷新。", "ルームが見つかりませんでした。EOS の検索制限の可能性があります。少し待ってから再更新してください。", "Комнаты не найдены: возможно, ограничение поиска EOS. Подождите и обновите.", "방을 찾지 못했습니다. EOS 검색 제한일 수 있습니다. 잠시 후 다시 새로고침하세요.", "Nenhuma sala encontrada: a busca EOS pode estar limitada. Aguarde e atualize.",
				"Nie znaleziono pokoi: wyszukiwanie EOS może być ograniczone. Poczekaj i odśwież.", "Oda bulunamadı: EOS araması sınırlanmış olabilir. Bekleyip yenileyin.", "Žádné místnosti nenalezeny: vyhledávání EOS může být omezeno. Počkejte a obnovte.", "No se encontraron salas: la búsqueda de EOS puede estar limitada. Espere y actualice.", "未搜尋到房間:可能觸發了 EOS 搜尋限流,請稍候再重新整理。"
			}
		},
		{
			"rooms_count",
			new string[15]
			{
				"{0} public rooms (click a card, then Join Room)", "{0} salons publics (cliquez, puis rejoindre)", "{0} stanze pubbliche (clicca, poi entra)", "{0} öffentliche Räume (klicken, dann beitreten)", "{0} salas públicas (clic, luego unirse)", "共 {0} 个公开房间(单击卡片选中,点「加入房间」进房)", "公開ルーム {0} 件(カードをクリックして選択、「ルームに参加」で入室)", "{0} публичных комнат (клик, затем войти)", "공개 방 {0}개 (카드를 클릭하여 선택, '방 참가'로 입장)", "{0} salas públicas (clique, depois entre)",
				"{0} publicznych pokoi (kliknij, potem dołącz)", "{0} genel oda (tıklayın, sonra katılın)", "{0} veřejných místností (klikněte, pak se připojte)", "{0} salas públicas (clic, luego unirse)", "共 {0} 個公開房間(按一下卡片選取,點「加入房間」進房)"
			}
		},
		{
			"selected",
			new string[15]
			{
				"Selected: {0} (code {1}). Press Join Room.", "Sélectionné : {0} (code {1}). Rejoignez.", "Selezionata: {0} (codice {1}). Entra.", "Ausgewählt: {0} (Code {1}). Beitreten.", "Seleccionada: {0} (código {1}). Unirse.", "已选中:{0}(码 {1})。点「加入房间」进房。", "選択中:{0}(コード {1})。「ルームに参加」で入室。", "Выбрано: {0} (код {1}). Войти.", "선택됨: {0} (코드 {1}). '방 참가'를 눌러 입장하세요.", "Selecionada: {0} (código {1}). Entre.",
				"Wybrano: {0} (kod {1}). Dołącz.", "Seçildi: {0} (kod {1}). Katılın.", "Vybráno: {0} (kód {1}). Připojit se.", "Seleccionada: {0} (código {1}). Unirse.", "已選取:{0}(碼 {1})。點「加入房間」進房。"
			}
		},
		{
			"select_first",
			new string[15]
			{
				"Select a room first.", "Sélectionnez d'abord une salle.", "Seleziona prima una stanza.", "Wählen Sie zuerst einen Raum.", "Seleccione una sala primero.", "请先单击选中一个房间。", "先にルームをクリックして選択してください。", "Сначала выберите комнату.", "먼저 방을 클릭하여 선택하세요.", "Selecione uma sala primeiro.",
				"Najpierw wybierz pokój.", "Önce bir oda seçin.", "Nejprve vyberte místnost.", "Seleccione una sala primero.", "請先按一下選取一個房間。"
			}
		},
		{
			"already_in_session",
			new string[15]
			{
				"Already in a session — leave it first.", "Déjà en session — quittez-la d'abord.", "Già in sessione — esci prima.", "Bereits in einer Sitzung — erst verlassen.", "Ya en una sesión: sal primero.", "你已在会话中,请先退出再加入。", "すでにセッション中です。先に退出してください。", "Вы уже в сессии — сначала выйдите.", "이미 세션 중입니다. 먼저 나가세요.", "Já em uma sessão — saia primeiro.",
				"Już w sesji — najpierw wyjdź.", "Zaten oturumdasınız — önce çıkın.", "Již v relaci — nejprve ji opusťte.", "Ya en una sesión: sal primero.", "你已在工作階段中,請先退出再加入。"
			}
		},
		{
			"join_no_error_ui",
			new string[15]
			{
				"Connecting directly — no error screen.", "Connexion directe — pas d'écran d'erreur.", "Connessione diretta — nessuna schermata.", "Direkte Verbindung — kein Fehlerbildschirm.", "Conexión directa — sin pantalla de error.", "已直接连接(此路径无原版错误界面)。", "直接接続中(エラー画面なし)。", "Прямое подключение — без экрана ошибки.", "직접 연결 중(오류 화면 없음).", "Conexão direta — sem tela de erro.",
				"Połączenie bezpośrednie — bez ekranu błędu.", "Doğrudan bağlanıyor — hata ekranı yok.", "Přímé připojení — bez chybové obrazovky.", "Conexión directa — sin pantalla de error.", "已直接連線(此路徑無原版錯誤介面)。"
			}
		},
		{
			"join_failed",
			new string[15]
			{
				"Join failed: {0}", "Échec de la connexion : {0}", "Errore di ingresso: {0}", "Beitritt fehlgeschlagen: {0}", "Error al unirse: {0}", "加入失败:{0}", "参加に失敗しました:{0}", "Ошибка входа: {0}", "참가 실패: {0}", "Falha ao entrar: {0}",
				"Błąd dołączenia: {0}", "Katılma hatası: {0}", "Chyba připojení: {0}", "Error al unirse: {0}", "加入失敗:{0}"
			}
		},
		{
			"showing",
			new string[15]
			{
				"Showing {0}/{1} rooms", "{0}/{1} salons", "{0}/{1} stanze", "{0}/{1} Räume", "{0}/{1} salas", "显示 {0}/{1} 个房间", "{0}/{1} 件表示", "Показано {0}/{1} комнат", "{0}/{1}개 표시", "Exibindo {0}/{1} salas",
				"Pokazano {0}/{1} pokoi", "{0}/{1} oda gösteriliyor", "Zobrazeno {0}/{1} místností", "{0}/{1} salas", "顯示 {0}/{1} 個房間"
			}
		},
		{
			"showing_lang",
			new string[15]
			{
				"Showing {0}/{1} rooms ({2} only {3})", "{0}/{1} salons ({2} seul {3})", "{0}/{1} stanze (solo {2} {3})", "{0}/{1} Räume (nur {2} {3})", "{0}/{1} salas (solo {2} {3})", "显示 {0}/{1} 个房间(仅{2} {3})", "{0}/{1} 件表示({2}のみ {3})", "Показано {0}/{1} комнат (только {2} {3})", "{0}/{1}개 표시 ({2}만 {3})", "{0}/{1} salas (só {2} {3})",
				"{0}/{1} pokoi (tylko {2} {3})", "{0}/{1} oda (sadece {2} {3})", "{0}/{1} místností (jen {2} {3})", "{0}/{1} salas (solo {2} {3})", "顯示 {0}/{1} 個房間(僅{2} {3})"
			}
		},
		{
			"lang_on",
			new string[15]
			{
				"ON", "ON", "ON", "AN", "ON", "开", "オン", "ВКЛ", "켜짐", "ON",
				"WŁ", "AÇIK", "ZAP", "ON", "開"
			}
		},
		{
			"lang_off",
			new string[15]
			{
				"OFF", "OFF", "OFF", "AUS", "OFF", "关", "オフ", "ВЫКЛ", "꺼짐", "OFF",
				"WYŁ", "KAPALI", "VYP", "OFF", "關"
			}
		},
		{
			"lang_group_name",
			new string[15]
			{
				"Latin", "latin", "latino", "Latein", "latín", "中文", "日本語", "RU", "한국어", "latim",
				"łacina", "Latin", "latinka", "latín", "中文"
			}
		},
		{
			"host",
			new string[15]
			{
				"Host: {0}", "Hôte : {0}", "Host: {0}", "Gastgeber: {0}", "Anfitrión: {0}", "房主:{0}", "ホスト:{0}", "Хост: {0}", "호스트: {0}", "Anfitrião: {0}",
				"Gospodarz: {0}", "Sunucu: {0}", "Hostitel: {0}", "Anfitrión: {0}", "房主:{0}"
			}
		},
		{
			"code",
			new string[15]
			{
				"Code {0}", "Code {0}", "Codice {0}", "Code {0}", "Código {0}", "码 {0}", "コード {0}", "Код {0}", "코드 {0}", "Código {0}",
				"Kod {0}", "Kod {0}", "Kód {0}", "Código {0}", "碼 {0}"
			}
		},
		{
			"host_label",
			new string[15]
			{
				"Host: ", "Hôte : ", "Host: ", "Gastgeber: ", "Anfitrión: ", "房主:", "ホスト:", "Хост: ", "호스트: ", "Anfitrião: ",
				"Gospodarz: ", "Sunucu: ", "Hostitel: ", "Anfitrión: ", "房主:"
			}
		},
		{
			"code_caption",
			new string[15]
			{
				"Room code", "Code", "Codice", "Raumcode", "Código", "房间码", "コード", "Код", "방 코드", "Código",
				"Kod", "Oda kodu", "Kód", "Código", "房間碼"
			}
		},
		{
			"pw_caption",
			new string[15]
			{
				"Password", "Mot de passe", "Password", "Passwort", "Contraseña", "密码", "パスワード", "Пароль", "비밀번호", "Senha",
				"Hasło", "Şifre", "Heslo", "Contraseña", "密碼"
			}
		},
		{
			"unknown_world",
			new string[15]
			{
				"Unnamed World", "Monde sans nom", "Mondo senza nome", "Unbenannte Welt", "Mundo sin nombre", "未命名世界", "名前のないワールド", "Безымянный мир", "이름 없는 월드", "Mundo sem nome",
				"Świat bez nazwy", "Adsız dünya", "Svět beze jména", "Mundo sin nombre", "未命名世界"
			}
		},
		{
			"unknown_host",
			new string[15]
			{
				"Unknown host", "Hôte inconnu", "Host sconosciuto", "Unbekannter Gastgeber", "Anfitrión desconocido", "未知房主", "不明なホスト", "Неизвестный хост", "알 수 없는 호스트", "Anfitrião desconhecido",
				"Nieznany gospodarz", "Bilinmeyen sunucu", "Neznámý hostitel", "Anfitrión desconocido", "未知房主"
			}
		},
		{
			"pw_unavailable",
			new string[15]
			{
				"unavailable", "indisponible", "non disponibile", "nicht verfügbar", "no disponible", "不可用", "利用不可", "недоступен", "사용 불가", "indisponível",
				"niedostępne", "kullanılamaz", "nedostupné", "no disponible", "不可用"
			}
		},
		{
			"pw_none",
			new string[15]
			{
				"(none)", "(aucun)", "(nessuna)", "(keins)", "(ninguna)", "(无)", "(なし)", "(нет)", "(없음)", "(nenhuma)",
				"(brak)", "(yok)", "(žádné)", "(ninguna)", "(無)"
			}
		},
		{
			"tag_off",
			new string[15]
			{
				"Room tag: OFF", "Étiquette salle: OFF", "Etichetta stanza: OFF", "Raum-Markierung: AUS", "Etiqueta de sala: OFF", "模组房标签: 关", "ルームタグ: オフ", "Метка комнаты: ВЫКЛ", "방 태그: 꺼짐", "Etiqueta da sala: OFF",
				"Znacznik pokoju: WYŁ", "Oda etiketi: KAPALI", "Značka místnosti: VYP", "Etiqueta de sala: OFF", "模組房標籤: 關"
			}
		},
		{
			"tag_on",
			new string[15]
			{
				"Room tag: ON", "Étiquette salle: ON", "Etichetta stanza: ON", "Raum-Markierung: AN", "Etiqueta de sala: ON", "模组房标签: 开", "ルームタグ: オン", "Метка комнаты: ВКЛ", "방 태그: 켜짐", "Etiqueta da sala: ON",
				"Znacznik pokoju: WŁ", "Oda etiketi: AÇIK", "Značka místnosti: ZAP", "Etiqueta de sala: ON", "模組房標籤: 開"
			}
		},
		{
			"tag_tip",
			new string[15]
			{
				"ON: your room is tagged and its password published (public data, readable by anyone) so players with this mod can join it from the Public Lobbies list. OFF: nothing is published.", "Activé : votre salon est étiqueté et son mot de passe publié (données publiques, lisibles par tous) afin que les joueurs avec ce mod puissent le rejoindre depuis la liste Salons publics. Désactivé : rien n'est publié.", "Attivo: la tua stanza viene etichettata e la sua password pubblicata (dati pubblici, leggibili da tutti) così i giocatori con questa mod possono entrare dall'elenco Stanze pubbliche. Disattivo: nulla viene pubblicato.", "An: Dein Raum wird markiert und sein Passwort veröffentlicht (öffentliche Daten, für jeden lesbar), damit Spieler mit dieser Mod ihn über die Liste Öffentliche Räume beitreten können. Aus: Es wird nichts veröffentlicht.", "Activado: tu sala se etiqueta y su contraseña se publica (datos públicos, legibles por cualquiera) para que los jugadores con este mod puedan unirse desde la lista Salas públicas. Desactivado: no se publica nada.", "开启:你的房间自动打模组房标签并公开当前密码(公开数据,任何人可读),其他装了本模组的玩家可在「公开房间」列表查看并加入。关闭:不发布任何信息。", "オン:作成した部屋にタグを付け、現在のパスワードを公開します(公開データ、誰でも読み取り可能)。このMODを入れたプレイヤーが「公開ルーム」リストから参加できます。オフ:何も公開しません。", "Вкл.: ваша комната помечается и её пароль публикуется (открытые данные, доступны всем), чтобы игроки с этим модом могли войти из списка «Публичные комнаты». Выкл.: ничего не публикуется.", "켜기: 방에 태그가 붙고 현재 비밀번호가 공개됩니다(공개 데이터, 누구나 읽을 수 있음). 이 모드를 설치한 플레이어가 '공개 방' 목록에서 참가할 수 있습니다. 끄기: 아무것도 공개하지 않습니다.", "Ligado: sua sala é marcada e a senha publicada (dados públicos, legíveis por qualquer um) para que jogadores com este mod possam entrar pela lista Salas públicas. Desligado: nada é publicado.",
				"Włączone: twój pokój jest oznaczany, a jego hasło publikowane (dane publiczne, czytelne dla każdego), by gracze z tym modem mogli dołączyć z listy Publiczne pokoje. Wyłączone: nic nie jest publikowane.", "Açık: odanız etiketlenir ve şifresi yayınlanır (herkesin okuyabileceği genel veri); bu modu kuran oyuncular Genel Odalar listesinden katılabilir. Kapalı: hiçbir şey yayınlanmaz.", "Zapnuto: vaše místnost je označena a její heslo zveřejněno (veřejná data, čitelná kýmkoli), aby hráči s tímto modem mohli vstoupit ze seznamu Veřejné místnosti. Vypnuto: nic se nezveřejňuje.", "Activado: tu sala se etiqueta y su contraseña se publica (datos públicos, legibles por cualquiera) para que los jugadores con este mod puedan unirse desde la lista Salas públicas. Desactivado: no se publica nada.", "開啟:你的房間自動加模組房標籤並公開目前密碼(公開資料,任何人可讀),其他安裝了本模組的玩家可在「公開房間」列表查看並加入。關閉:不發布任何資訊。"
			}
		}
	};

	private static readonly HashSet<string> _warnedKeys = new HashSet<string>();

	private static bool _fmtWarned;

	private static bool _langProbeWarned;

	private static readonly HashSet<char> SimplifiedOnlyChars = new HashSet<char>(new char[81]
	{
		'们', '这', '说', '时', '还', '个', '么', '让', '进', '过',
		'边', '给', '现', '从', '对', '车', '东', '买', '长', '问',
		'没', '谁', '两', '写', '习', '汉', '话', '语', '读', '书',
		'电', '视', '脑', '动', '开', '关', '门', '间', '场', '块',
		'环', '压', '厌', '历', '厅', '发', '马', '鱼', '鸟', '广',
		'龙', '你', '无', '兴', '办', '帮', '师', '帅', '卫', '业',
		'乡', '头', '欢', '乐', '齐', '华', '丽', '亲', '贝', '见',
		'页', '风', '飞', '银', '红', '蓝', '绿', '键', '码', '号',
		'酱'
	});

	private static readonly HashSet<char> JapaneseOnlyKanji = new HashSet<char>(new char[51]
	{
		'込', '辻', '峠', '畑', '働', '凪', '凧', '匂', '俣', '俵',
		'栃', '榊', '畠', '蛍', '噂', '桜', '広', '図', '価', '拡',
		'囲', '渋', '沢', '縦', '続', '検', '権', '険', '緑', '訳',
		'鉄', '歩', '辺', '売', '駅', '竜', '暦', '齢', '円', '児',
		'毎', '対', '変', '帰', '帯', '斎', '壊', '拠', '処', '従',
		'塀'
	});

	private static bool _init;

	private static TMP_Text _cachedSettingsTmp;

	private static LocalizationManager _cachedLm;

	private static float _lmRetryAt;

	private static float _nextPollTime;

	public static LangGroup CurrentGroup
	{
		get
		{
			switch (LangIndex)
			{
			case 5:
			case 14:
				return LangGroup.Chinese;
			case 6:
				return LangGroup.Japanese;
			case 7:
				return LangGroup.Russian;
			case 8:
				return LangGroup.Korean;
			default:
				return LangGroup.Latin;
			}
		}
	}

	public static string T(string key)
	{
		if (Texts.TryGetValue(key, out var value))
		{
			if (value.Length != 15 && _warnedKeys.Add(key))
			{
				DebugLog.Warn($"[本地化] 文案 key '{key}' 数组长度 {value.Length} != 15,缺失语言将按末项截断");
			}
			return value[Math.Min(LangIndex, value.Length - 1)];
		}
		DebugLog.Warn("[本地化] 未找到文案 key: " + key);
		return key;
	}

	public static string T(string key, params object[] args)
	{
		try
		{
			return string.Format(T(key), args);
		}
		catch (FormatException)
		{
			if (!_fmtWarned)
			{
				_fmtWarned = true;
				DebugLog.Warn("[本地化] 文案占位符与参数不匹配,已按原文返回: " + key);
			}
			return T(key);
		}
	}

	public static bool RoomInGroup(string worldName, string hostName)
	{
		switch (CurrentGroup)
		{
		case LangGroup.Chinese:
			if (IsNonChineseLike(worldName))
			{
				return false;
			}
			if (IsChineseLike(worldName))
			{
				return true;
			}
			if (IsNonChineseLike(hostName))
			{
				return false;
			}
			return IsChineseLike(hostName);
		case LangGroup.Japanese:
			if (!HasJapaneseSignal(worldName) || HasSimplifiedOnly(worldName))
			{
				if (HasJapaneseSignal(hostName))
				{
					return !HasSimplifiedOnly(hostName);
				}
				return false;
			}
			return true;
		default:
			if (!InGroup(worldName))
			{
				return InGroup(hostName);
			}
			return true;
		}
	}

	private static bool IsNonChineseLike(string s)
	{
		if (string.IsNullOrEmpty(s))
		{
			return false;
		}
		int num = 0;
		int num2 = 0;
		int num3 = 0;
		bool flag = false;
		bool flag2 = false;
		bool flag3 = false;
		foreach (char c in s)
		{
			if (IsHiragana(c))
			{
				num++;
			}
			else if (IsKatakana(c))
			{
				num2++;
			}
			else if (IsJapaneseMark(c))
			{
				flag = true;
			}
			else if (JapaneseOnlyKanji.Contains(c))
			{
				flag2 = true;
			}
			else if (SimplifiedOnlyChars.Contains(c))
			{
				num3++;
			}
			else if (IsHangulChar(c))
			{
				flag3 = true;
			}
		}
		if (flag3)
		{
			return true;
		}
		if (num3 >= 2)
		{
			return false;
		}
		if (num2 >= 1 || flag || flag2)
		{
			return true;
		}
		if (num >= 2)
		{
			return true;
		}
		if (num == 1 && num3 == 0)
		{
			return true;
		}
		return false;
	}

	private static bool IsChineseLike(string s)
	{
		if (string.IsNullOrEmpty(s))
		{
			return false;
		}
		foreach (char c in s)
		{
			if (SimplifiedOnlyChars.Contains(c) || IsHanChar(c))
			{
				return true;
			}
		}
		return false;
	}

	private static bool HasJapaneseSignal(string s)
	{
		if (string.IsNullOrEmpty(s))
		{
			return false;
		}
		foreach (char c in s)
		{
			if (IsHiragana(c) || IsKatakana(c) || IsJapaneseMark(c) || JapaneseOnlyKanji.Contains(c))
			{
				return true;
			}
		}
		return false;
	}

	private static bool HasSimplifiedOnly(string s)
	{
		if (string.IsNullOrEmpty(s))
		{
			return false;
		}
		foreach (char item in s)
		{
			if (SimplifiedOnlyChars.Contains(item))
			{
				return true;
			}
		}
		return false;
	}

	private static bool IsHiragana(char c)
	{
		if (c >= '\u3040')
		{
			return c <= 'ゟ';
		}
		return false;
	}

	private static bool IsKatakana(char c)
	{
		if ((c < '゠' || c > 'ヿ') && (c < '・' || c > '゚'))
		{
			if (c >= 'ㇰ')
			{
				return c <= 'ㇿ';
			}
			return false;
		}
		return true;
	}

	private static bool IsJapaneseMark(char c)
	{
		if (c != '々' && c != '〆' && c != '〻' && c != '〼')
		{
			return c == '〽';
		}
		return true;
	}

	private static bool IsHangulChar(char c)
	{
		if ((c < '가' || c > '힣') && (c < 'ᄀ' || c > 'ᇿ'))
		{
			if (c >= '\u3130')
			{
				return c <= '\u318f';
			}
			return false;
		}
		return true;
	}

	private static bool IsHanChar(char c)
	{
		if ((c < '㐀' || c > '䶿') && (c < '一' || c > '鿿'))
		{
			if (c >= '豈')
			{
				return c <= '\ufaff';
			}
			return false;
		}
		return true;
	}

	private static bool InGroup(string s)
	{
		if (string.IsNullOrEmpty(s))
		{
			return false;
		}
		for (int i = 0; i < s.Length; i++)
		{
			if (MatchGroup(s[i]))
			{
				return true;
			}
		}
		return false;
	}

	private static bool MatchGroup(char c)
	{
		switch (CurrentGroup)
		{
		case LangGroup.Korean:
			if ((c < '가' || c > '힣') && (c < 'ᄀ' || c > 'ᇿ'))
			{
				if (c >= '\u3130')
				{
					return c <= '\u318f';
				}
				return false;
			}
			return true;
		case LangGroup.Russian:
			if (c >= 'Ѐ')
			{
				return c <= 'ӿ';
			}
			return false;
		default:
			if ((c < 'A' || c > 'Z') && (c < 'a' || c > 'z'))
			{
				if (c >= 'À')
				{
					return c <= 'ɏ';
				}
				return false;
			}
			return true;
		}
	}

	public static bool Poll()
	{
		if (Time.time < _nextPollTime)
		{
			return false;
		}
		_nextPollTime = Time.time + 1f;
		int num = DetectLangIndex();
		if (!_init || num != LangIndex)
		{
			_init = true;
			LangIndex = num;
			DebugLog.Info($"[本地化] 游戏语言 → 索引 {num}");
			return true;
		}
		return false;
	}

	private static int DetectLangIndex()
	{
		//IL_0061: Unknown result type (might be due to invalid IL or missing references)
		//IL_0067: Expected I4, but got Unknown
		if (_cachedLm == null && Time.realtimeSinceStartup >= _lmRetryAt)
		{
			try
			{
				_cachedLm = LocalizationManager.instance;
			}
			catch (Exception ex)
			{
				_lmRetryAt = Time.realtimeSinceStartup + 1f;
				if (!_langProbeWarned)
				{
					_langProbeWarned = true;
					DebugLog.Debug("[本地化] LocalizationManager 尚未就绪(先用设置页文本回退): " + ex.Message);
				}
			}
		}
		try
		{
			if (_cachedLm != null)
			{
				int num = (int)_cachedLm.currentLanguage;
				if (num >= 1 && num <= 15)
				{
					return num - 1;
				}
			}
		}
		catch (Exception ex2)
		{
			_cachedLm = null;
			if (!_langProbeWarned)
			{
				_langProbeWarned = true;
				DebugLog.Warn("[本地化] LocalizationManager.currentLanguage 读取失败(改用设置页文本回退): " + ex2.Message);
			}
		}
		try
		{
			if ((Object)(object)ModState.TitleMenu == (Object)null)
			{
				return LangIndex;
			}
			if ((Object)(object)_cachedSettingsTmp == (Object)null)
			{
				Transform val = FindChildByName(((Component)ModState.TitleMenu).transform, "SettingsButton");
				if ((Object)(object)val == (Object)null)
				{
					return LangIndex;
				}
				_cachedSettingsTmp = ((Component)val).GetComponentInChildren<TMP_Text>(true);
				if ((Object)(object)_cachedSettingsTmp == (Object)null)
				{
					return LangIndex;
				}
			}
			string text = _cachedSettingsTmp.text;
			if (ContainsRange(text, '\u3040', 'ヿ'))
			{
				return 6;
			}
			if (ContainsRange(text, '가', '\ud7af'))
			{
				return 8;
			}
			if (ContainsHan(text))
			{
				return 5;
			}
			return 0;
		}
		catch (Exception ex3)
		{
			_cachedSettingsTmp = null;
			if (!_langProbeWarned)
			{
				_langProbeWarned = true;
				DebugLog.Debug("[本地化] 设置页文本探测失败(改用索引回退): " + ex3.Message);
			}
			return LangIndex;
		}
	}

	private static bool ContainsHan(string s)
	{
		foreach (char c in s)
		{
			if (c >= '一' && c <= '鿿')
			{
				return true;
			}
		}
		return false;
	}

	private static bool ContainsRange(string s, char lo, char hi)
	{
		foreach (char c in s)
		{
			if (c >= lo && c <= hi)
			{
				return true;
			}
		}
		return false;
	}

	internal static Transform FindChildByName(Transform root, string name)
	{
		if ((Object)(object)root == (Object)null)
		{
			return null;
		}
		if (((Object)root).name == name)
		{
			return root;
		}
		int childCount = root.childCount;
		for (int i = 0; i < childCount; i++)
		{
			Transform val = FindChildByName(root.GetChild(i), name);
			if ((Object)(object)val != (Object)null)
			{
				return val;
			}
		}
		return null;
	}
}
internal static class PasswordAutoFill
{
	private const float ArmedSeconds = 30f;

	private static string _password;

	private static string _joinCode;

	private static float _expireAt;

	public static void Apply(Harmony harmony)
	{
		//IL_0043: Unknown result type (might be due to invalid IL or missing references)
		//IL_0050: Expected O, but got Unknown
		try
		{
			MethodInfo methodInfo = AccessTools.DeclaredMethod(typeof(PasswordMenu), "OnEnable", (Type[])null, (Type[])null);
			if (methodInfo == null)
			{
				DebugLog.Warn("[加入] 找不到 PasswordMenu.OnEnable,自动填入密码不可用(仍可手动输入)");
				return;
			}
			harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(PasswordAutoFill).GetMethod("PasswordMenuEnabledPostfix")), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			DebugLog.Info("[加入] 已 patch PasswordMenu.OnEnable(公开密码自动填入)");
		}
		catch (Exception ex)
		{
			DebugLog.Error("[加入] 自动填入密码 patch 失败: " + ex);
		}
	}

	public static void Arm(string joinCode, string password)
	{
		if (!ModState.AutoFillPassword || string.IsNullOrEmpty(password))
		{
			Clear();
			return;
		}
		_password = password;
		_joinCode = joinCode;
		_expireAt = Time.realtimeSinceStartup + 30f;
		DebugLog.Debug("[加入] 已预置房间 " + joinCode + " 的公开密码,若弹出密码页将自动填入(值不记录)");
	}

	public static void Tick()
	{
		if (_password != null && Time.realtimeSinceStartup > _expireAt)
		{
			DebugLog.Debug("[加入] 预置密码到期自动作废(目标 " + _joinCode + ",未用到);不再驻留内存");
			Clear();
		}
	}

	public static void Clear()
	{
		_password = null;
		_joinCode = null;
		_expireAt = 0f;
	}

	public static void PasswordMenuEnabledPostfix(PasswordMenu __instance)
	{
		try
		{
			if (_password == null)
			{
				return;
			}
			if (!ModState.AutoFillPassword)
			{
				Clear();
				return;
			}
			if (Time.realtimeSinceStartup > _expireAt)
			{
				DebugLog.Debug("[加入] 预置密码已超时作废(目标 " + _joinCode + "),密码页保持空白");
				Clear();
				return;
			}
			if (!TargetMatches())
			{
				DebugLog.Debug("[加入] 当前连接目标与预置房间 " + _joinCode + " 不一致,作废预置(密码页保持空白)");
				Clear();
				return;
			}
			string password = _password;
			string joinCode = _joinCode;
			Clear();
			TMP_InputField val = (((Object)(object)__instance != (Object)null) ? __instance.passwordField : null);
			if ((Object)(object)val == (Object)null)
			{
				DebugLog.Warn("[加入] 密码页输入框不可用,未能自动填入(请手动输入)");
				return;
			}
			val.SetText(password, true);
			try
			{
				val.caretPosition = password.Length;
				val.stringPosition = password.Length;
			}
			catch (Exception ex)
			{
				DebugLog.Debug("[加入] 设置光标位置失败(不影响已填入的密码): " + ex.Message);
			}
			DebugLog.Info("[加入] 密码页已自动填入房间 " + joinCode + " 的公开密码(值不记录),请点确认加入");
		}
		catch (Exception ex2)
		{
			DebugLog.Warn("[加入] 自动填入密码失败(请手动输入): " + ex2.Message);
		}
	}

	private static bool TargetMatches()
	{
		try
		{
			NetworkManager singleton = NetworkManager.singleton;
			if ((Object)(object)singleton == (Object)null)
			{
				return true;
			}
			string networkAddress = singleton.networkAddress;
			if (string.IsNullOrEmpty(networkAddress) || string.IsNullOrEmpty(_joinCode))
			{
				return true;
			}
			return string.Equals(networkAddress.Trim(), _joinCode.Trim(), StringComparison.OrdinalIgnoreCase);
		}
		catch (Exception ex)
		{
			DebugLog.Debug("[加入] 读取当前连接目标失败(按预置继续): " + ex.Message);
			return true;
		}
	}
}
[BepInPlugin("com.bigwalk.publiclobbies", "BigWalk Public Lobbies", "1.6.0")]
public class Plugin : BasePlugin
{
	public const string GUID = "com.bigwalk.publiclobbies";

	public const string Name = "BigWalk Public Lobbies";

	public const string Version = "1.6.0";

	internal static ManualLogSource Log;

	private Harmony _harmony;

	public override void Load()
	{
		//IL_01a1: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ab: Expected O, but got Unknown
		Log = ((BasePlugin)this).Log;
		UiStyle.Unloaded = false;
		ConfigEntry<bool> verbose = ((BasePlugin)this).Config.Bind<bool>("Debug", "Verbose", false, "打印详细诊断日志(EOS 搜索、UI 结构探测、布局等)");
		DebugLog.Verbose = verbose.Value;
		verbose.SettingChanged += delegate
		{
			DebugLog.Verbose = verbose.Value;
		};
		ConfigEntry<int> taggedRounds = ((BasePlugin)this).Config.Bind<int>("Search", "TaggedRounds", 1, "一次刷新执行的定向搜索(模组房 bwpl_open)轮数:单轮上限 200,模组房总数远小于 200,1 轮即全量,多轮仅容错;0 = 关闭搜索(不发任何请求,立即显示空列表);最大 10(过大会单帧连发触发 EOS 限流)");
		ModState.TaggedRounds = NormalizeTaggedRounds(taggedRounds.Value);
		taggedRounds.SettingChanged += delegate
		{
			ModState.TaggedRounds = NormalizeTaggedRounds(taggedRounds.Value);
		};
		ModState.EnableRoomTagEntry = ((BasePlugin)this).Config.Bind<bool>("Mod", "EnableRoomTag", true, "建房时给房间打模组房标签并公开当前房间密码(EOS Lobby 公开属性 bwpl_open=\"1\" / bwpl_password)。默认开启。注意:密码是公开元数据,任何能搜到该房间的客户端都可读(仅省去手动输入,加入仍需原版密码验证),不要使用你在别处复用的密码。关闭方式:建房页(「建立主机」选择页/确认页)左侧的「模组房标签」开关(本局即时生效),或把本项改为 false。关闭后不再打标签、改密也不再同步;已发布到当前房间的属性不追溯移除(重开房间即消失)。");
		ModState.EnableRoomTag = ModState.EnableRoomTagEntry.Value;
		ModState.EnableRoomTagEntry.SettingChanged += delegate
		{
			ModState.EnableRoomTag = ModState.EnableRoomTagEntry.Value;
			LogPrivacyState();
		};
		ConfigEntry<bool> devMode = ((BasePlugin)this).Config.Bind<bool>("Dev", "DevMode", false, "开发者选项:开启后在任意场景按 F5 打开/关闭「公开房间」,用于测试自己的房间是否被识别为模组房");
		ModState.DevMode = devMode.Value;
		devMode.SettingChanged += delegate
		{
			ModState.DevMode = devMode.Value;
		};
		ConfigEntry<bool> autoFill = ((BasePlugin)this).Config.Bind<bool>("Join", "AutoFillPassword", true, "从「公开房间」列表加入带公开密码(bwpl_password)的房间时,自动把该密码填进原版密码页:只填入不提交,仍由你点原版确认按钮加入(可先核对/修改)。密码本就显示在列表里,此项仅省去手动输入;认证流程与原版完全一致。关闭后密码页保持空白,需手动输入");
		ModState.AutoFillPassword = autoFill.Value;
		autoFill.SettingChanged += delegate
		{
			ModState.AutoFillPassword = autoFill.Value;
		};
		((BasePlugin)this).AddComponent<PublicLobbyMod>();
		try
		{
			_harmony = new Harmony("com.bigwalk.publiclobbies");
		}
		catch (Exception ex)
		{
			DebugLog.Error("Harmony 实例创建失败: " + ex);
		}
		if (_harmony != null)
		{
			try
			{
				RoomTagPatch.ApplyRoomTagWrite(_harmony);
			}
			catch (Exception ex2)
			{
				DebugLog.Error("建房写标签 patch 初始化失败: " + ex2);
			}
			try
			{
				RoomTagPatch.ApplyPasswordSync(_harmony);
			}
			catch (Exception ex3)
			{
				DebugLog.Error("改密同步 patch 初始化失败: " + ex3);
			}
			try
			{
				PasswordAutoFill.Apply(_harmony);
			}
			catch (Exception ex4)
			{
				DebugLog.Error("密码自动填入 patch 初始化失败: " + ex4);
			}
			try
			{
				RoomTagPatch.CheckGameDependencies();
			}
			catch (Exception ex5)
			{
				DebugLog.Error("启动期依赖自检失败: " + ex5);
			}
		}
		ModState.Panel = new PublicLobbyPanel();
		ModState.Panel.Hide();
		DebugLog.Info($"{"BigWalk Public Lobbies"} v{"1.6.0"} 已加载(Verbose 日志={DebugLog.Verbose})。");
		LogPrivacyState();
	}

	public override bool Unload()
	{
		try
		{
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
			PasswordAutoFill.Clear();
			DebugLog.Info("[模组] 开始卸载清理(作废密码 / 回收句柄 / 摘除字体注册;不销毁任何资产)…");
			RoomPasswordSync.CleanupOnUnload();
			if (ModState.Panel != null)
			{
				ModState.Panel.Destroy();
			}
			LobbyScanner.ReleaseCarrierOnUnload();
			UiStyle.CleanupOnUnload();
			DebugLog.Info("[模组] 卸载清理完成(无异常)");
			return true;
		}
		catch (Exception ex)
		{
			DebugLog.Warn("Unload 清理失败: " + ex.Message);
			return false;
		}
	}

	private static void LogPrivacyState()
	{
		if (ModState.EnableRoomTag)
		{
			DebugLog.Info("[模组] 模组房标签=开启(默认):建房时会公开本房间的密码(EOS 公开属性,任何搜索者可读)——不想公开请在建房页关闭「模组房标签」开关,或把 config 里 [Mod] EnableRoomTag 改为 false。");
		}
		else
		{
			DebugLog.Info("[模组] 模组房标签=关闭:你的房间不会出现在别人的「公开房间」列表里。想让模组玩家看到你的房间:建房页打开「模组房标签」开关,或把 config 里 [Mod] EnableRoomTag 改为 true。");
		}
	}

	private static int NormalizeTaggedRounds(int value)
	{
		if (value >= 0)
		{
			if (value <= 10)
			{
				return value;
			}
			return 10;
		}
		return 0;
	}
}
public class PublicLobbyMod : MonoBehaviour
{
	private TitleMenu _titleMenu;

	private bool _buttonInjected;

	private GameObject _buttonTemplate;

	private GameObject _injectedButton;

	private TMP_Text _injectedTmp;

	private float _titleRetryTime;

	private float _templateRetryTime;

	private GameObject _hostSwitchGo;

	private readonly List<TMP_Text> _hostSwitchTmps = new List<TMP_Text>();

	private readonly List<TMP_Text> _hostTipTmps = new List<TMP_Text>();

	private bool _hostSwitchInjected;

	private bool _hostSwitchSelInjected;

	private bool _hostSwitchHcInjected;

	private float _hostSwitchLogTime;

	private float _hostSwitchFindTime;

	private bool _fontKickDone;

	private void Update()
	{
		try
		{
			UpdateCore();
		}
		catch (Exception ex)
		{
			DebugLog.Warn("[模组] Update 异常(已隔离,本帧跳过): " + ex.Message);
		}
	}

	private void UpdateCore()
	{
		if (UiStyle.Unloaded)
		{
			return;
		}
		if (!_fontKickDone)
		{
			_fontKickDone = true;
			UiStyle.EnsureFallbackFonts();
		}
		if (ModText.Poll())
		{
			try
			{
				UiStyle.RefreshFontMapping();
				UiStyle.EnsureFallbackFonts();
				ModState.Panel?.RefreshAllTexts(markCardDirty: true);
				RefreshMainButtonText();
				RefreshHostSwitchText();
			}
			catch (Exception ex)
			{
				DebugLog.Warn("[UI] 语言切换刷新异常: " + ex.Message);
			}
		}
		UiStyle.TickSpecialFontCreation();
		UiStyle.TickGlyphPreload();
		if (ModState.Panel != null)
		{
			ModState.Panel.Tick();
		}
		RoomPasswordSync.Tick();
		PasswordAutoFill.Tick();
		if (ModState.Panel != null && ModState.Panel.IsShown && Input.GetKeyDown((KeyCode)27))
		{
			ModState.Panel.Hide();
		}
		if (ModState.DevMode && Input.GetKeyDown((KeyCode)286))
		{
			if (ModState.Panel != null && ModState.Panel.IsShown)
			{
				DebugLog.Info("[模组] 开发者模式:F5 关闭公开房间");
				ModState.Panel.Hide();
			}
			else
			{
				DebugLog.Info("[模组] 开发者模式:F5 打开公开房间");
				ModState.Panel?.Show();
			}
		}
		UpdateHostSwitch();
		if (_buttonInjected)
		{
			if ((Object)(object)_injectedButton == (Object)null)
			{
				_buttonInjected = false;
				_injectedTmp = null;
				UiStyle.ButtonTextTemplate = null;
			}
			return;
		}
		if ((Object)(object)_titleMenu == (Object)null)
		{
			if (Time.time < _titleRetryTime)
			{
				return;
			}
			_titleRetryTime = Time.time + 0.5f;
			_titleMenu = Object.FindObjectOfType<TitleMenu>();
			if ((Object)(object)_titleMenu == (Object)null)
			{
				ModState.TitleMenu = null;
				ModState.StartButtons = null;
				return;
			}
		}
		ModState.TitleMenu = _titleMenu;
		if ((Object)(object)ModState.StartButtons == (Object)null)
		{
			Transform val = ModText.FindChildByName(((Component)_titleMenu).transform, "StartButtons");
			if ((Object)(object)val != (Object)null)
			{
				ModState.StartButtons = ((Component)val).gameObject;
			}
		}
		if ((Object)(object)_buttonTemplate == (Object)null)
		{
			if (Time.time < _templateRetryTime)
			{
				return;
			}
			_templateRetryTime = Time.time + 1f;
			try
			{
				_buttonTemplate = FindJoinMenuButton(_titleMenu);
			}
			catch (Exception ex2)
			{
				DebugLog.Warn("[模组] 查找「加入游戏」模板按钮失败: " + ex2.Message);
				_buttonTemplate = null;
			}
		}
		if (!((Object)(object)_buttonTemplate == (Object)null))
		{
			TryInjectButton();
		}
	}

	private static GameObject FindJoinMenuButton(TitleMenu titleMenu)
	{
		Il2CppArrayBase<Button> componentsInChildren = ((Component)titleMenu).GetComponentsInChildren<Button>(true);
		foreach (Button item in componentsInChildren)
		{
			try
			{
				if (item.onClick != null && ((UnityEventBase)item.onClick).GetPersistentEventCount() != 0 && ((UnityEventBase)item.onClick).GetPersistentMethodName(0) == "GoToJoinMenu")
				{
					Object persistentTarget = ((UnityEventBase)item.onClick).GetPersistentTarget(0);
					if (persistentTarget != (Object)null && persistentTarget is TitleMenu)
					{
						return ((Component)item).gameObject;
					}
				}
			}
			catch (Exception ex)
			{
				string text = (((Object)(object)item != (Object)null) ? ((Object)item).name : "?");
				DebugLog.Debug("[模组] 读取按钮持久事件失败(" + text + "): " + ex.Message);
			}
		}
		foreach (Button item2 in componentsInChildren)
		{
			if (((Component)item2).gameObject.activeSelf)
			{
				return ((Component)item2).gameObject;
			}
		}
		return null;
	}

	private void TryInjectButton()
	{
		//IL_002a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0114: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
		//IL_00df: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
		//IL_0109: Unknown result type (might be due to invalid IL or missing references)
		UiStyle.Capture(_buttonTemplate);
		GameObject val = UiStyle.CreateTextButton(((Component)_titleMenu).transform, "PublicLobbiesButton", ModText.T("title"), 36f, UiStyle.InkPrimary, delegate
		{
			ModState.Panel.Show();
		});
		if ((Object)(object)val == (Object)null)
		{
			DebugLog.Error("创建主菜单「公开房间」按钮失败");
			return;
		}
		_injectedButton = val;
		_injectedTmp = val.GetComponentInChildren<TMP_Text>(true);
		if ((Object)(object)_injectedTmp != (Object)null)
		{
			UiStyle.ButtonTextTemplate = ((Component)_injectedTmp).gameObject;
		}
		RectTransform component = val.GetComponent<RectTransform>();
		if ((Object)(object)component != (Object)null)
		{
			component.anchorMin = new Vector2(1f, 0.5f);
			component.anchorMax = new Vector2(1f, 0.5f);
			component.pivot = new Vector2(1f, 0.5f);
			component.sizeDelta = new Vector2(320f, 76f);
			component.anchoredPosition = new Vector2(-230f, -60f);
		}
		UiStyle.CreateRingBehind(val, UiStyle.AccentYellow, 116f);
		_buttonInjected = true;
		DebugLog.Info("[模组] 已注入「公开房间」按钮(右侧,挂在 TitleMenu 下)");
	}

	private void RefreshMainButtonText()
	{
		if ((Object)(object)_injectedTmp != (Object)null)
		{
			UiStyle.ApplyFont(_injectedTmp);
			_injectedTmp.text = ModText.T("title");
		}
	}

	private void UpdateHostSwitch()
	{
		if ((Object)(object)_titleMenu == (Object)null)
		{
			return;
		}
		if ((Object)(object)_hostSwitchGo == (Object)null)
		{
			_hostSwitchInjected = false;
			_hostSwitchSelInjected = false;
			_hostSwitchHcInjected = false;
			_hostSwitchTmps.Clear();
			_hostTipTmps.Clear();
		}
		if (_hostSwitchInjected)
		{
			return;
		}
		if ((Object)(object)UiStyle.ButtonTextTemplate == (Object)null)
		{
			if ((Object)(object)UiStyle.Font == (Object)null)
			{
				UiStyle.Capture(null);
			}
		}
		else
		{
			if (Time.time < _hostSwitchFindTime)
			{
				return;
			}
			_hostSwitchFindTime = Time.time + 1f;
			try
			{
				HostMenuSelect val = Object.FindObjectOfType<HostMenuSelect>(true);
				HostMenuConfirm val2 = Object.FindObjectOfType<HostMenuConfirm>(true);
				if ((Object)(object)val == (Object)null && (Object)(object)val2 == (Object)null)
				{
					if (Time.time > _hostSwitchLogTime)
					{
						_hostSwitchLogTime = Time.time + 10f;
						DebugLog.Warn("[模组] 未找到 HostMenuSelect/HostMenuConfirm(主菜单未就绪?),等待重试");
					}
					return;
				}
				if ((Object)(object)val != (Object)null && (Object)(object)((Component)val).gameObject != (Object)null && !_hostSwitchSelInjected)
				{
					InjectHostSwitch(((Component)val).transform);
					_hostSwitchSelInjected = true;
				}
				if ((Object)(object)val2 != (Object)null && (Object)(object)((Component)val2).gameObject != (Object)null && !_hostSwitchHcInjected)
				{
					InjectHostSwitch(((Component)val2).transform);
					_hostSwitchHcInjected = true;
				}
				_hostSwitchInjected = _hostSwitchSelInjected && _hostSwitchHcInjected;
				if (_hostSwitchInjected)
				{
					DebugLog.Info("[模组] 已在「建立主机」页(选择页+确认页)左侧注入模组房标签开关");
				}
			}
			catch (Exception ex)
			{
				DebugLog.Warn("[模组] 注入「建立主机」标签开关失败: " + ex.Message);
			}
		}
	}

	private void InjectHostSwitch(Transform parent)
	{
		//IL_003e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0053: Unknown result type (might be due to invalid IL or missing references)
		//IL_005e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0069: Unknown result type (might be due to invalid IL or missing references)
		//IL_007e: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
		//IL_012a: Unknown result type (might be due to invalid IL or missing references)
		//IL_013f: Unknown result type (might be due to invalid IL or missing references)
		//IL_014a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0154: Unknown result type (might be due to invalid IL or missing references)
		GameObject val = UiStyle.CreateOverlayButton(parent, "RoomTagToggle", ToggleRoomTag);
		if (!((Object)(object)val == (Object)null))
		{
			RectTransform component = val.GetComponent<RectTransform>();
			if ((Object)(object)component != (Object)null)
			{
				component.anchorMin = new Vector2(0.03f, 0.52f);
				component.anchorMax = new Vector2(0.17f, 0.58f);
				component.offsetMin = Vector2.zero;
				component.offsetMax = Vector2.zero;
				component.pivot = new Vector2(0.5f, 0.5f);
			}
			TMP_Text val2 = UiStyle.OverlayLabel(val, ModText.T(ModState.EnableRoomTag ? "tag_on" : "tag_off"), 30f, UiStyle.InkPrimary);
			if ((Object)(object)val2 != (Object)null)
			{
				_hostSwitchTmps.Add(val2);
			}
			else
			{
				DebugLog.Warn("[模组] host 开关标签文本创建失败(OverlayLabel 返回 null)");
			}
			TMP_Text val3 = UiStyle.CreateLabel("RoomTagTip", parent, ModText.T("tag_tip"), 30f, (TextAlignmentOptions)513, UiStyle.InkPrimary, allowWrap: true);
			if ((Object)(object)val3 == (Object)null)
			{
				DebugLog.Warn("[模组] host 开关提示文本创建失败(CreateLabel 返回 null)");
			}
			else
			{
				val3.overflowMode = (TextOverflowModes)0;
				RectTransform rectTransform = val3.rectTransform;
				rectTransform.anchorMin = new Vector2(0.03f, 0.3f);
				rectTransform.anchorMax = new Vector2(0.3f, 0.52f);
				rectTransform.offsetMin = Vector2.zero;
				rectTransform.offsetMax = Vector2.zero;
				_hostTipTmps.Add(val3);
			}
			if ((Object)(object)_hostSwitchGo == (Object)null)
			{
				_hostSwitchGo = val;
			}
		}
	}

	private void ToggleRoomTag()
	{
		ModState.EnableRoomTag = !ModState.EnableRoomTag;
		if (ModState.EnableRoomTagEntry != null)
		{
			ModState.EnableRoomTagEntry.Value = ModState.EnableRoomTag;
		}
		RefreshHostSwitchText();
		DebugLog.Info($"[模组] 模组房标签开关 → {ModState.EnableRoomTag}");
	}

	private void RefreshHostSwitchText()
	{
		string text = ModText.T(ModState.EnableRoomTag ? "tag_on" : "tag_off");
		string text2 = ModText.T("tag_tip");
		for (int i = 0; i < _hostSwitchTmps.Count; i++)
		{
			if ((Object)(object)_hostSwitchTmps[i] != (Object)null)
			{
				UiStyle.ApplyFont(_hostSwitchTmps[i]);
				_hostSwitchTmps[i].text = text;
			}
		}
		for (int j = 0; j < _hostTipTmps.Count; j++)
		{
			if ((Object)(object)_hostTipTmps[j] != (Object)null)
			{
				UiStyle.ApplyFont(_hostTipTmps[j]);
				_hostTipTmps[j].text = text2;
			}
		}
	}
}
public class PublicLobbyPanel
{
	private sealed class CardSlot
	{
		public int Index = -1;

		public RoomInfo Room;

		public readonly RectTransform Rect;

		public readonly Image Image;

		public readonly Button Button;

		public readonly TMP_Text NameT;

		public readonly TMP_Text HostT;

		public readonly TMP_Text CodeT;

		public readonly TMP_Text PwT;

		public readonly Image Bar;

		public readonly Image Rule;

		public readonly GameObject Box;

		public readonly Image Stub;

		public readonly Image Tear;

		public readonly GameObject JoinGo;

		public readonly Button JoinBtn;

		public readonly TMP_Text JoinTmp;

		public readonly TMP_Text CodeCapT;

		public readonly TMP_Text PwCapT;

		private readonly PublicLobbyPanel _panel;

		public CardSlot(PublicLobbyPanel panel, RectTransform rect, Image image, Button button, TMP_Text nameT, TMP_Text hostT, TMP_Text codeT, TMP_Text pwT, Image bar, Image rule, GameObject box, Image stub, Image tear, GameObject joinGo, Button joinBtn, TMP_Text joinTmp, TMP_Text codeCapT, TMP_Text pwCapT)
		{
			_panel = panel;
			Rect = rect;
			Image = image;
			Button = button;
			NameT = nameT;
			HostT = hostT;
			CodeT = codeT;
			PwT = pwT;
			Bar = bar;
			Rule = rule;
			Box = box;
			Stub = stub;
			Tear = tear;
			JoinGo = joinGo;
			JoinBtn = joinBtn;
			JoinTmp = joinTmp;
			CodeCapT = codeCapT;
			PwCapT = pwCapT;
		}

		public void OnRowJoin()
		{
			if (_panel != null && Room != null)
			{
				_panel.SelectRoom(Room);
				_panel.JoinSelected();
			}
		}

		public void OnClick()
		{
			if (_panel != null && Room != null)
			{
				_panel.SelectRoom(Room);
			}
		}
	}

	private GameObject _root;

	private RectTransform _content;

	private TMP_InputField _searchInput;

	private TMP_Text _statusText;

	private TMP_Text _titleTmp;

	private TMP_Text _langOnlyTmp;

	private TMP_Text _refreshTmp;

	private TMP_Text _joinTmp;

	private TMP_Text _backTmp;

	private TMP_Text _searchPh;

	private TMP_Text _searchingText;

	private string _statusKey;

	private object[] _statusArgs;

	private bool _statusError;

	private string _warnPrefix;

	private GameObject _joinButton;

	private GameObject _langOnlyButton;

	private GameObject _windowGo;

	private Scrollbar _scrollbarCmp;

	private ScrollRect _scrollRect;

	private RectTransform _viewportRt;

	private bool _joining;

	private Image _windowImg;

	private Image _listImg;

	private Image _handleImg;

	private Image _headerRule;

	private Image _listBottomRule;

	private Image _statusBar;

	private Image _refreshRule;

	private Image _backRule;

	private Image _langDot;

	private Image _joinDot;

	private GameObject _emptyGo;

	private GameObject _loadingGo;

	private Image _loadingRule;

	private TMP_Text _emptyTitleTmp;

	private TMP_Text _emptyHintTmp;

	private string _emptyBranch;

	private Button _joinBtn;

	private Button _refreshBtn;

	private Button _backBtn;

	private Button _langBtn;

	private bool _scanCompleted;

	private readonly List<RoomInfo> _rooms = new List<RoomInfo>();

	private string _lastSelectedKey;

	private readonly List<GameObject> _hiddenMenuButtons = new List<GameObject>();

	private string _filter = "";

	private bool _langOnly;

	private int _activeRoomCount;

	private bool _scanning;

	private int _lastDots = -1;

	private readonly string[] _dotTexts = new string[3];

	private string _dotTextsBase;

	private bool _needsFinalRender;

	private RoomInfo _selected;

	private bool _uiBuilt;

	private bool _buttonsHidden;

	private bool _loadedOnce;

	private int _lastSceneIndex = int.MinValue;

	private float _fallbackEnsureTime;

	private bool _uiModeOwned;

	private float _uiModeReassertAt;

	private float _filterRebuildTime;

	private const float CardHeight = 64f;

	private const float CardSpacing = 8f;

	private const float CardStride = 72f;

	private const int SlotAboveBuffer = 2;

	private const int SlotBelowBuffer = 2;

	private const float Row1Top = 3f;

	private const float Row1Bottom = 36f;

	private const float Row2Top = 40f;

	private const float Row2Bottom = 3f;

	private const float CardW = 569.4f;

	private const float WorldX = 14f;

	private const float WorldTextRight = 259f;

	private const float WorldInsetR = 6f;

	private const float TearX = 341f;

	private const float TearInsetY = 8f;

	private const float CapX0 = 351f;

	private const float CapX1 = 415f;

	private const float BoxX0 = 419f;

	private const float BoxX1 = 549f;

	private const float BoxY0 = 6f;

	private const float BoxY1 = 34f;

	private const float PwX0 = 419f;

	private const float PwX1 = 549f;

	private const float PwY0 = 36f;

	private const float PwY1 = 58f;

	private const float JoinX0 = 271f;

	private const float JoinX1 = 333.4f;

	private const float JoinY0 = 19f;

	private const float JoinY1 = 45f;

	private const float StubX0 = 343f;

	private Color _rowTintBase;

	private readonly List<RoomInfo> _filtered = new List<RoomInfo>();

	private readonly List<CardSlot> _slotPool = new List<CardSlot>();

	private int _windowFirst = -1;

	private int _windowLast = -1;

	private bool _langFilterDirty;

	private bool _hidStartButtons;

	public bool IsShown
	{
		get
		{
			if ((Object)(object)_root != (Object)null)
			{
				return _root.activeSelf;
			}
			return false;
		}
	}

	public bool IsScanning => _scanning;

	private static float RowTintAlphaFor(Color target)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0005: Unknown result type (might be due to invalid IL or missing references)
		//IL_0006: Unknown result type (might be due to invalid IL or missing references)
		//IL_001a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0020: Unknown result type (might be due to invalid IL or missing references)
		Color cardPaper = UiStyle.CardPaper;
		float num = ((cardPaper.r > 0.0001f) ? (target.r / cardPaper.r) : 1f);
		return Mathf.Clamp01(1f - num);
	}

	public void Show()
	{
		try
		{
			EnsureUi();
			if ((Object)(object)_root == (Object)null)
			{
				return;
			}
			UiStyle.EnsureFallbackFonts();
			_root.SetActive(true);
			HideMainMenuButtons();
			EnterUiMode();
			if (!_loadedOnce)
			{
				_loadedOnce = true;
				StartScan();
			}
			RefreshAllTexts();
			_joining = false;
			SetLoadingState(_scanning);
			SetRefreshBusy(_scanning);
			UpdateJoinState(joining: false);
			UpdateLangDot();
			UpdateEmptyState();
			if (_langFilterDirty)
			{
				_langFilterDirty = false;
				if (_langOnly && _rooms.Count > 0)
				{
					int num = RebuildWindow();
					SetStatus("showing_lang", num, _activeRoomCount, ModText.T("lang_group_name"), ModText.T("lang_on"));
				}
			}
		}
		catch (Exception ex)
		{
			DebugLog.Error("[页面] Show 失败: " + ex);
		}
	}

	public void Hide()
	{
		try
		{
			HideCore();
		}
		catch (Exception ex)
		{
			DebugLog.Warn("[页面] Hide 异常(已隔离): " + ex.Message);
		}
	}

	private void HideCore()
	{
		if ((Object)(object)_root != (Object)null)
		{
			_root.SetActive(false);
		}
		ExitUiMode();
		RestoreMainMenuButtons();
		if (_scanning)
		{
			_scanning = false;
			LobbyScanner.ResetBatch(startSearches: false);
			if (_rooms.Count == 0)
			{
				_loadedOnce = false;
			}
		}
	}

	public void Destroy()
	{
		try
		{
			HideCore();
			if ((Object)(object)_root != (Object)null)
			{
				Object.Destroy((Object)(object)_root);
				_root = null;
				_windowGo = null;
			}
			PasswordAutoFill.Clear();
			LobbyScanner.ResetBatch(startSearches: false);
			_slotPool.Clear();
			_filtered.Clear();
			_windowFirst = -1;
			_windowLast = -1;
			_uiBuilt = false;
		}
		catch (Exception ex)
		{
			DebugLog.Warn("[页面] Destroy 失败: " + ex.Message);
		}
	}

	private void EnterUiMode()
	{
		try
		{
			if ((Object)(object)WorldManager.instance != (Object)null)
			{
				WorldManager.SetToUIMode();
				_uiModeOwned = true;
			}
			else
			{
				_uiModeOwned = false;
			}
		}
		catch (Exception ex)
		{
			_uiModeOwned = false;
			DebugLog.Warn("[页面] EnterUiMode 失败: " + ex.Message);
		}
	}

	private void KeepUiMode()
	{
		if (Time.unscaledTime - _uiModeReassertAt < 0.25f)
		{
			return;
		}
		_uiModeReassertAt = Time.unscaledTime;
		try
		{
			WorldManager instance = WorldManager.instance;
			if ((Object)(object)instance != (Object)null && !instance.inUI)
			{
				WorldManager.SetToUIMode();
				DebugLog.Debug("[页面] 检测到游戏输入模式被切回,重新 SetToUIMode 保持");
			}
		}
		catch (Exception ex)
		{
			DebugLog.Debug("[页面] KeepUiMode 失败: " + ex.Message);
		}
	}

	private void ExitUiMode()
	{
		if (!_uiModeOwned)
		{
			return;
		}
		_uiModeOwned = false;
		try
		{
			WorldManager.SetToGameMode();
		}
		catch (Exception ex)
		{
			DebugLog.Warn("[页面] ExitUiMode 失败: " + ex.Message);
		}
	}

	private void HideMainMenuButtons()
	{
		if (_buttonsHidden)
		{
			return;
		}
		_buttonsHidden = true;
		try
		{
			if ((Object)(object)ModState.StartButtons != (Object)null && ModState.StartButtons.activeSelf)
			{
				ModState.StartButtons.SetActive(false);
				_hidStartButtons = true;
			}
			else
			{
				if (!((Object)(object)ModState.TitleMenu != (Object)null))
				{
					return;
				}
				_hiddenMenuButtons.Clear();
				{
					foreach (Button componentsInChild in ((Component)ModState.TitleMenu).GetComponentsInChildren<Button>(true))
					{
						if ((Object)(object)componentsInChild != (Object)null && ((Component)componentsInChild).gameObject.activeSelf && !IsOurButton(componentsInChild))
						{
							((Component)componentsInChild).gameObject.SetActive(false);
							_hiddenMenuButtons.Add(((Component)componentsInChild).gameObject);
						}
					}
					return;
				}
			}
		}
		catch (Exception ex)
		{
			DebugLog.Warn("[页面] 隐藏主菜单按钮失败: " + ex.Message);
		}
	}

	private static bool IsOurButton(Button b)
	{
		Canvas componentInParent = ((Component)b).GetComponentInParent<Canvas>(true);
		if ((Object)(object)componentInParent != (Object)null)
		{
			return ((Object)componentInParent).name == "PublicLobbyPanel";
		}
		return false;
	}

	private void RestoreMainMenuButtons()
	{
		if (!_buttonsHidden)
		{
			return;
		}
		_buttonsHidden = false;
		try
		{
			foreach (GameObject hiddenMenuButton in _hiddenMenuButtons)
			{
				if ((Object)(object)hiddenMenuButton != (Object)null)
				{
					hiddenMenuButton.SetActive(true);
				}
			}
			_hiddenMenuButtons.Clear();
			if (_hidStartButtons && (Object)(object)ModState.StartButtons != (Object)null)
			{
				ModState.StartButtons.SetActive(true);
			}
			_hidStartButtons = false;
		}
		catch (Exception ex)
		{
			DebugLog.Warn("[页面] 恢复主菜单按钮失败: " + ex.Message);
		}
	}

	private void EnsureUi()
	{
		if (_uiBuilt)
		{
			return;
		}
		try
		{
			BuildUi();
			_uiBuilt = true;
		}
		catch (Exception ex)
		{
			DebugLog.Error("构建公开房间页面失败: " + ex);
			if ((Object)(object)_root != (Object)null)
			{
				Object.Destroy((Object)(object)_root);
				_root = null;
				_windowGo = null;
			}
		}
	}

	private void BuildUi()
	{
		//IL_000b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0011: Expected O, but got Unknown
		//IL_004a: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
		//IL_017c: Unknown result type (might be due to invalid IL or missing references)
		//IL_01f6: Unknown result type (might be due to invalid IL or missing references)
		//IL_02b6: Unknown result type (might be due to invalid IL or missing references)
		//IL_034d: Unknown result type (might be due to invalid IL or missing references)
		//IL_03d2: Unknown result type (might be due to invalid IL or missing references)
		//IL_03dc: Expected O, but got Unknown
		//IL_041a: Unknown result type (might be due to invalid IL or missing references)
		//IL_042f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0444: Unknown result type (might be due to invalid IL or missing references)
		//IL_0459: Unknown result type (might be due to invalid IL or missing references)
		//IL_0463: Unknown result type (might be due to invalid IL or missing references)
		//IL_0492: Unknown result type (might be due to invalid IL or missing references)
		//IL_04bd: Unknown result type (might be due to invalid IL or missing references)
		//IL_04d2: Unknown result type (might be due to invalid IL or missing references)
		//IL_04e7: Unknown result type (might be due to invalid IL or missing references)
		//IL_04fc: Unknown result type (might be due to invalid IL or missing references)
		//IL_0510: Unknown result type (might be due to invalid IL or missing references)
		//IL_0526: Unknown result type (might be due to invalid IL or missing references)
		//IL_0550: Unknown result type (might be due to invalid IL or missing references)
		//IL_0565: Unknown result type (might be due to invalid IL or missing references)
		//IL_057a: Unknown result type (might be due to invalid IL or missing references)
		//IL_058f: Unknown result type (might be due to invalid IL or missing references)
		//IL_05a3: Unknown result type (might be due to invalid IL or missing references)
		//IL_05cb: Unknown result type (might be due to invalid IL or missing references)
		//IL_05fc: Unknown result type (might be due to invalid IL or missing references)
		//IL_0611: Unknown result type (might be due to invalid IL or missing references)
		//IL_0626: Unknown result type (might be due to invalid IL or missing references)
		//IL_063b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0645: Unknown result type (might be due to invalid IL or missing references)
		//IL_0685: Unknown result type (might be due to invalid IL or missing references)
		//IL_0749: Unknown result type (might be due to invalid IL or missing references)
		//IL_076a: Unknown result type (might be due to invalid IL or missing references)
		//IL_079b: Unknown result type (might be due to invalid IL or missing references)
		//IL_07b0: Unknown result type (might be due to invalid IL or missing references)
		//IL_07c5: Unknown result type (might be due to invalid IL or missing references)
		//IL_07da: Unknown result type (might be due to invalid IL or missing references)
		//IL_07ee: Unknown result type (might be due to invalid IL or missing references)
		//IL_085c: Unknown result type (might be due to invalid IL or missing references)
		//IL_02e6: Unknown result type (might be due to invalid IL or missing references)
		//IL_02f0: Unknown result type (might be due to invalid IL or missing references)
		UiStyle.Capture(null);
		GameObject val = (_root = new GameObject("PublicLobbyPanel"));
		Object.DontDestroyOnLoad((Object)(object)val);
		val.AddComponent<Canvas>().renderMode = (RenderMode)0;
		val.AddComponent<CanvasScaler>().uiScaleMode = (ScaleMode)1;
		val.AddComponent<GraphicRaycaster>();
		_windowImg = NewImage("Window", val.transform, UiStyle.PanelBackground);
		_windowGo = ((Component)_windowImg).gameObject;
		SetAnchors(((Graphic)_windowImg).rectTransform, 0.08f, 0.05f, 0.92f, 0.95f);
		_titleTmp = UiStyle.CreateLabel("Title", _windowGo.transform, ModText.T("title"), 18f, (TextAlignmentOptions)513, UiStyle.InkPrimary, allowWrap: false, 14f);
		_titleTmp.overflowMode = (TextOverflowModes)1;
		SetAnchors(_titleTmp.rectTransform, 0.06f, 0.918f, 0.291f, 0.978f);
		_headerRule = MakeHRule(_windowGo.transform, 0.06f, 0.94f, 0.906f);
		_searchInput = UiStyle.CreateInput(_windowGo.transform);
		SetAnchors(((Component)_searchInput).GetComponent<RectTransform>(), 0.3f, 0.918f, 0.622f, 0.978f);
		_searchPh = UiStyle.CreateLabel("SearchHint", _windowGo.transform, ModText.T("search_ph"), 16f, (TextAlignmentOptions)513, UiStyle.InkSecondary, allowWrap: false, 12f);
		_searchPh.fontSizeMax = 16f;
		_searchPh.verticalAlignment = (VerticalAlignmentOptions)512;
		_searchPh.overflowMode = (TextOverflowModes)1;
		SetAnchors(_searchPh.rectTransform, 0.3f, 0.918f, 0.622f, 0.978f);
		_searchPh.rectTransform.offsetMin = new Vector2(8f, 0f);
		((UnityEventBase)_searchInput.onValueChanged).RemoveAllListeners();
		((UnityEvent<string>)(object)_searchInput.onValueChanged).AddListener(UiDelegates.Ua1(OnSearchChanged));
		_langOnlyButton = UiStyle.CreateOverlayButton(_windowGo.transform, "BtnLangOnly", ToggleLangOnly);
		SetAnchors(_langOnlyButton.GetComponent<RectTransform>(), 0.631f, 0.918f, 0.806f, 0.978f);
		_langBtn = _langOnlyButton.GetComponent<Button>();
		_langDot = MakeDot(_langOnlyButton.transform);
		_langOnlyTmp = UiStyle.OverlayLabel(_langOnlyButton, LangToggleText(), 16f, UiStyle.InkPrimary);
		if ((Object)(object)_langOnlyTmp != (Object)null)
		{
			RectTransform rectTransform = _langOnlyTmp.rectTransform;
			rectTransform.offsetMin = new Vector2(18f, rectTransform.offsetMin.y);
		}
		GameObject val2 = UiStyle.CreateOverlayButton(_windowGo.transform, "BtnRefresh", StartScan);
		SetAnchors(val2.GetComponent<RectTransform>(), 0.815f, 0.918f, 0.926f, 0.978f);
		_refreshTmp = UiStyle.OverlayLabel(val2, ModText.T("refresh"), 16f, UiStyle.InkPrimary);
		_refreshRule = MakeUnderline(val2.transform);
		_refreshBtn = val2.GetComponent<Button>();
		_content = BuildScrollView(_windowGo.transform);
		_listBottomRule = MakeHRule(_windowGo.transform, 0.06f, 0.94f, 0.098f);
		_emptyGo = BuildEmptyState(_windowGo.transform);
		_loadingGo = new GameObject("LoadingState");
		_loadingGo.AddComponent<RectTransform>();
		_loadingGo.transform.SetParent(_windowGo.transform, false);
		RectTransform component = _loadingGo.GetComponent<RectTransform>();
		component.anchorMin = new Vector2(0.5f, 0.46f);
		component.anchorMax = new Vector2(0.5f, 0.46f);
		component.pivot = new Vector2(0.5f, 0.5f);
		component.sizeDelta = new Vector2(300f, 60f);
		component.anchoredPosition = Vector2.zero;
		_searchingText = UiStyle.CreateLabel("SearchingHint", _loadingGo.transform, ModText.T("searching"), 20f, (TextAlignmentOptions)514, UiStyle.InkPrimary, allowWrap: false, 18f);
		RectTransform rectTransform2 = _searchingText.rectTransform;
		rectTransform2.anchorMin = new Vector2(0.5f, 0.5f);
		rectTransform2.anchorMax = new Vector2(0.5f, 0.5f);
		rectTransform2.pivot = new Vector2(0.5f, 0.5f);
		rectTransform2.sizeDelta = new Vector2(300f, 34f);
		rectTransform2.anchoredPosition = new Vector2(0f, 10f);
		_loadingRule = UiStyle.CreateRule(_loadingGo.transform, UiStyle.RuleSoft);
		RectTransform rectTransform3 = ((Graphic)_loadingRule).rectTransform;
		rectTransform3.anchorMin = new Vector2(0.5f, 0.5f);
		rectTransform3.anchorMax = new Vector2(0.5f, 0.5f);
		rectTransform3.pivot = new Vector2(0.5f, 0.5f);
		rectTransform3.sizeDelta = new Vector2(220f, 1f);
		rectTransform3.anchoredPosition = new Vector2(0f, -14f);
		_loadingGo.SetActive(false);
		_statusBar = NewImage("StatusErrorBar", _windowGo.transform, UiStyle.AccentRed);
		((Graphic)_statusBar).raycastTarget = false;
		RectTransform rectTransform4 = ((Graphic)_statusBar).rectTransform;
		rectTransform4.anchorMin = new Vector2(0.06f, 0.024f);
		rectTransform4.anchorMax = new Vector2(0.06f, 0.084f);
		rectTransform4.pivot = new Vector2(0f, 0.5f);
		rectTransform4.sizeDelta = new Vector2(3f, 0f);
		rectTransform4.anchoredPosition = Vector2.zero;
		((Component)_statusBar).gameObject.SetActive(false);
		_statusText = UiStyle.CreateLabel("Status", _windowGo.transform, ModText.T("loading"), 14f, (TextAlignmentOptions)513, UiStyle.InkSecondary, allowWrap: false, 12f);
		_statusText.overflowMode = (TextOverflowModes)1;
		_statusText.richText = false;
		SetAnchors(_statusText.rectTransform, 0.066f, 0.024f, 0.7f, 0.084f);
		_joinButton = UiStyle.CreateOverlayButton(_windowGo.transform, "BtnJoin", JoinSelected, primary: true);
		SetAnchors(_joinButton.GetComponent<RectTransform>(), 0.72f, 0.024f, 0.86f, 0.084f);
		_joinBtn = _joinButton.GetComponent<Button>();
		_joinTmp = UiStyle.OverlayLabel(_joinButton, ModText.T("join"), 16f, UiStyle.Paper);
		_joinDot = NewImage("JoinAccent", _joinButton.transform, UiStyle.AccentYellow);
		((Graphic)_joinDot).raycastTarget = false;
		RectTransform rectTransform5 = ((Graphic)_joinDot).rectTransform;
		rectTransform5.anchorMin = new Vector2(0f, 0.5f);
		rectTransform5.anchorMax = new Vector2(0f, 0.5f);
		rectTransform5.pivot = new Vector2(0.5f, 0.5f);
		rectTransform5.sizeDelta = new Vector2(8f, 8f);
		rectTransform5.anchoredPosition = new Vector2(10f, 0f);
		((Component)_joinDot).gameObject.SetActive(false);
		GameObject val3 = UiStyle.CreateOverlayButton(_windowGo.transform, "BtnBack", Hide);
		SetAnchors(val3.GetComponent<RectTransform>(), 0.885f, 0.024f, 0.97f, 0.084f);
		_backTmp = UiStyle.OverlayLabel(val3, ModText.T("back"), 16f, UiStyle.InkPrimary);
		_backRule = MakeUnderline(val3.transform);
		_backBtn = val3.GetComponent<Button>();
		UpdateJoinState(joining: false);
		UpdateLangDot();
		Hide();
	}

	private void ApplyPanelFonts()
	{
		UiStyle.ApplyFont(_titleTmp);
		UiStyle.ApplyFont(_langOnlyTmp);
		UiStyle.ApplyFont(_refreshTmp);
		UiStyle.ApplyFont(_joinTmp);
		UiStyle.ApplyFont(_backTmp);
		UiStyle.ApplyFont(_searchPh);
		UiStyle.ApplyFont(_searchingText);
		UiStyle.ApplyFont(_statusText);
	}

	public void RefreshAllTexts(bool markCardDirty = false)
	{
		ApplyPanelFonts();
		if ((Object)(object)_titleTmp != (Object)null)
		{
			_titleTmp.text = ModText.T("title");
		}
		if ((Object)(object)_langOnlyTmp != (Object)null)
		{
			_langOnlyTmp.text = LangToggleText();
		}
		if ((Object)(object)_refreshTmp != (Object)null)
		{
			_refreshTmp.text = ModText.T("refresh");
		}
		if ((Object)(object)_joinTmp != (Object)null)
		{
			_joinTmp.text = ModText.T("join");
		}
		if ((Object)(object)_backTmp != (Object)null)
		{
			_backTmp.text = ModText.T("back");
		}
		if ((Object)(object)_searchPh != (Object)null)
		{
			_searchPh.text = ModText.T("search_ph");
			((Graphic)_searchPh).SetAllDirty();
			UpdateSearchHint();
		}
		ApplyStatus();
		UpdateLangDot();
		UpdateJoinState(_joining);
		SetRefreshBusy(_scanning);
		UpdateEmptyState();
		if (markCardDirty)
		{
			for (int i = 0; i < _slotPool.Count; i++)
			{
				CardSlot cardSlot = _slotPool[i];
				if (cardSlot.Index >= 0)
				{
					UpdateSlotTexts(cardSlot);
				}
			}
		}
		if (markCardDirty && _langOnly && _rooms.Count > 0)
		{
			if (IsShown)
			{
				int num = RebuildWindow();
				SetStatus("showing_lang", num, _activeRoomCount, ModText.T("lang_group_name"), ModText.T("lang_on"));
			}
			else
			{
				_langFilterDirty = true;
			}
		}
	}

	private Image NewImage(string name, Transform parent, Color color)
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		//IL_0006: 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)
		//IL_0021: Unknown result type (might be due to invalid IL or missing references)
		GameObject val = new GameObject(name);
		val.AddComponent<RectTransform>();
		Image val2 = val.AddComponent<Image>();
		val.transform.SetParent(parent, false);
		((Graphic)val2).color = color;
		((Graphic)val2).raycastT