Decompiled source of NetworkBlacklist v1.0.6

NetworkBlacklist.dll

Decompiled 13 hours ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using ExitGames.Client.Photon;
using HarmonyLib;
using Photon.Pun;
using Photon.Realtime;
using SummitWatchCN.Blacklist;
using SummitWatchCN.I18n;
using UnityEngine;
using UnityEngine.SceneManagement;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("SummitWatchCN")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.6.0")]
[assembly: AssemblyInformationalVersion("1.0.6")]
[assembly: AssemblyProduct("SummitWatch Chinese Localization & Blacklist Addon")]
[assembly: AssemblyTitle("NetworkBlacklist")]
[assembly: AssemblyVersion("1.0.6.0")]
namespace SummitWatchCN
{
	public static class API
	{
		public static string Version => "1.0.6";

		public static bool IsReady => NetworkBlacklist.Ready;

		public static bool IsOnline => !NetworkBlacklist.Offline;

		public static string LastSyncResult => NetworkBlacklist.LastResult;

		public static DateTime LastSyncTime => NetworkBlacklist.LastRefresh;

		public static bool HasPeakSentinel => AntiGriefDetector.HasPeakSentinel();

		public static bool IsNetworkBanned(string steamId)
		{
			return NetworkBlacklist.IsNetworkBanned(steamId);
		}

		public static bool IsPardoned(string steamId)
		{
			return NetworkBlacklist.IsPardoned(steamId);
		}

		public static bool IsLocalBanned(string steamId, string name = null)
		{
			BlacklistEntry match;
			return BlacklistStore.IsListed(steamId, name, out match);
		}

		public static bool IsBannedAnywhere(string steamId, string name = null)
		{
			if (!IsNetworkBanned(steamId))
			{
				return IsLocalBanned(steamId, name);
			}
			return true;
		}

		public static IReadOnlyList<NetEntrySnapshot> GetNetworkEntries()
		{
			return NetworkBlacklist.Entries.Select((NetEntry e) => new NetEntrySnapshot(e.id, e.name, e.reason, e.added)).ToList();
		}

		public static IReadOnlyList<LocalEntrySnapshot> GetLocalEntries()
		{
			return BlacklistStore.Entries.Select((BlacklistEntry e) => new LocalEntrySnapshot(e.id, e.name, e.note, e.added)).ToList();
		}

		public static void RefreshNetworkNow()
		{
			NetworkBlacklist.RefreshAsync();
		}
	}
	[BepInPlugin("summitwatch.cn.blacklist", "网络黑名单 / Network Blacklist", "1.0.6")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string GuidValue = "summitwatch.cn.blacklist";

		public const string PluginName = "网络黑名单 / Network Blacklist";

		public const string VersionValue = "1.0.6";

		internal static ManualLogSource Log;

		private Harmony _harmony;

		private BlacklistManager _manager;

		private void Awake()
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Expected O, but got Unknown
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			BlacklistConfig.Init(((BaseUnityPlugin)this).Config);
			BlacklistStore.Load();
			NetworkBlacklist.PreloadCache();
			NetworkBlacklist.RefreshAsync();
			_harmony = new Harmony("summitwatch.cn.blacklist");
			GuiTranslatePatches.Apply(_harmony);
			KickPatch.Apply(_harmony);
			RpcBlockPatch.Apply(_harmony);
			AntiGriefDetector.Apply();
			GameObject val = new GameObject("SummitWatchCN")
			{
				hideFlags = (HideFlags)61
			};
			Object.DontDestroyOnLoad((Object)(object)val);
			_manager = val.AddComponent<BlacklistManager>();
			SessionLogger.Install();
			Log.LogInfo((object)"SummitWatch 汉化与黑名单附属 v1.0.6 已加载。");
			Log.LogInfo((object)("黑名单文件: " + BlacklistStore.FilePath));
		}

		private void OnDestroy()
		{
			try
			{
				BlacklistStore.Save();
			}
			catch
			{
			}
			try
			{
				AntiGriefDetector.Unapply();
			}
			catch
			{
			}
			if (_harmony != null)
			{
				_harmony.UnpatchSelf();
			}
		}
	}
}
namespace SummitWatchCN.I18n
{
	internal static class GuiTranslatePatches
	{
		private static Assembly _swAssembly;

		private static Assembly _sentinelAssembly;

		private static readonly ConcurrentDictionary<MethodBase, byte> CallerCache = new ConcurrentDictionary<MethodBase, byte>();

		public static bool Diag = InitDiag();

		private static int _diagCalls;

		private static int _diagHits;

		private static Regex _tagRegex = new Regex("<[^>]+>", RegexOptions.Compiled);

		private static bool _loggedError = false;

		private static int _lastFrame = -1;

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

		private static Type _tmpType;

		private static PropertyInfo _tmpTextProp;

		private static Type _uiTextType;

		private static PropertyInfo _uiTextProp;

		private static readonly List<Transform> _roots = new List<Transform>();

		private static Component[] _cachedComps;

		private static float _nextDiscovery;

		private static float _nextCacheRebuild;

		private static Type _canvasType;

		private static int _gsDrawCalls;

		private static int _prefixStringCalled;

		private static int _prefixContentCalled;

		private static bool InitDiag()
		{
			try
			{
				if (Environment.GetEnvironmentVariable("SWCN_DIAG") == "1")
				{
					return true;
				}
				return File.Exists(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "BepInEx", "config", "SWCN_DIAG"));
			}
			catch
			{
				return false;
			}
		}

		public static void Apply(Harmony harmony)
		{
			try
			{
				Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
				foreach (Assembly assembly in assemblies)
				{
					string? name = assembly.GetName().Name;
					if (name == "SummitWatch")
					{
						_swAssembly = assembly;
					}
					if (name == "PeakSentinel")
					{
						_sentinelAssembly = assembly;
					}
				}
				if (_swAssembly == null && _sentinelAssembly == null)
				{
					Plugin.Log.LogWarning((object)"未找到 SummitWatch / PeakSentinel 程序集,汉化补丁未安装。");
					return;
				}
				Type typeFromHandle = typeof(GUI);
				Type typeFromHandle2 = typeof(GUILayout);
				Type typeFromHandle3 = typeof(GUIStyle);
				Type typeFromHandle4 = typeof(GUILayoutOption[]);
				Type typeFromHandle5 = typeof(Rect);
				Type typeFromHandle6 = typeof(string);
				Type typeFromHandle7 = typeof(GUIContent);
				Type typeFromHandle8 = typeof(int);
				Type typeFromHandle9 = typeof(WindowFunction);
				Type typeFromHandle10 = typeof(bool);
				Patch(harmony, typeFromHandle2, "Label", new Type[3] { typeFromHandle6, typeFromHandle3, typeFromHandle4 });
				Patch(harmony, typeFromHandle2, "Label", new Type[2] { typeFromHandle6, typeFromHandle4 });
				Patch(harmony, typeFromHandle2, "Button", new Type[3] { typeFromHandle6, typeFromHandle3, typeFromHandle4 });
				Patch(harmony, typeFromHandle2, "Button", new Type[2] { typeFromHandle6, typeFromHandle4 });
				Patch(harmony, typeFromHandle2, "Toggle", new Type[4] { typeFromHandle10, typeFromHandle6, typeFromHandle3, typeFromHandle4 });
				Patch(harmony, typeFromHandle2, "Toggle", new Type[3] { typeFromHandle10, typeFromHandle6, typeFromHandle4 });
				Patch(harmony, typeFromHandle, "Toggle", new Type[4] { typeFromHandle5, typeFromHandle10, typeFromHandle6, typeFromHandle3 });
				Patch(harmony, typeFromHandle, "Toggle", new Type[3] { typeFromHandle5, typeFromHandle10, typeFromHandle6 });
				Patch(harmony, typeFromHandle2, "Window", new Type[6] { typeFromHandle8, typeFromHandle5, typeFromHandle9, typeFromHandle6, typeFromHandle3, typeFromHandle4 });
				Patch(harmony, typeFromHandle, "Window", new Type[5] { typeFromHandle8, typeFromHandle5, typeFromHandle9, typeFromHandle6, typeFromHandle3 });
				Patch(harmony, typeFromHandle, "Window", new Type[4] { typeFromHandle8, typeFromHandle5, typeFromHandle9, typeFromHandle6 });
				Patch(harmony, typeFromHandle, "Box", new Type[3] { typeFromHandle5, typeFromHandle6, typeFromHandle3 });
				Patch(harmony, typeFromHandle, "Label", new Type[3] { typeFromHandle5, typeFromHandle6, typeFromHandle3 });
				Patch(harmony, typeFromHandle, "Label", new Type[2] { typeFromHandle5, typeFromHandle6 });
				PatchContent(harmony, typeFromHandle2, "Label", new Type[3] { typeFromHandle7, typeFromHandle3, typeFromHandle4 });
				PatchContent(harmony, typeFromHandle2, "Label", new Type[2] { typeFromHandle7, typeFromHandle4 });
				PatchContent(harmony, typeFromHandle2, "Button", new Type[3] { typeFromHandle7, typeFromHandle3, typeFromHandle4 });
				PatchContent(harmony, typeFromHandle2, "Button", new Type[2] { typeFromHandle7, typeFromHandle4 });
				PatchContent(harmony, typeFromHandle2, "Toggle", new Type[4] { typeFromHandle10, typeFromHandle7, typeFromHandle3, typeFromHandle4 });
				PatchContent(harmony, typeFromHandle2, "Toggle", new Type[3] { typeFromHandle10, typeFromHandle7, typeFromHandle4 });
				PatchContent(harmony, typeFromHandle, "Label", new Type[3] { typeFromHandle5, typeFromHandle7, typeFromHandle3 });
				PatchContent(harmony, typeFromHandle, "Label", new Type[2] { typeFromHandle5, typeFromHandle7 });
				PatchContent(harmony, typeFromHandle, "Button", new Type[3] { typeFromHandle5, typeFromHandle7, typeFromHandle3 });
				PatchContent(harmony, typeFromHandle, "Button", new Type[2] { typeFromHandle5, typeFromHandle7 });
				PatchContent(harmony, typeFromHandle, "Toggle", new Type[4] { typeFromHandle5, typeFromHandle10, typeFromHandle7, typeFromHandle3 });
				PatchContent(harmony, typeFromHandle, "Toggle", new Type[3] { typeFromHandle5, typeFromHandle10, typeFromHandle7 });
				PatchContent(harmony, typeFromHandle, "Window", new Type[5] { typeFromHandle8, typeFromHandle5, typeFromHandle9, typeFromHandle7, typeFromHandle3 });
				PatchContent(harmony, typeFromHandle, "Box", new Type[3] { typeFromHandle5, typeFromHandle7, typeFromHandle3 });
				Patch(harmony, typeFromHandle, "Button", new Type[2] { typeFromHandle5, typeFromHandle6 });
				Patch(harmony, typeFromHandle, "RepeatButton", new Type[3] { typeFromHandle5, typeFromHandle6, typeFromHandle3 });
				Patch(harmony, typeFromHandle, "RepeatButton", new Type[2] { typeFromHandle5, typeFromHandle6 });
				PatchContent(harmony, typeFromHandle, "RepeatButton", new Type[3] { typeFromHandle5, typeFromHandle7, typeFromHandle3 });
				PatchContent(harmony, typeFromHandle, "RepeatButton", new Type[2] { typeFromHandle5, typeFromHandle7 });
				Plugin.Log.LogInfo((object)"[汉化] PeakSentinel uGUI 翻译采用窗口子树扫描模式(无 setter 补丁开销)。");
				Plugin.Log.LogInfo((object)"Summit Watch 界面汉化补丁已安装(面板/资料/迷你悬浮窗/提示/弹窗)。");
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("汉化补丁应用失败: " + ex));
			}
		}

		private static void Patch(Harmony harmony, Type type, string name, Type[] args)
		{
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Expected O, but got Unknown
			MethodInfo methodInfo = AccessTools.Method(type, name, args, (Type[])null);
			if (methodInfo == null)
			{
				Plugin.Log.LogWarning((object)("找不到待汉化的 IMGUI 方法: " + type.Name + "." + name));
				return;
			}
			harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(GuiTranslatePatches), "Prefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			if (Diag)
			{
				Plugin.Log.LogInfo((object)("[DIAG] 已补丁目标: " + methodInfo.DeclaringType.FullName + "." + methodInfo.Name + " token=0x" + methodInfo.MetadataToken.ToString("X")));
			}
		}

		private static void PatchContent(Harmony harmony, Type type, string name, Type[] args)
		{
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Expected O, but got Unknown
			MethodInfo methodInfo = AccessTools.Method(type, name, args, (Type[])null);
			if (methodInfo == null)
			{
				Plugin.Log.LogWarning((object)("找不到待汉化的 IMGUI(GUIContent) 方法: " + type.Name + "." + name));
			}
			else
			{
				harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(GuiTranslatePatches), "PrefixContent", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
		}

		private static void BrutePatchAll(Harmony harmony, Type type)
		{
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cb: Expected O, but got Unknown
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: Expected O, but got Unknown
			int num = 0;
			int num2 = 0;
			MethodInfo[] methods = type.GetMethods(BindingFlags.Static | BindingFlags.Public);
			foreach (MethodInfo methodInfo in methods)
			{
				ParameterInfo[] parameters = methodInfo.GetParameters();
				bool flag = false;
				bool flag2 = false;
				ParameterInfo[] array = parameters;
				foreach (ParameterInfo obj in array)
				{
					if (obj.ParameterType == typeof(string))
					{
						flag = true;
					}
					if (obj.ParameterType == typeof(GUIContent))
					{
						flag2 = true;
					}
				}
				if (!flag && !flag2)
				{
					num2++;
					continue;
				}
				try
				{
					if (flag2)
					{
						harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(GuiTranslatePatches), "PrefixContent", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					}
					else
					{
						harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(GuiTranslatePatches), "Prefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					}
					num++;
				}
				catch
				{
				}
			}
			Plugin.Log.LogInfo((object)("[汉化] 暴力补丁 " + type.Name + ": 成功 " + num + " 个方法, 跳过 " + num2));
		}

		private static string TranslateCached(string raw)
		{
			int frameCount = Time.frameCount;
			if (frameCount != _lastFrame)
			{
				_frameCache.Clear();
				_lastFrame = frameCount;
			}
			if (_frameCache.TryGetValue(raw, out var value))
			{
				return value;
			}
			string text = DoTranslate(raw);
			if (_frameCache.Count < 500)
			{
				_frameCache[raw] = text;
			}
			return text;
		}

		private static string SuffixOf(string v)
		{
			int num = v.IndexOf("→");
			if (num < 0)
			{
				return "";
			}
			return v.Substring(num).Replace("→ protects only you", "→ 仅保护你").Replace("→ protects everyone", "→ 保护所有人")
				.Replace("→ does nothing now: you are not the host", "→ 当前不生效:你不是主机")
				.Replace("(as host: protects everyone)", "(作为主机:保护所有人)")
				.Replace("→ only writes to the log", "→ 仅记录到日志");
		}

		private static string DoTranslate(string value)
		{
			if (string.IsNullOrEmpty(value))
			{
				return value;
			}
			foreach (char c in value)
			{
				if (c >= '一' && c <= '鿿')
				{
					return value;
				}
			}
			string text = ((value.IndexOf('<') >= 0) ? _tagRegex.Replace(value, "") : value);
			bool flag = false;
			for (int j = 0; j < text.Length; j++)
			{
				switch (text[j])
				{
				case '\t':
				case '\n':
				case '\r':
					flag = true;
					break;
				case ' ':
					if (j + 1 >= text.Length || text[j + 1] != ' ')
					{
						continue;
					}
					flag = true;
					break;
				default:
					continue;
				}
				break;
			}
			if (flag)
			{
				text = Regex.Replace(text, "\\s+", " ").Trim();
			}
			string text2 = L10n.T(text);
			if (text2 == text && (text.Contains("If on:") || text.Contains("→") || text.StartsWith("Glitched:") || text.StartsWith("Any%:") || text.Contains("self-check:") || text.Contains("another mod patches")))
			{
				string text3 = text;
				if (text3.StartsWith("If on: the attacker"))
				{
					text3 = text3.Replace("If on: the attacker is logged; Block: the command does nothing on your side, others are safe only with their own Sentinel", "开启后:攻击者被记录;拦截:该指令在你这边无效,其他人需要自己的哨兵才安全");
				}
				if (text3.StartsWith("If on: the player is logged"))
				{
					text3 = text3.Replace("If on: the player is logged; Block: the forced action does not happen on your side", "开启后:该玩家被记录;拦截:强制动作在你这边不发生");
				}
				if (text3.StartsWith("If on: the non-host is logged"))
				{
					text3 = text3.Replace("If on: the non-host is logged; Block: the event does not happen on your side, others need their own Sentinel", "开启后:非主机被记录;拦截:该事件在你这边不发生,其他人需要自己的哨兵");
				}
				if (text3.StartsWith("If on: the spawner is logged"))
				{
					text3 = text3.Replace("If on: the spawner is logged; Block: not created on your side: as host they are removed for everyone", "开启后:生成者被记录;拦截:在你这边不生成;作为主机会为所有人移除");
				}
				text3 = text3.Replace("If on: cheat item spawns and forged pickups are refused, the item is never created", "开启后:作弊物品生成和伪造拾取物将被拒绝,物品不会被创建");
				text3 = text3.Replace("If on: your own spawner keeps working", "开启后:你自己的生成器继续工作");
				text3 = text3.Replace("If on: an item forced into your hands is removed from your inventory", "开启后:强制塞给你的物品会从背包移除");
				text3 = text3.Replace("If on: every enabled check also runs on your own commands, spawns, flight, stamina, effects, markers and name", "开启后:所有启用的检测也会运行在你自己的指令、生成、飞行、体力、效果、标记和名称上");
				text3 = text3.Replace("If on: the attacker is logged: Block: the command does nothing on your side, others are safe only with their own Sentinel", "开启后:攻击者被记录;拦截:该指令在你这边无效,其他人需要自己的哨兵才安全");
				text3 = text3.Replace("If on: the player is logged: Block: the forced action does not happen on your side", "开启后:该玩家被记录;拦截:强制动作在你这边不发生");
				text3 = text3.Replace("If on: the non-host is logged: Block: the event does not happen on your side, others need their own Sentinel", "开启后:非主机被记录;拦截:该事件在你这边不发生,其他人需要自己的哨兵");
				text3 = text3.Replace("If on: off: you are revived and the reviver is only logged", "开启后/关闭:你被复活,复活者仅被记录");
				text3 = text3.Replace("If on: the spawner is logged: Block: not created on your side: as host they are removed for everyone", "开启后:生成者被记录;拦截:在你这边不生成;作为主机会为所有人移除");
				text3 = text3.Replace("If on: flyers are logged: Snap back: the flyer is teleported back and knocked down for everyone", "开启后:飞行者被记录;拉回:飞行者被传送回原位并对所有人击倒");
				text3 = text3.Replace("If on: players whose stamina never goes down are logged", "开启后:体力不下降的玩家被记录");
				text3 = text3.Replace("If on: protection or stamina effects with no source are logged", "开启后:无来源的保护或体力效果被记录");
				text3 = text3.Replace("If on: players with a fake name or someone else's Steam ID are logged", "开启后:使用假名称或他人 Steam ID 的玩家被记录");
				text3 = text3.Replace("If on: Cherry, Atlas and PEAK-MX users are logged: Kick: they are kicked from the room", "开启后:Cherry、Atlas 和 PEAK-MX 用户被记录;踢出:他们被踢出房间");
				text3 = text3.Replace("If on: a player flying around and knocking you away is snapped back to where they really are, on your side: also the Resync button in Players (on your own row it resyncs you for everyone)", "开启后:在你这边飞来飞去把你推开的玩家会被拉回真实位置;玩家页的重新同步按钮也会生效");
				text3 = text3.Replace("If on: if someone takes the host from you, you take it back at once: Kick the thief: they are kicked", "开启后:如果有人从你手里抢走主机,你立即夺回;踢出抢夺者:他们会被踢出");
				text3 = text3.Replace("If on: kicks from others do not knock or push you: with the right box honest kicks still work", "开启后:其他人的踢出不会让你倒地或推你;勾选右侧时正常踢出仍有效");
				text3 = text3.Replace("If on: anyone kicked while kicks are off in the run is stood back up (the push still lands)", "开启后:本局踢出关闭时被踢的人会被扶起(推挤仍然生效)");
				text3 = text3.Replace("If on: also stands up players kicked honestly while kicks are on", "开启后:即使踢出开启也扶起正常被踢的玩家");
				text3 = text3.Replace("If on: the protection of everyone also works while you are not the host", "开启后:即使你不是主机,保护所有人也生效");
				text3 = text3.Replace("If on: a pick-up while you are awake is refused and dropped for everyone: carrying you when you are passed out still works", "开启后:你清醒时被背起会被拒绝并对所有人放下;你昏迷时被背仍然有效");
				text3 = text3.Replace("If on: a sacrifice with a ritual dagger is refused, you stay alive: the others still get the dagger's buff", "开启后:仪式匕首献祭会被拒绝,你存活;其他人仍获得匕首增益");
				text3 = text3.Replace("→ protects only you", "→ 仅保护你");
				text3 = text3.Replace("→ protects everyone", "→ 保护所有人");
				text3 = text3.Replace("→ does nothing now: you are not the host", "→ 当前不生效:你不是主机");
				text3 = text3.Replace("(as host: protects everyone)", "(作为主机:保护所有人)");
				text3 = text3.Replace("(as host: protects everyone", "(作为主机:保护所有人");
				text3 = text3.Replace("→ only writes to the log", "→ 仅记录到日志");
				text3 = text3.Replace("Taking control of another player's character", "接管其他玩家的角色");
				text3 = text3.Replace("Taking control of another player’s character", "接管其他玩家的角色");
				text3 = text3.Replace("Building with someone else's item or build spam (flags...)", "使用他人物品建造或刷屏建造(仅标记)");
				text3 = text3.Replace("Building with someone else’s item or build spam (flags...)", "使用他人物品建造或刷屏建造(仅标记)");
				text3 = text3.Replace("Network flood: too many commands or spawns per second (lag and crash cheats)", "网络洪水:每秒过多指令或生成(卡顿和崩溃作弊)");
				text3 = text3.Replace("Mute them", "静音他们");
				if (text3.StartsWith("If on: the spawner is logged;"))
				{
					text3 = "开启后:生成者被记录;拦截:在你这边不生成;作为主机会为所有人移除 " + SuffixOf(text3);
				}
				if (text3.StartsWith("If on: the player is logged; Block: the object"))
				{
					text3 = "开启后:该玩家被记录;拦截:物体在你这边不被破坏 " + SuffixOf(text3);
				}
				if (text3.StartsWith("If on: the player is logged; Block: the takeover"))
				{
					text3 = "开启后:该玩家被记录;拦截:接管在你这边被忽略 " + SuffixOf(text3);
				}
				if (text3.StartsWith("If on: the builder is logged;"))
				{
					text3 = "开启后:建造者被记录;拦截:建造在你这边不显示 " + SuffixOf(text3);
				}
				if (text3.StartsWith("If on: the flooder is logged;"))
				{
					text3 = "开启后:洪水发送者被记录;静音:本次会话中他们发送的一切都被忽略,包括语音 " + SuffixOf(text3);
				}
				if (text3.StartsWith("If on: flyers are logged;"))
				{
					text3 = "开启后:飞行者被记录;拉回:飞行者被传送回原位并对所有人击倒 " + SuffixOf(text3);
				}
				if (text3.StartsWith("If on: protection or stamina effects"))
				{
					text3 = "开启后:无来源的保护或体力效果被记录 " + SuffixOf(text3);
				}
				if (text3.StartsWith("If on: Cherry, Atlas and PEAK-MX"))
				{
					text3 = "开启后:Cherry、Atlas 和 PEAK-MX 用户被记录;踢出:他们被踢出房间 " + SuffixOf(text3);
				}
				if (text3.StartsWith("If on: a player flying around"))
				{
					text3 = "开启后:在你这边飞来飞去把你推开的玩家会被拉回真实位置;玩家页的重新同步按钮也会生效 " + SuffixOf(text3);
				}
				if (text3.StartsWith("If on: if someone takes the host"))
				{
					text3 = "开启后:如果有人从你手里抢走主机,你立即夺回;踢出抢夺者:他们会被踢出 " + SuffixOf(text3);
				}
				if (text3.StartsWith("If on: kicks from others"))
				{
					text3 = "开启后:其他人的踢出不会让你倒地或推你;勾选右侧时正常踢出仍有效 " + SuffixOf(text3);
				}
				if (text3.StartsWith("If on: a pick-up while you are awake"))
				{
					text3 = "开启后:你清醒时被背起会被拒绝并对所有人放下;你昏迷时被背仍然有效 " + SuffixOf(text3);
				}
				if (text3.StartsWith("If on: a sacrifice with a ritual dagger"))
				{
					text3 = "开启后:仪式匕首献祭会被拒绝,你存活;其他人仍获得匕首增益 " + SuffixOf(text3);
				}
				if (text3.StartsWith("Glitched: flight and speed checks"))
				{
					text3 = "Glitched:关闭你自己的飞行和速度检测(普通和快速飞行、仙人掌投掷),关闭摔伤检测(滑翔休息取消摔伤)。运气、完整性、体力、日志和报告保持开启。";
				}
				if (text3.StartsWith("Any%: like Glitched"))
				{
					text3 = "Any%:类似 Glitched(大炮漏洞、出界),关闭你自己的飞行、速度和摔伤检测。运气、完整性、体力、日志和报告保持开启。";
				}
				text3 = text3.Replace("Banned players (kicked on join while you host):", "封禁玩家(你当主机时进房即踢出):");
				text3 = text3.Replace("Peak Sentinel (Peak Sentinel)", "Peak 哨兵(Peak 哨兵)");
				text3 = text3.Replace("Tools that help speedrun moderators check a run. Everything is off until you tick it. Chests and map items are rolled by the host, so luck works only while you are the host.", "帮助速通审核员检查一局的工具。所有项默认关闭,勾选后才生效。宝箱和地图物品由主机决定,所以运气只在你当主机时生效。");
				text3 = text3.Replace("NMG: every check on. Cactus Grapple and the Shroomalloon combo with a balloon bunch are allowed here, report a false flight alarm if one shows.", "NMG:所有检测开启。仙人掌抓钩和蘑菇球加气球束组合在此允许,如果误报飞行警报请报告。");
				text3 = text3.Replace("self-check: speedrun check: another mod patches randomness:", "自检:速通检测:另一个模组补丁了随机数:");
				text3 = text3.Replace("another mod patches randomness:", "另一个模组补丁了随机数:");
				text3 = text3.Replace("(your own action, only a warning; nothing was blocked)", "(你自己的操作,仅警告:未拦截任何内容)");
				text3 = text3.Replace("(your own action, only a warning: nothing was blocked)", "(你自己的操作,仅警告:未拦截任何内容)");
				text3 = text3.Replace("Alerts in this attempt:", "本次尝试的警报:");
				text2 = text3;
			}
			text2 = text2.Replace("protects only you", "仅保护你").Replace("protects everyone", "保护所有人").Replace("not needed while you are the host", "你是主机时无需此项")
				.Replace("only writes to the log", "仅记录到日志");
			return text2.Replace("you are the host: the lobby is protected by your Peak Sentinel", "你是主机:大厅已由你的 Peak 哨兵保护").Replace("got the airport 'Passport'", "获得机场通行证").Replace("got the airport ‘Passport’", "获得机场通行证")
				.Replace("self-check: is a PEAK-MX cheat user", "自检:是 PEAK-MX 作弊用户")
				.Replace("(your own action, only a warning: nothing was blocked)", "(你自己的操作,仅警告:未拦截任何内容)")
				.Replace("(your own action, only a warning; nothing was blocked)", "(你自己的操作,仅警告:未拦截任何内容)");
		}

		public static void ScanSentinelWindow()
		{
			try
			{
				if (!BlacklistConfig.LocalizationEnabled.Value)
				{
					return;
				}
				if (_tmpType == null)
				{
					_tmpType = AccessTools.TypeByName("TMPro.TMP_Text");
					_uiTextType = AccessTools.TypeByName("UnityEngine.UI.Text");
					if (_tmpType != null)
					{
						_tmpTextProp = _tmpType.GetProperty("text");
					}
					if (_uiTextType != null)
					{
						_uiTextProp = _uiTextType.GetProperty("text");
					}
					if (_tmpType == null && _uiTextType == null)
					{
						return;
					}
				}
				PruneDestroyedRoots();
				if (_roots.Count == 0 || Time.unscaledTime >= _nextDiscovery)
				{
					_nextDiscovery = Time.unscaledTime + 5f;
					DiscoverRoots();
					if (_roots.Count == 0)
					{
						return;
					}
					_cachedComps = null;
				}
				bool flag = false;
				foreach (Transform root in _roots)
				{
					if ((Object)(object)root != (Object)null && ((Component)root).gameObject.activeInHierarchy)
					{
						flag = true;
						break;
					}
				}
				if (!flag)
				{
					return;
				}
				if (_cachedComps == null)
				{
					RebuildComponentCache();
				}
				else if (Time.unscaledTime >= _nextCacheRebuild)
				{
					RebuildComponentCache();
				}
				_nextCacheRebuild = Time.unscaledTime + 2f;
				if (_cachedComps == null || _cachedComps.Length == 0)
				{
					return;
				}
				for (int i = 0; i < _cachedComps.Length; i++)
				{
					Component val = _cachedComps[i];
					if ((Object)(object)val == (Object)null)
					{
						continue;
					}
					PropertyInfo propertyInfo = ((_uiTextType != null && _uiTextType.IsInstanceOfType(val)) ? _uiTextProp : _tmpTextProp);
					if (propertyInfo == null)
					{
						continue;
					}
					string text = (string)propertyInfo.GetValue(val, null);
					if (!string.IsNullOrEmpty(text) && !HasCjk(text))
					{
						string text2 = TranslateCached(text);
						if (text2 != text)
						{
							propertyInfo.SetValue(val, text2, null);
						}
					}
				}
			}
			catch (Exception ex)
			{
				if (!_loggedError)
				{
					_loggedError = true;
					Plugin.Log.LogWarning((object)("[汉化] ScanSentinelWindow 异常: " + ex));
				}
			}
		}

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

		private static void PruneDestroyedRoots()
		{
			for (int num = _roots.Count - 1; num >= 0; num--)
			{
				if ((Object)(object)_roots[num] == (Object)null || (Object)(object)((Component)_roots[num]).gameObject == (Object)null)
				{
					_roots.RemoveAt(num);
				}
			}
		}

		private static void DiscoverRoots()
		{
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			_roots.Clear();
			if (_canvasType == null)
			{
				_canvasType = AccessTools.TypeByName("UnityEngine.Canvas");
			}
			if (_tmpType == null && _uiTextType == null)
			{
				return;
			}
			Array array = null;
			if (_tmpType != null)
			{
				array = Object.FindObjectsOfType(_tmpType);
			}
			if ((array == null || array.Length == 0) && _uiTextType != null)
			{
				array = Object.FindObjectsOfType(_uiTextType);
			}
			if (array == null)
			{
				return;
			}
			foreach (object item in array)
			{
				Component val = (Component)((item is Component) ? item : null);
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				GameObject gameObject = val.gameObject;
				if ((Object)(object)gameObject == (Object)null)
				{
					continue;
				}
				Scene scene = gameObject.scene;
				if (!((Scene)(ref scene)).IsValid())
				{
					continue;
				}
				bool flag = (((Object)gameObject).name ?? "").IndexOf("Sentinel", StringComparison.OrdinalIgnoreCase) >= 0;
				if (!flag)
				{
					string text = (string)((_uiTextType != null && _uiTextType.IsInstanceOfType(val)) ? _uiTextProp : _tmpTextProp)?.GetValue(val, null);
					flag = !string.IsNullOrEmpty(text) && ContainsSentinelText(text);
				}
				if (flag)
				{
					Transform val2 = ClimbToWindowRoot(val.transform);
					if ((Object)(object)val2 != (Object)null && !_roots.Contains(val2))
					{
						_roots.Add(val2);
					}
				}
			}
			if (_roots.Count > 0)
			{
				Plugin.Log.LogInfo((object)("[汉化] 已定位 Sentinel 根节点 " + _roots.Count + " 个: " + string.Join(", ", _roots.ConvertAll((Transform r) => ((Object)r).name).ToArray())));
			}
		}

		private static bool ContainsSentinelText(string t)
		{
			if (t.IndexOf("If on:", StringComparison.Ordinal) < 0 && t.IndexOf("protects only you", StringComparison.Ordinal) < 0 && t.IndexOf("protects everyone", StringComparison.Ordinal) < 0 && t.IndexOf("only writes to the log", StringComparison.Ordinal) < 0 && t.IndexOf("not needed while you are the host", StringComparison.Ordinal) < 0 && t.IndexOf("the lobby is protected by your Peak Sentinel", StringComparison.Ordinal) < 0 && t.IndexOf("got the airport", StringComparison.Ordinal) < 0 && t.IndexOf("PEAK-MX cheat user", StringComparison.Ordinal) < 0 && t.IndexOf("Network flood", StringComparison.Ordinal) < 0 && t.IndexOf("flyers are logged", StringComparison.Ordinal) < 0 && t.IndexOf("Banned players", StringComparison.Ordinal) < 0 && t.IndexOf("another mod patches", StringComparison.Ordinal) < 0)
			{
				return t.IndexOf("Sentinel", StringComparison.OrdinalIgnoreCase) >= 0;
			}
			return true;
		}

		private static Transform ClimbToWindowRoot(Transform from)
		{
			Transform val = from;
			Transform val2 = null;
			while ((Object)(object)val != (Object)null)
			{
				if (_canvasType != null && (Object)(object)((Component)val).GetComponent(_canvasType) != (Object)null)
				{
					val2 = val;
				}
				if ((Object)(object)val.parent == (Object)null)
				{
					break;
				}
				val = val.parent;
			}
			if (!((Object)(object)val2 != (Object)null))
			{
				return from;
			}
			return val2;
		}

		private static void RebuildComponentCache()
		{
			List<Component> list = new List<Component>();
			foreach (Transform root in _roots)
			{
				if (!((Object)(object)root == (Object)null))
				{
					if (_tmpType != null)
					{
						list.AddRange(((Component)root).GetComponentsInChildren(_tmpType, true));
					}
					if (_uiTextType != null)
					{
						list.AddRange(((Component)root).GetComponentsInChildren(_uiTextType, true));
					}
				}
			}
			_cachedComps = list.ToArray();
		}

		private static void PrefixContent(object[] __args)
		{
			if (!BlacklistConfig.LocalizationEnabled.Value)
			{
				return;
			}
			GUIContent val = null;
			if (__args != null)
			{
				foreach (object obj in __args)
				{
					GUIContent val2 = (GUIContent)((obj is GUIContent) ? obj : null);
					if (val2 != null)
					{
						val = val2;
						break;
					}
				}
			}
			if (val != null && !string.IsNullOrEmpty(val.text))
			{
				string text = val.text;
				if (Diag && (IsKnownSentinelText(text) || text.Contains("Peak Sentinel") || text.Contains("finding") || text.Contains("cheat") || text.Contains("lobby") || text.Contains("kicked") || text.Contains("Log") || text.Contains("Items") || text.Contains("Bans")))
				{
					Plugin.Log.LogInfo((object)("[DIAG-C] text=" + TextSnippet(text)));
				}
				string text2 = L10n.T(text);
				if (text2 != text)
				{
					val.text = text2;
				}
			}
		}

		private static void Prefix(ref string text)
		{
			try
			{
				if (text != null && text.Length != 0 && BlacklistConfig.LocalizationEnabled.Value)
				{
					string text2 = L10n.T(text);
					if (text2 != text)
					{
						text = text2;
					}
				}
			}
			catch
			{
			}
		}

		private static bool IsKnownSentinelText(string text)
		{
			if (string.IsNullOrEmpty(text))
			{
				return false;
			}
			if (text.StartsWith("Peak Sentinel ", StringComparison.OrdinalIgnoreCase))
			{
				return true;
			}
			if (text.StartsWith("Window key:", StringComparison.OrdinalIgnoreCase))
			{
				return true;
			}
			if (text.StartsWith("Stored findings:", StringComparison.OrdinalIgnoreCase))
			{
				return true;
			}
			if (text.StartsWith("No alerts", StringComparison.OrdinalIgnoreCase))
			{
				return true;
			}
			switch (text)
			{
			case "Other protection":
			case "cheat or blocked":
			case "fix applied":
			case "no body yet":
			case "Show notices":
			case "keep at most":
			case "suspicion, may be wrong":
			case "info, nothing wrong":
			case "no findings yet":
			case "no issues":
			case "Follow newest":
			case "Search nick or text...":
			case "Banned players (kicked on join while you host)":
				return true;
			default:
				return false;
			}
		}

		private static string TextSnippet(string s)
		{
			if (s == null)
			{
				return "<null>";
			}
			s = s.Replace('\n', ' ').Replace('\r', ' ');
			if (s.Length > 40)
			{
				return s.Substring(0, 40);
			}
			return s;
		}

		private static bool FromModAssembly()
		{
			Assembly swAssembly = _swAssembly;
			Assembly sentinelAssembly = _sentinelAssembly;
			StackTrace stackTrace = new StackTrace(1, fNeedFileInfo: false);
			MethodBase methodBase = null;
			bool flag = false;
			for (int i = 1; i < Math.Min(stackTrace.FrameCount, 20); i++)
			{
				MethodBase method = stackTrace.GetFrame(i).GetMethod();
				Type type = ((method != null) ? method.DeclaringType : null);
				Assembly assembly = ((type != null) ? type.Assembly : null);
				if (!(assembly == typeof(GUI).Assembly) && !(assembly == typeof(GUILayout).Assembly) && !(assembly == Assembly.GetExecutingAssembly()) && (!(assembly != null) || !(assembly.GetName().Name == "0Harmony")))
				{
					if (methodBase == null)
					{
						methodBase = method;
					}
					if (assembly == swAssembly || assembly == sentinelAssembly)
					{
						flag = true;
						break;
					}
				}
			}
			if (methodBase == null)
			{
				return false;
			}
			if (CallerCache.TryGetValue(methodBase, out var value))
			{
				return value == 1;
			}
			CallerCache[methodBase] = (flag ? ((byte)1) : ((byte)0));
			return flag;
		}
	}
	internal static class L10n
	{
		private sealed class Rule
		{
			public readonly Regex Rx;

			public readonly MatchEvaluator Ev;

			public Rule(string pattern, MatchEvaluator ev)
			{
				Rx = new Regex(pattern, RegexOptions.Compiled | RegexOptions.CultureInvariant);
				Ev = ev;
			}
		}

		private static readonly Dictionary<string, string> Exact;

		private static readonly HashSet<string> DetectorNames;

		private static readonly List<Rule> Rules;

		private static readonly Dictionary<string, string> Cache;

		private const int CacheCap = 4000;

		public static string T(string text)
		{
			if (string.IsNullOrEmpty(text))
			{
				return text;
			}
			lock (Cache)
			{
				if (Cache.TryGetValue(text, out var value))
				{
					return value;
				}
			}
			string text2 = Translate(text);
			lock (Cache)
			{
				if (Cache.Count < 4000)
				{
					Cache[text] = text2;
				}
			}
			return text2;
		}

		private static string Translate(string text)
		{
			if (Exact.TryGetValue(text, out var value))
			{
				return value;
			}
			foreach (Rule rule in Rules)
			{
				Match match = rule.Rx.Match(text);
				if (match.Success)
				{
					string text2;
					try
					{
						text2 = rule.Ev(match);
					}
					catch
					{
						text2 = null;
					}
					if (text2 != null)
					{
						return text2;
					}
				}
			}
			return text;
		}

		private static string TranslateReasons(string text)
		{
			string[] array = text.Split(new string[1] { "; " }, StringSplitOptions.None);
			for (int i = 0; i < array.Length; i++)
			{
				array[i] = T(array[i]);
			}
			return string.Join(";", array);
		}

		static L10n()
		{
			Exact = new Dictionary<string, string>
			{
				{ "Not in a lobby.", "当前不在大厅中。" },
				{ "Kicking: OFF", "踢出:关" },
				{ "Kicking: on", "踢出:开" },
				{ "Host rule: no kicking", "主机规则:禁止踢出" },
				{ "Evidence log: path hidden", "证据日志:路径已隐藏" },
				{ "Hide path", "隐藏路径" },
				{ "Show path", "显示路径" },
				{ "No other scouts yet", "暂无其他登山者" },
				{ "You're the host: you set who is trusted (in a player's profile) and can kick.", "你是主机:可以在玩家资料中设置信任角色,并可以踢出玩家。" },
				{ "No other scouts in this lobby yet.", "大厅中暂时没有其他登山者。" },
				{ "Click a name for their profile.", "点击玩家名称可查看详细资料。" },
				{ "Trusted by the host (still logged)", "主机信任的玩家(仍会记录)" },
				{ "Warn lobby", "警告全厅" },
				{ "Mute", "静音" },
				{ "Unmute", "取消静音" },
				{ "IGNORED KICK", "踢出被无视" },
				{ "CONFIRMED", "已确认" },
				{ "CONFIRMED cheating", "已确认作弊" },
				{ "Watch", "观察" },
				{ "Likely", "可疑" },
				{ "Very likely", "非常可疑" },
				{ "Clean", "正常" },
				{ "Proof", "实证" },
				{ "Strong", "强" },
				{ "Medium", "中" },
				{ "Weak", "弱" },
				{ "unknown", "未知" },
				{ "Copy ID", "复制ID" },
				{ "Copy report", "复制举报文本" },
				{ "HOST", "主机" },
				{ "Kick", "踢出" },
				{ "Ignore", "忽略" },
				{ "Request kick", "请求踢出" },
				{ "Click again to kick", "再次点击确认踢出" },
				{ "Role:", "角色:" },
				{ "Saved for when you host:", "保存(你当主机时生效):" },
				{ "Not trusted", "不受信任" },
				{ "Trusted", "受信任" },
				{ "Shield bypass", "护盾豁免" },
				{ "Not trusted (the host doesn't have Summit Watch)", "不受信任(主机未安装峰会监视)" },
				{ "no id yet", "暂无ID" },
				{ "Evidence by detector", "各检测器证据" },
				{ "No current evidence.", "暂无证据。" },
				{ "None.", "无。" },
				{ "Dead", "已死亡" },
				{ "Passed out", "已昏迷" },
				{ "Alive", "存活" },
				{ "No scout right now", "当前无角色" },
				{ "empty hands", "空手" },
				{ "distance unknown", "距离未知" },
				{ "they left", "对方已离开" },
				{ "nobody can kick the host", "任何人都无法踢出主机" },
				{ "already kicked", "已经执行过踢出" },
				{ "the host doesn't have Summit Watch", "主机未安装峰会监视" },
				{ "only the host and shield-bypass players can kick", "只有主机和护盾豁免玩家可以踢出" },
				{ "only the host can kick trusted players", "只有主机能踢出受信任玩家" },
				{ "no evidence", "无证据" },
				{ "requested", "应请求踢出" },
				{ "Puppeteer", "操纵他人" },
				{ "Force spam", "连续推搡" },
				{ "Foreign revive", "违规复活他人" },
				{ "Self-warp", "自我传送" },
				{ "Item from nothing", "凭空生成物品" },
				{ "Flight", "飞行" },
				{ "Noclip", "穿墙" },
				{ "Endless stamina", "无限体力" },
				{ "Teleport", "瞬移" },
				{ "No hunger", "无饥饿" },
				{ "Speed", "超速" },
				{ "Impossible landing", "异常落地" },
				{ "Replayed clip", "重复录音" },
				{ "Earrape", "爆音" },
				{ "Not speech", "无人声" },
				{ "Nonstop audio", "音频不停" },
				{ "Kick without being host", "非主机踢人" },
				{ "Timer change without being host", "非主机改时间" },
				{ "Immune to cold/heat", "免疫冷热" },
				{ "Super jump", "超级跳" },
				{ "Spawn request to host", "向主机请求生成" },
				{ "Impossible item", "不可能的物品" },
				{ "Out-of-biome item", "跨生态区物品" },
				{ "Unnatural item", "异常物品" },
				{ "Item flood", "物品洪流" },
				{ "Forced mute", "强制静音" },
				{ "Forced run end", "强制结束本局" },
				{ "Fling", "甩飞" },
				{ "Ignored a kick", "无视踢出" },
				{ "Impossible push", "异常推搡" },
				{ "Defy gravity", "反重力" },
				{ "Defying gravity", "反重力漂浮" },
				{ "Walking on air", "空中行走" },
				{ "Grounded in air", "空中落地判定异常" },
				{ "Flight lock", "飞行锁定" },
				{ "Unkicked", "反踢出" },
				{ "Mod-friendly: ON", "模组友好:开" },
				{ "Mod-friendly: on", "模组友好:开" },
				{ "Mod-friendly: off", "模组友好:关" },
				{ "Mod-friendly: OFF", "模组友好:关" },
				{ "Ground flyers: ON", "地面飞人:开" },
				{ "Ground flyers: on", "地面飞人:开" },
				{ "Ground flyers: OFF", "地面飞人:关" },
				{ "Ground flyers: off", "地面飞人:关" },
				{ "Host rule: ground flyers", "主机规则:地面飞人" },
				{ "Host rule: kicking", "主机规则:踢出" },
				{ "Host rule: the host", "主机规则:主机" },
				{ "FLIGHT LOCK", "飞行锁定" },
				{ "Unlock", "解锁" },
				{ "Unkick", "反踢出" },
				{ "Peak Sentinel", "Peak 哨兵" },
				{ "PeakSentinel", "PeakSentinel" },
				{ "BLOCKED", "已拦截" },
				{ "KICKED", "已踢出" },
				{ "BANNED", "已封禁" },
				{ "Banned", "已封禁" },
				{ "Blocked", "已拦截" },
				{ "Kicked", "已踢出" },
				{ "< Back", "< 返回" },
				{ "no findings yet", "暂无发现" },
				{ "no issues", "无问题" },
				{ "no body yet", "暂无角色" },
				{ "trusted", "受信任" },
				{ "muted", "已静音" },
				{ "by you", "由你" },
				{ "no longer trusted", "不再受信任" },
				{ "General cheat detection", "通用作弊检测" },
				{ "Banned players (kicked on join while you host)", "封禁玩家(你当主机时进房即踢出)" },
				{ "Infinite stamina: sprinting or climbing while stamina does not go down, cannot be blocked", "无限体力:冲刺或攀爬时体力不下降,无法拦截" },
				{ "trusted: Sentinel does not block, undo or kick anything they do", "受信任:哨兵不会拦截、撤销或踢出其任何操作" },
				{ "kicked with the Kick button", "通过踢出按钮踢出" },
				{ "was banned by you", "已被你封禁" },
				{ "kicked you, the kick was ignored", "踢了你,已被忽略" },
				{ "blocked in your game only (you are not the host): ", "仅在你的游戏中拦截(你不是主机):" },
				{ "(your own action, only a warning: nothing was blocked)", "(你自己的操作,仅警告:未拦截任何内容)" },
				{ "(heuristic, may be wrong)", "(启发式判断,可能有误)" },
				{ "(heuristic: detection and snap back may be wrong)", "(启发式:检测和拉回可能有误)" },
				{ "(other anticheats may see this as suspicious)", "(其他反作弊可能视为可疑)" },
				{ "(trusted by you, allowed)", "(你已信任,放行)" },
				{ "protect only you", "仅保护你自己" },
				{ "does nothing now: you are not the host", "当前不生效:你不是主机" },
				{ "(as host: protects everyone)", "(作为主机:保护所有人)" },
				{ "Infinite stamina", "无限体力" },
				{ "cheat or blocked", "作弊或已拦截" },
				{ "No alerts", "无警报" },
				{ "== Map ==", "== 地图 ==" },
				{ "== Integrity ==", "== 完整性 ==" },
				{ "== Sentinel findings about you since the attempt started ==", "== 本次尝试以来哨兵对你的发现 ==" },
				{ "== How to check the journal ==", "== 如何查看日志 ==" },
				{ "Loot luck", "掉落运气" },
				{ "== Chest rolls this run (item, chance in that roll) ==", "== 本局宝箱投掷记录(物品,该次投掷概率)==" },
				{ "no rolls", "无投掷记录" },
				{ "Not in a lobby", "当前不在大厅中" },
				{ "Log", "日志" },
				{ "Items", "物品" },
				{ "Cheats", "作弊" },
				{ "Other", "其他" },
				{ "Players", "玩家" },
				{ "Bans", "封禁" },
				{ "Speedrun", "速通" },
				{ "keep at most", "最多保留" },
				{ "Export", "导出" },
				{ "suspicion, may be wrong", "可疑,可能误判" },
				{ "fix applied", "已修复" },
				{ "info, nothing wrong", "信息,无异常" },
				{ "Show notices", "显示通知" },
				{ "Follow newest", "跟随最新" },
				{ "Search nick or text...", "搜索昵称或文本..." },
				{ "Clear", "清空" },
				{ "Item spawn blocker", "物品生成拦截器" },
				{ "Block cheat item spawns (spawners, forged pickups and inventories)", "拦截作弊物品生成(生成器、伪造拾取物和背包)" },
				{ "Allow my own spawns (host)", "允许我自己的生成(主机)" },
				{ "Refuse items forced into my hands (when not host)", "拒绝强制塞到我手里的物品(非主机时)" },
				{ "protects only you", "仅保护你" },
				{ "Block", "拦截" },
				{ "If on: cheat item spawns and forged pickups are refused, the item is never created", "开启后:作弊物品生成和伪造拾取物将被拒绝,物品不会被创建" },
				{ "If on: your own spawner keeps working", "开启后:你自己的生成器继续工作" },
				{ "If on: an item forced into your hands is removed from your inventory", "开启后:强制塞给你的物品会从背包移除" },
				{ "Check myself too, like any other player (only warnings, nothing of mine is blocked)", "也检测我自己,像其他玩家一样(仅警告,不拦截我的行为)" },
				{ "If on: every enabled check also runs on your own commands, spawns, flight, stamina, effects, markers and name", "开启后:所有启用的检测也会运行在你自己的指令、生成、飞行、体力、效果、标记和名称上" },
				{ "Kills, knock-outs, curses, teleports, flings and pushes of other players", "其他玩家的击杀、击晕、诅咒、传送、甩飞和推挤" },
				{ "If on: the attacker is logged: Block: the command does nothing on your side, others are safe only with their own Sentinel", "开启后:攻击者被记录;拦截:该指令在你这边无效,其他人需要自己的哨兵才安全" },
				{ "Forcing another player to jump, crouch, let go, drop items, emote or change statuses", "强制其他玩家跳跃、蹲下、松手、丢物品、做表情或改变状态" },
				{ "If on: the player is logged: Block: the forced action does not happen on your side", "开启后:该玩家被记录;拦截:强制动作在你这边不发生" },
				{ "Host-only events sent by a non-host (end game, revive, fog, bridges...)", "非主机发送的仅主机事件(结束游戏、复活、雾、桥...)" },
				{ "If on: the non-host is logged: Block: the event does not happen on your side, others need their own Sentinel", "开启后:非主机被记录;拦截:该事件在你这边不发生,其他人需要自己的哨兵" },
				{ "Block revives of me with no source (a cheat revives you)", "拦截无来源的复活(作弊复活)" },
				{ "If on: off: you are revived and the reviver is only logged", "开启后/关闭:你被复活,复活者仅被记录" },
				{ "Bees, tornados, zombies, Scoutmaster and Bing Bong spawned by a non-host", "非主机生成的蜜蜂、龙卷风、僵尸、侦察队长和叮当" },
				{ "If on: the spawner is logged: Block: not created on your side: as host they are removed for everyone", "开启后:生成者被记录;拦截:在你这边不生成;作为主机会为所有人移除" },
				{ "Destroying objects that belong to another player", "破坏属于其他玩家的物体" },
				{ "Other protection", "其他保护" },
				{ "Fix players whose body goes crazy only on my side (desync after joining)", "修复加入后仅在我这边身体异常的玩家(不同步)" },
				{ "Take the host back if another player takes it (when I am host)", "如果其他玩家抢主机则夺回(我当主机时)" },
				{ "Kick the thief", "踢出抢夺者" },
				{ "Protect me: ignore vanilla kicks from other players (no ragdoll, no push)", "保护我:忽略其他玩家的原版踢出(不倒地、不被推)" },
				{ "Off when kicks are on in the run", "局内开启踢出时关闭" },
				{ "Protect everyone when I am host: stand up anyone kicked while kicks are off in this run", "我当主机时保护所有人:本局踢出关闭时被踢的人会被扶起" },
				{ "Protect everyone even when kicks are on in this run (honest kicks too)", "即使本局踢出开启也保护所有人(包括正常踢出)" },
				{ "Protect everyone also when I am a client (other anticheats may see this as suspicious)", "我当客户端时也保护所有人(其他反作弊可能视为可疑)" },
				{ "Protect me: nobody can pick me up onto their back unless I am knocked out", "保护我:除非我倒地,否则没人能把我背起来" },
				{ "Protect me: no ritual dagger can kill me", "保护我:仪式匕首无法杀死我" },
				{ "protects everyone", "保护所有人" },
				{ "Blocking and kick work only while you are the host", "拦截和踢出仅在你当主机时生效" },
				{ "Resync all", "全部重新同步" },
				{ "not in a room", "不在房间中" },
				{ "Banned players (kicked on join while you host):", "封禁玩家(你当主机时进房即踢出):" },
				{ "Copy list", "复制列表" },
				{ "Add from clipboard", "从剪贴板添加" },
				{ "ban date unknown", "封禁日期未知" },
				{ "Unban", "解除封禁" },
				{ "This run: no chests yet", "本局:暂无宝箱" },
				{ "Reset run", "重置本局" },
				{ "Today: no chests yet", "今日:暂无宝箱" },
				{ "Reset today", "重置今日" },
				{ "No alerts in this attempt", "本次尝试无警报" },
				{ "0 chests, 0 items", "0 宝箱,0 物品" },
				{ "0 runs, 0 chests, 0 items (all runs since midnight while the game is open)", "0 局,0 宝箱,0 物品(自午夜起游戏开启期间的所有局)" },
				{ "Map: scene Title, biomes", "地图:场景 Title,生物群系" },
				{ "Category:", "类别:" },
				{ "Export run report", "导出本局报告" },
				{ "Flying: staying in the air without gravity (heuristic: detection and snap back may be wrong)", "飞行:无重力滞空(启发式:检测和拉回可能误判)" },
				{ "Snap back (host)", "拉回(主机)" },
				{ "Infinite stamina: sprinting or climbing while stamina does not go down, cannot be blocked (heuristic: may be wrong)", "无限体力:奔跑或攀爬时体力不下降,无法拦截(启发式:可能误判)" },
				{ "Protection or stamina effect with no source (milk, golden Bing Bong...), cannot be blocked (heuristic: may be wrong)", "无来源的保护或体力效果(牛奶、金色叮当...),无法拦截(启发式:可能误判)" },
				{ "Name or Steam ID spoofing, renaming mid-game (heuristic: may be wrong)", "名称或 Steam ID 伪造、游戏中改名(启发式:可能误判)" },
				{ "Cherry, Atlas and PEAK-MX markers", "Cherry、Atlas 和 PEAK-MX 标记" },
				{ "Kick (host)", "踢出(主机)" },
				{ "Loot luck: log chests and map items with the game's odds, show the mushroom table", "战利品运气:按游戏概率记录宝箱和地图物品,显示蘑菇表" },
				{ "Overlay in the top right corner (clover luck, version, attempt, run ID, integrity)", "右上角悬浮窗(四叶草运气、版本、尝试次数、本局ID、完整性)" },
				{ "Check game integrity: patches on randomness, foreign code, game, loot table and movement codes, game speed", "检查游戏完整性:随机数补丁、外部代码、游戏、掉落表和移动代码、游戏速度" },
				{ "Check myself: flight, speed, stamina and fall damage checks on my own character", "检测我自己:对我自己的角色检测飞行、速度、体力和摔伤" },
				{ "Speedrun journal: attempts, chests and alerts in a daily file chained by hashes", "速通日志:每天一个哈希链文件记录尝试、宝箱和警报" },
				{ "If on: a player flying around and knocking you away is snapped back to where they really are, on your side: also the Resync button in Players (on your own row it resyncs you for everyone)", "开启后:在你这边飞来飞去把你推开的玩家会被拉回真实位置;玩家页的重新同步按钮也会生效" },
				{ "If on: if someone takes the host from you, you take it back at once: Kick the thief: they are kicked", "开启后:如果有人从你手里抢走主机,你立即夺回;踢出抢夺者:他们会被踢出" },
				{ "If on: kicks from others do not knock or push you: with the right box honest kicks still work", "开启后:其他人的踢出不会让你倒地或推你;勾选右侧时正常踢出仍有效" },
				{ "If on: a pick-up while you are awake is refused and dropped for everyone: carrying you when you are passed out still works", "开启后:你清醒时被背起会被拒绝并对所有人放下;你昏迷时被背仍然有效" },
				{ "If on: a sacrifice with a ritual dagger is refused, you stay alive: the others still get the dagger's buff", "开启后:仪式匕首献祭会被拒绝,你存活;其他人仍获得匕首增益" },
				{ "Taking control of another player's character", "接管其他玩家的角色" },
				{ "Building with someone else's item or build spam (flags...)", "使用他人物品建造或刷屏建造(仅标记)" },
				{ "Network flood: too many commands or spawns per second (lag and crash cheats)", "网络洪水:每秒过多指令或生成(卡顿和崩溃作弊)" },
				{ "Mute them", "静音他们" },
				{ "If on: the spawner is logged; Block: not created on your side: as host they are removed for everyone", "开启后:生成者被记录;拦截:在你这边不生成;作为主机会为所有人移除" },
				{ "If on: the player is logged; Block: the object is not destroyed on your side", "开启后:该玩家被记录;拦截:物体在你这边不被破坏" },
				{ "If on: the player is logged; Block: the takeover is ignored on your side", "开启后:该玩家被记录;拦截:接管在你这边被忽略" },
				{ "If on: the builder is logged; Block: the build does not appear on your side", "开启后:建造者被记录;拦截:建造在你这边不显示" },
				{ "If on: the flooder is logged; Mute them: everything they send is ignored on your side for this session, voice included", "开启后:洪水发送者被记录;静音:本次会话中他们发送的一切都被忽略,包括语音" },
				{ "If on: flyers are logged; Snap back: the flyer is teleported back and knocked down for everyone", "开启后:飞行者被记录;拉回:飞行者被传送回原位并对所有人击倒" },
				{ "If on: protection or stamina effects with no source are logged", "开启后:无来源的保护或体力效果被记录" },
				{ "If on: Cherry, Atlas and PEAK-MX users are logged; Kick: they are kicked from the room", "开启后:Cherry、Atlas 和 PEAK-MX 用户被记录;踢出:他们被踢出房间" },
				{ "Peak Sentinel (Peak Sentinel)", "Peak 哨兵(Peak 哨兵)" },
				{ "Tools that help speedrun moderators check a run. Everything is off until you tick it. Chests and map items are rolled by the host, so luck works only while you are the host.", "帮助速通审核员检查一局的工具。所有项默认关闭,勾选后才生效。宝箱和地图物品由主机决定,所以运气只在你当主机时生效。" },
				{ "NMG: every check on. Cactus Grapple and the Shroomalloon combo with a balloon bunch are allowed here, report a false flight alarm if one shows.", "NMG:所有检测开启。仙人掌抓钩和蘑菇球加气球束组合在此允许,如果误报飞行警报请报告。" },
				{ "Taking control of another player’s character", "接管其他玩家的角色" },
				{ "Building with someone else’s item or build spam (flags...)", "使用他人物品建造或刷屏建造(仅标记)" },
				{ "If on: a sacrifice with a ritual dagger is refused, you stay alive: the others still get the dagger’s buff", "开启后:仪式匕首献祭会被拒绝,你存活;其他人仍获得匕首增益" },
				{ "Keep at most", "最多保留" },
				{ "Lobby protected: you are the host", "大厅已保护:你是主机" },
				{ "→ protects only you", "→ 仅保护你" },
				{ "→ protects everyone", "→ 保护所有人" },
				{ "→ does nothing now: you are not the host", "→ 当前不生效:你不是主机" },
				{ "→ not needed while you are the host", "→ 你是主机时无需此项" },
				{ "Window key: {0}", "窗口快捷键:{0}" },
				{ "Press a key... (Esc cancels)", "按下按键…(Esc 取消)" },
				{ "Confirm ban?", "确认封禁?" },
				{ "Confirm kick?", "确认踢出?" },
				{ "Confirm bring?", "确认背起?" },
				{ "Not host! Fix?", "非主机!修复?" },
				{ "nobody matches the search", "没有匹配结果" },
				{ "Voice volume", "语音音量" },
				{ "Code from outside the game and BepInEx: none", "游戏与 BepInEx 之外无外部代码" },
				{ "Other mods patching randomness: none", "其他模组未修改随机数" },
				{ "Other mods patching randomness:", "其他模组修改随机数:" },
				{ "You are not the host: the host rolls the chests, only the host's Sentinel can log them.", "你不是主机:宝箱由主机投掷,只有主机的哨兵能记录它们。" }
			};
			DetectorNames = new HashSet<string>
			{
				"Puppeteer", "Force spam", "Foreign revive", "Self-warp", "Item from nothing", "Flight", "Noclip", "Endless stamina", "Teleport", "No hunger",
				"Speed", "Impossible landing", "Replayed clip", "Earrape", "Not speech", "Nonstop audio", "Kick without being host", "Timer change without being host", "Immune to cold/heat", "Super jump",
				"Spawn request to host", "Impossible item", "Out-of-biome item", "Unnatural item", "Item flood", "Forced mute", "Forced run end", "Fling", "Ignored a kick", "Impossible push",
				"Defy gravity", "Defying gravity", "Walking on air", "Grounded in air", "Flight lock", "Unkicked"
			};
			Rules = new List<Rule>();
			Cache = new Dictionary<string, string>();
			Add("^Summit Watch  \\((.+?) to close\\)$", (Match m) => "峰会监视(按 " + m.Groups[1].Value + " 关闭)");
			Add("^Summit Watch  \\((.+?)\\)$", (Match m) => "峰会监视(" + m.Groups[1].Value + ")");
			Add("^\\+(\\d+) more$", (Match m) => "另有 " + m.Groups[1].Value + " 人");
			Add("^(CONFIRMED|\\d+%)( · .+)?$", delegate(Match m)
			{
				string obj = ((m.Groups[1].Value == "CONFIRMED") ? "已确认" : m.Groups[1].Value);
				string text = (m.Groups[2].Success ? m.Groups[2].Value : "");
				text = text.Replace(" · trusted", " · 受信任").Replace(" · bypass", " · 护盾豁免").Replace(" · ignored kick", " · 无视踢出");
				return obj + text;
			});
			Add("^> (Not trusted|Trusted|Shield bypass)$", (Match m) => "✓ " + T(m.Groups[1].Value));
			Add("^Summit Watch: (.+)$", (Match m) => "峰会监视:" + T(m.Groups[1].Value));
			Add("^(.+?) asks to kick (.+?) \\((.+?) to answer\\)$", (Match m) => m.Groups[1].Value + " 请求踢出 " + m.Groups[2].Value + "(按 " + m.Groups[3].Value + " 回应)");
			Add("^(.+?) asks to kick (.+?) \\((.+)\\)$", (Match m) => m.Groups[1].Value + " 请求踢出 " + m.Groups[2].Value + "(" + T(m.Groups[3].Value) + ")");
			Add("^(.*)   \\((.+?) to answer\\)$", (Match m) => m.Groups[1].Value + "   (按 " + m.Groups[2].Value + " 回应)");
			Add("^Profile: (.+)$", (Match m) => "玩家资料:" + m.Groups[1].Value);
			Add("^Host: (.+), no Summit Watch: nobody is trusted and kicking is off\\.$", (Match m) => "主机:" + m.Groups[1].Value + "(未安装峰会监视):无人受信任,踢出功能关闭。");
			Add("^Host: (.+) \\(Summit Watch\\)\\.  Your role: (.+)\\.$", (Match m) => "主机:" + m.Groups[1].Value + "(已安装峰会监视)。你的角色:" + T(m.Groups[2].Value) + "。");
			Add("^(.+?)   ·   watching (.+?)   ·   (\\d+) offenses$", delegate(Match m)
			{
				string text = T(m.Groups[1].Value);
				string text2 = TranslateDuration(m.Groups[2].Value);
				return text + "   ·   已监视 " + text2 + "   ·   " + m.Groups[3].Value + " 条违规";
			});
			Add("^(Dead|Passed out|Alive|No scout right now)   ·   (.+?)   ·   (.+)$", (Match m) => T(m.Groups[1].Value) + "   ·   " + T(m.Groups[2].Value) + "   ·   " + T(m.Groups[3].Value));
			Add("^(\\d{2}:\\d{2}:\\d{2}) · (.+)$", (Match m) => m.Groups[1].Value + " · " + T(m.Groups[2].Value));
			Add("^Evidence log: (.+)$", (Match m) => "证据日志:" + m.Groups[1].Value);
			Add("^Role here: (.+) \\(set by the host\\)$", (Match m) => "当前大厅角色:" + T(m.Groups[1].Value) + "(由主机设定)");
			Add("^Held at 59%: (.+)$", (Match m) => "停留在 59%:" + m.Groups[1].Value.Replace("only one kind of evidence so far.", "目前只有一类证据。").Replace("A second, independent signal is needed to reach Likely.", "需要第二个独立信号才能升至“可疑”。"));
			Add("^CONFIRMED: (.+)$", (Match m) => "已确认:" + T(m.Groups[1].Value));
			Add("^(\\d+)%: (.+)$", (Match m) => m.Groups[1].Value + "%:" + T(m.Groups[2].Value));
			Add("^    (.+)$", (Match m) => "    " + TranslateReasons(m.Groups[1].Value));
			Add("^(.+?)( \\(PROOF\\))?: (.+)$", delegate(Match m)
			{
				string value = m.Groups[1].Value;
				if (!DetectorNames.Contains(value))
				{
					return (string)null;
				}
				string text = (m.Groups[2].Success ? "(实证)" : "");
				return Exact[value] + text + ":" + T(m.Groups[3].Value);
			});
			Add("^Id: (.+)$", (Match m) => "平台ID:" + m.Groups[1].Value);
			Add("^Actor (\\d+)$", (Match m) => "房间编号 " + m.Groups[1].Value);
			Add("^Kick: (.+)$", (Match m) => "踢出:" + T(m.Groups[1].Value));
			Add("^Request kick: (.+)$", (Match m) => "请求踢出:" + T(m.Groups[1].Value));
			Add("^you can ask again in (\\d+) s$", (Match m) => "你还需等待 " + m.Groups[1].Value + " 秒才能再次请求");
			Add("^All offenses \\((\\d+), newest first\\)$", (Match m) => "全部违规(" + m.Groups[1].Value + " 条,最新在前)");
			Add("^(\\d+)%  (.+)$", delegate(Match m)
			{
				string text = (Exact.ContainsKey(m.Groups[2].Value) ? Exact[m.Groups[2].Value] : m.Groups[2].Value);
				return m.Groups[1].Value + "%  " + text;
			});
			Add("^ (\\d+)%  x(\\d+)$", (Match m) => " " + m.Groups[1].Value + "%  ×" + m.Groups[2].Value);
			Add("^(.+) CONFIRMED cheating$", (Match m) => m.Groups[1].Value + " 已确认作弊");
			Add("^(.+) is (\\d+)% likely griefing$", (Match m) => m.Groups[1].Value + " 有 " + m.Groups[2].Value + "% 概率恶意捣乱");
			Add("^you're no longer trusted in this lobby$", (Match m) => "你在本大厅中已不再受信任");
			Add("^you're now (.+) in this lobby$", (Match m) => "你在本大厅中的角色已变为" + T(m.Groups[1].Value));
			Add("^kick request for (.+) sent to the host and moderators$", (Match m) => "已向主机和管理员发送对 " + m.Groups[1].Value + " 的踢出请求");
			Add("^kicked (.+) with an impossible kick \\(force (\\d+(?:\\.\\d+)?), the game's strongest is about 30\\)( \\((\\d+)x\\))?$", (Match m) => "以不可能的踢击力度踢了 " + m.Groups[1].Value + "(力度 " + m.Groups[2].Value + ",游戏内最高约 30)" + (m.Groups[4].Success ? ("(" + m.Groups[4].Value + " 次)") : ""));
			Add("^kicked (.+) down for (\\d+(?:\\.\\d+)?) s \\(a kick lasts 1 s\\)( \\((\\d+)x\\))?$", (Match m) => "把 " + m.Groups[1].Value + " 踢倒长达 " + m.Groups[2].Value + " 秒(正常踢击只持续 1 秒)" + (m.Groups[4].Success ? ("(" + m.Groups[4].Value + " 次)") : ""));
			Add("^kicked (.+) from (\\d+(?:\\.\\d+)?) m away \\(a kick reaches 1 m\\)( \\((\\d+)x\\))?$", (Match m) => "在 " + m.Groups[2].Value + " 米外踢了 " + m.Groups[1].Value + "(踢击射程仅 1 米)" + (m.Groups[4].Success ? ("(" + m.Groups[4].Value + " 次)") : ""));
			Add("^kicked (.+)$", (Match m) => "已踢 " + m.Groups[1].Value);
			Add("^(.+) was kicked by (.+)$", (Match m) => m.Groups[1].Value + " 已被 " + m.Groups[2].Value + " 踢出");
			Add("^(.+) ignored the kick: Summit Watch is treating them as gone$", (Match m) => m.Groups[1].Value + " 无视了踢出指令:峰会监视已将其视为离开");
			Add("^(.+) ignored the kick$", (Match m) => m.Groups[1].Value + " 无视了踢出指令");
			Add("^blocked (.+) (from .+)$", (Match m) => "已阻止 " + m.Groups[1].Value + TranslateRefused(m.Groups[2].Value));
			Add("^(.+) is flooding spawn requests \\((.+)\\): blocked$", (Match m) => m.Groups[1].Value + " 在疯狂请求生成物品(" + m.Groups[2].Value + "):已拦截");
			Add("^(.+) is flooding (.+?): (.+)$", (Match m) => m.Groups[1].Value + " 在大量刷出 " + m.Groups[2].Value + ":" + m.Groups[3].Value);
			Add("^(.+) is flinging: their body can't touch you now$", (Match m) => m.Groups[1].Value + " 正在甩飞玩家:其身体已无法接触到你");
			Add("^saved: (.+) is (.+) when you host$", (Match m) => "已保存:你当主机时 " + m.Groups[1].Value + " 的角色为" + T(m.Groups[2].Value));
			Add("^(.+) kept rejoining: kicked automatically$", (Match m) => m.Groups[1].Value + " 反复进出大厅,已被自动踢出");
			Add("^(.+) demoted: removed (\\d+) items they spawned$", (Match m) => m.Groups[1].Value + " 已被降级:移除了其刷出的 " + m.Groups[2].Value + " 个物品");
			Add("^flung by (.+): sent you back to the last place you stood$", (Match m) => "被 " + m.Groups[1].Value + " 甩飞:已将你送回最后站立的位置");
			Add("^you were flown around: sent you back to the last place you stood$", (Match m) => "你被带着乱飞:已将你送回最后站立的位置");
			Add("^you were dragged through solid rock: sent you back to the last place you stood$", (Match m) => "你被拖穿过实体岩石:已将你送回最后站立的位置");
			Add("^the host turned kicking back on$", (Match m) => "主机已重新开启踢出功能");
			Add("^the host turned kicking off: kicks on you are refused$", (Match m) => "主机已关闭踢出功能:针对你的踢击将被拒绝");
			Add("^mod-friendly: Summit Watch won't touch your game \\(still watching\\)$", (Match m) => "已开启模组友好模式:不会改动你的游戏(仍在监视)");
			Add("^Mod-friendly: Summit Watch won't touch your game \\(still watching\\)$", (Match m) => "已开启模组友好模式:不会改动你的游戏(仍在监视)");
			Add("^mod-friendly: protection back on$", (Match m) => "保护功能已恢复");
			Add("^mod-friendly: protection off$", (Match m) => "保护功能已关闭");
			Add("^mod-friendly: (.+)$", (Match m) => m.Groups[1].Value);
			Add("^Summit Watch · mod-friendly \\(Ctrl\\+0\\)$", (Match m) => "峰会监视 · 模组友好(Ctrl+0)");
			Add("^Summit Watch · (.+) \\(Ctrl\\+0\\)$", (Match m) => "峰会监视 · " + m.Groups[1].Value + "(Ctrl+0)");
			Add("^(.+) moved impossibly fast next to you: it can't touch you now$", (Match m) => m.Groups[1].Value + " 在你身边异常快速移动:已暂时阻止其与你交互");
			Add("^(.+) is flight-locked: pulled down on every takeoff$", (Match m) => m.Groups[1].Value + " 已被飞行锁定:每次起飞都会被强制拉回");
			Add("^(.+) unlocked by you$", (Match m) => m.Groups[1].Value + " 已由你解除锁定");
			Add("^(.+) was flung, but we never saw them stand anywhere$", (Match m) => m.Groups[1].Value + " 被甩飞了,但未检测到其站立位置");
			Add("^(.+) was flung, pulled them back to where they stood$", (Match m) => m.Groups[1].Value + " 被甩飞:已送回其原站立位置");
			Add("^(.+) was flung: pulled them back$", (Match m) => m.Groups[1].Value + " 被甩飞:已送回");
			Add("^(.+) is flinging: their body can't touch you now$", (Match m) => m.Groups[1].Value + " 正在甩人:已暂时隔离其身体与你的交互");
			Add("^(.+) unkicked: they can rejoin$", (Match m) => m.Groups[1].Value + " 反踢出:其可重新加入");
			Add("^(.+) was unkicked by (.+)$", (Match m) => m.Groups[1].Value + " 已由 " + m.Groups[2].Value + " 取消踢出");
			Add("^(.+) kept rejoining: kicked automatically$", (Match m) => m.Groups[1].Value + " 反复加入:已自动踢出");
			Add("^blocked\\s+(.+?)\\s*from kicking you$", (Match m) => "已阻止 " + m.Groups[1].Value + " 踢你");
			Add("^(.+)'s body moved impossibly fast next to you: it can't touch you now$", (Match m) => m.Groups[1].Value + " 的身体在你身边异常快速移动:已暂时阻止其与你交互");
			Add("^(.+)'s body moved impossibly fast next to you$", (Match m) => m.Groups[1].Value + " 的身体在你身边异常快速移动:已暂时阻止其与你交互");
			Add("^Flight lock: pulled down on every takeoff \\(caught flying repeatedly\\)\\.$", (Match m) => "飞行锁定:每次起飞都会被强制拉回(已多次检测到飞行)。");
			Add("^pulled down on every takeoff \\(caught flying repeatedly\\)\\.?$", (Match m) => "每次起飞都会被强制拉回(已多次检测到飞行)。");
			Add("^(.+) was caught flying again; pulled down on every takeoff from now on$", (Match m) => m.Groups[1].Value + " 再次检测到飞行:此后每次起飞都会被强制拉回");
			Add("^kicked (.+) with an impossible kick \\(force ([\\d.]+), the game's strongest is about 30\\)$", (Match m) => "已踢 " + m.Groups[1].Value + "(异常踢出力度 " + m.Groups[2].Value + ",游戏内最高约 30)");
			Add("^shoved (.+) with an impossible force \\(([\\d.]+), the game never pushes harder than (.+)\\)$", (Match m) => "已推搡 " + m.Groups[1].Value + "(异常力度 " + m.Groups[2].Value + ",游戏内不会超过 " + m.Groups[3].Value + ")");
			Add("^walked or stood in mid-air: their game says they're on the ground, (\\d+(?:\\.\\d+)?) m up$", (Match m) => "在空中行走或站立:其客户端称在地面,实际悬空 " + m.Groups[1].Value + " 米");
			Add("^floated in the air without falling \\((\\d+(?:\\.\\d+)?) s off the ground\\)$", (Match m) => "悬浮在空中未下落(离地 " + m.Groups[1].Value + " 秒)");
			Add("^floated in the air without fail(?:ing)? \\((\\d+(?:\\.\\d+)?) s off the grou(?:nd)?\\)?$", (Match m) => "悬浮在空中未下落(离地 " + m.Groups[1].Value + " 秒)");
			Add("^(?:Defying gravity|Defy gravity|Walking on air|Grounded in air): (.+)$", (Match m) => T(m.Groups[1].Value));
			Add("^kept rising in the air \\((\\d+(?:\\.\\d+)?) m/s up, (\\d+(?:\\.\\d+)?) s off the ground\\)$", (Match m) => "持续向上升空(上升 " + m.Groups[1].Value + " 米/秒,离地 " + m.Groups[2].Value + " 秒)");
			Add("^jumped (\\d+(?:\\.\\d+)?) m high from flat ground$", (Match m) => "从平地起跳高达 " + m.Groups[1].Value + " 米");
			Add("^ran (\\d+(?:\\.\\d+)?) m/s on the ground$", (Match m) => "在地面移动 " + m.Groups[1].Value + " 米/秒");
			Add("^(.+) moved impossibly fast next to you: it can't touch you now$", (Match m) => m.Groups[1].Value + " 在你身边异常快速移动:现在它打不到你");
			Add("^(.+) moved impossibly fast: it can't touch you now$", (Match m) => m.Groups[1].Value + " 异常快速移动:现在它打不到你");
			Add("^moved impossibly fast next to you: it can't touch you now$", (Match m) => "有人在你身边异常快速移动:现在它打不到你");
			Add("^made (.+) (die|pass out|turn to stone|get stuck in place|teleport) \\(sent (.+) on a scout they don't control\\)$", (Match m) => "操纵 " + m.Groups[1].Value + Verbs(m.Groups[2].Value) + "(对不受其控制的登山者发送 " + m.Groups[3].Value + ")");
			Add("^tried to mute (.+) for everyone \\(sent (.+), which the game never sends\\)$", (Match m) => "试图让所有人静音 " + m.Groups[1].Value + "(发送了游戏绝不会发送的 " + m.Groups[2].Value + " 指令)");
			Add("^forced the run to end, which deletes everyone's save \\(sent (.+), a developer command\\)$", (Match m) => "强制结束本局,会删除所有人的存档(发送了开发者指令 " + m.Groups[1].Value + ")");
			Add("^teleported themselves (\\S+) m with no warp item$", (Match m) => "没有传送道具却传送了自己 " + m.Groups[1].Value + " 米");
			Add("^revived (.+) without an effigy or respawn chest$", (Match m) => "没有替身像或复活箱就复活了 " + m.Groups[1].Value);
			Add("^asked the host to spawn a (.+) (in their hand|into their hands)$", (Match m) => "要求主机" + HandPhrase(m.Groups[2].Value) + "生成一个 " + m.Groups[1].Value);
			Add("^(.+), an item that never appears naturally$", (Match m) => T(m.Groups[1].Value) + ",这是游戏中绝不会自然出现的物品");
			Add("^(.+), which only appears in a later biome$", (Match m) => T(m.Groups[1].Value) + ",该物品只会在后续生态区出现");
			Add("^a? ?(.+?) went straight into their hands, which this game (?:never|doesn'?t) (?:appears?|spawns?) naturally$", (Match m) => "一个 " + m.Groups[1].Value + " 直接出现在手中(这是游戏中绝不会自然出现的物品)");
			Add("^a (.+) (went straight into their hands|appeared right in front of them|appeared next to them) from nowhere$", (Match m) => "一个 " + m.Groups[1].Value + AppearPhrase(m.Groups[2].Value) + ",凭空出现");
			Add("^spawned a (.+) (straight into their hands|right in front of them|out of nothing)$", (Match m) => "凭空" + AppearPhrase(m.Groups[2].Value) + "一个 " + m.Groups[1].Value);
			Add("^a (.+) appeared (went straight into their hands|right in front of them|next to them|out of nothing)$", (Match m) => "一个 " + m.Groups[1].Value + AppearPhrase("appeared " + m.Groups[2].Value));
			Add("^tried to kick (.+) without being host$", (Match m) => "不是主机却试图踢出 " + m.Groups[1].Value);
			Add("^changed the run timer without being host$", (Match m) => "不是主机却修改本局计时器");
			Add("^blasted clipping-loud audio for (\\S+) s$", (Match m) => "持续 " + m.Groups[1].Value + " 秒播放削波爆音");
			Add("^(\\S+) s of audio with no speech rhythm$", (Match m) => m.Groups[1].Value + " 秒音频没有任何人声节奏");
			Add("^played the same (\\S+) s clip again \\((\\d+)x\\)$", (Match m) => "重复播放同一段 " + m.Groups[1].Value + " 秒的录音(第 " + m.Groups[2].Value + " 次)");
			Add("^mic never paused for (\\S+) s$", (Match m) => "麦克风连续 " + m.Groups[1].Value + " 秒没有停顿");
			Add("^airborne (\\S+) s without falling$", (Match m) => "已滞空 " + m.Groups[1].Value + " 秒且没有下落");
			Add("^flooded the area with (\\d+) (.+) in (\\S+) s$", (Match m) => "在 " + m.Groups[3].Value + " 秒内刷出 " + m.Groups[1].Value + " 个 " + m.Groups[2].Value + ",淹没了区域");
			Add("^jittered back and forth (\\S+) m, (\\d+) times in (\\S+) s \\(fling cheat\\)$", (Match m) => "在 " + m.Groups[3].Value + " 秒内来回抖动 " + m.Groups[1].Value + " 米、共 " + m.Groups[2].Value + " 次(甩飞作弊)");
			Add("^shoved (.+) (\\d+) times in (\\S+) s$", (Match m) => "在 " + m.Groups[3].Value + " 秒内连续推搡 " + m.Groups[1].Value + " " + m.Groups[2].Value + " 次");
			Add("^hunger didn't rise for (\\S+) min$", (Match m) => "饥饿值连续 " + m.Groups[1].Value + " 分钟没有增长");
			Add("^landed a (\\S+) m fall unhurt$", (Match m) => "从 " + m.Groups[1].Value + " 米高处坠落却毫发无伤");
			Add("^moved through solid terrain \\((\\S+) m\\)$", (Match m) => "穿过了实体地形(厚 " + m.Groups[1].Value + " 米)");
			Add("^ran (\\S+) m/s on the ground$", (Match m) => "在地面以 " + m.Groups[1].Value + " 米/秒奔跑");
			Add("^jumped (\\S+) m in one update(.*)$", (Match m) => "单次更新内跳跃位移 " + m.Groups[1].Value + " 米" + (m.Groups[2].Success ? TranslateMoreSuffix(m.Groups[2].Value) : ""));
			Add("^jumped (\\S+) m high from flat ground$", (Match m) => "从平地跳起 " + m.Groups[1].Value + " 米高");
			Add("^stamina never dropped during (\\S+) s of sprinting or climbing$", (Match m) => "冲刺或攀爬 " + m.Groups[1].Value + " 秒期间体力从未下降");
			Add("^(cold|hot) never rose while (\\d+) scouts nearby were (freezing|overheating)$", (Match m) => "附近有 " + m.Groups[2].Value + " 名登山者" + TempArg(m.Groups[3].Value) + "时,其" + TempStatus(m.Groups[1].Value) + "从未增长");
			Add("^stayed in the lobby after the host kicked them \\(a cheat menu blocks the kick\\)$", (Match m) => "主机踢出后仍留在大厅(作弊菜单拦截了踢出)");
			Add("^knocked (.+) down for (\\d+) s \\(the game never does more than (\\d+)\\)$", (Match m) => "把 " + m.Groups[1].Value + " 击倒长达 " + m.Groups[2].Value + " 秒(游戏正常上限为 " + m.Groups[3].Value + " 秒)");
			Add("^shoved (.+) with an impossible force \\((\\d+), the game never pushes harder than (\\d+)\\)$", (Match m) => "以不可能的力度推搡 " + m.Groups[1].Value + "(力度 " + m.Groups[2].Value + ",游戏上限为 " + m.Groups[3].Value + ")");
			Add("^joined and left more than (\\d+) times in (\\d+) s$", (Match m) => "在 " + m.Groups[2].Value + " 秒内加入并离开超过 " + m.Groups[1].Value + " 次");
			Add("^(.+) \\((\\d+)x\\)$", (Match m) => T(m.Groups[1].Value) + "(" + m.Groups[2].Value + " 次)");
			Add("^(\\d+) m away$", (Match m) => "距离 " + m.Groups[1].Value + " 米");
			Add("^holding (.+)$", (Match m) => "手持:" + m.Groups[1].Value);
			Add("^(\\d+(?:\\.\\d+)?)s ago$", (Match m) => m.Groups[1].Value + " 秒前");
			Add("^(\\d+)m ago$", (Match m) => m.Groups[1].Value + " 分钟前");
			Add("^(\\d+(?:\\.\\d+)?)h ago$", (Match m) => m.Groups[1].Value + " 小时前");
			Add("^Lobby protected: host (.+) runs Peak Sentinel$", (Match m) => "大厅受保护:主机 " + m.Groups[1].Value + " 运行 Peak 哨兵");
			Add("^Sentinel (.+)$", (Match m) => "哨兵 " + m.Groups[1].Value);
			Add("^(\\d+) alerts$", (Match m) => m.Groups[1].Value + " 条警报");
			Add("^No alerts in this attempt$", (Match m) => "本次尝试无警报");
			Add("^(.+) was banned by you$", (Match m) => m.Groups[1].Value + " 已被你封禁");
			Add("^(.+) kicked you, the kick was ignored$", (Match m) => m.Groups[1].Value + " 踢了你,已被忽略");
			Add("^(.+) is flying: (.+)$", (Match m) => m.Groups[1].Value + " 正在飞行:" + m.Groups[2].Value);
			Add("^blocked in your game only \\(you are not the host\\): (.+)$", (Match m) => "仅在你的游戏中拦截(你不是主机):" + m.Groups[1].Value);
			Add("^Peak Sentinel (\\d+\\.\\d+\\.\\d+)$", (Match m) => "Peak 哨兵 " + m.Groups[1].Value);
			Add("^Window key: (.+)$", (Match m) => "窗口快捷键:" + m.Groups[1].Value);
			Add("^Stored findings: (\\d+) / (\\d+)$", (Match m) => "已存储发现:" + m.Groups[1].Value + " / " + m.Groups[2].Value);
			Add("^Banned players \\(kicked on join while you host\\): (\\d+)$", (Match m) => "封禁玩家(你当主机时进房即踢出):" + m.Groups[1].Value);
			Add("^If on: the spawner is logged; Block: not created on your side: as host they are removed for everyone(.*)$", (Match m) => "开启后:生成者被记录;拦截:在你这边不生成;作为主机会为所有人移除" + m.Groups[1].Value);
			Add("^If on: the player is logged; Block: the object is not destroyed on your side(.*)$", (Match m) => "开启后:该玩家被记录;拦截:物体在你这边不被破坏" + m.Groups[1].Value);
			Add("^If on: the player is logged; Block: the takeover is ignored on your side(.*)$", (Match m) => "开启后:该玩家被记录;拦截:接管在你这边被忽略" + m.Groups[1].Value);
			Add("^If on: the builder is logged; Block: the build does not appear on your side(.*)$", (Match m) => "开启后:建造者被记录;拦截:建造在你这边不显示" + m.Groups[1].Value);
			Add("^If on: the flooder is logged; Mute them: everything they send is ignored on your side for this session, voice included(.*)$", (Match m) => "开启后:洪水发送者被记录;静音:本次会话中他们发送的一切都被忽略,包括语音" + m.Groups[1].Value);
			Add("^If on: flyers are logged; Snap back: the flyer is teleported back and knocked down for everyone(.*)$", (Match m) => "开启后:飞行者被记录;拉回:飞行者被传送回原位并对所有人击倒" + m.Groups[1].Value);
			Add("^If on: protection or stamina effects with no source are logged(.*)$", (Match m) => "开启后:无来源的保护或体力效果被记录" + m.Groups[1].Value);
			Add("^If on: Cherry, Atlas and PEAK-MX users are logged; Kick: they are kicked from the room(.*)$", (Match m) => "开启后:Cherry、Atlas 和 PEAK-MX 用户被记录;踢出:他们被踢出房间" + m.Groups[1].Value);
			Add("^If on: a player flying around and knocking you away is snapped back to where they really are, on your side: also the Resync button in Players \\(on your own row it resyncs you for everyone\\)(.*)$", (Match m) => "开启后:在你这边飞来飞去把你推开的玩家会被拉回真实位置;玩家页的重新同步按钮也会生效" + m.Groups[1].Value);
			Add("^If on: if someone takes the host from you, you take it back at once: Kick the thief: they are kicked(.*)$", (Match m) => "开启后:如果有人从你手里抢走主机,你立即夺回;踢出抢夺者:他们会被踢出" + m.Groups[1].Value);
			Add("^If on: kicks from others do not knock or push you: with the right box honest kicks still work(.*)$", (Match m) => "开启后:其他人的踢出不会让你倒地或推你;勾选右侧时正常踢出仍有效" + m.Groups[1].Value);
			Add("^If on: a pick-up while you are awake is refused and dropped for everyone: carrying you when you are passed out still works(.*)$", (Match m) => "开启后:你清醒时被背起会被拒绝并对所有人放下;你昏迷时被背仍然有效" + m.Groups[1].Value);
			Add("^If on: a sacrifice with a ritual dagger is refused, you stay alive: the others still get the dagger.s buff(.*)$", (Match m) => "开启后:仪式匕首献祭会被拒绝,你存活;其他人仍获得匕首增益" + m.Groups[1].Value);
			Add("^Alerts in this attempt:$", (Match m) => "本次尝试的警报:");
			Add("^self-check: speedrun check: another mod patches randomness: (.+)$", (Match m) => "自检:速通检测:另一个模组补丁了随机数:" + m.Groups[1].Value);
			Add("^another mod patches randomness: (.+)$", (Match m) => "另一个模组补丁了随机数:" + m.Groups[1].Value);
			Add("^\\(your own action, only a warning; nothing was blocked\\)$", (Match m) => "(你自己的操作,仅警告:未拦截任何内容)");
			Add("^\\(your own action, only a warning: nothing was blocked\\)$", (Match m) => "(你自己的操作,仅警告:未拦截任何内容)");
			Add("Lobby protected: host (.+?) runs Peak Sentinel", (Match m) => "大厅已保护:主机 " + m.Groups[1].Value + " 运行 Peak 哨兵");
			Add("you are the host: the lobby is protected by your Peak Sentinel", (Match m) => "你是主机:大厅已由你的 Peak 哨兵保护");
			Add("got the airport [‘']Passport[’']", (Match m) => "获得机场通行证");
			Add("self-check: is a PEAK-MX cheat user", (Match m) => "自检:是 PEAK-MX 作弊用户");
			Add("\\(your own action, only a warning[;:] nothing was blocked\\)", (Match m) => "(你自己的操作,仅警告:未拦截任何内容)");
			Add("Map: scene (.+?), biomes(.*)$", (Match m) => "地图:场景 " + m.Groups[1].Value + ",生物群系" + m.Groups[2].Value);
			Add("^(\\d+) alerts$", (Match m) => m.Groups[1].Value + " 条警报");
			Add("^unbanned \\(", (Match m) => "已解除封禁(");
			Add("^Game (.+?), scene (.+)$", (Match m) => "游戏 " + m.Groups[1].Value + ",场景 " + m.Groups[2].Value);
		}

		private static void Add(string pattern, MatchEvaluator ev)
		{
			Rules.Add(new Rule(pattern, ev));
		}

		private static string TranslateDuration(string d)
		{
			Match match = Regex.Match(d, "^(\\d+(?:\\.\\d+)?)(s|m|h)$");
			if (!match.Success)
			{
				return d;
			}
			string text = ((match.Groups[2].Value == "s") ? " 秒" : ((match.Groups[2].Value == "m") ? " 分钟" : " 小时"));
			return match.Groups[1].Value + text;
		}

		private static string TranslateMoreSuffix(string s)
		{
			Match match = Regex.Match(s, "^ \\(\\+(\\d+) more since the last one\\)$");
			if (!match.Success)
			{
				return s;
			}
			return "(另有 " + match.Groups[1].Value + " 次未逐一报告)";
		}

		private static string Verbs(string v)
		{
			return v switch
			{
				"die" => "死亡", 
				"pass out" => "昏迷", 
				"turn to stone" => "被石化", 
				"get stuck in place" => "被定在原地", 
				"teleport" => "被传送", 
				_ => v, 
			};
		}

		private static string TempStatus(string s)
		{
			if (!(s == "hot"))
			{
				return "寒冷值";
			}
			return "炎热值";
		}

		private static string TempArg(string s)
		{
			if (!(s == "overheating"))
			{
				return "受冻";
			}
			return "中暑";
		}

		private static string HandPhrase(string s)
		{
			if (!(s == "in their hand"))
			{
				if (s == "into their hands")
				{
					return "在手中";
				}
				return s;
			}
			return "在手上";
		}

		private static string AppearPhrase(string s)
		{
			switch (s)
			{
			case "went straight into their hands":
			case "appeared went straight into their hands":
			case "straight into their hands":
				return "直接出现在手中";
			case "appeared right in front of them":
			case "right in front of them":
				return "正好出现在面前";
			case "appeared next to them":
			case "next to them":
				return "出现在身旁";
			case "out of nothing":
				return "出现";
			default:
				return s;
			}
		}

		private static string TranslateRefused(string r)
		{
			switch (r)
			{
			case "from killing you":
				return "击杀你";
			case "from knocking you out":
				return "使你昏迷";
			case "from teleporting you":
				return "传送你";
			case "from teleporting you (a fake revive)":
				return "用假复活传送你";
			case "from turning you to stone":
				return "把你石化";
			case "from sticking you in place":
				return "把你定在原地";
			case "from muting you for everyone":
				return "让所有人静音你";
			case "from ending the run (it deletes the save)":
				return "结束本局(会删除存档)";
			case "from flinging you":
				return "甩飞你";
			case "from kicking you":
				return "踢你";
			default:
			{
				Match match = Regex.Match(r, "^from muting (.+) for everyone$");
				if (match.Success)
				{
					return "让所有人静音 " + match.Groups[1].Value;
				}
				return r;
			}
			}
		}
	}
}
namespace SummitWatchCN.Blacklist
{
	public readonly struct NetEntrySnapshot
	{
		public readonly string Id;

		public readonly string Name;

		public readonly string Reason;

		public readonly string Added;

		public NetEntrySnapshot(string id, string name, string reason, string added)
		{
			Id = id;
			Name = name;
			Reason = reason;
			Added = added;
		}
	}
	public readonly struct LocalEntrySnapshot
	{
		public readonly string Id;

		public readonly string Name;

		public readonly string Reason;

		public readonly string Added;

		public LocalEntrySnapshot(string id, string name, string reason, string added)
		{
			Id = id;
			Name = name;
			Reason = reason;
			Added = added;
		}
	}
	public enum ClientBlockAction
	{
		LocalBlock,
		LeaveRoom,
		AskHost,
		LogOnly
	}
	internal static class BlacklistConfig
	{
		public static ConfigEntry<bool> LocalizationEnabled;

		public static ConfigEntry<KeyCode> PanelKey;

		public static ConfigEntry<bool> Enabled;

		public static ConfigEntry<bool> ToastsEnabled;

		public static ConfigEntry<bool> HostAutoKick;

		public static ConfigEntry<bool> MatchByName;

		public static ConfigEntry<ClientBlockAction> ClientAction;

		public static ConfigEntry<float> KickRetrySeconds;

		public static ConfigEntry<int> KickMaxRetries;

		public static ConfigEntry<bool> FallbackLocalBlock;

		public static ConfigEntry<bool> LocalMute;

		public static ConfigEntry<bool> LocalHide;

		public static ConfigEntry<bool> EnableNetwork;

		public static ConfigEntry<string> NetworkUrl;

		public static ConfigEntry<int> NetworkRefreshMinutes;

		public static ConfigEntry<bool> AutoBlacklistOnHostKick;

		public static ConfigEntry<int> DefaultPardonDays;

		public static ConfigEntry<bool> MaliciousRpcAutoKick;

		public static ConfigEntry<bool> RpcDebugLog;

		public static void Init(ConfigFile cfg)
		{
			LocalizationEnabled = cfg.Bind<bool>("General", "LocalizationEnabled", true, "汉化 Summit Watch 的面板、玩家资料、按钮、提示和弹窗(不影响举报文本和证据日志)。");
			PanelKey = cfg.Bind<KeyCode>("General", "BlacklistPanelKey", (KeyCode)289, "打开/关闭黑名单管理窗口的按键(默认 F8;若与其他模组冲突可自行修改)。");
			Enabled = cfg.Bind<bool>("Blacklist", "Enabled", true, "启用黑名单自动处理:发现黑名单玩家时按下面的规则自动处理。");
			ToastsEnabled = cfg.Bind<bool>("Blacklist", "ToastsEnabled", true, "发现并处理黑名单玩家时在屏幕右上角弹出中文提示。");
			HostAutoKick = cfg.Bind<bool>("Blacklist", "HostAutoKick", true, "你是主机时:黑名单玩家进房立即踢出(游戏踢出 RPC + 断开 Photon 连接),被无视时持续重试。");
			MatchByName = cfg.Bind<bool>("Blacklist", "MatchByName", true, "黑名单条目没有平台ID(SteamID)时,是否按玩家名称匹配(不区分大小写)。");
			ClientAction = cfg.Bind<ClientBlockAction>("Blacklist", "ClientAction", ClientBlockAction.LocalBlock, "你不是主机时遇到黑名单玩家的处理方式:LocalBlock=本地屏蔽(静音并在本地隐藏其角色);LeaveRoom=自己离开大厅;AskHost=请求主机踢出(主机需装 Summit Watch,否则退回本地屏蔽);LogOnly=仅提示记录。");
			KickRetrySeconds = cfg.Bind<float>("Blacklist", "KickRetrySeconds", 3f, "主机踢出后对方若没离开(作弊菜单拦截),每隔多少秒重试一次。");
			KickMaxRetries = cfg.Bind<int>("Blacklist", "KickMaxRetries", 5, "主机踢出最多重试多少次,超过后按 FallbackLocalBlock 处理。");
			FallbackLocalBlock = cfg.Bind<bool>("Blacklist", "FallbackLocalBlock", true, "主机踢出被对方无视、重试耗尽后,是否在本地将其屏蔽(静音 + 隐藏角色)。");
			LocalMute = cfg.Bind<bool>("Blacklist", "LocalMute", true, "本地屏蔽时是否同时静音该玩家(游戏自带的按人静音)。");
			LocalHide = cfg.Bind<bool>("Blacklist", "LocalHide", true, "本地屏蔽时是否在你的屏幕上隐藏其角色(仅本地生效,不影响其他玩家)。");
			EnableNetwork = cfg.Bind<bool>("Network", "EnableNetwork", true, "启用网络黑名单:自动从网络名单同步封禁列表(下载),与本地名单合并生效。");
			NetworkUrl = cfg.Bind<string>("Network", "NetworkUrl", "https://raw.githubusercontent.com/linling000I/SummitWatchCN/main/blacklist.json", "网络黑名单 JSON 的地址(raw 链接)。");
			NetworkRefreshMinutes = cfg.Bind<int>("Network", "RefreshMinutes", 10, "每隔多少分钟自动重新同步一次网络黑名单。");
			AutoBlacklistOnHostKick = cfg.Bind<bool>("Blacklist", "AutoBlacklistOnHostKick", true, "你作为主机踢出任何玩家时,自动把对方加入本地黑名单。");
			DefaultPardonDays = cfg.Bind<int>("Network", "DefaultPardonDays", 7, "限时赦免默认天数(管理员可随时修改、对单个玩家重新设置)。");
			MaliciousRpcAutoKick = cfg.Bind<bool>("AntiGrief", "MaliciousRpcAutoKick", true, "独立检测恶意 RPC:非主机发送“强制结束本局 / 击杀 / 石化他人”时立即拉黑,主机直接踢出。不依赖 SummitWatch,与其不冲突。");
			RpcDebugLog = cfg.Bind<bool>("AntiGrief", "RpcDebugLog", false, "调试用:输出每个 Photon 事件/RPC 的码与结构到日志。多人局默认关闭,避免同步写日志造成卡顿。");
		}

		public static string Label(ClientBlockAction a)
		{
			return a switch
			{
				ClientBlockAction.LocalBlock => "本地屏蔽", 
				ClientBlockAction.LeaveRoom => "离开大厅", 
				ClientBlockAction.AskHost => "请求主机踢出", 
				_ => "仅提示记录", 
			};
		}
	}
	internal static class AntiGriefDetector
	{
		private class CallbackTarget : MonoBehaviour, IInRoomCallbacks, IMatchmakingCallbacks
		{
			public void OnPlayerEnteredRoom(Player newPlayer)
			{
			}

			public void OnPlayerLeftRoom(Player otherPlayer)
			{
			}

			public void OnRoomPropertiesUpdate(Hashtable propertiesThatChanged)
			{
			}

			public void OnMasterClientSwitched(Player newMasterClient)
			{
			}

			public void OnJoinedRoom()
			{
				try
				{
					MarkSelfAsModded();
				}
				catch
				{
				}
			}

			public void OnCreateRoomFailed(short returnCode, string message)
			{
			}

			public void OnJoinRoomFailed(short returnCode, string message)
			{
			}

			public void OnJoinRandomFailed(short returnCode, string message)
			{
			}

			public void OnCreatedRoom()
			{
			}

			public void OnLeftRoom()
			{
			}

			public void OnFriendListUpdate(List<FriendInfo> friendList)
			{
			}

			public void OnPlayerPropertiesUpdate(Player target, Hashtable changedProps)
			{
				try
				{
					if (((Dictionary<object, object>)(object)changedProps).ContainsKey((object)"swcn_kick"))
					{
						string text = target.NickName ?? "";
						string id = target.UserId ?? "";
						string text2 = (changedProps[(object)"swcn_kick"] as string) ?? "主机自动拉黑";
						BlacklistStore.Add(id, text, text2, out var _);
						Plugin.Log.LogWarning((object)("[拉黑同步] 收到主机拉黑标记: " + text + " / " + text2));
						if ((Object)(object)BlacklistManager.Instance != (Object)null)
						{
							BlacklistManager.Instance.ShowToast("主机已拉黑 " + text);
						}
					}
				}
				catch (Exception ex)
				{
					Plugin.Log.LogWarning((object)("[拉黑同步] 异常: " + ex.Message));
				}
			}
		}

		private static readonly Dictionary<string, string> Watchlist = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
		{
			{ "RPCEndGame_ForceWin", "恶意炸房:强制结束本局(删除所有人存档)" },
			{ "RPCA_Die", "恶意击杀:强制他人死亡" },
			{ "RPCA_SetDead", "恶意击杀:强制他人死亡" },
			{ "RPCA_PassOut", "恶意击杀:强制他人昏迷" },
			{ "RPC_PetrifyInstantly", "恶意石化:立即石化他人" },
			{ "RPCA_Zombify", "恶意击杀:僵尸化他人" }
		};

		private static readonly Dictionary<int, double> _lastHit = new Dictionary<int, double>();

		private const double CooldownSeconds = 15.0;

		private static int _errCount;

		private static int _diagCount;

		private static int _diagRpcCount;

		public static void Apply()
		{
			try
			{
				if (HasPeakSentinel())
				{
					Plugin.Log.LogInfo((object)"[AntiGrief] 检测到 PeakSentinel,反炸房检测交由 PeakSentinel 处理,本模组不再重复拦截。");
					return;
				}
				PhotonNetwork.NetworkingClient.EventReceived += OnEvent;
				PhotonNetwork.AddCallbackTarget((object)new CallbackTarget());
				try
				{
					MarkSelfAsModded();
				}
				catch
				{
				}
				Plugin.Log.LogInfo((object)"恶意 RPC 检测器已挂载。");
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("AntiGriefDetector 挂载失败: " + ex));
			}
		}

		public static bool HasPeakSentinel()
		{
			try
			{
				Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
				for (int i = 0; i < assemblies.Length; i++)
				{
					if (assemblies[i].GetName().Name == "PeakSentinel")
					{
						return true;
					}
				}
			}
			catch
			{
			}
			return false;
		}

		public static void MarkSelfAsModded()
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Expected O, but got Unknown
			try
			{
				if (PhotonNetwork.InRoom && PhotonNetwork.LocalPlayer != null)
				{
					Hashtable val = new Hashtable { [(object)"swcn_mod"] = "1" };
					PhotonNetwork.LocalPlayer.SetCustomProperties(val, (Hashtable)null, (WebFlags)null);
				}
			}
			catch
			{
			}
		}

		public static void BroadcastBlacklistAdd(string userId, string name, string reason)
		{
			//IL_0038: 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_004e: Expected O, but got Unknown
			try
			{
				if (!PhotonNetwork.InRoom || !PhotonNetwork.IsMasterClient)
				{
					return;
				}
				Player[] playerList = PhotonNetwork.PlayerList;
				foreach (Player val in playerList)
				{
					if ((val.UserId ?? "") == userId)
					{
						Hashtable val2 = new Hashtable { [(object)"swcn_kick"] = reason };
						val.SetCustomProperties(val2, (Hashtable)null, (WebFlags)null);
						Plugin.Log.LogWarning((object)("[拉黑广播] 已设置拉黑属性给 " + name));
						break;
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[拉黑广播] 失败: " + ex.Message));
			}
		}

		public static void Unapply()
		{
			try
			{
				PhotonNetwork.NetworkingClient.EventReceived -= OnEvent;
			}
			catch
			{
			}