Decompiled source of Moderation Improvements v1.0.1
BepInEx/plugins/ModerationImprovements.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.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Text; using System.Text.Json; using System.Text.RegularExpressions; using System.Threading; using BepInEx; using BepInEx.Configuration; using BepInEx.Core.Logging.Interpolation; using BepInEx.Logging; using BepInEx.Unity.IL2CPP; using Epic.OnlineServices; using Epic.OnlineServices.Connect; using HarmonyLib; using Il2CppInterop.Runtime.Injection; using Il2CppInterop.Runtime.InteropTypes; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppSystem.Collections.Generic; using Microsoft.CodeAnalysis; using Mirror; using Mirror.Authenticators; using ModSettingsMenu.Api; using PlayEveryWare.EpicOnlineServices; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("AdamMady")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("ModerationImprovements")] [assembly: AssemblyTitle("ModerationImprovements")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace BigOrb { internal static class Chime { private static bool _broken; private static AudioSource _source; private static AudioClip _join; private static AudioClip _leave; private static readonly Queue<bool> _pending = new Queue<bool>(); private static float _nextPlay; private const int Rate = 44100; private const float NoteLen = 0.32f; private const float NoteFade = 0.03f; private const float NoteGap = 0.115f; private const float Spacing = 0.45f; private const int MaxQueued = 3; internal static bool Enabled { get { if (!_broken) { return Plugin.ChimeEnabled.Value; } return false; } } internal static void SetEnabled(bool on) { Plugin.ChimeEnabled.Value = on; if (on) { _broken = false; } } internal static void Play(bool join) { if (Enabled && _pending.Count < 3) { _pending.Enqueue(join); } } internal static void Step(GameObject holder) { if (_pending.Count == 0 || Time.unscaledTime < _nextPlay) { return; } _nextPlay = Time.unscaledTime + 0.45f; bool flag = _pending.Dequeue(); try { if (Ensure(holder)) { AudioClip val = (flag ? _join : _leave); if ((Object)(object)val != (Object)null) { _source.PlayOneShot(val, Mathf.Clamp01(Plugin.ChimeVolume.Value)); } } } catch (Exception ex) { Plugin.Logger.LogError((object)("chime: " + ex.Message)); _pending.Clear(); _broken = true; } } private static bool Ensure(GameObject holder) { if ((Object)(object)_source != (Object)null && (Object)(object)_join != (Object)null && (Object)(object)_leave != (Object)null) { return true; } if ((Object)(object)holder == (Object)null) { return false; } if ((Object)(object)_source == (Object)null) { _source = holder.GetComponent<AudioSource>(); if ((Object)(object)_source == (Object)null) { _source = holder.AddComponent<AudioSource>(); } _source.playOnAwake = false; _source.spatialBlend = 0f; _source.bypassEffects = true; _source.bypassListenerEffects = true; _source.bypassReverbZones = true; _source.ignoreListenerPause = true; _source.ignoreListenerVolume = true; } if (_join == null) { _join = Build("orbJoin", 659.25f, 880f); } if (_leave == null) { _leave = Build("orbLeave", 880f, 587.33f); } if ((Object)(object)_source != (Object)null && (Object)(object)_join != (Object)null) { return (Object)(object)_leave != (Object)null; } return false; } private static AudioClip Build(string name, float f1, float f2) { int num = 19183; Il2CppStructArray<float> val = new Il2CppStructArray<float>((long)num); for (int i = 0; i < num; i++) { float num2 = (float)i / 44100f; ((Il2CppArrayBase<float>)(object)val)[i] = Mathf.Clamp(0.5f * (Note(num2, f1) + Note(num2 - 0.115f, f2)), -1f, 1f); } AudioClip obj = AudioClip.Create(name, num, 1, 44100, false); obj.SetData(val, 0); return obj; } private static float Note(float t, float freq) { if (t < 0f || t > 0.32f) { return 0f; } float num = Mathf.Min(1f, t / 0.006f) * Mathf.Exp((0f - t) * 14f); if (t > 0.29f) { num *= (0.32f - t) / 0.03f; } return num * (Mathf.Sin((float)Math.PI * 2f * freq * t) + 0.35f * Mathf.Sin((float)Math.PI * 4f * freq * t)); } } internal static class Guard { [HarmonyPatch(typeof(HouseAuthenticator), "OnInitialAuthRequestMessage")] internal static class AuthPatch { private static void Postfix(NetworkConnectionToClient __0, InitialialAuthRequestMessage __1) { try { Patches.MarkFired("auth"); if (__0 == null) { return; } string text = null; string text2 = null; string text3 = null; try { text = __0.address; } catch { } try { text2 = ((__1 != null) ? __1.playerIdentifier : null); } catch { } try { text3 = ((__1 != null) ? __1.versionNumber : null); } catch { } OrbState.AuthSeen(((NetworkConnection)__0).connectionId, text, text2, text3); string name = OrbState.RosterName(text2) ?? text2 ?? "?"; OrbState.AddEvent("auth", text2, name, $"conn {((NetworkConnection)__0).connectionId} addr {text ?? "?"} claims {text2 ?? "?"} v{text3 ?? "?"}"); if (OrbState.IsBannedAddress(text)) { OrbState.AddEvent("autokick", text2, name, "banned address " + text + " tried to join as " + text2); ((NetworkConnection)__0).Disconnect(); } else if (OrbState.IsBanned(text2)) { OrbState.AddEvent("autokick", text2, name, "banned identifier tried to join from " + (text ?? "?")); OrbState.BanAttachAddress(text2, text); ((NetworkConnection)__0).Disconnect(); } else if (!OrbState.LobbyAllows(text2, text)) { OrbState.AddEvent("lockreject", text2, name, "lobby locked; connection " + (text ?? "?") + " was not present when locked"); ((NetworkConnection)__0).Disconnect(); } else { if (string.IsNullOrEmpty(text) || string.IsNullOrEmpty(text2) || text == "localhost") { return; } if (IsSteam(text) && IsSteam(text2) && text != text2) { Act("idspoof", text2, name, "claims SteamID " + text2 + " but the connection is " + text, text, __0); return; } string item = Base(text2); lock (IdsByAddress) { if (!IdsByAddress.TryGetValue(text, out var value)) { value = (IdsByAddress[text] = new HashSet<string>()); } value.Add(item); if (value.Count >= 2) { string detail = $"address {text} has claimed {value.Count} identifiers: {string.Join(", ", value)}"; if (value.Count >= 3) { Act("idrotate", text2, name, detail, text, __0); return; } OrbState.AddAlert("idrotate", text2, name, detail); } } Eos.Lookup(text, text2); } } catch (Exception ex) { Plugin.Logger.LogError((object)("auth gate: " + ex.Message)); } } } internal static class Eos { private struct QueryOptions { public int ApiVersion; public IntPtr LocalUserId; public IntPtr AccountIdTypeDeprecated; public IntPtr ProductUserIds; public uint Count; } private struct QueryInfo { public int ResultCode; public IntPtr ClientData; public IntPtr LocalUserId; } private struct CountOptions { public int ApiVersion; public IntPtr TargetUserId; } private struct CopyOptions { public int ApiVersion; public IntPtr TargetUserId; public uint Index; } private struct AccountInfo { public int ApiVersion; public IntPtr ProductUserId; public IntPtr DisplayName; public IntPtr AccountId; public int AccountIdType; public long LastLoginTime; } [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void OnQuery(IntPtr info); internal class Mapping { public string Type; public string AccountId; public string DisplayName; public string When; public List<string> All = new List<string>(); } private const string Dll = "EOSSDK-Win64-Shipping"; private static OnQuery _cb; private static readonly string[] Types = new string[15] { "epic", "steam", "psn", "xbl", "discord", "gog", "nintendo", "uplay", "openid", "apple", "google", "oculus", "itchio", "amazon", "viveport" }; internal static readonly Dictionary<string, Mapping> Map = new Dictionary<string, Mapping>(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary<string, string> ClaimedBy = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); private static readonly HashSet<string> Pending = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary<long, string> Inflight = new Dictionary<long, string>(); private static long _seq; private static string File => Path.Combine(OrbState.DataDir, "eosmap.tsv"); [DllImport("EOSSDK-Win64-Shipping", CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr EOS_ProductUserId_FromString(string s); [DllImport("EOSSDK-Win64-Shipping", CallingConvention = CallingConvention.Cdecl)] private static extern void EOS_Connect_QueryProductUserIdMappings(IntPtr h, ref QueryOptions o, IntPtr clientData, OnQuery cb); [DllImport("EOSSDK-Win64-Shipping", CallingConvention = CallingConvention.Cdecl)] private static extern uint EOS_Connect_GetProductUserExternalAccountCount(IntPtr h, ref CountOptions o); [DllImport("EOSSDK-Win64-Shipping", CallingConvention = CallingConvention.Cdecl)] private static extern int EOS_Connect_CopyProductUserExternalAccountByIndex(IntPtr h, ref CopyOptions o, out IntPtr info); [DllImport("EOSSDK-Win64-Shipping", CallingConvention = CallingConvention.Cdecl)] private static extern void EOS_Connect_ExternalAccountInfo_Release(IntPtr info); internal static void Load() { try { if (!System.IO.File.Exists(File)) { return; } string[] array = System.IO.File.ReadAllLines(File); for (int i = 0; i < array.Length; i++) { string[] array2 = array[i].Split('\t'); if (array2.Length >= 5) { Mapping mapping = new Mapping { Type = array2[1], AccountId = array2[2], DisplayName = array2[3], When = array2[4] }; if (array2.Length > 5) { mapping.All.AddRange(array2[5].Split('|')); } Map[array2[0]] = mapping; } } } catch { } } internal static void Lookup(string addr, string claimed) { if (!IsEosAddr(addr)) { return; } ClaimedBy[addr] = claimed; if (Map.TryGetValue(addr, out var value)) { Compare(addr, value, claimed); } else { if (Pending.Contains(addr)) { return; } try { EOSSingleton instance = EOSManager.Instance; ConnectInterface val = ((instance != null) ? instance.GetEOSConnectInterface() : null); ProductUserId val2 = ((instance != null) ? instance.GetProductUserId() : null); if ((Handle)(object)val == (Handle)null || (Handle)(object)val2 == (Handle)null) { return; } IntPtr intPtr = EOS_ProductUserId_FromString(addr); if (!(intPtr == IntPtr.Zero)) { IntPtr intPtr2 = Marshal.AllocHGlobal(IntPtr.Size); Marshal.WriteIntPtr(intPtr2, intPtr); long num = ++_seq; lock (Inflight) { Inflight[num] = addr; } if (_cb == null) { _cb = OnQueried; } QueryOptions o = new QueryOptions { ApiVersion = 2, LocalUserId = ((Handle)val2).InnerHandle, ProductUserIds = intPtr2, Count = 1u }; Pending.Add(addr); EOS_Connect_QueryProductUserIdMappings(((Handle)val).InnerHandle, ref o, (IntPtr)num, _cb); } } catch (Exception ex) { Plugin.Logger.LogError((object)("eos lookup: " + ex.Message)); Pending.Remove(addr); } } } private static void OnQueried(IntPtr infoPtr) { try { QueryInfo queryInfo = Marshal.PtrToStructure<QueryInfo>(infoPtr); string value; lock (Inflight) { Inflight.TryGetValue((long)queryInfo.ClientData, out value); Inflight.Remove((long)queryInfo.ClientData); } if (value != null) { Pending.Remove(value); if (queryInfo.ResultCode != 0) { OrbState.AddEvent("eos", null, "host", $"lookup {value} → result {queryInfo.ResultCode}"); } else { Read(value); } } } catch (Exception ex) { Plugin.Logger.LogError((object)("eos callback: " + ex.Message)); } } private static void Read(string addr) { EOSSingleton instance = EOSManager.Instance; ConnectInterface val = ((instance != null) ? instance.GetEOSConnectInterface() : null); if ((Handle)(object)val == (Handle)null) { return; } IntPtr targetUserId = EOS_ProductUserId_FromString(addr); CountOptions o = new CountOptions { ApiVersion = 1, TargetUserId = targetUserId }; uint num = EOS_Connect_GetProductUserExternalAccountCount(((Handle)val).InnerHandle, ref o); Mapping mapping = new Mapping { When = OrbState.Now() }; for (uint num2 = 0u; num2 < num; num2++) { CopyOptions o2 = new CopyOptions { ApiVersion = 1, TargetUserId = targetUserId, Index = num2 }; if (EOS_Connect_CopyProductUserExternalAccountByIndex(((Handle)val).InnerHandle, ref o2, out var info) != 0 || info == IntPtr.Zero) { continue; } try { AccountInfo accountInfo = Marshal.PtrToStructure<AccountInfo>(info); string text = ((accountInfo.AccountIdType >= 0 && accountInfo.AccountIdType < Types.Length) ? Types[accountInfo.AccountIdType] : ("type" + accountInfo.AccountIdType)); string text2 = ((accountInfo.AccountId != IntPtr.Zero) ? Marshal.PtrToStringUTF8(accountInfo.AccountId) : ""); string text3 = ((accountInfo.DisplayName != IntPtr.Zero) ? Marshal.PtrToStringUTF8(accountInfo.DisplayName) : ""); mapping.All.Add($"{text}:{text2}:{text3}"); if (mapping.Type == null || (mapping.Type == "epic" && text != "epic")) { mapping.Type = text; mapping.AccountId = text2; mapping.DisplayName = text3; } } finally { EOS_Connect_ExternalAccountInfo_Release(info); } } ClaimedBy.TryGetValue(addr, out var value); if (num == 0) { mapping.Type = "none"; } Map[addr] = mapping; Save(addr, mapping); OrbState.AddEvent("eos", value, OrbState.RosterName(value) ?? value ?? "?", (num == 0) ? (addr + " has no linked account") : $"{addr} → {mapping.Type} {mapping.AccountId} \"{mapping.DisplayName}\""); if (num == 0) { string name = OrbState.RosterName(value) ?? value ?? "?"; string text4 = addr + " has no external account on Epic: anonymous device-id login, not a real Steam/PSN/Xbox client (claimed " + (value ?? "?") + ")"; OrbState.AddAlert("eosanon", value, name, text4); if (Plugin.GuardBanAnonymous.Value && AutoBan) { BanAddress(addr, name, "eosanon: " + text4, value); } } else { Compare(addr, mapping, value); } } private static void Compare(string addr, Mapping m, string claimed) { if (string.IsNullOrEmpty(claimed) || m == null || m.Type == "none") { return; } string text = Base(claimed); bool flag = false; bool flag2 = false; foreach (string item in m.All) { string[] array = item.Split(':'); if (array.Length > 1 && array[1].Length > 0) { flag = true; if (array[1] == text) { flag2 = true; } } } if (!(!flag || flag2)) { string name = OrbState.RosterName(claimed) ?? claimed; string text2 = $"Epic says {addr} is {m.Type} account {m.AccountId} (\"{m.DisplayName}\") but the client claimed {claimed}"; OrbState.AddAlert("eosspoof", claimed, name, text2); if (AutoBan) { BanAddress(addr, name, "eosspoof: " + text2, claimed); } } } private static void Save(string addr, Mapping m) { try { List<string> list = new List<string>(); if (System.IO.File.Exists(File)) { list.AddRange(System.IO.File.ReadAllLines(File)); } list.RemoveAll((string l) => l.StartsWith(addr + "\t", StringComparison.OrdinalIgnoreCase)); list.Add($"{addr}\t{m.Type}\t{m.AccountId}\t{(m.DisplayName ?? "").Replace('\t', ' ')}\t{m.When}\t{string.Join("|", m.All).Replace('\t', ' ')}"); System.IO.File.WriteAllLines(File, list); } catch { } } internal static string Json() { StringBuilder stringBuilder = new StringBuilder("{"); foreach (KeyValuePair<string, Mapping> item in Map) { if (stringBuilder.Length > 1) { stringBuilder.Append(','); } stringBuilder.Append(OrbState.J(item.Key)).Append(":{\"type\":").Append(OrbState.J(item.Value.Type)) .Append(",\"accountId\":") .Append(OrbState.J(item.Value.AccountId)) .Append(",\"displayName\":") .Append(OrbState.J(item.Value.DisplayName)) .Append(",\"when\":") .Append(OrbState.J(item.Value.When)) .Append('}'); } return stringBuilder.Append('}').ToString(); } } internal static class Voice { private sealed class Bucket { public int Count; public int LastRate; public int Dropped; public float Window; public float OverSince = -1f; public float LastAlert = -999f; } private static readonly Dictionary<int, Bucket> B = new Dictionary<int, Bucket>(); private const float BanAfter = 5f; internal static void Patch(Harmony harmony) { //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Expected O, but got Unknown MethodInfo methodInfo = null; try { Type[] types = typeof(PlayerNetworking).Assembly.GetTypes(); foreach (Type type in types) { if (type.Name == "MirrorIgnoranceServer") { methodInfo = type.GetMethod("OnMessageReceived", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); break; } } } catch (Exception ex) { Plugin.Logger.LogWarning((object)("voice relay type scan: " + ex.Message)); } if (methodInfo == null) { Plugin.Logger.LogWarning((object)"voice relay hook not found, voice limiter off"); return; } try { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(Voice).GetMethod("Prefix", BindingFlags.Static | BindingFlags.Public)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } catch (Exception ex2) { Plugin.Logger.LogWarning((object)("voice relay patch: " + ex2.Message)); } } public static bool Prefix(object __0) { int value = Plugin.GuardVoiceLimit.Value; if (value <= 0) { return true; } try { NetworkConnection val = (NetworkConnection)((__0 is NetworkConnection) ? __0 : null); if (val == null) { return true; } int connectionId = val.connectionId; if (connectionId == 0) { return true; } float unscaledTime = Time.unscaledTime; lock (B) { if (!B.TryGetValue(connectionId, out var value2)) { Dictionary<int, Bucket> b = B; Bucket obj = new Bucket { Window = unscaledTime }; value2 = obj; b[connectionId] = obj; } if (unscaledTime - value2.Window >= 1f) { value2.LastRate = value2.Count; value2.Count = 0; value2.Window = unscaledTime; } value2.Count++; if (value2.Count > value) { value2.Dropped++; return false; } } } catch { } return true; } internal static void Tick() { int value = Plugin.GuardVoiceLimit.Value; if (value <= 0) { return; } float unscaledTime = Time.unscaledTime; List<(int, Bucket)> list = null; lock (B) { foreach (KeyValuePair<int, Bucket> item in B) { Bucket value2 = item.Value; if (value2.Count > value || (unscaledTime - value2.Window < 1f && value2.LastRate > value)) { if (value2.OverSince < 0f) { value2.OverSince = unscaledTime; } (list ?? (list = new List<(int, Bucket)>())).Add((item.Key, value2)); } else { value2.OverSince = -1f; } } foreach (int item2 in B.Keys.Where((int c) => !NetworkServer.connections.ContainsKey(c)).ToList()) { B.Remove(item2); } } if (list == null) { return; } NetworkConnectionToClient val = default(NetworkConnectionToClient); foreach (var (num, bucket) in list) { if (!NetworkServer.connections.TryGetValue(num, ref val) || val == null) { continue; } string text = OrbState.AuthFor(num)?.ClaimedId; string name = OrbState.RosterName(text) ?? text ?? $"conn {num}"; if (unscaledTime - bucket.LastAlert > 30f) { bucket.LastAlert = unscaledTime; OrbState.AddAlert("voiceflood", text, name, $"{Math.Max(bucket.Count, bucket.LastRate)} voice packets/s (limit {value}), {bucket.Dropped} dropped"); } if (unscaledTime - bucket.OverSince >= 5f) { string address = null; try { address = val.address; } catch { } if (AutoBan) { BanAddress(address, name, $"voiceflood: {5f:0}s sustained over {value} packets/s", text); } try { ((NetworkConnection)val).Disconnect(); } catch { } bucket.OverSince = -1f; } } } } [HarmonyPatch(typeof(PlayerNetworking), "InvokeUserCode_CmdSendTextChatMessage__String")] internal static class FakeChatPatch { private static bool Prefix(NetworkBehaviour __0, NetworkReader __1) { if (!Plugin.GuardChatFilter.Value) { return true; } try { PlayerNetworking val = ((Il2CppObjectBase)__0).TryCast<PlayerNetworking>(); if ((Object)(object)val == (Object)null || ((NetworkBehaviour)val).isLocalPlayer) { return true; } int position = __1.Position; string text = null; try { text = NetworkReaderExtensions.ReadString(__1); } catch { } __1.Position = position; if (string.IsNullOrEmpty(text) || !FakeSystem.IsMatch(text)) { return true; } Patches.MarkFired("chat/fake"); OrbState.AddAlert("fakesys", val.identifier, Patches.Display(val), "chat styled as a system message, dropped: \"" + text + "\""); return false; } catch { return true; } } } private const float KickGrace = 1f; private static readonly Dictionary<int, float> PendingDisconnect = new Dictionary<int, float>(); private static readonly Dictionary<string, HashSet<string>> IdsByAddress = new Dictionary<string, HashSet<string>>(); private static readonly Dictionary<uint, string> Judged = new Dictionary<uint, string>(); private static readonly Dictionary<string, int> Refused = new Dictionary<string, int>(); private static readonly Regex FakeSystem = new Regex("^\\s*(?:\\[?(?:server|system|host|admin)\\]?\\s*[:\\-]|(?:the\\s+)?(?:host|server|admin|you|player|[\\w\\-]+)\\s+(?:has|have)\\s+been\\s+(?:removed|kicked|banned|disconnected)|[\\w\\-]+\\s+(?:has|have)\\s+(?:joined|left)\\s+the\\s+(?:game|lobby|server)|(?:you\\s+(?:have\\s+been|were)\\s+)?(?:kicked|banned)\\s+(?:by|from)\\s+(?:the\\s+)?(?:host|server)|(?:connection|host)\\s+(?:lost|timed\\s+out)|lost\\s+connection\\s+to\\s+(?:the\\s+)?host)", RegexOptions.IgnoreCase | RegexOptions.Compiled); internal static bool AutoBan => Plugin.GuardAutoBan.Value; internal static void Kick(PlayerNetworking pn, string reason = null) { if ((Object)(object)pn == (Object)null) { return; } NetworkConnectionToClient val = null; try { val = ((NetworkBehaviour)pn).connectionToClient; } catch { } if (val == null) { return; } lock (PendingDisconnect) { if (PendingDisconnect.ContainsKey(((NetworkConnection)val).connectionId)) { return; } PendingDisconnect[((NetworkConnection)val).connectionId] = Time.unscaledTime + 1f; } try { pn.RPCKickUser(val); } catch { } } internal static void StepKicks() { List<int> list = null; lock (PendingDisconnect) { foreach (KeyValuePair<int, float> item in PendingDisconnect) { if (Time.unscaledTime >= item.Value) { (list ?? (list = new List<int>())).Add(item.Key); } } } if (list == null) { return; } NetworkConnectionToClient val = default(NetworkConnectionToClient); foreach (int item2 in list) { lock (PendingDisconnect) { PendingDisconnect.Remove(item2); } try { if (NetworkServer.connections.TryGetValue(item2, ref val) && val != null) { ((NetworkConnection)val).Disconnect(); } } catch { } } } internal static void BanAddress(string address, string name, string why, string id = null) { if (string.IsNullOrEmpty(address) || address == "localhost") { return; } if (!OrbState.IsBannedAddress(address)) { OrbState.BanAdd(id, name, 0uL, address); } OrbState.AddEvent("autoban", id, name, why); Enumerator<int, NetworkConnectionToClient> enumerator = NetworkServer.connections.GetEnumerator(); while (enumerator.MoveNext()) { NetworkConnectionToClient value = enumerator.Current.Value; if (value == null || ((NetworkConnection)value).connectionId == 0) { continue; } string text = null; try { text = value.address; } catch { } if (text == address) { try { ((NetworkConnection)value).Disconnect(); } catch { } } } } private static void Act(string kind, string id, string name, string detail, string address, NetworkConnectionToClient conn) { OrbState.AddAlert(kind, id, name, detail); if (AutoBan) { BanAddress(address, name ?? id ?? "?", kind + ": " + detail, id); return; } try { if (conn != null) { ((NetworkConnection)conn).Disconnect(); } } catch { } } private static string Base(string id) { if (id != null) { return Regex.Replace(id, "-\\d+$", ""); } return null; } private static bool IsSteam(string id) { if (id != null && id.Length == 17 && id.StartsWith("7656119")) { return id.All(char.IsDigit); } return false; } private static bool IsNumeric(string id) { if (!string.IsNullOrEmpty(id) && id.Length >= 15) { return id.All(char.IsDigit); } return false; } private static bool IsEosAddr(string a) { if (a != null && a.Length == 32) { return a.All(Uri.IsHexDigit); } return false; } internal static void Tick() { StepKicks(); Voice.Tick(); foreach (PlayerCharacter item in OrbBehaviour.Players()) { try { PlayerNetworking playerNetworking = item.playerNetworking; if ((Object)(object)playerNetworking == (Object)null || ((NetworkBehaviour)playerNetworking).isLocalPlayer) { continue; } string text = null; string text2 = null; string text3 = null; ulong num = 0uL; try { text = playerNetworking.identifier; } catch { } try { NetworkConnectionToClient connectionToClient = ((NetworkBehaviour)playerNetworking).connectionToClient; text2 = ((connectionToClient != null) ? connectionToClient.address : null); } catch { } try { text3 = playerNetworking.epicUserId; } catch { } try { num = playerNetworking.userPlatformId; } catch { } if (OrbState.IsUnsetIdentifier(text) || text2 == null) { continue; } string text4 = text3 + "|" + num; if (Judged.TryGetValue(((NetworkBehaviour)playerNetworking).netId, out var value) && value == text4) { continue; } Judged[((NetworkBehaviour)playerNetworking).netId] = text4; string name = Patches.Display(playerNetworking); if (IsEosAddr(text2) && !string.IsNullOrEmpty(text3) && !string.Equals(text3, text2, StringComparison.OrdinalIgnoreCase)) { Act("epicspoof", text, name, "connection is " + text2 + " but the client reports epic id " + text3, text2, ((NetworkBehaviour)playerNetworking).connectionToClient); } else { if (num == 0L || !IsNumeric(Base(text)) || !(num.ToString() != Base(text))) { continue; } string text5 = null; foreach (PlayerCharacter item2 in OrbBehaviour.Players()) { PlayerNetworking playerNetworking2 = item2.playerNetworking; if ((Object)(object)playerNetworking2 != (Object)null && ((Il2CppObjectBase)playerNetworking2).Pointer != ((Il2CppObjectBase)playerNetworking).Pointer && Base(playerNetworking2.identifier) == num.ToString()) { text5 = Patches.Display(playerNetworking2); break; } } Act("platspoof", text, name, $"claims identifier {text} but platform id is {num}" + ((text5 != null) ? (" (" + text5 + "'s, who is connected)") : ""), text2, ((NetworkBehaviour)playerNetworking).connectionToClient); continue; } } catch { } } if (Judged.Count <= 64) { return; } HashSet<uint> live = new HashSet<uint>(); foreach (PlayerCharacter item3 in OrbBehaviour.Players()) { try { live.Add(((NetworkBehaviour)item3.playerNetworking).netId); } catch { } } foreach (uint item4 in Judged.Keys.Where((uint k) => !live.Contains(k)).ToList()) { Judged.Remove(item4); } } } public class OrbBehaviour : MonoBehaviour { private class Track { public Vector3 LastPos; public float Speed; public float AirSec; public float SpeedSec; public float LastFlag = -999f; public bool Seen; public string Name; } internal static readonly string[] LookHex = new string[24] { "#4b72af", "#bb3102", "#f4cc48", "#4895a8", "#264186", "#ff2c2b", "#eaaa32", "#295b35", "#4a1538", "#2b2b2b", "#d55701", "#f9b8be", "#cfe190", "#8f8a84", "#962550", "#1c9063", "#60361d", "#7a021b", "#ede3d9", "#00a996", "#b29672", "#ffa300", "#ecfaff", "#9f500e" }; internal static volatile bool NametagsOn; private readonly Dictionary<string, Track> _tracks = new Dictionary<string, Track>(); private readonly Dictionary<string, GameObject> _tags = new Dictionary<string, GameObject>(); private float _nextTick; private float _nextSnap; private bool _wasHosting; public OrbBehaviour(IntPtr ptr) : base(ptr) { } private void Update() { Action result; while (OrbState.MainQueue.TryDequeue(out result)) { try { result(); } catch (Exception ex) { Plugin.Logger.LogError((object)("cmd: " + ex.Message)); } } bool active = NetworkServer.active; if (active && !_wasHosting) { string worldName = null; try { worldName = SaveManager.worldName; } catch { } OrbState.NewSession(worldName); } _wasHosting = active; if (Time.unscaledTime >= _nextTick) { _nextTick = Time.unscaledTime + 0.25f; try { Tick(active); } catch (Exception ex2) { Plugin.Logger.LogError((object)("tick: " + ex2.Message)); } } if (Time.unscaledTime >= _nextSnap) { _nextSnap = Time.unscaledTime + 0.5f; try { Snapshot(active); } catch (Exception ex3) { Plugin.Logger.LogError((object)("snap: " + ex3.Message)); } } Chime.Step(((Component)this).gameObject); try { UpdateNametags(); } catch { } } internal static IEnumerable<PlayerCharacter> Players() { List<PlayerCharacter> all = PlayerCharacter.allPlayerCharacters; if (all == null) { yield break; } for (int i = 0; i < all.Count; i++) { PlayerCharacter val = all[i]; if ((Object)(object)val != (Object)null && (Object)(object)val.playerNetworking != (Object)null) { yield return val; } } } internal static PlayerCharacter ById(string id) { foreach (PlayerCharacter item in Players()) { if (item.playerNetworking.identifier == id) { return item; } } return null; } private static PeckEffectTextInput FindSign(uint netId) { Il2CppArrayBase<PeckEffectTextInput> val = Object.FindObjectsOfType<PeckEffectTextInput>(true); for (int i = 0; i < val.Length; i++) { if ((Object)(object)val[i] != (Object)null && ((NetworkBehaviour)val[i]).netId == netId) { return val[i]; } } return null; } internal static PlayerCharacter Local() { foreach (PlayerCharacter item in Players()) { if (((NetworkBehaviour)item.playerNetworking).isLocalPlayer) { return item; } } return null; } internal static string LobbyCode() { try { EOSLobbyManager instance = EOSLobbyManager.Instance; return ((instance != null) ? instance.CurrentLobbyCode : null) ?? ""; } catch { return ""; } } internal static HouseAuthenticator Auth() { try { NetworkManager singleton = NetworkManager.singleton; object result; if (singleton == null) { result = null; } else { NetworkAuthenticator authenticator = singleton.authenticator; result = ((authenticator != null) ? ((Il2CppObjectBase)authenticator).TryCast<HouseAuthenticator>() : null); } return (HouseAuthenticator)result; } catch { return null; } } private void Tick(bool hosting) { //IL_018c: Unknown result type (might be due to invalid IL or missing references) //IL_0190: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Unknown result type (might be due to invalid IL or missing references) //IL_019a: Unknown result type (might be due to invalid IL or missing references) //IL_019c: Unknown result type (might be due to invalid IL or missing references) //IL_01a8: Unknown result type (might be due to invalid IL or missing references) //IL_01af: Unknown result type (might be due to invalid IL or missing references) //IL_01b4: Unknown result type (might be due to invalid IL or missing references) //IL_01cd: Unknown result type (might be due to invalid IL or missing references) //IL_01cf: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_027d: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_03ca: Unknown result type (might be due to invalid IL or missing references) //IL_03e9: Unknown result type (might be due to invalid IL or missing references) //IL_0408: Unknown result type (might be due to invalid IL or missing references) if (hosting) { try { Guard.Tick(); } catch (Exception ex) { Plugin.Logger.LogError((object)("guard: " + ex.Message)); } } foreach (Track value3 in _tracks.Values) { value3.Seen = false; } foreach (PlayerCharacter item in Players()) { PlayerNetworking pn = item.playerNetworking; string identifier = pn.identifier; if (string.IsNullOrEmpty(identifier)) { continue; } string name = Patches.Display(pn); Vector3 position = ((Component)item).transform.position; if (!_tracks.TryGetValue(identifier, out var value)) { Dictionary<string, Track> tracks = _tracks; Track obj = new Track { LastPos = position, Name = name }; value = obj; tracks[identifier] = obj; OrbState.AddEvent("join", identifier, name, OrbState.Platform(identifier)); Chime.Play(join: true); } value.Seen = true; value.Name = name; try { OrbState.RosterSeen(identifier, name, SafeStr(() => pn.username), pn.userPlatformId, Hex(pn.lookIdHead), Hex(pn.lookIdTorso), Hex(pn.lookIdLegs), online: true); } catch { } float num = 0.25f; Vector3 val = position - value.LastPos; Vector3 val2 = new Vector3(val.x, 0f, val.z); float num2 = (value.Speed = ((Vector3)(ref val2)).magnitude / num); value.LastPos = position; if (!hosting || ((NetworkBehaviour)pn).isLocalPlayer) { continue; } string address = null; try { NetworkConnectionToClient connectionToClient = ((NetworkBehaviour)pn).connectionToClient; address = ((connectionToClient != null) ? connectionToClient.address : null); } catch { } if (OrbState.IsBanned(identifier) || OrbState.IsBannedAddress(address)) { OrbState.AddEvent("autokick", identifier, name, "banned while in session"); OrbState.BanAttachAddress(identifier, address); Guard.Kick(pn); continue; } if (!OrbState.LobbyAllows(identifier, address)) { OrbState.AddEvent("lockreject", identifier, name, "lobby locked; player was not present when locked"); Guard.Kick(pn); continue; } float num3 = val.y / num; value.AirSec = ((num3 > 1.5f) ? (value.AirSec + num) : 0f); value.SpeedSec = ((num2 > Plugin.FlyMaxSpeed.Value) ? (value.SpeedSec + num) : 0f); bool flag = value.AirSec > Plugin.FlyMaxAirSeconds.Value; bool flag2 = value.SpeedSec > 1.5f; if ((flag || flag2) && Time.unscaledTime - value.LastFlag > 60f) { value.LastFlag = Time.unscaledTime; string text = (flag ? $"climbing steadily for {value.AirSec:0.0}s" : $"sustained {num2:0.0} m/s"); OrbState.AddAlert(flag ? "fly" : "speed", identifier, name, text + $" at {position.x:0},{position.y:0},{position.z:0}"); if (Plugin.FlyAutoKick.Value) { OrbState.AddEvent("autokick", identifier, name, "anticheat: " + text); Guard.Kick(pn); } } } List<string> list = new List<string>(); foreach (KeyValuePair<string, Track> track in _tracks) { if (!track.Value.Seen) { OrbState.AddEvent("leave", track.Key, track.Value.Name); Chime.Play(join: false); OrbState.RosterOffline(track.Key); if (_tags.TryGetValue(track.Key, out var value2) && (Object)(object)value2 != (Object)null) { Object.Destroy((Object)(object)value2); } _tags.Remove(track.Key); list.Add(track.Key); } } foreach (string item2 in list) { _tracks.Remove(item2); } } private void Snapshot(bool hosting) { //IL_0159: Unknown result type (might be due to invalid IL or missing references) //IL_015e: Unknown result type (might be due to invalid IL or missing references) //IL_0334: Unknown result type (might be due to invalid IL or missing references) //IL_0348: Unknown result type (might be due to invalid IL or missing references) //IL_035c: Unknown result type (might be due to invalid IL or missing references) StringBuilder stringBuilder = new StringBuilder(4096); stringBuilder.Append("{\"hosting\":").Append(hosting ? "true" : "false").Append(",\"session\":") .Append(OrbState.J(OrbState.SessionName)) .Append(",\"nametags\":") .Append(NametagsOn ? "true" : "false") .Append(",\"locked\":") .Append(OrbState.LobbyLocked ? "true" : "false") .Append(",\"lockAllowed\":") .Append(OrbState.LobbyLockCount) .Append(",\"code\":") .Append(OrbState.J(hosting ? LobbyCode() : "")) .Append(",\"password\":") .Append(OrbState.J(hosting ? SafeStr(delegate { HouseAuthenticator obj = Auth(); return (obj == null) ? null : obj.password; }) : "")) .Append(",\"players\":["); bool flag = true; foreach (PlayerCharacter item in Players()) { PlayerNetworking pn = item.playerNetworking; string text = pn.identifier ?? ""; Vector3 position = ((Component)item).transform.position; _tracks.TryGetValue(text, out var value); if (!flag) { stringBuilder.Append(','); } flag = false; stringBuilder.Append('{').Append("\"id\":").Append(OrbState.J(text)) .Append(",\"name\":") .Append(OrbState.J(Patches.Display(pn))) .Append(",\"username\":") .Append(OrbState.J(SafeStr(() => pn.username))) .Append(",\"modName\":") .Append(OrbState.J(SafeStr(() => pn.moderationNameSanitized))) .Append(",\"platformId\":\"") .Append(pn.userPlatformId) .Append('"') .Append(",\"platform\":") .Append(OrbState.J(OrbState.Platform(text))) .Append(",\"local\":") .Append(((NetworkBehaviour)pn).isLocalPlayer ? "true" : "false") .Append(",\"isHost\":") .Append(pn.isHost ? "true" : "false") .Append(",\"muted\":") .Append(pn.isMuted ? "true" : "false") .Append(",\"colors\":[\"") .Append(Hex(pn.lookIdHead)) .Append("\",\"") .Append(Hex(pn.lookIdTorso)) .Append("\",\"") .Append(Hex(pn.lookIdLegs)) .Append("\"]") .Append(",\"pos\":[") .Append((int)position.x) .Append(',') .Append((int)position.y) .Append(',') .Append((int)position.z) .Append(']') .Append(",\"speed\":") .Append((value?.Speed ?? 0f).ToString("0.0", CultureInfo.InvariantCulture)) .Append(",\"air\":") .Append((value?.AirSec ?? 0f).ToString("0.0", CultureInfo.InvariantCulture)) .Append(",\"banned\":") .Append(OrbState.IsBanned(text) ? "true" : "false") .Append(",\"addr\":") .Append(OrbState.J(SafeStr(delegate { if (!((NetworkBehaviour)pn).isLocalPlayer) { NetworkConnectionToClient connectionToClient = ((NetworkBehaviour)pn).connectionToClient; if (connectionToClient == null) { return (string)null; } return connectionToClient.address; } return ""; }))) .Append('}'); } stringBuilder.Append("]}"); OrbState.SnapshotJson = stringBuilder.ToString(); } private static string Hex(int lookId) { return LookHex[(lookId % 24 + 24) % 24]; } private static string SafeStr(Func<string> get) { try { return get() ?? ""; } catch { return ""; } } internal static void Cmd(string action, string id, string key, int val, string text = null) { OrbState.MainQueue.Enqueue(delegate { Run(action, id, key, val, text); }); } private static void Run(string action, string id, string key, int val, string text) { PlayerCharacter val2 = ((id != null) ? ById(id) : null); PlayerNetworking val3 = (((Object)(object)val2 != (Object)null) ? val2.playerNetworking : null); if (action == "ban" && IsHostBanTarget(id, val3)) { OrbState.AddEvent("banblocked", id, ((Object)(object)val3 != (Object)null) ? Patches.Display(val3) : "host", "the lobby host cannot be banned"); return; } switch (action) { case "kick": if ((Object)(object)val3 != (Object)null) { OrbState.AddEvent("kick", id, Patches.Display(val3)); Guard.Kick(val3); break; } goto default; case "ban": if ((Object)(object)val3 != (Object)null) { string address = null; try { NetworkConnectionToClient connectionToClient3 = ((NetworkBehaviour)val3).connectionToClient; address = ((connectionToClient3 != null) ? connectionToClient3.address : null); } catch { } if (OrbState.BanAdd(id, Patches.Display(val3), val3.userPlatformId, address)) { Guard.Kick(val3); } } else { OrbState.BanAdd(id, OrbState.RosterName(id) ?? "(offline ban)", 0uL, OrbState.LastAddressFor(id)); } break; case "banaddr": if (!string.IsNullOrEmpty(key)) { Guard.BanAddress(key, string.IsNullOrEmpty(text) ? "(address ban)" : text, "manual address ban"); break; } goto default; case "banimport": { (int, int, int) tuple = OrbState.BansImport(text); OrbState.AddEvent("banimport", null, "host", $"import: {tuple.Item1} added, {tuple.Item2} skipped, {tuple.Item3} bad"); break; } case "eoslookup": { string text2 = key; if (string.IsNullOrEmpty(text2) && (Object)(object)val3 != (Object)null) { try { NetworkConnectionToClient connectionToClient2 = ((NetworkBehaviour)val3).connectionToClient; text2 = ((connectionToClient2 != null) ? connectionToClient2.address : null); } catch { } } if (!string.IsNullOrEmpty(text2)) { Guard.Eos.Lookup(text2, ((Object)(object)val3 != (Object)null) ? id : null); } break; } case "unban": OrbState.BanRemove(id); break; case "locklobby": { List<string> list = new List<string>(); List<string> list2 = new List<string>(); foreach (PlayerCharacter item in Players()) { PlayerNetworking playerNetworking = item.playerNetworking; if ((Object)(object)playerNetworking == (Object)null) { continue; } list.Add(playerNetworking.identifier); if (!((NetworkBehaviour)playerNetworking).isLocalPlayer) { try { NetworkConnectionToClient connectionToClient = ((NetworkBehaviour)playerNetworking).connectionToClient; list2.Add((connectionToClient != null) ? connectionToClient.address : null); } catch { } } } int value = OrbState.SetLobbyLocked(list, list2); OrbState.AddEvent("lobbylock", null, "host", $"locked to {value} current player identifier(s)"); break; } case "unlocklobby": OrbState.UnlockLobby(); OrbState.AddEvent("lobbyunlock", null, "host", "new players may join again"); break; case "signset": { if (key != null && uint.TryParse(key, out var result)) { PeckEffectTextInput val5 = FindSign(result); if ((Object)(object)val5 != (Object)null) { PlayerCharacter val6 = Local(); string text3 = (((Object)(object)val6 != (Object)null) ? val6.playerNetworking.identifier : ""); val5.UserCode_CmdSendNewText__String__String(text ?? "", text3); if (OrbState.LockedText(result) != null) { OrbState.LockSign(result, ((Object)((Component)val5).gameObject).name, text ?? ""); } OrbState.AddEvent("signset", null, "host", string.IsNullOrEmpty(text) ? $"erased sign {result}" : $"set sign {result} to \"{text}\""); } else { OrbState.AddEvent("signset", null, "host", $"FAILED: sign netId {result} not found"); } break; } goto default; } case "signlock": { if (key != null && uint.TryParse(key, out var result3)) { PeckEffectTextInput val7 = FindSign(result3); if ((Object)(object)val7 == (Object)null) { OrbState.AddEvent("signlock", null, "host", $"FAILED: sign netId {result3} not found"); break; } string text4 = null; try { text4 = val7.networkedText; } catch { } string text5 = text ?? text4 ?? ""; PlayerCharacter val8 = Local(); string text6 = (((Object)(object)val8 != (Object)null) ? val8.playerNetworking.identifier : ""); if (text5 != text4) { val7.UserCode_CmdSendNewText__String__String(text5, text6); } string text7 = "sign"; try { text7 = ((Object)((Component)val7).gameObject).name; } catch { } OrbState.LockSign(result3, text7, text5); OrbState.AddEvent("signlock", null, "host", $"locked sign {result3} ({text7}) as \"{text5}\""); break; } goto default; } case "signunlock": { if (key != null && uint.TryParse(key, out var result2)) { if (OrbState.UnlockSign(result2)) { OrbState.AddEvent("signunlock", null, "host", $"unlocked sign {result2}"); } break; } goto default; } case "setpassword": { HouseAuthenticator val4 = Auth(); if ((Object)(object)val4 == (Object)null) { OrbState.AddEvent("password", null, "host", "FAILED: no authenticator (not hosting?)"); break; } val4.password = text ?? ""; OrbState.AddEvent("password", null, "host", string.IsNullOrEmpty(text) ? "password removed" : "password changed"); break; } case "nametags": NametagsOn = val != 0; break; default: OrbState.AddEvent("cmd", null, "host", "unknown action \"" + action + "\""); break; } } private static bool IsHostBanTarget(string id, PlayerNetworking target) { try { if ((Object)(object)target != (Object)null && (((NetworkBehaviour)target).isLocalPlayer || target.isHost)) { return true; } PlayerCharacter obj = Local(); PlayerNetworking val = ((obj != null) ? obj.playerNetworking : null); return (Object)(object)val != (Object)null && !string.IsNullOrEmpty(id) && id == val.identifier; } catch { return false; } } private void UpdateNametags() { //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Expected O, but got Unknown //IL_015e: Unknown result type (might be due to invalid IL or missing references) //IL_0163: Unknown result type (might be due to invalid IL or missing references) //IL_016d: Unknown result type (might be due to invalid IL or missing references) //IL_0172: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_0193: Unknown result type (might be due to invalid IL or missing references) //IL_019e: Unknown result type (might be due to invalid IL or missing references) //IL_01a3: Unknown result type (might be due to invalid IL or missing references) //IL_01a8: Unknown result type (might be due to invalid IL or missing references) if (!NametagsOn) { if (_tags.Count <= 0) { return; } foreach (GameObject value2 in _tags.Values) { if ((Object)(object)value2 != (Object)null) { Object.Destroy((Object)(object)value2); } } _tags.Clear(); return; } Camera main = Camera.main; Color color = default(Color); foreach (PlayerCharacter item in Players()) { PlayerNetworking playerNetworking = item.playerNetworking; if (((NetworkBehaviour)playerNetworking).isLocalPlayer) { continue; } string identifier = playerNetworking.identifier; if (!string.IsNullOrEmpty(identifier)) { if (!_tags.TryGetValue(identifier, out var value) || (Object)(object)value == (Object)null) { value = new GameObject("OrbTag_" + identifier); ((Object)value).hideFlags = (HideFlags)61; TextMesh obj = value.AddComponent<TextMesh>(); obj.fontSize = 48; obj.characterSize = 0.022f; obj.anchor = (TextAnchor)7; obj.alignment = (TextAlignment)1; _tags[identifier] = value; } TextMesh component = value.GetComponent<TextMesh>(); component.text = Patches.Display(playerNetworking); if (ColorUtility.TryParseHtmlString(Hex(playerNetworking.lookIdHead), ref color)) { component.color = color; } value.transform.position = ((Component)item).transform.position + Vector3.up * 1.75f; if ((Object)(object)main != (Object)null) { value.transform.rotation = Quaternion.LookRotation(value.transform.position - ((Component)main).transform.position); } } } } } public class OrbMenu : MonoBehaviour { private const string DiscordUrl = "https://discord.gg/5z3WvVhxCf"; private const string SourceUrl = "https://github.com/RadioFreeOpportunity/bigorb"; private const string RepoUrl = "https://github.com/AdamMady/Moderation-Improvements/"; private readonly string[] _tabs = new string[5] { "Players", "Bans & Roster", "Signs", "Logs", "Settings" }; private bool _open; private bool _oldCursorVisible; private bool _oldMenuMode; private bool _showPassword; private CursorLockMode _oldCursorLock; private int _tab; private float _openedAt; private float _nextRefresh; private float _scroll; private float _contentHeight; private float _y; private float _width; private float _viewHeight; private string _focus; private string _status = ""; private string _password = ""; private string _banId = ""; private string _csvPath = ""; private string _signText = ""; private string _logKind = "alerts"; private JsonDocument _state; private Action _pending; private string _confirmation; private GUIStyle _label; private GUIStyle _heading; public static bool IsOpen { get; private set; } private JsonElement Root { get { if (_state != null) { return _state.RootElement; } return default(JsonElement); } } public OrbMenu(IntPtr ptr) : base(ptr) { } private static JsonElement Get(JsonElement value, string key) { if (value.ValueKind != JsonValueKind.Object || !value.TryGetProperty(key, out var value2)) { return default(JsonElement); } return value2; } private static string Str(JsonElement value, string key) { return Get(value, key).ToString(); } private static bool Flag(JsonElement value, string key) { return Get(value, key).ValueKind == JsonValueKind.True; } private static IEnumerable<JsonElement> Rows(JsonElement value) { if (value.ValueKind != JsonValueKind.Array) { return Enumerable.Empty<JsonElement>(); } return value.EnumerateArray(); } private void Update() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) bool keyDown = Input.GetKeyDown(Plugin.MenuKey.Value); if (!_open) { if (keyDown) { SetOpen(value: true); } return; } if (Time.unscaledTime - _openedAt > 0.15f && (keyDown || Input.GetKeyDown((KeyCode)27))) { SetOpen(value: false); return; } Cursor.lockState = (CursorLockMode)0; Cursor.visible = true; if (!(Time.unscaledTime < _nextRefresh)) { _nextRefresh = Time.unscaledTime + 0.5f; RefreshState(); } } private void RefreshState() { OrbState.Polled(); try { JsonDocument state = JsonDocument.Parse("{\"snap\":" + OrbState.SnapshotJson + ",\"bans\":" + OrbState.BansJson() + ",\"roster\":" + OrbState.RosterJson() + ",\"signlocks\":" + OrbState.SignLocksJson() + ",\"signs\":" + OrbState.Tail("signs") + ",\"chat\":" + OrbState.Tail("chat") + ",\"alerts\":" + OrbState.Tail("alerts") + ",\"events\":" + OrbState.Tail("events") + ",\"identities\":" + Guard.Eos.Json() + "}"); _state?.Dispose(); _state = state; } catch (Exception ex) { _status = "Could not refresh: " + ex.Message; } } private void SetOpen(bool value) { //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) if (_open != value) { _open = value; IsOpen = value; _focus = null; _pending = null; if (_open) { CloseMapMenu(); _openedAt = Time.unscaledTime; _oldCursorLock = Cursor.lockState; _oldCursorVisible = Cursor.visible; _oldMenuMode = ControlsManager.menuModeActive; ControlsManager.SetMenuMode(true); Cursor.lockState = (CursorLockMode)0; Cursor.visible = true; _nextRefresh = 0f; RefreshState(); } else { ControlsManager.SetMenuMode(_oldMenuMode); Cursor.lockState = _oldCursorLock; Cursor.visible = _oldCursorVisible; } } } private static void CloseMapMenu() { try { Type.GetType("AdamMady.Minimap.MapController, AdamMady_Minimap", throwOnError: false)?.GetMethod("CloseMenu", BindingFlags.Static | BindingFlags.Public)?.Invoke(null, null); } catch { } } public static void CloseMenu() { Il2CppArrayBase<OrbMenu> val = Object.FindObjectsOfType<OrbMenu>(true); for (int i = 0; i < val.Length; i++) { val[i]?.SetOpen(value: false); } } private void OnDisable() { SetOpen(value: false); } private void OnDestroy() { SetOpen(value: false); _state?.Dispose(); } private void OnGUI() { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Invalid comparison between Unknown and I4 //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Expected O, but got Unknown //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Expected O, but got Unknown //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Invalid comparison between Unknown and I4 //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Unknown result type (might be due to invalid IL or missing references) //IL_0190: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Unknown result type (might be due to invalid IL or missing references) //IL_0263: Unknown result type (might be due to invalid IL or missing references) //IL_0289: Unknown result type (might be due to invalid IL or missing references) //IL_01f9: Unknown result type (might be due to invalid IL or missing references) //IL_01e3: Unknown result type (might be due to invalid IL or missing references) //IL_02f5: Unknown result type (might be due to invalid IL or missing references) //IL_02fb: Invalid comparison between Unknown and I4 //IL_021f: Unknown result type (might be due to invalid IL or missing references) //IL_034e: Unknown result type (might be due to invalid IL or missing references) //IL_0354: Invalid comparison between Unknown and I4 //IL_0305: Unknown result type (might be due to invalid IL or missing references) //IL_049f: Unknown result type (might be due to invalid IL or missing references) //IL_0465: Unknown result type (might be due to invalid IL or missing references) //IL_035a: Unknown result type (might be due to invalid IL or missing references) //IL_0364: Invalid comparison between Unknown and I4 //IL_037b: Unknown result type (might be due to invalid IL or missing references) //IL_0385: Invalid comparison between Unknown and I4 //IL_0367: Unknown result type (might be due to invalid IL or missing references) //IL_0371: Invalid comparison between Unknown and I4 //IL_03ca: Unknown result type (might be due to invalid IL or missing references) //IL_03d4: Invalid comparison between Unknown and I4 //IL_03eb: Unknown result type (might be due to invalid IL or missing references) //IL_03f5: Invalid comparison between Unknown and I4 //IL_03d7: Unknown result type (might be due to invalid IL or missing references) //IL_03e1: Invalid comparison between Unknown and I4 //IL_0652: Unknown result type (might be due to invalid IL or missing references) //IL_0582: Unknown result type (might be due to invalid IL or missing references) //IL_05b2: Unknown result type (might be due to invalid IL or missing references) //IL_05ec: Unknown result type (might be due to invalid IL or missing references) //IL_0661: Unknown result type (might be due to invalid IL or missing references) //IL_0667: Invalid comparison between Unknown and I4 //IL_0628: Unknown result type (might be due to invalid IL or missing references) //IL_066a: Unknown result type (might be due to invalid IL or missing references) //IL_0670: Invalid comparison between Unknown and I4 //IL_0673: Unknown result type (might be due to invalid IL or missing references) //IL_0679: Invalid comparison between Unknown and I4 if (!_open) { return; } Event current = Event.current; if ((int)current.type == 4 && Time.unscaledTime - _openedAt > 0.15f && (int)current.keyCode == 27) { current.Use(); SetOpen(value: false); return; } if (_label == null) { _label = new GUIStyle(GUI.skin.label) { fontSize = 15, wordWrap = true }; _heading = new GUIStyle(_label) { fontSize = 23, fontStyle = (FontStyle)1 }; } GUI.depth = -1000; Color color = GUI.color; GUI.color = new Color(0.055f, 0.065f, 0.09f, 0.94f); GUI.DrawTexture(new Rect(0f, 0f, (float)Screen.width, (float)Screen.height), (Texture)(object)Texture2D.whiteTexture); GUI.color = color; GUI.Label(new Rect(24f, 16f, (float)(Screen.width - 500), 36f), "MODERATION IMPROVEMENTS / " + (NetworkServer.active ? OrbState.SessionName : "Not hosting"), _heading); if (GUI.Button(new Rect((float)(Screen.width - 306), 18f, 142f, 32f), "Join The Discord!")) { Application.OpenURL("https://discord.gg/5z3WvVhxCf"); } if (GUI.Button(new Rect((float)(Screen.width - 156), 18f, 132f, 32f), "Close [" + ((object)Plugin.MenuKey.Value/*cast due to .constrained prefix*/).ToString() + "]")) { SetOpen(value: false); return; } float num = ((float)Screen.width - 48f) / (float)_tabs.Length; for (int i = 0; i < _tabs.Length; i++) { GUI.backgroundColor = (Color)((i == _tab) ? new Color(0.35f, 0.7f, 1f) : Color.white); if (GUI.Button(new Rect(24f + (float)i * num, 64f, num - 4f, 38f), _tabs[i])) { _tab = i; _scroll = 0f; _focus = null; } } GUI.backgroundColor = Color.white; GUI.Label(new Rect(24f, (float)(Screen.height - 35), (float)(Screen.width - 48), 30f), _status, _label); Rect val = default(Rect); ((Rect)(ref val))..ctor(24f, 114f, (float)(Screen.width - 48), (float)Math.Max(50, Screen.height - 160)); _viewHeight = ((Rect)(ref val)).height; _width = ((Rect)(ref val)).width - 22f; if (_pending == null && (int)current.type == 6) { _scroll = Mathf.Clamp(_scroll + current.delta.y * 35f, 0f, Math.Max(0f, _contentHeight - _viewHeight)); current.Use(); } if (_pending == null && (int)current.type == 4) { if ((int)current.keyCode == 281 || (int)current.keyCode == 274) { _scroll = Mathf.Clamp(_scroll + (((int)current.keyCode == 281) ? (_viewHeight * 0.8f) : 42f), 0f, Math.Max(0f, _contentHeight - _viewHeight)); current.Use(); } else if ((int)current.keyCode == 280 || (int)current.keyCode == 273) { _scroll = Mathf.Clamp(_scroll - (((int)current.keyCode == 280) ? (_viewHeight * 0.8f) : 42f), 0f, Math.Max(0f, _contentHeight - _viewHeight)); current.Use(); } } if (_contentHeight > _viewHeight) { _scroll = GUI.VerticalScrollbar(new Rect(((Rect)(ref val)).xMax - 16f, ((Rect)(ref val)).y, 16f, _viewHeight), _scroll, _viewHeight, 0f, _contentHeight); } bool enabled = GUI.enabled; GUI.enabled = _pending == null; GUI.BeginGroup(val); _y = 0f; try { switch (_tab) { case 0: Players(); break; case 1: Bans(); break; case 2: Signs(); break; case 3: Logs(); break; case 4: Settings(); break; } _contentHeight = _y; } finally { GUI.EndGroup(); GUI.enabled = enabled; } _scroll = Mathf.Clamp(_scroll, 0f, Math.Max(0f, _contentHeight - _viewHeight)); if (_pending != null) { Rect val2 = default(Rect); ((Rect)(ref val2))..ctor((float)(Screen.width - 520) / 2f, (float)(Screen.height - 190) / 2f, 520f, 190f); GUI.Box(val2, "Confirm action"); GUI.Label(new Rect(((Rect)(ref val2)).x + 20f, ((Rect)(ref val2)).y + 35f, 480f, 90f), _confirmation, _label); if (GUI.Button(new Rect(((Rect)(ref val2)).x + 20f, ((Rect)(ref val2)).y + 140f, 230f, 32f), "Cancel")) { _pending = null; } if (GUI.Button(new Rect(((Rect)(ref val2)).x + 270f, ((Rect)(ref val2)).y + 140f, 230f, 32f), "Confirm")) { Action pending = _pending; _pending = null; pending?.Invoke(); } } if ((int)current.type == 0) { _focus = null; } if ((int)current.type == 4 || (int)current.type == 5 || (int)current.type == 6) { current.Use(); } } private Rect Line(float height = 34f) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) Rect result = new Rect(0f, _y - _scroll, _width, height); _y += height + 6f; return result; } private bool Visible(Rect rect) { if (((Rect)(ref rect)).yMax >= 0f) { return ((Rect)(ref rect)).y < _viewHeight; } return false; } private void Text(string text, bool title = false) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003b: 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_0045: Unknown result type (might be due to invalid IL or missing references) GUIStyle val = (title ? _heading : _label); float height = Math.Max(title ? 34 : 24, val.CalcHeight(new GUIContent(text), _width)); Rect val2 = Line(height); if (Visible(val2)) { GUI.Label(val2, text, val); } } private void Buttons(params (string label, Action action)[] buttons) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) int num = Math.Max(1, (int)(_width / 185f)); for (int i = 0; i < buttons.Length; i += num) { Rect rect = Line(); int num2 = Math.Min(num, buttons.Length - i); float num3 = _width / (float)num2; if (!Visible(rect)) { continue; } for (int j = 0; j < num2; j++) { if (GUI.Button(new Rect((float)j * num3, ((Rect)(ref rect)).y, num3 - 6f, ((Rect)(ref rect)).height), buttons[i + j].label)) { _focus = null; buttons[i + j].action(); } } } } private void InputField(string key, string caption, ref string value) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Invalid comparison between Unknown and I4 //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Invalid comparison between Unknown and I4 //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Invalid comparison between Unknown and I4 //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Invalid comparison between Unknown and I4 //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Invalid comparison between Unknown and I4 //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Invalid comparison between Unknown and I4 //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Invalid comparison between Unknown and I4 Text(caption); Rect val = Line(); if (!Visible(val)) { return; } GUI.Box(val, value + ((_focus == key) ? " |" : ""), GUI.skin.textField); Event current = Event.current; if (GUI.enabled && (int)current.type == 0 && ((Rect)(ref val)).Contains(current.mousePosition)) { _focus = key; current.Use(); } if (GUI.enabled && !(_focus != key) && (int)current.type == 4) { if ((current.control || current.command) && (int)current.keyCode == 118) { value += GUIUtility.systemCopyBuffer; } else if ((int)current.keyCode == 8 && value.Length > 0) { value = value.Substring(0, value.Length - 1); } else if ((int)current.keyCode == 127) { value = ""; } else if ((int)current.keyCode == 13 || (int)current.keyCode == 271 || (int)current.keyCode == 9) { _focus = null; } else if (!current.control && !current.alt && !char.IsControl(current.character)) { value += current.character; } current.Use(); } } private void Send(string action, string id = null, string key = null, int val = 0, string text = null, bool confirm = false) { bool flag = action == "nametags" || action == "ban" || action == "unban"; if (!NetworkServer.active && !flag) { _status = "Host a lobby to use this control."; } else if (confirm) { _confirmation = action + " - " + (text ?? key ?? id ?? "selected target") + "?"; _pending = delegate { Send(action, id, key, val, text); }; } else { OrbBehaviour.Cmd(action, id, key, val, text); _status = ((!NetworkServer.active && (action == "ban" || action == "unban")) ? "Local ban list updated for future hosted lobbies." : ("Queued: " + action + ". Check Events for outcome.")); } } private void Details(JsonElement value) { if (value.ValueKind != JsonValueKind.Object) { Text(value.ToString()); return; } Text(string.Join(" | ", from p in value.EnumerateObject() where p.Value.ValueKind != JsonValueKind.Array && p.Value.ValueKind != JsonValueKind.Object select p.Name + ": " + p.Value)); } private void Players() { //IL_0181: Unknown result type (might be due to invalid IL or missing references) //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_01ae: Unknown result type (might be due to invalid IL or missing references) //IL_01c7: Unknown result type (might be due to invalid IL or missing references) //IL_01e5: Unknown result type (might be due to invalid IL or missing references) //IL_01f4: Unknown result type (might be due to invalid IL or missing references) JsonElement snap = Get(Root, "snap"); Text("Join code: " + Str(snap, "code")); Buttons(("Copy join code", delegate { GUIUtility.systemCopyBuffer = Str(snap, "code"); }), ("Name tags: " + OrbBehaviour.NametagsOn, delegate { Send("nametags", null, null, (!OrbBehaviour.NametagsOn) ? 1 : 0); })); if (NetworkServer.active) { bool locked = Flag(snap, "locked"); Buttons((locked ? "Unlock Lobby" : "Lock Lobby (keep current players)", delegate { Send(locked ? "unlocklobby" : "locklobby", null, null, 0, null, confirm: true); })); if (locked) { Text("Locked: current players may reconnect; new players are rejected."); } } Color color = default(Color); foreach (JsonElement item in Rows(Get(snap, "players"))) { Text(Str(item, "name") + (Flag(item, "local") ? " (you)" : "") + " - " + Str(item, "platform"), title: true); Rect rect = Line(16f); int num = 0; foreach (JsonElement item2 in Rows(Get(item, "colors"))) { if (Visible(rect) && ColorUtility.TryParseHtmlString(item2.GetString(), ref color)) { GUI.color = color; GUI.DrawTexture(new Rect((float)(num * 32), ((Rect)(ref rect)).y, 26f, 16f), (Texture)(object)Texture2D.whiteTexture); GUI.color = Color.white; } num++; } Text("ID: " + Str(item, "id") + " Position: " + Get(item, "pos").ToString() + " Speed: " + Str(item, "speed") + " m/s"); Text("Username: " + Str(item, "username") + " Moderation name: " + Str(item, "modName")); if (Flag(item, "local") || Flag(item, "isHost")) { continue; } string id = Str(item, "id"); if (NetworkServer.active) { Buttons(("Kick", delegate { Send("kick", id, null, 0, null, confirm: true); }), ("Ban", delegate { Send("ban", id, null, 0, null, confirm: true); }), ("Lookup identity", delegate { Send("eoslookup", id); })); } else { Buttons(("Ban for future hosted lobbies", delegate { Send("ban", id, null, 0, null, confirm: true); })); } } InputField("password", "New session password (empty removes password)", ref _password); Text("Current password: " + (_showPassword ? Str(snap, "password") : "[hidden]")); Buttons(("Set password", delegate { Send("setpassword", null, null, 0, _password); }), (_showPassword ? "Hide password" : "Show password", delegate { _showPassword = !_showPassword; })); } private void Bans() { InputField("ban", "Ban identifier or connection address", ref _banId); Buttons(("Ban identifier", delegate { if (!string.IsNullOrWhiteSpace(_banId)) { Send("ban", _banId.Trim(), null, 0, null, confirm: true); } }), ("Ban address", delegate { if (!string.IsNullOrWhiteSpace(_banId)) { Send("banaddr", null, _banId.Trim(), 0, "Manual ban", confirm: true); } })); InputField("csv", "CSV file path (empty uses config/ModerationImprovements/bans-export.csv)", ref _csvPath); Buttons(("Export bans CSV", delegate { Csv(import: false); }), ("Import bans CSV", delegate { Csv(import: true); })); Text("Banned players", title: true); foreach (JsonElement item in Rows(Get(Root, "bans"))) { Details(item); string id = Str(item, "id"); Buttons(("Unban", delegate { Send("unban", id); })); } Text("Everyone this session", title: true); foreach (JsonElement item2 in Rows(Get(Root, "roster"))) { Details(item2); string id2 = Str(item2, "id"); bool banned = Flag(item2, "banned"); PlayerCharacter obj = OrbBehaviour.Local(); PlayerNetworking val = ((obj != null) ? obj.playerNetworking : null); if (!((Object)(object)val != (Object)null) || !(id2 == val.identifier)) { Buttons((banned ? "Unban" : "Ban", delegate { Send(banned ? "unban" : "ban", id2, null, 0, null, !banned); })); } } } private void Csv(bool import) { try { string text = (string.IsNullOrWhiteSpace(_csvPath) ? Path.Combine(OrbState.DataDir, "bans-export.csv") : _csvPath.Trim()); if (import) { Send("banimport", null, null, 0, File.ReadAllText(text)); return; } File.WriteAllText(text, OrbState.BansCsv()); _status = "Exported: " + text; } catch (Exception ex) { _status = "CSV: " + ex.Message; } } private void Signs() { if (!NetworkServer.active) { Text("Whiteboard controls are available only while hosting."); return; } InputField("sign", "Replacement sign text (used by Set text)", ref _signText); Text("Locked signs", title: true); foreach (JsonElement item in Rows(Get(Root, "signlocks"))) { Details(item); string key = Str(item, "net"); Buttons(("Unlock", delegate { Send("signunlock", null, key); })); } Text("Sign edit history", title: true); foreach (JsonElement item2 in Rows(Get(Root, "signs")).Reverse()) { Details(item2); string key2 = Str(item2, "net"); string original = Str(item2, "text"); if (!string.IsNullOrEmpty(key2) && !(key2 == "0")) { Buttons(("Erase", delegate { Send("signset", null, key2, 0, ""); }), ("Restore this text", delegate { Send("signset", null, key2, 0, original); }), ("Lock this text", delegate { Send("signlock", null, key2, 0, original); }), ("Set text", delegate { Send("signset", null, key2, 0, _signText); })); } } } private void Logs() { Buttons(("Alerts", delegate { _logKind = "alerts"; _scroll = 0f; }), ("Chat", delegate { _logKind = "chat"; _scroll = 0f; }), ("Events", delegate { _logKind = "events"; _scroll = 0f; }), ("Identity lookups", delegate { _logKind = "identities"; _scroll = 0f; })); Text(_logKind, title: true); JsonElement value = Get(Root, _logKind); if (value.ValueKind == JsonValueKind.Array) { foreach (JsonElement item in Rows(value).Reverse()) { Details(item); } return; } if (value.ValueKind != JsonValueKind.Object) { return; } foreach (JsonProperty item2 in value.EnumerateObject()) { Text(item2.Name); Details(item2.Value); } } private void Settings() { //IL_0280: Unknown result type (might be due to invalid IL or missing references) //IL_0285: Unknown result type (might be due to invalid IL or missing references) Text("Moderation settings", title: true); Buttons(("Join/leave chime: " + Plugin.ChimeEnabled.Value, delegate { Chime.SetEnabled(!Plugin.ChimeEnabled.Value); })); Slider("Chime volume", Plugin.ChimeVolume, 0f, 1f); Buttons(("Auto-kick speed/fly: " + Plugin.FlyAutoKick.Value, delegate { Plugin.FlyAutoKick.Value = !Plugin.FlyAutoKick.Value; }), ("Guard auto-ban: " + Plugin.GuardAutoBan.Value, delegate { Plugin.GuardAutoBan.Value = !Plugin.GuardAutoBan.Value; }), ("Anonymous login guard: " + Plugin.GuardBanAnonymous.Value, delegate { Plugin.GuardBanAnonymous.Value = !Plugin.GuardBanAnonymous.Value; }), ("Filter fake system chat: " + Plugin.GuardChatFilter.Value, delegate { Plugin.GuardChatFilter.Value = !Plugin.GuardChatFilter.Value; })); Text("Speed/fly flags are estimates. Carrying, launches, and lag can trigger them."); Slider("Speed limit (m/s)", Plugin.FlyMaxSpeed, 1f, 100f); Slider("Climbing time limit (seconds)", Plugin.FlyMaxAirSeconds, 1f, 60f); Text("Voice packets / second: " + Plugin.GuardVoiceLimit.Value + " (0 disables limit)"); Buttons(("-10 packets/s", delegate { Plugin.GuardVoiceLimit.Value = Math.Max(0, Plugin.GuardVoiceLimit.Value - 10); }), ("+10 packets/s", delegate { ConfigEntry<int> guardVoiceLimit = Plugin.GuardVoiceLimit; guardVoiceLimit.Value += 10; })); Text("Press " + ((object)Plugin.MenuKey.Value/*cast due to .constrained prefix*/).ToString() + " or Escape to close."); Text("Moderation code taken from Big Orb by RadioFreeOpportunity. AdamMady made this in-game menu."); Buttons(("Open Original Big Orb Repository", delegate { Application.OpenURL("https://github.com/RadioFreeOpportunity/bigorb"); }), ("Moderation Improvements Repository", delegate { Application.OpenURL("https://github.com/AdamMady/Moderation-Improvements/"); })); } private void Slider(string caption, ConfigEntry<float> config, float min, float max) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) Text(caption + ": " + config.Value.ToString("0.00")); Rect val = Line(22f); if (Visible(val)) { float num = GUI.HorizontalSlider(val, config.Value, min, max); if (Math.Abs(num - config.Value) > 0.001f) { config.Value = num; } } } } internal static class OrbState { internal class BanRecord { public string Identifier; public string Name; public ulong PlatformId; public string When; public string Address; } internal class SeenRecord { public string Id; public string Name; public string Username; public ulong PlatformId; public string C1; public string C2; public string C3; public string FirstSeen; public string LastSeen; public bool Online; } internal class AuthInfo { public int ConnId; public string Address; public string ClaimedId; public string Version; public string When; } internal class SignLock { public uint NetId; public string Key; public string Text; public string When; } internal static readonly ConcurrentQueue<Action> MainQueue = new ConcurrentQueue<Action>(); internal static string DataDir; private static readonly object Lock = new object(); private static readonly List<string> Chat = new List<string>(); private static readonly List<string> Signs = new List<string>(); private static readonly List<string> Alerts = new List<string>(); private static readonly List<string> Events = new List<string>(); internal static readonly Dictionary<string, BanRecord> Bans = new Dictionary<string, BanRecord>(); private static readonly HashSet<string> LobbyLockIds = new HashSet<string>(StringComparer.Ordinal); private static readonly HashSet<string> LobbyLockAddresses = new HashSet<string>(StringComparer.Ordinal); internal static volatile string SnapshotJson = "{\"hosting\":false,\"players\":[]}"; private static long LastPollTicks; internal static volatile string SessionName = "no session"; private static volatile string _sessionTag = "nosession"; private static readonly Dictionary<string, SeenRecord> Roster = new Dictionary<string, SeenRecord>(); private static readonly (string addr, string name)[] DefaultBans = new(string, string)[1] { ("0002f3f3f940422b9e85ae1057c285c9", "spoofing client (anonymous EOS login, impersonates players, voice flood)") }; private static readonly Dictionary<int, AuthInfo> AuthByConn = new Dictionary<int, AuthInfo>(); private static readonly Dictionary<string, string> AddrById = new Dictionary<string, string>(); private static readonly Dictionary<uint, SignLock> SignLocks = new Dictionary<uint, SignLock>(); internal static bool LobbyLocked { get; private set; } internal static int LobbyLockCount { get { lock (Lock) { return LobbyLockIds.Count; } } } private static string BansPath => Path.Combine(DataDir, "bans.json"); internal static void Polled() { Interlocked.Exchange(ref LastPollTicks, Environment.TickCount64); } internal static void NewSession(string worldName) { SessionName = (string.IsNullOrEmpty(worldName) ? "unnamed" : worldName); string value = new string(SessionName.Select((char c) => (!char.IsLetterOrDigit(c)) ? '_' : c).ToArray()); _sessionTag = $"{value}-{DateTime.Now:yyyyMMdd-HHmmss}"; lock (Lock) { Chat.Clear(); Signs.Clear(); Alerts.Clear(); Events.Clear(); Roster.Clear(); SignLocks.Clear(); LobbyLocked = false; LobbyLockIds.Clear(); LobbyLockAddresses.Clear(); } AddEvent("session", null, "host", "hosting started: " + SessionName); } internal static int SetLobbyLocked(IEnumerable<string> ids, IEnumerable<string> addresses) { lock (Lock) { LobbyLockIds.Clear(); LobbyLockAddresses.Clear(); foreach (string item in ids ?? Enumerable.Empty<string>()) { if (!IsUnsetIdentifier(item)) { LobbyLockIds.Add(item); } } foreach (string item2 in addresses ?? Enumerable.Empty<string>()) { if (!string.IsNullOrEmpty(item2) && item2 != "localhost") { LobbyLockAddresses.Add(item2); } } LobbyLocked = true; return LobbyLockIds.Count; } } internal static void UnlockLobby() { lock (Lock) { LobbyLocked = false; LobbyLockIds.Clear(); LobbyLockAddresses.Clear(); } } internal static bool LobbyAllows(string id, string address) { lock (Lock) { return !LobbyLocked || (!IsUnsetIdentifier(id) && LobbyLockIds.Contains(id)) || (!string.IsNullOrEmpty(address) && LobbyLockAddresses.Contains(address)); } } internal static void RosterSeen(string id, string name, string username, ulong platformId, string c1, string c2, string c3, bool online) { lock (Lock) { if (!Roster.TryGetValue(id, out var value)) { Dictionary<string, SeenRecord> roster = Roster; SeenRecord obj = new SeenRecord { Id = id, FirstSeen = DateTime.Now.ToString("HH:mm:ss") }; value = obj; roster[id] = obj; } value.Name = name; value.Username = username; value.PlatformId = platformId; value.C1 = c1; value.C2 = c2; value.C3 = c3; value.Online = online; if (online) { value.LastSeen = DateTime.Now.ToString("HH:mm:ss"); } } } internal static void RosterOffline(string id) { lock (Lock) { if (Roster.TryGetValue(id, out var value)) { value.Online = false; } } } internal static string RosterName(string id) { if (string.IsNullOrEmpty(id)) { return null; } lock (Lock) { SeenRecord value; return Roster.TryGetValue(id, out value) ? value.Name : null; } } internal static string RosterJson() { lock (Lock) { return "[" + string.Join(",", from r in Roster.Values orderby r.FirstSeen select $"{{\"id\":{J(r.Id)},\"name\":{J(r.Name)},\"username\":{J(r.Username)},\"platformId\":\"{r.PlatformId}\",\"colors\":[{J(r.C1)},{J(r.C2)},{J(r.C3)}],\"first\":{J(r.FirstSeen)},\"last\":{J(r.LastSeen)},\"online\":{(r.Online ? "true" : "false")},\"banned\":{(IsBanned(r.Id) ? "true" : "false")}}}") + "]"; } } internal static void Init() { DataDir = Path.Combine(Paths.ConfigPath, "ModerationImprovements"); Directory.CreateDirectory(DataDir); Directory.CreateDirectory(Path.Combine(DataDir, "logs")); LoadBans(); SeedDefaultBans(); } internal static string J(string s) { if (s == null) { return "null"; } StringBuilder stringBuilder = new StringBuilder("\""); for (int i = 0; i < s.Length; i++) { char c = s[i]; StringBuilder stringBuilder2 = stringBuilder; stringBuilder2.Append(c switch { '"' => "\\\"", '\\' => "\\\\", '\n' => "\\n", '\r' => "\\r", '\t' => "\\t", _ => (c >= ' ') ? c.ToString() : $"\\u{c:x4}", }); } return stringBuilder.Append('"').ToString(); } internal static string Now() { return DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); } private static void Add(List<string> list, string json, string file) { lock (Lock) { list.Add(json); if (list.Count > 500) { list.RemoveAt(0); } } try { File.AppendAllText(Path.Combine(DataDir, "logs", _sessionTag + "-" + file + ".jsonl"), json + "\n"); } catch { } } internal static void AddChat(string id, string name, string msg) { Add(Chat, $"{{\"t\":{J(Now())},\"id\":{J(id)},\"name\":{J(name)},\"msg\":{J(msg)}}}", "chat"); } internal static void AddSign(string key, string text, string byId, string byName, uint netId = 0u) { Add(Signs, $"{{\"t\":{J(Now())},\"key\":{J(key)},\"text\":{J(text)},\"byId\":{J(byId)},\"byName\":{J(byName)},\"net\":{netId}}}", "signs"); } internal static void AddAlert(string kind, string id, string name, string detail) { Add(Alerts, $"{{\"t\":{J(Now())},\"kind\":{J(kind)},\"id\":{J(id)},\"name\":{J(name)},\"detail\":{J(detail)}}}", "alerts"); } internal static void AddEvent(string kind, string id, string name, string detail = null) { Add(Events, $"{{\"t\":{J(Now())},\"kind\":{J(kind)},\"id\":{J(id)},\"name\":{J(name)},\"detail\":{J(detail)}}}", "events"); } internal static string Tail(string which, int n = 100) { List<string> list = which switch { "chat" => Chat, "signs" => Signs, "alerts" => Alerts, _ => Events, }; lock (Lock) { return "[" + string.Join(",", list.Skip(Math.Max(0, list.Count - n))) + "]"; } } internal static void LoadBans() { try { if (!File.Exists(BansPath)) { return; } lock (Lock) { Bans.Clear(); string[] array = File.ReadAllLines(BansPath); for (int i = 0; i < array.Length; i++) { string[] array2 = array[i].Split('\t'); if (array2.Length >= 4 && array2[0].Length > 0) { Bans[array2[0]] = new BanRecord { Identifier = array2[0], Name = array2[1], PlatformId = (ulong.TryParse(array2[2], out var result) ? result : 0), When = array2[3], Address = ((array2.Length >= 5 && array2[4].Length > 0) ? array2[4] : null) }; } } } } catch (Exception ex) { Plugin.Logger.LogError((object)("loading bans: " + ex.Message)); } } private static void SaveBans() { lock (Lock) { File.WriteAllLines(BansPath, Bans.Values.Select((BanRecord b) => $"{b.Identifier}\t{b.Name?.Replace('\t', ' ')}\t{b.PlatformId}\t{b.When}\t{b.Address ?? ""}")); } } internal static bool IsUnsetIdentifier(string id) { if (!string.IsNullOrEmpty(id)) { return id == "0"; } return true; } internal static string Platform(string id) { if (IsUnsetIdentifier(id)) { return "unset"; } if (id.Length == 17 && id.StartsWith("7656119") && id.All(char.IsDigit)) { return "steam"; } if (id.Length >= 18 && id.All(char.IsDigit)) { return "psn"; } if (id.Length >= 36 && id[8] == '-' && id[13] == '-' && id[18] == '-' && id[23] == '-') { if (id.Length <= 36 || id[36] != '-') { return "uuid"; } return "uuid" + id.Substring(36); } return "other"; } internal static bool BanAdd(string id, string name, ulong platformId, string address = null) { if (IsUnsetIdentifier(id)) { if (string.IsNullOrEmpty(address)) { AddAlert("banfail", id, name, "identifier not synced yet, ban NOT recorded, retry in a moment"); return false; } id = "addr:" + address; } lock (Lock) { Bans[id] = new BanRecord { Identifier = id, Name = name, PlatformId = platformId, When = Now(), Address = address }; } SaveBans(); AddEvent("ban", id, name, Platform(id) + ((address != null) ? (" addr " + address) : "")); return true; } internal static bool IsBannedAddress(string address) { if (string.IsNullOrEmpty(address) || address == "localhost") { return false; } lock (Lock) { return Bans.Values.Any((BanRecord b) => b.Address == address); } } internal static void BanAttachAddress(string id, string address) { if (string.IsNullOrEmpty(address)) { return; } bool flag = false; lock (Lock) { if (Bans.TryGetValue(id, out var value) && value.Address == null) { value.Address = address; flag = true; } } if (flag) { SaveBans(); AddEvent("ban", id, RosterName(id), "ban now carries address " + address); } } internal static string BansCsv() { StringBuilder stringBuilder = new StringBuilder("identifier,name,platformId,when,address\n"); lock (Lock) { foreach (BanRecord item in Bans.Values.OrderBy((BanRecord b) => b.When)) { stringBuilder.Append(Csv(item.Identifier)).Append(',').Append(Csv(item.Name)) .Append(',') .Append(item.PlatformId) .Append(',') .Append(Csv(item.When)) .Append(',') .Append(Csv(item.Address)) .Append('\n'); } } return stringBuilder.ToString(); } private static string Csv(string s) { if (s == null) { s = ""; } if (s.IndexOfAny(new char[3] { ',', '"', '\n' }) < 0) { return s; } return "\"" + s.Replace("\"", "\"\"") + "\""; } internal static (int added, int skipped, int bad) BansImport(string csv) { int num = 0; int num2 = 0; int num3 = 0; if (string.IsNullOrEmpty(csv)) { return (added: 0, skipped: 0, bad: 0); } string[] array = csv.Split('\n'); foreach (string obj in array) { string text = obj.TrimEnd('\r').Trim(); if (text.Length == 0 || text.StartsWith("identifier,") || text.StartsWith("#")) { continue; } List<string> list = SplitCsv(text); if (list.Count < 1) { num3++; continue; } string text2 = list[0].Trim(); string name = ((list.Count > 1) ? list[1] : ""); string addr = ((list.Count > 4) ? list[4].Trim() : ""); ulong.TryParse((list.Count > 2) ? list[2] : "0", out var result); if (addr.Length == 0 && text2.StartsWith("addr:")) { addr = text2.Substring(5); } if (IsUnsetIdentifier(text2) && addr.Length == 0) { num3++; continue; } if (text2.StartsWith("addr:") || IsUnsetIdentifier(text2)) { text2 = "addr:" + addr; } bool flag; lock (Lock) { flag = Bans.ContainsKey(text2) || (addr.Length > 0 && Bans.Values.Any((BanRecord b) => b.Address == addr)); } if (flag) { num2++; continue; } lock (Lock) { Bans[text2] = new BanRecord { Identifier = text2, Name = name, PlatformId = result, When = ((list.Count > 3 && list[3].Length > 0) ? list[3] : Now()), Address = ((addr.Length > 0) ? addr : null) }; } num++; } if (num > 0) { SaveBans(); } AddEvent("banimport", null, "host", $"{num} added, {num2} already banned, {num3} unreadable"); return (added: num, skipped: num2, bad: num3); } private static List<string> SplitCsv(string line) { List<string> list = new List<string>(); StringBuilder stringBuilder = new StringBuilder(); bool flag = false; for (int i = 0; i < line.Length; i++) { char c = line[i]; if (flag) { if (c == '"') { if (i + 1 < line.Length && line[i + 1] == '"') { stringBuilder.Append('"'); i++; } else { flag = false; } } else { stringBuilder.Append(c); } continue; } switch (c) { case '"': flag = true; break; case ',': list.Add(stringBuilder.ToString()); stringBuilder.Clear(); break; default: stringBuilder.Append(c); break; } } list.Add(stringBuilder.ToString()); return list; } internal static void SeedDefaultBans() { int num = 0; (string, string)[] defaultBans = DefaultBans; for (int i = 0; i < defaultBans.Length; i++) { var (text, name) = defaultBans[i]; if (!IsBannedAddress(text)) { lock (Lock) { Bans["addr:" + text] = new BanRecord { Identifier = "addr:" + text, Name = name, When = Now(), Address = text }; } num++; } } if (num > 0) { SaveBans(); AddEvent("ban", null, "host", $"{num} known bad address(es) added from the built-in list"); } } internal static void BanRemove(string id) { BanRecord value = null; lock (Lock) { if (Bans.TryGetValue(id, out value)) { Bans.Remove(id); } } if (value != null) { SaveBans(); AddEvent("unban", id, value.Name); } } internal static bool IsBanned(string id) { lock (Lock) { return id != null && Bans.ContainsKey(id); } } internal static string LastAddressFor(string id) { lock (Lock) { string value; return AddrById.TryGetValue(id ?? "", out value) ? value : null; } } internal static AuthInfo AuthSeen(int connId, string address, string claimedId, string version) { AuthInfo authInfo = new AuthInfo { ConnId = connId, Address = address, ClaimedId = claimedId, Version = version, When = Now() }; lock (Lock) { AuthByConn[connId] = authInfo; if (!string.IsNullOrEmpty(address) && !string.IsNullOrEmpty(claimedId)) { AddrById[claimedId] = address; } } return authInfo; } internal static AuthInfo AuthFor(int connId) { lock (Lock) { AuthInfo value; return AuthByConn.TryGetValue(connId, out value) ? value : null; } } internal static void LockSign(uint netId, string key, string text) { lock (Lock) { SignLocks[netId] = new SignLock { NetId = netId, Key = key, Text = (text ?? ""), When = Now() }; } } internal static bool UnlockSign(uint netId) { lock (Lock) { return SignLocks.Remove(netId); } } internal static string LockedText(uint netId) { lock (Lock) { SignLock value; return SignLocks.TryGetValue(netId, out value) ? value.Text : null; } } internal static string SignLocksJson() { lock (Lock) { return "[" + string.Join(",", from l in SignLocks.Values orderby l.When select $"{{\"net\":{l.NetId},\"key\":{J(l.Key)},\"text\":{J(l.Text)},\"when\":{J(l.When)}}}") + "]"; } } internal static string BansJson() { lock (Lock) { return "[" + string.Join(",", Bans.Values.Select((BanRecord b) => $"{{\"id\":{J(b.Identifier)},\"name\":{J(b.Name)},\"platformId\":\"{b.PlatformId}\",\"when\":{J(b.When)},\"addr\":{J(b.Address)}}}")) + "]"; } } } internal static class Patches { [HarmonyPatch(typeof(PlayerNetworking), "UserCode_CmdSendTextChatMessage__String")] internal static class ChatCmdPatch { private static void Postfix(PlayerNetworking __instance, string __0) { try { LogChat("chat/cmd", __instance.identifier, Display(__instance), __0); } catch { } } } [HarmonyPatch(typeof(PlayerNetworking), "RpcTextChatMessage")] internal static class ChatRpcPatch { private static void Postfix(PlayerNetworking __instance, string __0) { try { LogChat("chat/rpc", __instance.identifier, Display(__instance), __0); } catch { } } } [HarmonyPatch(typeof(PlayerTexter), "ReceieveMessage")] internal static class ChatReceivePatch { private static void Postfix(PlayerTexter __instance, string __0) { try { PlayerCharacter playerCharacter = __instance.playerCharacter; PlayerNetworking val = ((playerCharacter != null) ? playerCharacter.playerNetworking : null); LogChat("chat/receive", (val != null) ? val.identifier : null, ((Object)(object)val != (Object)null) ? Display(val) : "?", __0); } catch { } } } [HarmonyPatch(typeof(PlayerTexter), "DisplayMessage")] internal static class ChatDisplayPatch { private static void Postfix(PlayerTexter __instance, string __0) { try { PlayerCharacter playerCharacter = __instance.playerCharacter; PlayerNetworking val = ((playerCharacter != null) ? playerCharacter.playerNetworking : null); LogChat("chat/display", (val != null) ? val.identifier : null, ((Object)(object)val != (Object)null) ? Display(val) : "?", __0); } catch { } } } [HarmonyPatch(typeof(PeckEffectTextInput), "CmdSendNewText")] internal static class SignCmdPatch { private static void Postfix(PeckEffectTextInput __instance, string __0, string __1) { try { LogSign("sign/cmd", SignKey(__instance), __0, __1, SignNetId(__instance)); } catch { } } } [HarmonyPatch(typeof(PeckEffectTextInput), "OnChangeNetworkedText")] internal static class SignSyncPatch { private static void Postfix(PeckEffectTextInput __instance, string __1) { try { MarkFired("sign/sync"); PeckEffectTextInput inst = __instance;