Decompiled source of SummitWatchCN v1.0.3
SummitWatchCN.dll
Decompiled a day ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; 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 HarmonyLib; using Photon.Pun; using Photon.Realtime; using SummitWatchCN.Blacklist; using SummitWatchCN.I18n; using UnityEngine; [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.2.0")] [assembly: AssemblyInformationalVersion("1.0.2")] [assembly: AssemblyProduct("SummitWatch Chinese Localization & Blacklist Addon")] [assembly: AssemblyTitle("SummitWatchCN")] [assembly: AssemblyVersion("1.0.2.0")] namespace SummitWatchCN { [BepInPlugin("summitwatch.cn.blacklist", "SummitWatch 汉化与黑名单", "1.1.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] public sealed class Plugin : BaseUnityPlugin { public const string GuidValue = "summitwatch.cn.blacklist"; public const string PluginName = "SummitWatch 汉化与黑名单"; public const string VersionValue = "1.1.0"; 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_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005e: 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); GameObject val = new GameObject("SummitWatchCN") { hideFlags = (HideFlags)61 }; Object.DontDestroyOnLoad((Object)(object)val); _manager = val.AddComponent<BlacklistManager>(); Log.LogInfo((object)"SummitWatch 汉化与黑名单附属 v1.1.0 已加载。"); Log.LogInfo((object)("黑名单文件: " + BlacklistStore.FilePath)); } private void OnDestroy() { try { BlacklistStore.Save(); } catch { } if (_harmony != null) { _harmony.UnpatchSelf(); } } } } namespace SummitWatchCN.I18n { internal static class GuiTranslatePatches { private static Assembly _swAssembly; 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 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) { if (assembly.GetName().Name == "SummitWatch") { _swAssembly = assembly; break; } } if (_swAssembly == null) { Plugin.Log.LogWarning((object)"未找到 SummitWatch 程序集,汉化补丁未安装。"); 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(int); Type typeFromHandle8 = typeof(WindowFunction); 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, "Window", new Type[6] { typeFromHandle7, typeFromHandle5, typeFromHandle8, typeFromHandle6, typeFromHandle3, typeFromHandle4 }); 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 }); 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 Prefix(ref string text) { if (text == null || text.Length == 0 || !BlacklistConfig.LocalizationEnabled.Value) { return; } bool flag = FromSummitWatch(); if (Diag && Interlocked.Increment(ref _diagCalls) <= 80) { Plugin.Log.LogInfo((object)("[DIAG] IMGUI 文本调用 #" + _diagCalls + " fromSW=" + flag + " text=" + TextSnippet(text))); } if (flag) { string text2 = L10n.T(text); if (Diag && text2 != text && Interlocked.Increment(ref _diagHits) <= 40) { Plugin.Log.LogInfo((object)("[DIAG] 汉化命中: " + TextSnippet(text) + " => " + TextSnippet(text2))); } text = text2; } } 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 FromSummitWatch() { Assembly swAssembly = _swAssembly; if (swAssembly == null) { return false; } StackTrace stackTrace = new StackTrace(1, fNeedFileInfo: false); MethodBase methodBase = null; bool flag = false; bool flag2 = Diag && _diagCalls <= 4; if (flag2) { Plugin.Log.LogInfo((object)"[DIAG] ---- 调用栈帧 (skip=1) ----"); } 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 (flag2) { string text = ((assembly != null) ? assembly.GetName().Name : "<null>"); Plugin.Log.LogInfo((object)("[DIAG] frame" + i + " [" + text + "] " + ((type != null) ? type.FullName : "<null type>") + "::" + ((method != null) ? method.Name : "<null method>"))); } 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) { 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", "异常推搡" } }; 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" }; 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+), the game's strongest is about 30\\)$", (Match m) => "以不可能的踢击力度踢了 " + m.Groups[1].Value + "(力度 " + m.Groups[2].Value + ",游戏内最高约 30)"); Add("^kicked (.+) down for (\\d+) s \\(a kick lasts 1 s\\)$", (Match m) => "把 " + m.Groups[1].Value + " 踢倒长达 " + m.Groups[2].Value + " 秒(正常踢击只持续 1 秒)"); Add("^kicked (.+) from (\\d+) m away \\(a kick reaches 1 m\\)$", (Match m) => "在 " + m.Groups[2].Value + " 米外踢了 " + m.Groups[1].Value + "(踢击射程仅 1 米)"); 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("^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|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 + " 小时前"); } 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 "甩飞你"; default: { Match match = Regex.Match(r, "^from muting (.+) for everyone$"); if (match.Success) { return "让所有人静音 " + match.Groups[1].Value; } return r; } } } } } namespace SummitWatchCN.Blacklist { 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 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, "限时赦免默认天数(管理员可随时修改、对单个玩家重新设置)。"); } public static string Label(ClientBlockAction a) { return a switch { ClientBlockAction.LocalBlock => "本地屏蔽", ClientBlockAction.LeaveRoom => "离开大厅", ClientBlockAction.AskHost => "请求主机踢出", _ => "仅提示记录", }; } } [Serializable] public sealed class BlacklistEntry { public string id = ""; public string name = ""; public string note = ""; public string added = ""; public string DisplayName { get { if (!string.IsNullOrEmpty(name)) { return name; } return "(未记录名称)"; } } public string DisplayId { get { if (!string.IsNullOrEmpty(id)) { return id; } return "(仅按名称匹配)"; } } public BlacklistEntry() { } public BlacklistEntry(string id, string name, string note) { this.id = id ?? ""; this.name = name ?? ""; this.note = note ?? ""; added = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); } } internal sealed class TargetState { public bool Handled; public BlacklistEntry Entry; public bool Kicking; public int Kicks; public float LastKickAt; public bool Blocking; public bool AskedHost; public string Name = ""; public string UserId = ""; } public sealed class BlacklistManager : MonoBehaviour { private BlacklistWindow _window; private readonly Dictionary<int, TargetState> _states = new Dictionary<int, TargetState>(); private string _room; private float _nextPoll; private float _nextNetRefresh; private bool _leftThisRoom; private string _toast = ""; private double _toastUntil; private float _nextFontApply; private int _diagGui; private int _updateErrors; private static int _fontErrors; public static BlacklistManager Instance { get; private set; } public BlacklistWindow Window => _window; private void Awake() { Instance = this; _window = new BlacklistWindow(this); } private void Update() { try { UpdateImpl(); } catch (Exception ex) { if (_updateErrors++ < 10) { Plugin.Log.LogError((object)("黑名单管理器轮询异常: " + ex)); } } } private void UpdateImpl() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) if (Input.GetKeyDown(BlacklistConfig.PanelKey.Value)) { _window.IsOpen = !_window.IsOpen; } if (Time.unscaledTime >= _nextPoll) { _nextPoll = Time.unscaledTime + 1f; Poll(); } if (_nextNetRefresh < Time.unscaledTime) { int num = Mathf.Max(1, BlacklistConfig.NetworkRefreshMinutes.Value); _nextNetRefresh = Time.unscaledTime + (float)num * 60f; NetworkBlacklist.RefreshAsync(); } } private static void ApplyFonts() { try { Font cjk = FontHelper.Cjk; if (!((Object)(object)cjk == (Object)null) && !((Object)(object)GUI.skin == (Object)null)) { GUI.skin.font = cjk; GUI.skin.label.font = cjk; GUI.skin.button.font = cjk; GUI.skin.box.font = cjk; GUI.skin.window.font = cjk; GUI.skin.toggle.font = cjk; GUI.skin.textField.font = cjk; GUI.skin.textArea.font = cjk; SwAccess.ApplySummitWatchFonts(cjk); } } catch (Exception ex) { if (_fontErrors++ < 3) { Plugin.Log.LogWarning((object)("字体应用失败: " + ex.GetType().Name + " " + ex.Message)); } } } private void OnGUI() { if (Time.unscaledTime >= _nextFontApply) { _nextFontApply = Time.unscaledTime + 2f; ApplyFonts(); } try { _window.Draw(); DrawToast(); } catch (Exception ex) { if (_diagGui++ < 20) { Plugin.Log.LogError((object)("黑名单窗口绘制异常: " + ex)); } } } public void ShowToast(string text) { _toast = text; _toastUntil = Time.unscaledTimeAsDouble + 6.0; } private void DrawToast() { //IL_0034: Unknown result type (might be due to invalid IL or missing references) if (!string.IsNullOrEmpty(_toast) && !(Time.unscaledTimeAsDouble > _toastUntil)) { Ui.Ensure(); GUI.Box(new Rect(20f, 20f, 430f, 56f), "黑名单:\n" + _toast, Ui.Toast); } } private void ResetRoom() { _states.Clear(); _leftThisRoom = false; } private void Poll() { if (!PhotonNetwork.InRoom || PhotonNetwork.CurrentRoom == null) { if (_room != null) { _room = null; ResetRoom(); } return; } string name = PhotonNetwork.CurrentRoom.Name; if (name != _room) { _room = name; ResetRoom(); SwAccess.EvidenceLine("[黑名单附属] 进入大厅 " + name); } HashSet<int> hashSet = new HashSet<int>(); Player[] playerList = PhotonNetwork.PlayerList; foreach (Player val in playerList) { if (val == null || val.IsLocal || val.IsInactive) { continue; } hashSet.Add(val.ActorNumber); if (!_states.TryGetValue(val.ActorNumber, out var value)) { value = new TargetState(); _states[val.ActorNumber] = value; } value.Name = SwAccess.SanitizeName(val.NickName); value.UserId = val.UserId ?? ""; BlacklistEntry match; bool flag = BlacklistStore.IsListed(val.UserId, val.NickName, out match); bool flag2 = NetworkBlacklist.IsNetworkBanned(value.UserId); if (!value.Handled && BlacklistConfig.Enabled.Value && (flag || flag2)) { value.Handled = true; value.Entry = match; if (value.Entry == null) { value.Entry = new BlacklistEntry(value.UserId, value.Name, "网络黑名单"); } HandleSeen(val, value); } TickKicking(val, value); TickBlocking(val, value); } if (_states.Count == hashSet.Count) { return; } List<int> list = new List<int>(); foreach (int key in _states.Keys) { if (!hashSet.Contains(key)) { list.Add(key); } } foreach (int item in list) { _states.Remove(item); } } private void HandleSeen(Player p, TargetState st) { string text = st.Name + "(ID: " + (string.IsNullOrEmpty(st.UserId) ? "未知" : st.UserId) + ")"; string text2 = "[黑名单附属] 发现黑名单玩家 " + text; if (st.Entry != null && !string.IsNullOrEmpty(st.Entry.note)) { text2 = text2 + ",备注:" + st.Entry.note; } Plugin.Log.LogInfo((object)text2); SwAccess.EvidenceLine(text2); bool isMasterClient = PhotonNetwork.IsMasterClient; if (isMasterClient && BlacklistConfig.HostAutoKick.Value && !p.IsMasterClient) { ShowToast("发现黑名单玩家 " + st.Name + ",正在踢出…"); BeginHostKick(p, st, "进房自动踢出"); return; } if (isMasterClient) { ShowToast("发现黑名单玩家 " + st.Name + "(主机自动踢出已关闭,可在黑名单窗口手动踢出)"); return; } switch (BlacklistConfig.ClientAction.Value) { case ClientBlockAction.LocalBlock: st.Blocking = true; TickBlocking(p, st, force: true); ShowToast("发现黑名单玩家 " + st.Name + ",已在本地屏蔽"); SwAccess.EvidenceLine("[黑名单附属] 已在本地屏蔽 " + text); break; case ClientBlockAction.LeaveRoom: if (!_leftThisRoom) { _leftThisRoom = true; ShowToast("发现黑名单玩家 " + st.Name + ",正在离开大厅…"); SwAccess.EvidenceLine("[黑名单附属] 因黑名单玩家 " + text + " 离开大厅"); try { PhotonNetwork.LeaveRoom(true); break; } catch (Exception ex) { Plugin.Log.LogWarning((object)("离开大厅失败: " + ex)); break; } } break; case ClientBlockAction.AskHost: if (SwAccess.HostHasMod) { if (!st.AskedHost) { st.AskedHost = true; SwAccess.InvokeRequestKick(p.ActorNumber); ShowToast("发现黑名单玩家 " + st.Name + ",已请求主机踢出"); SwAccess.EvidenceLine("[黑名单附属] 请求主机踢出 " + text); } } else { st.Blocking = true; TickBlocking(p, st, force: true); ShowToast("发现黑名单玩家 " + st.Name + ",主机未装 Summit Watch,已本地屏蔽"); SwAccess.EvidenceLine("[黑名单附属] 主机无 Summit Watch,本地屏蔽 " + text); } break; default: ShowToast("发现黑名单玩家 " + st.Name + "(当前为仅提示模式)"); break; } } private void BeginHostKick(Player p, TargetState st, string why) { st.Kicking = true; st.Kicks = 1; st.LastKickAt = Time.unscaledTime; GameKick.HostKick(p); string text = st.Name + "(ID: " + (string.IsNullOrEmpty(st.UserId) ? "未知" : st.UserId) + ")"; SwAccess.EvidenceLine("[黑名单附属] 主机踢出 " + text + ",原因:" + why); Plugin.Log.LogInfo((object)("已对黑名单玩家执行踢出: " + text)); } private void TickKicking(Player p, TargetState st) { if (!st.Kicking || !PhotonNetwork.IsMasterClient) { return; } if (!GameKick.StillInRoom(p.ActorNumber)) { st.Kicking = false; ShowToast(st.Name + " 已被踢出大厅"); SwAccess.EvidenceLine("[黑名单附属] " + st.Name + " 已离开大厅,踢出成功"); } else { if (Time.unscaledTime - st.LastKickAt < Mathf.Max(1f, BlacklistConfig.KickRetrySeconds.Value)) { return; } if (st.Kicks >= Mathf.Max(1, BlacklistConfig.KickMaxRetries.Value)) { st.Kicking = false; string text = st.Name + " 无视了踢出(疑似作弊菜单拦截)"; if (BlacklistConfig.FallbackLocalBlock.Value) { st.Blocking = true; TickBlocking(p, st, force: true); text += ",已在本地屏蔽"; } ShowToast(text); SwAccess.EvidenceLine("[黑名单附属] " + text); } else { st.Kicks++; st.LastKickAt = Time.unscaledTime; GameKick.HostKick(p); Plugin.Log.LogInfo((object)("重试踢出 " + st.Name + "(第 " + st.Kicks + " 次)")); } } } private void TickBlocking(Player p, TargetState st, bool force = false) { if (st.Blocking) { if (BlacklistConfig.LocalMute.Value) { GameKick.LocalMute(p); } if (BlacklistConfig.LocalHide.Value) { GameKick.LocalHide(p); } } } public void ForgetActor(int actor) { _states.Remove(actor); } public bool IsHandled(int actor) { if (_states.TryGetValue(actor, out var value)) { return value.Handled; } return false; } public bool IsBlocking(int actor) { if (_states.TryGetValue(actor, out var value)) { return value.Blocking; } return false; } public bool IsKicking(int actor) { if (_states.TryGetValue(actor, out var value)) { return value.Kicking; } return false; } public void AddCurrentPlayer(int actor, string note) { Player val = ((PhotonNetwork.InRoom && PhotonNetwork.CurrentRoom != null) ? PhotonNetwork.CurrentRoom.GetPlayer(actor, false) : null); if (val != null) { string text = SwAccess.SanitizeName(val.NickName); BlacklistEntry entry; bool flag = BlacklistStore.Add(val.UserId, text, note, out entry); ShowToast((flag ? "已加入黑名单: " : "已更新黑名单条目: ") + text); SwAccess.EvidenceLine("[黑名单附属] 手动" + (flag ? "加入" : "更新") + " " + text + "(ID: " + (val.UserId ?? "未知") + ")"); if (!_states.TryGetValue(actor, out var value)) { value = new TargetState { Name = text, UserId = (val.UserId ?? "") }; _states[actor] = value; } if (!value.Handled && BlacklistConfig.Enabled.Value) { value.Handled = true; value.Entry = entry; HandleSeen(val, value); } } } public void ManualKick(int actor) { Player val = ((PhotonNetwork.InRoom && PhotonNetwork.CurrentRoom != null) ? PhotonNetwork.CurrentRoom.GetPlayer(actor, false) : null); if (val == null) { ShowToast("该玩家已不在大厅"); return; } if (!PhotonNetwork.IsMasterClient) { ShowToast("只有主机可以直接踢出玩家"); return; } if (val.IsMasterClient) { ShowToast("不能踢出主机"); return; } if (!_states.TryGetValue(actor, out var value)) { value = new TargetState { Name = SwAccess.SanitizeName(val.NickName), UserId = (val.UserId ?? "") }; _states[actor] = value; } BeginHostKick(val, value, "黑名单窗口手动踢出"); ShowToast("已踢出 " + value.Name); } public void ManualBlock(int actor) { Player val = ((PhotonNetwork.InRoom && PhotonNetwork.CurrentRoom != null) ? PhotonNetwork.CurrentRoom.GetPlayer(actor, false) : null); if (val == null) { ShowToast("该玩家已不在大厅"); return; } if (!_states.TryGetValue(actor, out var value)) { value = new TargetState { Name = SwAccess.SanitizeName(val.NickName), UserId = (val.UserId ?? "") }; _states[actor] = value; } value.Blocking = true; TickBlocking(val, value, force: true); ShowToast("已在本地屏蔽 " + value.Name); SwAccess.EvidenceLine("[黑名单附属] 手动本地屏蔽 " + value.Name); } } [Serializable] public sealed class BlacklistFile { public int version = 1; public List<BlacklistEntry> entries = new List<BlacklistEntry>(); } internal static class BlacklistStore { private static readonly object Gate = new object(); private static BlacklistFile _data = new BlacklistFile(); public static string FilePath { get; private set; } = ""; public static IReadOnlyList<BlacklistEntry> Entries { get { lock (Gate) { return _data.entries; } } } public static void Load() { try { FilePath = Path.Combine(Paths.ConfigPath, "summitwatch-blacklist.json"); if (File.Exists(FilePath)) { _data = ParseFile(File.ReadAllText(FilePath, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false))); } else { _data = new BlacklistFile(); Save(); } Plugin.Log.LogInfo((object)("黑名单加载完成:" + _data.entries.Count + " 条记录。")); } catch (Exception ex) { Plugin.Log.LogWarning((object)("加载黑名单失败: " + ex)); _data = new BlacklistFile(); } } private static string Escape(string s) { if (string.IsNullOrEmpty(s)) { return ""; } StringBuilder stringBuilder = new StringBuilder(s.Length); foreach (char c in s) { switch (c) { case '"': stringBuilder.Append("\\\""); continue; case '\\': stringBuilder.Append("\\\\"); continue; case '\b': stringBuilder.Append("\\b"); continue; case '\f': stringBuilder.Append("\\f"); continue; case '\n': stringBuilder.Append("\\n"); continue; case '\r': stringBuilder.Append("\\r"); continue; case '\t': stringBuilder.Append("\\t"); continue; } if (c < ' ') { StringBuilder stringBuilder2 = stringBuilder.Append("\\u"); int num = c; stringBuilder2.Append(num.ToString("x4")); } else { stringBuilder.Append(c); } } return stringBuilder.ToString(); } private static string BuildJson() { StringBuilder stringBuilder = new StringBuilder(256 + Math.Max(0, _data.entries.Count) * 128); stringBuilder.Append("{\n \"version\": 1,\n \"entries\": ["); for (int i = 0; i < _data.entries.Count; i++) { BlacklistEntry blacklistEntry = _data.entries[i]; stringBuilder.Append((i == 0) ? "\n " : ",\n "); stringBuilder.Append("{\"id\":\"").Append(Escape(blacklistEntry.id)).Append('"'); stringBuilder.Append(",\"name\":\"").Append(Escape(blacklistEntry.name)).Append('"'); stringBuilder.Append(",\"note\":\"").Append(Escape(blacklistEntry.note)).Append('"'); stringBuilder.Append(",\"added\":\"").Append(Escape(blacklistEntry.added)).Append("\"}"); } if (_data.entries.Count > 0) { stringBuilder.Append('\n'); } stringBuilder.Append(" ]\n}\n"); return stringBuilder.ToString(); } private static void SkipWs(string s, ref int p) { while (p < s.Length && char.IsWhiteSpace(s[p])) { p++; } } private static string ReadJsonString(string s, ref int p) { if (p >= s.Length || s[p] != '"') { return ""; } p++; StringBuilder stringBuilder = new StringBuilder(); while (p < s.Length) { char c = s[p++]; if (c == '"') { break; } if (c == '\\' && p < s.Length) { char c2 = s[p++]; switch (c2) { case '"': stringBuilder.Append('"'); break; case '\\': stringBuilder.Append('\\'); break; case '/': stringBuilder.Append('/'); break; case 'n': stringBuilder.Append('\n'); break; case 't': stringBuilder.Append('\t'); break; case 'r': stringBuilder.Append('\r'); break; case 'b': stringBuilder.Append('\b'); break; case 'f': stringBuilder.Append('\f'); break; case 'u': if (p + 3 < s.Length) { if (int.TryParse(s.Substring(p, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var result)) { stringBuilder.Append((char)result); } p += 4; } break; default: stringBuilder.Append(c2); break; } } else { stringBuilder.Append(c); } } return stringBuilder.ToString(); } private static BlacklistFile ParseFile(string s) { BlacklistFile blacklistFile = new BlacklistFile(); if (string.IsNullOrEmpty(s)) { return blacklistFile; } int num = s.IndexOf("\"entries\"", StringComparison.Ordinal); if (num < 0) { return blacklistFile; } int num2 = s.IndexOf('[', num); int num3 = s.IndexOf(']', (num2 < 0) ? num : num2); if (num2 < 0 || num3 < 0 || num3 <= num2) { return blacklistFile; } int p = num2 + 1; while (p < num3) { SkipWs(s, ref p); if (p >= num3) { break; } if (s[p] != '{') { p++; continue; } p++; BlacklistEntry blacklistEntry = new BlacklistEntry(); while (p < num3) { SkipWs(s, ref p); if (p >= num3) { break; } if (s[p] == '}') { p++; break; } if (s[p] != '"') { p++; continue; } string text = ReadJsonString(s, ref p); SkipWs(s, ref p); if (p < num3 && s[p] == ':') { p++; } SkipWs(s, ref p); string text2 = ((p < num3 && s[p] == '"') ? ReadJsonString(s, ref p) : ""); switch (text) { case "id": blacklistEntry.id = text2; break; case "name": blacklistEntry.name = text2; break; case "note": blacklistEntry.note = text2; break; case "added": blacklistEntry.added = text2; break; } SkipWs(s, ref p); if (p < num3 && s[p] == ',') { p++; } } blacklistFile.entries.Add(blacklistEntry); } return blacklistFile; } public static void Save() { lock (Gate) { try { if (string.IsNullOrEmpty(FilePath)) { FilePath = Path.Combine(Paths.ConfigPath, "summitwatch-blacklist.json"); } string directoryName = Path.GetDirectoryName(FilePath); if (!string.IsNullOrEmpty(directoryName)) { Directory.CreateDirectory(directoryName); } string contents = BuildJson(); string text = FilePath + ".tmp"; File.WriteAllText(text, contents, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); if (File.Exists(FilePath)) { File.Replace(text, FilePath, null); } else { File.Move(text, FilePath); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("保存黑名单失败: " + ex)); } } } public static void Reload() { Load(); } public static bool Add(string id, string name, string note, out BlacklistEntry entry) { bool result; lock (Gate) { id = (id ?? "").Trim(); name = (name ?? "").Trim(); note = note ?? ""; BlacklistEntry blacklistEntry = FindLocked(id, name); if (blacklistEntry != null) { if (!string.IsNullOrEmpty(id)) { blacklistEntry.id = id; } if (!string.IsNullOrEmpty(name)) { blacklistEntry.name = name; } if (!string.IsNullOrEmpty(note)) { blacklistEntry.note = note; } entry = blacklistEntry; result = false; } else { entry = new BlacklistEntry(id, name, note); _data.entries.Add(entry); result = true; } } Save(); return result; } public static bool RemoveAt(int index) { lock (Gate) { if (index < 0 || index >= _data.entries.Count) { return false; } _data.entries.RemoveAt(index); } Save(); return true; } public static void Remove(BlacklistEntry e) { lock (Gate) { _data.entries.Remove(e); } Save(); } private static BlacklistEntry FindLocked(string id, string name) { if (!string.IsNullOrEmpty(id)) { foreach (BlacklistEntry entry in _data.entries) { if (string.Equals(entry.id, id, StringComparison.Ordinal)) { return entry; } } } if (!string.IsNullOrEmpty(name)) { foreach (BlacklistEntry entry2 in _data.entries) { if (string.Equals(entry2.name, name, StringComparison.OrdinalIgnoreCase)) { return entry2; } } } return null; } public static bool IsListed(string userId, string playerName, out BlacklistEntry match) { lock (Gate) { userId = (userId ?? "").Trim(); playerName = (playerName ?? "").Trim(); if (userId.Length > 0) { foreach (BlacklistEntry entry in _data.entries) { if (string.Equals(entry.id, userId, StringComparison.Ordinal)) { match = entry; return true; } } } if (BlacklistConfig.MatchByName.Value && playerName.Length > 0) { foreach (BlacklistEntry entry2 in _data.entries) { if (string.IsNullOrEmpty(entry2.id) && string.Equals(entry2.name, playerName, StringComparison.OrdinalIgnoreCase)) { match = entry2; return true; } } } match = null; return false; } } } public sealed class BlacklistWindow { private const int WindowId = 1398240999; private readonly BlacklistManager _mgr; private Rect _rect = new Rect(80f, 120f, 780f, 680f); private Vector2 _playerScroll; private Vector2 _listScroll; private Vector2 _netScroll; private string _newName = ""; private string _newId = ""; private string _newNote = ""; private string _lastToastResult = ""; public bool IsOpen; public BlacklistWindow(BlacklistManager mgr) { //IL_0015: 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) _mgr = mgr; } public void Draw() { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Expected O, but got Unknown //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) if (IsOpen) { Ui.Ensure(); string text = "峰会监视 · 黑名单管理 (按 " + ((object)BlacklistConfig.PanelKey.Value/*cast due to .constrained prefix*/).ToString() + " 关闭)"; _rect = GUILayout.Window(1398240999, _rect, new WindowFunction(DrawWindow), text, Ui.Window, Array.Empty<GUILayoutOption>()); } } private void DrawWindow(int id) { DrawConfig(); GUILayout.Space(6f); DrawCurrentPlayers(); GUILayout.Space(6f); DrawBlacklist(); GUILayout.Space(6f); DrawNetwork(); GUILayout.Space(6f); DrawManualAdd(); GUILayout.Space(4f); GUILayout.Label("数据文件:BepInEx/config/summitwatch-blacklist.json(ID 即平台ID,通常为 SteamID64);本地屏蔽/静音只影响你自己,主机踢出对全大厅生效。", Ui.Small, Array.Empty<GUILayoutOption>()); GUI.DragWindow(); } private void DrawConfig() { GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); ConfigToggle(BlacklistConfig.Enabled, "启用黑名单", 110f); ConfigToggle(BlacklistConfig.HostAutoKick, "主机自动踢出", 130f); ConfigToggle(BlacklistConfig.MatchByName, "允许按名称匹配", 140f); ConfigToggle(BlacklistConfig.ToastsEnabled, "弹出提示", 100f); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); ClientBlockAction value = BlacklistConfig.ClientAction.Value; if (GUILayout.Button("非主机时:" + BlacklistConfig.Label(value), Ui.Button, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(220f) })) { ClientBlockAction[] array = (ClientBlockAction[])Enum.GetValues(typeof(ClientBlockAction)); int num = Array.IndexOf(array, value); BlacklistConfig.ClientAction.Value = array[(num + 1) % array.Length]; } ConfigToggle(BlacklistConfig.FallbackLocalBlock, "踢出被无视后本地屏蔽", 190f); ConfigToggle(BlacklistConfig.LocalMute, "本地屏蔽时静音", 150f); ConfigToggle(BlacklistConfig.LocalHide, "本地屏蔽时隐藏", 150f); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); GUILayout.FlexibleSpace(); if (GUILayout.Button("重载名单", Ui.Button, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(90f) })) { BlacklistStore.Reload(); _mgr.ShowToast("黑名单已重新加载"); } if (GUILayout.Button("打开文件", Ui.Button, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(90f) })) { try { Application.OpenURL("file:///" + BlacklistStore.FilePath.Replace('\\', '/')); } catch (Exception ex) { Plugin.Log.LogWarning((object)("打开黑名单文件失败: " + ex)); } } GUILayout.EndHorizontal(); } private static void ConfigToggle(ConfigEntry<bool> entry, string label, float width) { bool flag = GUILayout.Toggle(entry.Value, label, Ui.Toggle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(width) }); if (flag != entry.Value) { entry.Value = flag; } } private void DrawCurrentPlayers() { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) GUILayout.Label("当前大厅玩家", Ui.Header, Array.Empty<GUILayoutOption>()); if (!PhotonNetwork.InRoom) { GUILayout.Label("当前不在大厅中。", Ui.Small, Array.Empty<GUILayoutOption>()); return; } _playerScroll = GUILayout.BeginScrollView(_playerScroll, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(210f) }); Player[] playerListOthers = PhotonNetwork.PlayerListOthers; if (playerListOthers == null || playerListOthers.Length == 0) { GUILayout.Label("大厅中只有你一个人。", Ui.Small, Array.Empty<GUILayoutOption>()); } else { Player[] array = playerListOthers; foreach (Player val in array) { if (val != null && !val.IsInactive) { DrawPlayerRow(val); } } } GUILayout.EndScrollView(); } private void DrawPlayerRow(Player p) { //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Unknown result type (might be due to invalid IL or missing references) //IL_01b0: Unknown result type (might be due to invalid IL or missing references) //IL_017e: Unknown result type (might be due to invalid IL or missing references) //IL_01fe: Unknown result type (might be due to invalid IL or missing references) //IL_0261: Unknown result type (might be due to invalid IL or missing references) string text = SwAccess.SanitizeName(p.NickName); string text2 = p.UserId ?? ""; BlacklistEntry match; bool flag = BlacklistStore.IsListed(text2, p.NickName, out match); bool isMasterClient = PhotonNetwork.IsMasterClient; GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); GUILayout.Label(text, Ui.Label, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(110f) }); GUILayout.Label((text2.Length > 21) ? (text2.Substring(0, 21) + "…") : ((text2.Length == 0) ? "(无ID)" : text2), Ui.Small, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(160f) }); if (p.IsMasterClient) { GUILayout.Label("主机", Ui.Header, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(40f) }); } else { GUILayout.Label("", Ui.Small, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(40f) }); } Color contentColor = GUI.contentColor; string text3; if (_mgr.IsKicking(p.ActorNumber)) { text3 = "踢出中…"; GUI.contentColor = new Color(1f, 0.86f, 0.3f); } else if (_mgr.IsBlocking(p.ActorNumber)) { text3 = "已本地屏蔽"; GUI.contentColor = new Color(1f, 0.58f, 0.22f); } else if (flag) { text3 = "黑名单"; GUI.contentColor = new Color(1f, 0.36f, 0.36f); } else { text3 = ""; } GUILayout.Label(text3, Ui.Small, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(86f) }); GUI.contentColor = contentColor; if (GUILayout.Button("资料", Ui.Button, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(56f) })) { SwAccess.SetPanelOpen(open: true); SwAccess.OpenProfile(p.ActorNumber); } if (flag) { GUI.contentColor = new Color(1f, 0.6f, 0.6f); if (GUILayout.Button("移出黑名单", Ui.Button, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(104f) })) { if (match != null) { BlacklistStore.Remove(match); } _mgr.ForgetActor(p.ActorNumber); _mgr.ShowToast("已将 " + text + " 移出黑名单"); } GUI.contentColor = contentColor; } else if (GUILayout.Button("加入黑名单", Ui.Button, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(104f) })) { _mgr.AddCurrentPlayer(p.ActorNumber, "面板手动加入"); } GUI.enabled = isMasterClient && !p.IsMasterClient; if (GUILayout.Button("踢出", Ui.Button, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(56f) })) { _mgr.ManualKick(p.ActorNumber); } GUI.enabled = true; if (GUILayout.Button("屏蔽", Ui.Button, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(56f) })) { _mgr.ManualBlock(p.ActorNumber); } GUILayout.EndHorizontal(); } private void DrawBlacklist() { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_022e: Unknown result type (might be due to invalid IL or missing references) //IL_0273: Unknown result type (might be due to invalid IL or missing references) int count = BlacklistStore.Entries.Count; GUILayout.Label("个人黑名单(" + count + ")", Ui.Header, Array.Empty<GUILayoutOption>()); _listScroll = GUILayout.BeginScrollView(_listScroll, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(220f) }); if (count == 0) { GUILayout.Label("黑名单为空。在上方玩家列表点击“加入黑名单”,或在下方手动填写。", Ui.Small, Array.Empty<GUILayoutOption>()); } else { for (int num = BlacklistStore.Entries.Count - 1; num >= 0; num--) { BlacklistEntry blacklistEntry = BlacklistStore.Entries[num]; GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); GUILayout.Label(blacklistEntry.DisplayName, Ui.Label, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(110f) }); GUILayout.Label(blacklistEntry.DisplayId, Ui.Small, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(175f) }); GUILayout.Label(string.IsNullOrEmpty(blacklistEntry.note) ? "" : blacklistEntry.note, Ui.Small, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(110f) }); GUILayout.Label(blacklistEntry.added, Ui.Small, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(130f) }); if (GUILayout.Button("复制ID", Ui.Button, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(64f) })) { GUIUtility.systemCopyBuffer = blacklistEntry.id; _mgr.ShowToast("已复制ID: " + blacklistEntry.DisplayName); } if (GUILayout.Button("投稿", Ui.Button, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(56f) })) { GUIUtility.systemCopyBuffer = "被举报人 SteamID64: " + blacklistEntry.id + "\n名称: " + blacklistEntry.DisplayName + "\n理由: " + (string.IsNullOrEmpty(blacklistEntry.note) ? "(请补充)" : blacklistEntry.note) + "\n投稿人联系方式: (请填写,便于回访)"; Application.OpenURL("https://linling000I.github.io/SummitWatchCN/admin.html#submit"); _mgr.ShowToast("已复制投稿信息,浏览器将打开发稿页"); } GUI.contentColor = new Color(1f, 0.6f, 0.6f); if (GUILayout.Button("移除", Ui.Button, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(56f) })) { BlacklistStore.RemoveAt(num); _mgr.ShowToast("已移除一条黑名单记录"); } GUI.contentColor = Color.white; GUILayout.EndHorizontal(); } } GUILayout.EndScrollView(); } private void DrawNetwork() { //IL_0165: Unknown result type (might be due to invalid IL or missing references) //IL_0183: Unknown result type (might be due to invalid IL or missing references) //IL_0227: Unknown result type (might be due to invalid IL or missing references) //IL_023f: Unknown result type (might be due to invalid IL or missing references) //IL_0244: Unknown result type (might be due to invalid IL or missing references) //IL_030f: Unknown result type (might be due to invalid IL or missing references) //IL_033b: Unknown result type (might be due to invalid IL or missing references) bool flag = NetworkBlacklist.IsLocalAdmin((PhotonNetwork.InRoom && PhotonNetwork.LocalPlayer != null) ? (PhotonNetwork.LocalPlayer.UserId ?? "") : ""); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); ConfigToggle(BlacklistConfig.EnableNetwork, "启用网络黑名单", 150f); string text = ((!BlacklistConfig.EnableNetwork.Value) ? "(已关闭,仅使用个人名单)" : ((!NetworkBlacklist.Ready) ? "(同步中…)" : (NetworkBlacklist.Offline ? ("(离线·已缓存 " + NetworkBlacklist.Entries.Count + " 条 · " + NetworkBlacklist.LastRefresh.ToString("HH:mm") + ")") : ((string.IsNullOrEmpty(NetworkBlacklist.LastError) || NetworkBlacklist.Entries.Count != 0) ? ("(已同步 " + NetworkBlacklist.Entries.Count + " 条 · " + NetworkBlacklist.LastRefresh.ToString("HH:mm") + ")") : ("(同步失败:" + NetworkBlacklist.LastError + ")"))))); GUILayout.Label(text, Ui.Small, Array.Empty<GUILayoutOption>()); if (flag) { GUI.contentColor = new Color(0.5f, 1f, 0.6f); GUILayout.Label("【你是网络名单管理员】", Ui.Header, Array.Empty<GUILayoutOption>()); GUI.contentColor = Color.white; } GUILayout.FlexibleSpace(); if (GUILayout.Button("立即同步", Ui.Button, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(90f) })) { NetworkBlacklist.RefreshAsync(); _mgr.ShowToast("正在同步网络黑名单…"); } GUILayout.EndHorizontal(); if (!string.IsNullOrEmpty(NetworkBlacklist.LastResult) && NetworkBlacklist.LastResult != _lastToastResult) { _lastToastResult = NetworkBlacklist.LastResult; _mgr.ShowToast(NetworkBlacklist.LastResult); } if (!NetworkBlacklist.IsEnabled) { return; } GUILayout.Label("网络黑名单(只读同步;以 SteamID 为主、名称为辅)", Ui.Small, Array.Empty<GUILayoutOption>()); _netScroll = GUILayout.BeginScrollView(_netScroll, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(120f) }); if (NetworkBlacklist.Entries.Count == 0) { GUILayout.Label("网络名单为空。", Ui.Small, Array.Empty<GUILayoutOption>()); } else { foreach (NetEntry entry in NetworkBlacklist.Entries) { bool flag2 = NetworkBlacklist.IsPardoned(entry.id); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); GUILayout.Label(entry.id, Ui.Small, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(220f) }); GUILayout.Label(string.IsNullOrEmpty(entry.name) ? "(无名称)" : entry.name, Ui.Label, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(140f) }); if (flag2) { GUI.contentColor = new Color(1f, 0.85f, 0.4f); GUILayout.Label("限时赦免观察中", Ui.Small, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(120f) }); GUI.contentColor = Color.white; } GUILayout.Label(string.IsNullOrEmpty(entry.reason) ? "" : entry.reason, Ui.Small, Array.Empty<GUILayoutOption>()); GUILayout.EndHorizontal(); } } GUILayout.EndScrollView(); } private void DrawManualAdd() { GUILayout.Label("手动添加(ID 与名称至少填一个;优先使用 ID,名称仅在无 ID 时用于匹配)", Ui.Small, Array.Empty<GUILayoutOption>()); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); GUILayout.Label("名称:", Ui.Label, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(44f) }); _newName = GUILayout.TextField(_newName, Ui.TextField, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(120f) }); GUILayout.Label("SteamID/平台ID:", Ui.Label, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(124f) }); _newId = GUILayout.TextField(_newId, Ui.TextField, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(170f) }); GUILayout.Label("备注:", Ui.Label, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(40f) }); _newNote = GUILayout.TextField(_newNote, Ui.TextField, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(120f) }); GUI.enabled = _newName.Trim().Length > 0 || _newId.Trim().Length > 0; if (GUILayout.Button("添加", Ui.Button, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(70f) })) { string text = _newId.Trim(); if (text.Length > 0) { bool flag = true; string text2 = text; foreach (char c in text2) { if (c < '0' || c > '9') { flag = false; break; } } if (!flag || text.Length != 17) { _mgr.ShowToast("SteamID64 应为 17 位纯数字,请检查"); GUI.enabled = true; return; } } BlacklistEntry entry; bool flag2 = BlacklistStore.Add(_newId, _newName, _newNote, out entry); _mgr.ShowToast((flag2 ? "已添加黑名单: " : "已更新黑名单条目: ") + (string.IsNullOrEmpty(_newName.Trim()) ? _newId.Trim() : _newName.Trim())); _newName = ""; _newId = ""; _newNote = ""; } GUI.enabled = true; GUILayout.EndHorizontal(); } } internal static class FontHelper { private static Font _cjk; public static Font Cjk { get { if ((Object)(object)_cjk == (Object)null) { try { _cjk = Font.CreateDynamicFontFromOSFont(new string[5] { "Microsoft YaHei", "微软雅黑", "SimHei", "Arial Unicode MS", "Arial" }, 14); } catch { _cjk = (((Object)(object)GUI.skin != (Object)null) ? GUI.skin.font : null); } } return _cjk; } } } internal static class GameKick { public static void HostKick(Player target) { if (target == null || target.IsLocal || target.IsMasterClient) { return; } try { PlayerHandler.Kick(target.ActorNumber); } catch (Exception ex) { Plugin.Log.LogWarning((object)("PlayerHandler.Kick 失败: " + ex.Message)); } try { PhotonNetwork.CloseConnection(target); } catch (Exception ex2) { Plugin.Log.LogWarning((object)("PhotonNetwork.CloseConnection 失败: " + ex2.Message)); } } public static bool LocalMute(Player target) { try { Character playerCharacter = PlayerHandler.GetPlayerCharacter(target); if ((Object)(object)playerCharacter != (Object)null && (Object)(object)playerCharacter.data != (Object)null && !playerCharacter.data.isMuted) { playerCharacter.data.isMuted = true; return true; } } catch (Exception ex) { Plugin.Log.LogWarning((object)("本地静音失败: " + ex.Message)); } return false; } public static bool LocalHide(Player target) { try { Player player = PlayerHandler.GetPlayer(target); if ((Object)(object)player != (Object)null && (Object)(object)((Component)player).gameObject != (Object)null && ((Component)player).gameObject.activeSelf) { ((Component)player).gameObject.SetActive(false); return true; } } catch (Exception ex) { Plugin.Log.LogWarning((object)("本地隐藏失败: " + ex.Message)); } return false; } public static bool StillInRoom(int actor) { try { return PhotonNetwork.InRoom && PhotonNetwork.CurrentRoom != null && PhotonNetwork.CurrentRoom.GetPlayer(actor, false) != null; } catch { return false; } } } internal static class KickPatch { public static void Apply(Harmony harmony) { //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Expected O, but got Unknown try { Type type = AccessTools.TypeByName("PlayerHandler"); if (type == null) { Plugin.Log.LogWarning((object)"KickPatch: 未找到 PlayerHandler,自动拉黑不生效。"); return; } MethodInfo methodInfo = null; MethodInfo[] methods = type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo2 in methods) { if (!(methodInfo2.Name != "Kick")) { ParameterInfo[] parameters = methodInfo2.GetParameters(); if (parameters.Length == 1 && parameters[0].ParameterType == typeof(int)) { methodInfo = methodInfo2; break; } } } if (methodInfo == null) { Plugin.Log.LogWarning((object)"KickPatch: 未找到 PlayerHandler.Kick(int)。"); return; } harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(KickPatch), "AfterKick", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.LogInfo((object)"KickPatch 已安装:主机踢出玩家将自动加入本地黑名单。"); } catch (Exception ex) { Plugin.Log.LogWarning((object)("KickPatch 安装失败: " + ex)); } } private static void AfterKick(int actorNumber) { try { if (!BlacklistConfig.Enabled.Value || !BlacklistConfig.AutoBlacklistOnHostKick.Value || !PhotonNetwork.IsMasterClient || !PhotonNetwork.InRoom || PhotonNetwork.CurrentRoom == null) { return; } Player player = PhotonNetwork.CurrentRoom.GetPlayer(actorNumber, false); if (player == null || player.IsLocal) { return; } string text = player.NickName ?? ""; string text2 = player.UserId ?? ""; if ((!string.IsNullOrEmpty(text2) || !string.IsNullOrEmpty(text)) && !BlacklistStore.IsListed(text2, text, out var match)) { BlacklistStore.Add(text2, text, "主机踢出自动拉黑", out match); Plugin.Log.LogInfo((object)("[黑名单] 主机踢出 " + text + ",已自动加入本地黑名单。")); if ((Object)(object)BlacklistManager.Instance != (Object)null) { BlacklistManager.Instance.ShowToast("已自动把被踢出玩家 " + text + " 加入本地黑名单。"); } } } catch (Exception ex) { Plugin.Log.LogWarning((object)("KickPatch.AfterKick: " + ex.Message)); } } } public sealed class NetEntry { public string id = ""; public string name = ""; public string reason = ""; public string added = ""; } public sealed class Pardon { public string id = ""; public string until = ""; public int strikes; } internal static class NetworkBlacklist { private sealed class TimeoutWebClient : WebClient { public int TimeoutMs { get; set; } = 8000; protected override WebRequest GetWebRequest(Uri uri) { WebRequest webRequest = base.GetWebRequest(uri); webRequest.Timeout = TimeoutMs; return webRequest; } } public static readonly List<NetEntry> Entries = new List<NetEntry>(); public static readonly List<Pardon> Pardons = new List<Pardon>(); public static readonly HashSet<string> Admins = new HashSet<string>(StringComparer.OrdinalIgnoreCase); public static DateTime LastRefresh = DateTime.MinValue; public static string LastError = ""; public static bool Ready; public static bool Offline; public static string LastResult = ""; private static readonly object Gate = new object(); private static int _busy; private static string CachePath => Path.Combine(Paths.ConfigPath, "summitwatch-netcache.json"); public static bool IsEnabled { get { if (BlacklistConfig.EnableNetwork.Value) { return Ready; } return false; } } public static void PreloadCache() { try { if (File.Exists(CachePath)) { string text = File.ReadAllText(CachePath, Encoding.UTF8); if (!string.IsNullOrEmpty(text)) { Parse(text); Offline = true; Ready = true; Plugin.Log.LogInfo((object)("已加载网络名单本地缓存: 封禁 " + Entries.Count + " 条。")); } } } catch (Exception ex) { Plugin.Log.LogWarning((object)("预读网络名单缓存失败: " + ex.Message)); } } public static void RefreshAsync() { if (!BlacklistConfig.EnableNetwork.Value || Interlocked.Exchange(ref _busy, 1) == 1) { return; } string primary = BlacklistConfig.NetworkUrl.Value; ThreadPool.QueueUserWorkItem(delegate { string text = null; string text2 = ""; string[] array = new string[2] { "https://api.summitwatchcn.icu/blacklist", primary }; foreach (string text3 in array) { if (!string.IsNullOrWhiteSpace(text3)) { try { using TimeoutWebClient timeoutWebClient = new TimeoutWebClient(); timeoutWebClient.Encoding = Encoding.UTF8; string text4 = timeoutWebClient.DownloadString(text3); if (!string.IsNullOrEmpty(text4) && text4.Contains("\"id\"")) { text = text4; text2 = text3; break; } } catch (Exception ex) { LastError = ex.Message; Plugin.Log.LogWarning((object)("网络黑名单下载失败 [" + text3 + "]: " + ex.Message)); } } } try { if (!string.IsNullOrEmpty(text)) { Parse(text); if (Entries.Count > 0 || Pardons.Count > 0) { try { File.WriteAllText(CachePath, text, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); } catch (Exception ex2) { Plugin.Log.LogWarning((object)("写入网络名单缓存失败: " + ex2.Message)); } } Offline = false; LastResult = "网络黑名单同步成功:封禁 " + Entries.Count + " 条,赦免观察 " + Pardons.Count + " 条(来源 " + text2 + ")"; Plugin.Log.LogInfo((object)("网络黑名单来源: " + text2)); } else { string text5 = null; try { if (File.Exists(CachePath)) { text5 = File.ReadAllText(CachePath, Encoding.UTF8); } } catch (Exception ex3) { Plugin.Log.LogWarning((object)("读取网络名单缓存失败: " + ex3.Message)); } if (!string.IsNullOrEmpty(text5)) { Parse(text5); Offline = true; LastResult = "网络同步失败,已使用本地缓存(封禁 " + Entries.Count + " 条)"; Plugin.Log.LogWarning((object)"网络不可用,已使用本地缓存的网络黑名单。"); } else { LastResult = "网络同步失败且无本地缓存:" + (LastError ?? "未知错误"); Plugin.Log.LogWarning((object)"网络不可用且无缓存,本次未更新网络黑名单。"); } } LastRefresh = DateTime.Now; Ready = true; } finally { Interlocked.Exchange(ref _busy, 0); } }); } public static bool IsNetworkBanned(string steamId) { if (!IsEnabled || string.IsNullOrEmpty(steamId)) { return false; } lock (Gate) { if (IsPardonedLocked(steamId)) { return false; } foreach (NetEntry entry in Entries) { if (string.Equals(entry.id.Trim(), steamId.Trim(), StringComparison.OrdinalIgnoreCase)) { return true; } } } return false; } public static bool IsPardoned(string steamId) { if (!IsEnabled || string.IsNullOrEmpty(steamId)) { return false; } lock (Gate) { return IsPardonedLocked(steamId); } } private static bool IsPardonedLocked(string steamId) { foreach (Pardon pardon in Pardons) { if (string.Equals(pardon.id.Trim(), steamId.Trim(), StringComparison.OrdinalIgnoreCase) && DateTime.TryParse(pardon.until, out var result) && DateTime.Now < result) { return true; } } return false; } public static bool IsLocalAdmin(string steamId) { if (string.IsNullOrEmpty(steamId)) { return false; } lock (Gate) { return Admins.Contains(steamId.Trim()); } } private static void Parse(string text) { List<NetEntry> entries = new List<NetEntry>(); List<Pardon> pardons = new List<Pardon>(); HashSet<string> admins = new HashSet<string>(StringComparer.OrdinalIgnoreCase); ParseArrayValues(text, "admins", delegate(string s) { admins.Add(s); }); ParseArrayObjects(text, "entries", delegate(string obj) { entries.Add(new NetEntry { id = ReadStr(obj, "id"), name = ReadStr(obj, "name"), reason = ReadStr(obj, "reason"), added = ReadStr(obj, "added") }); }); ParseArrayObjects(text, "pardons", delegate(string obj) { pardons.Add(new Pardon { id = ReadStr(obj, "id"), until = ReadStr(obj, "until"), strikes = ReadInt(obj, "strikes") }); }); lock (Gate) { Entries.Clear(); Entries.AddRange(entries); Pardons.Clear(); Pardons.AddRange(pardons); Admins.Clear(); foreach (string item in admins) { Admins.Add(item); } } Plugin.Log.LogInfo((object)$"网络黑名单已同步:封禁 {entries.Count} 条,赦免观察 {pardons.Count} 条,管理员 {admins.Count} 人。"); } private static string ExtractArray(string text, string key, out int length) { length = 0; int num = text.IndexOf("\"" + key + "\"", StringComparison.Ordinal); if (num < 0) { return null; } int num2 = text.IndexOf('[', num); if (num2 < 0) { return null; } int num3 = 0; int num4 = -1; for (int i = num2; i < text.Length; i++) { switch (text[i]) { case '[': num3++; continue; case ']': num3--; if (num3 != 0) { continue; } break; default: continue; } num4 = i; break; } if (num4 < 0) { return null; } return text.Substring(num2 + 1, num4 - num2 - 1); } private static void ParseArrayValues(string text, string key, Action<string> onValue) { int length; string text2 = ExtractArray(text, key, out length); if (text2 == null) { return; } int startIndex = 0; while (true) { int num = text2.IndexOf('"', startIndex); if (num >= 0) { int num2 = text2.IndexOf('"', num + 1); if (num2 >= 0) { onValue(text2.Substring(num + 1, num2 - num - 1)); startIndex = num2 + 1; continue; } break; } break; } } private static void ParseArrayObjects(string text, string key, Action<string> onObject) { int length; string text2 = ExtractArray(text, key, out length); if (text2 == null) { return; } int startIndex = 0; while (true) { int num = text2.IndexOf('{', startIndex); if (num < 0) { break; } int num2 = 0; int num3 = -1; for (int i = num; i < text2.Length; i++) { switch (text2[i]) { case '{': num2++; continue; case '}': num2--; if (num2 != 0) { continue; } break; default: continue; } num3 = i; break; } if (num3 >= 0) { onObject(text2.Substring(num + 1, num3 - num - 1)); startIndex = num3 + 1; continue; } break; } } private static string ReadStr(string obj, string key) { int num = obj.IndexOf("\"" + key + "\"", StringComparison.Ordinal); if (num < 0) { return ""; } for (num = obj.IndexOf(':', num) + 1; num < obj.Length && (obj[num] == ' ' || obj[num] == '\t'); num++) { } if (num >= obj.Length || obj[num] != '"') { return ""; } num++; StringBuilder stringBuilder = new StringBuilder(); while (num < obj.Length) { char c = obj[num]; if (c == '\\' && num + 1 < obj.Length) { char c2 = obj[num + 1]; switch (c2) { case 'n': stringBuilder.Append('\n'); break; case '"': stringBuilder.Append('"'); break; case '\\': stringBuilder.Append('\\'); break; default: stringBuilder.Append(c2); break; } num += 2; } else { if (c == '"') { break; } stringBuilder.Append(c); num++; } } return stringBuilder.ToString(); } private static int ReadInt(string obj, string key) { int num = obj.IndexOf("\"" + key + "\"", StringComparison.Ordinal); if (num < 0) { return 0; } num = obj.IndexOf(':', num) + 1; int i; for (i = num; i < obj.Length && (obj[i] == '-' || char.IsDigit(obj[i])); i++) { } int.TryParse(obj.Substring(num, i - num), out var result); return result; } } public sealed class LobbyPlayer { public int Actor; public string Name; public string UserId; public bool IsHost; public bool IsLocal; public object Record; public bool HasRecord; } internal static class SwAccess { private static Type _watchType; private static PropertyInfo _instanceProp; private static FieldInfo _recordsField; private static FieldInfo _profileField; private static PropertyInfo _hostHasModProp; private static PropertyInfo _myRoleProp; private static PropertyInfo _panelOpenProp; private static PropertyInfo _modeProp; private static MethodInfo _kickMethod; private static MethodInfo _requestKickMethod; private static MethodInfo _profileOpen; private static MethodInfo _profileClose; private static MethodInfo _evidenceLine; private static Type _stylesType; private static FieldInfo[] _stylesFields; private static Type _recordType; private static FieldInfo _recordActor; private static FieldInfo _recordName; private static FieldInfo _recordUserId; private static bool _initialized; public static object Instance { get { if (!Init() || _instanceProp == null) { return null; } try { return _instanceProp.GetValue(null); } catch { return null; } } } public static bool PanelOpen { get { if (!Init() || _panelOpenProp == null) { return false; } try { return (bool)_panelOpenProp.GetValue(null); } catch { return false; } } } public static bool HostHasMod { get { object instance = Instance; if (instance == null || _hostHasModProp == null) { return false; } try { return (bool)_hostHasModProp.GetValue(instance); } catch { return false; } } } public static object MyRole { get { object instance = Instance; if (instance == null || _myRoleProp == null) { return null; } try { return _myRoleProp.GetValue(instance); } catch { return null; } } } public static bool Init() { if (_initialized) { return _watchType != null; } _initialized = true; try { _watchType = AccessTools.TypeByName("SummitWatch.Watch"); if (_watchType == null) { return false; } _instanceProp = AccessTools.Property(_watchType, "Instance"); _recordsField = AccessTools.Field(_watchType, "Records"); _profileField = AccessTools.Field(_watchType, "Profile"); _hostHasModProp = AccessTools.Property(_watchType, "HostHasMod"); _myRoleProp = AccessTools.Property(_watchType, "MyRole"); _panelOpenProp = AccessTools.Property(_watchType, "PanelOpen"); _modeProp = AccessTools.Property(_watchType, "Mode"); _kickMethod = AccessTools.Method(_watchType, "Kick", (Type[])null, (Type[])null); _requestKickMethod = AccessTools.Method(_watchType, "RequestKick", (Type[])null, (Type[])null); _recordType = AccessTools.TypeByName("SummitWatch.PlayerRecord"); if (_recordType != null) { _recordActor = AccessTools.Field(_recordType, "Actor"); _recordName = AccessTools.Field(_recordType, "Name"); _recordUserId = AccessTools.Field(_recordType, "UserId"); } Type type = AccessTools.TypeByName("SummitWatch.ProfileWindow"); if (type != null) { _profileOpen = AccessTools.Method(type, "Open", (Type[])null, (Type[])null); _profileClose = AccessTools.Method(type, "Close", (Type[])null, (Type[])null); } Type type2 = AccessTools.TypeByName("SummitWatch.EvidenceLog"); if (type2 != null) { _evidenceLine = AccessTools.Method(type2, "Line", (Type[])null, (Type[])null); } return true; } catch (Exception ex) { Plugin.Log.LogWarning((object)("初始化 SummitWatch 反射访问失败: " + ex)); return false; } } public static void SetPanelOpen(bool open) { if (!Init()) { return; } try { if (_modeProp != null) { MethodInfo setMethod = _modeProp.GetSetMethod(nonPublic: true); if (setMethod != null) { object obj = Enum.Parse(_modeProp.PropertyType, open ? "Full" : "Off"); setMethod.Invoke(null, new object[1] { obj }); return; } } if (_panelOpenProp != null) { MethodInfo setMethod2 = _panelOpenProp.GetSetMethod(nonPublic: true); if (setMethod2 != null) { setMethod2.Invoke(null, new object[1] { open }); } } } catch { } } public static void ApplySummitWatchFonts(Font font) { if ((Object)(object)font == (Object)null || !Init()) { return; } try { if (_stylesType == null) { _stylesType = AccessTools.TypeByName("SummitWatch.Styles"); if (_stylesType != null) { List<FieldInfo> list = new List<FieldInfo>(); FieldInfo[] fields = _stylesType.GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { if (fieldInfo.FieldType == typeof(GUIStyle)) { list.Add(fieldInfo); } } _stylesFields = list.ToArray(); } } if (_stylesFields != null) { FieldInfo[] fields = _stylesFields; for (int i = 0; i < fields.Length; i++) { object? value = fields[i].GetValue(null); GUIStyle val = (GUIStyle)((value is GUIStyle) ? value : null); if (val != null && (Object)(object)val.font != (Object)(object)font) { val.font = font; } } } FixMiniOverlayStyles(); } catch { } } private static void FixMiniOverlayStyles() { try { Type type = AccessTools.TypeByName("SummitWatch.Watch"); MethodInfo methodInfo = ((type != null) ? AccessTools.PropertyGetter(type, "Instance") : null); object obj = ((methodInfo != null) ? methodInfo.Invoke(null, null) : null); FieldInfo fieldInfo = ((type != null) ? AccessTools.Field(type, "_mini") : null); object obj2 = ((obj != null && fieldInfo != null) ? fieldInfo.GetValue(obj) : null); if (obj2 == null) { return; } string[] array = new string[2] { "_name", "_value" }; foreach (string text in array) { FieldInfo fieldInfo2 = AccessTools.Field(obj2.GetType(), text); GUIStyle val = (GUIStyle)((fieldInfo2 != null) ? /*isinst with value type is only supported in some contexts*/: null); if (val != null) { val.clipping = (TextClipping)0; if (text == "_name" && val.fontSize > 13) { val.fontSize = 13; } } } } catch { } } public static void EvidenceLine(string text) { if (!Init() || _evidenceLine == null) { return; } try { _evidenceLine.Invoke(null, new object[1] { text }); } catch { } } public static string SanitizeName(string name) { if (string.IsNullOrEmpty(name)) { return "Unknown"; } return name; } private static Dictionary<int, object> RecordMap() { object instance = Instance; Dictionary<int, object> dictionary = new Dictionary<int, object>(); if (instance == null || _recordsField == null) { return dictionary; } try { if (_recordsField.GetValue(instance) is IDictionary dictionary2) { foreach (DictionaryEntry item in dictionary2) { if (item.Key is int key && item.Value != null) { dictionary[key] = item.Value; } } } } catch (Exception ex) { Plugin.Log.LogWarning((object)("读取 SummitWatch.Records 失败: " + ex)); } return dictionary; } private static void FillRecord(object record, LobbyPlayer p) { if (record == null || _recordType == null) { return; } try { if (_recordActor != null) { p.Actor = (int)_recordActor.GetValue(record); } if (_recordName != null) { string text = _recordName.GetValue(record) as string; if (!string.IsNullOrEmpty(text)) { p.Name = text; } } if (_recordUserId != null) { string text2 = _recordUserId.GetValue(record) as string; if (!string.IsNullOrEmpty(text2)) { p.UserId = text2; } } p.Record = record; p.HasRecord = true; } catch { } } public static List<LobbyPlayer> SnapshotRecords() { List<LobbyPlayer> list = new List<LobbyPlayer>(); if (!Init()) { return list; } foreach (KeyValuePair<int, object> item in RecordMap()) { LobbyPlayer lobbyPlayer = new LobbyPlayer { Actor = item.Key, Name = "", UserId = "" }; FillRecord(item.Value, lobbyPlayer); list.Add(lobbyPlayer); } return list; } public static object RecordOf(int actor) { if (!Init()) { return null; } if (!RecordMap().TryGetValue(actor, out var value)) { return null; } return value; } public static void OpenProfile(int actor) { object instance = Instance; if (instance == null || _profileField == null || _profileOpen == null) { return; } try { object value = _profileField.GetValue(instance); _profileOpen.Invoke(value, new object[1] { actor }); } catch { } } public static void InvokeKick(int actor) { object instance = Instance; object obj = RecordOf(actor); if (instance == null || obj == null || _kickMethod == null) { return; } try { _kickMethod.Invoke(instance, new object[1] { obj }); } catch (Exception ex) { Plugin.Log.LogWarning((object)("调用 SummitWatch.Kick 失败: " + ex)); } } public static void InvokeRequestKick(int actor) { object instance = Instance; object obj = RecordOf(actor); if (instance == null || obj == null || _requestKickMethod == null) { return; } try { _requestKickMethod.Invoke(instance, new object[1] { obj }); } catch (Exception ex) { Plugin.Log.LogWarning((object)("调用 SummitWatch.RequestKick 失败: " + ex)); } } } internal static class Ui { public static GUIStyle Window; public static GUIStyle Label; public static GUIStyle Small; public static GUIStyle Header; public static GUIStyle Button; public static GUIStyle Toggle; public static GUIStyle TextField; public static GUIStyle Toast; private static Texture2D _white; public static Texture2D White { get { //IL_000d: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_white == (Object)null) { _white = Tex(Color.white); } return _white; } } public static void Ensure() { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references