Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of PerPlayerSettings v0.5.0
PerPlayerSettings.dll
Decompiled 8 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using JG224.ModCore.API; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("PerPlayerSettings")] [assembly: AssemblyDescription("Per-player, server-forced Valheim mod settings.")] [assembly: AssemblyCompany("jg224")] [assembly: AssemblyProduct("PerPlayerSettings")] [assembly: AssemblyFileVersion("0.5.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyVersion("0.5.0.0")] namespace PerPlayerSettings; public sealed class EditBudget { private readonly Dictionary<long, Queue<double>> _windows = new Dictionary<long, Queue<double>>(); public bool Accept(long peer, double now) { if (double.IsNaN(now) || double.IsInfinity(now)) { return false; } if (!_windows.TryGetValue(peer, out var value)) { _windows.Add(peer, value = new Queue<double>()); } while (value.Count > 0 && now - value.Peek() >= 1.0) { value.Dequeue(); } if (value.Count >= 20) { return false; } value.Enqueue(now); return true; } public void Retain(ISet<long> peers) { foreach (long item in new List<long>(_windows.Keys)) { if (!peers.Contains(item)) { _windows.Remove(item); } } } public void Clear() { _windows.Clear(); } } internal static class MiniJson { public static object Parse(string json) { int pos = 0; object result = ParseValue(json, ref pos); SkipWhitespace(json, ref pos); if (pos != json.Length) { throw new FormatException("Trailing characters at position " + pos); } return result; } private static object ParseValue(string s, ref int pos) { SkipWhitespace(s, ref pos); if (pos >= s.Length) { throw new FormatException("Unexpected end of JSON"); } switch (s[pos]) { case '{': return ParseObject(s, ref pos); case '[': return ParseArray(s, ref pos); case '"': return ParseString(s, ref pos); case 't': Expect(s, ref pos, "true"); return true; case 'f': Expect(s, ref pos, "false"); return false; case 'n': Expect(s, ref pos, "null"); return null; default: return ParseNumber(s, ref pos); } } private static Dictionary<string, object> ParseObject(string s, ref int pos) { Dictionary<string, object> dictionary = new Dictionary<string, object>(StringComparer.Ordinal); pos++; SkipWhitespace(s, ref pos); if (pos < s.Length && s[pos] == '}') { pos++; return dictionary; } while (true) { SkipWhitespace(s, ref pos); if (pos >= s.Length || s[pos] != '"') { throw new FormatException("Expected object key at position " + pos); } string key = ParseString(s, ref pos); SkipWhitespace(s, ref pos); if (pos >= s.Length || s[pos] != ':') { throw new FormatException("Expected ':' at position " + pos); } pos++; dictionary[key] = ParseValue(s, ref pos); SkipWhitespace(s, ref pos); if (pos >= s.Length) { throw new FormatException("Unterminated object"); } if (s[pos] != ',') { break; } pos++; } if (s[pos] == '}') { pos++; return dictionary; } throw new FormatException("Expected ',' or '}' at position " + pos); } private static List<object> ParseArray(string s, ref int pos) { List<object> list = new List<object>(); pos++; SkipWhitespace(s, ref pos); if (pos < s.Length && s[pos] == ']') { pos++; return list; } while (true) { list.Add(ParseValue(s, ref pos)); SkipWhitespace(s, ref pos); if (pos >= s.Length) { throw new FormatException("Unterminated array"); } if (s[pos] != ',') { break; } pos++; } if (s[pos] == ']') { pos++; return list; } throw new FormatException("Expected ',' or ']' at position " + pos); } private static string ParseString(string s, ref int pos) { pos++; StringBuilder stringBuilder = new StringBuilder(); while (pos < s.Length) { char c = s[pos++]; switch (c) { case '"': return stringBuilder.ToString(); default: stringBuilder.Append(c); continue; case '\\': break; } if (pos >= s.Length) { break; } char c2 = s[pos++]; switch (c2) { case '"': stringBuilder.Append('"'); break; case '\\': stringBuilder.Append('\\'); break; case '/': stringBuilder.Append('/'); break; case 'b': stringBuilder.Append('\b'); break; case 'f': stringBuilder.Append('\f'); break; case 'n': stringBuilder.Append('\n'); break; case 'r': stringBuilder.Append('\r'); break; case 't': stringBuilder.Append('\t'); break; case 'u': if (pos + 4 > s.Length) { throw new FormatException("Bad \\u escape at position " + pos); } stringBuilder.Append((char)Convert.ToInt32(s.Substring(pos, 4), 16)); pos += 4; break; default: throw new FormatException("Bad escape '\\" + c2 + "' at position " + pos); } } throw new FormatException("Unterminated string"); } private static object ParseNumber(string s, ref int pos) { int num = pos; while (pos < s.Length && "+-0123456789.eE".IndexOf(s[pos]) >= 0) { pos++; } string text = s.Substring(num, pos - num); if (long.TryParse(text, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { return result; } if (double.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out var result2)) { return result2; } throw new FormatException("Invalid number '" + text + "' at position " + num); } private static void Expect(string s, ref int pos, string literal) { if (string.CompareOrdinal(s, pos, literal, 0, literal.Length) != 0) { throw new FormatException("Invalid literal at position " + pos); } pos += literal.Length; } private static void SkipWhitespace(string s, ref int pos) { while (pos < s.Length && char.IsWhiteSpace(s[pos])) { pos++; } } public static string Serialize(object value) { StringBuilder stringBuilder = new StringBuilder(); WriteValue(stringBuilder, value); return stringBuilder.ToString(); } private static void WriteValue(StringBuilder sb, object value) { if (value == null) { sb.Append("null"); } else if (value is string) { WriteString(sb, (string)value); } else if (value is bool) { sb.Append(((bool)value) ? "true" : "false"); } else if (value is double || value is float) { sb.Append(Convert.ToDouble(value, CultureInfo.InvariantCulture).ToString("R", CultureInfo.InvariantCulture)); } else if (value is Enum) { WriteString(sb, value.ToString()); } else if (value is Dictionary<string, object> dictionary) { sb.Append('{'); bool flag = true; foreach (KeyValuePair<string, object> item in dictionary) { if (!flag) { sb.Append(','); } flag = false; WriteString(sb, item.Key); sb.Append(':'); WriteValue(sb, item.Value); } sb.Append('}'); } else if (value is IEnumerable enumerable) { sb.Append('['); bool flag2 = true; foreach (object item2 in enumerable) { if (!flag2) { sb.Append(','); } flag2 = false; WriteValue(sb, item2); } sb.Append(']'); } else if (value is IConvertible) { sb.Append(Convert.ToString(value, CultureInfo.InvariantCulture)); } else { WriteString(sb, value.ToString()); } } private static void WriteString(StringBuilder sb, string s) { sb.Append('"'); foreach (char c in s) { switch (c) { case '"': sb.Append("\\\""); continue; case '\\': sb.Append("\\\\"); continue; case '\b': sb.Append("\\b"); continue; case '\f': sb.Append("\\f"); continue; case '\n': sb.Append("\\n"); continue; case '\r': sb.Append("\\r"); continue; case '\t': sb.Append("\\t"); continue; } if (c < ' ') { StringBuilder stringBuilder = sb.Append("\\u"); int num = c; stringBuilder.Append(num.ToString("x4", CultureInfo.InvariantCulture)); } else { sb.Append(c); } } sb.Append('"'); } } public class PlayerData { public string Name = ""; public bool Preregistered; public Dictionary<string, string> Settings = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); } public class PlayerStore { private sealed class UnsupportedSchemaException : FormatException { internal UnsupportedSchemaException() : base("Unsupported player store schema version") { } } private const long SchemaVersion = 1L; private readonly string _path; private readonly Dictionary<string, PlayerData> _players = new Dictionary<string, PlayerData>(StringComparer.Ordinal); private readonly List<Dictionary<string, object>> _audit = new List<Dictionary<string, object>>(); private bool _batching; public bool HasUnsavedChanges { get; private set; } public bool ReadOnly { get; private set; } public string LastSaveError { get; private set; } public IEnumerable<string> AuditDescriptions => _audit.Select((Dictionary<string, object> entry) => entry["id"]?.ToString() + " | " + entry["utc"]?.ToString() + " | " + entry["actor"]?.ToString() + " | " + entry["command"]); public IEnumerable<KeyValuePair<string, PlayerData>> All => _players; public PlayerStore(string path) { _path = path; } public void Load() { _players.Clear(); _audit.Clear(); ReadOnly = false; HasUnsavedChanges = false; LastSaveError = null; if (TryLoadFile(_path, out var players, out var error)) { Adopt(players); LoadAudit(_path); return; } bool flag = File.Exists(_path); if (error is UnsupportedSchemaException) { ReadOnly = true; LastSaveError = error.Message; PerPlayerSettingsPlugin.Log.LogError((object)"Player store was written with an unsupported schema. The primary and backup are preserved; writes are blocked until a compatible plugin is installed."); return; } if (flag && error != null) { string text = _path + ".corrupt-" + DateTime.Now.ToString("yyyyMMdd-HHmmss-fff"); try { File.Copy(_path, text, overwrite: false); PerPlayerSettingsPlugin.Log.LogError((object)("Failed to parse " + _path + " (" + error.Message + "). Copied the damaged file to " + text + ".")); } catch (Exception ex) { PerPlayerSettingsPlugin.Log.LogError((object)("Failed to parse " + _path + " (" + error.Message + ") and could not preserve it: " + ex.Message)); } } string text2 = _path + ".bak"; if (TryLoadFile(text2, out players, out var error2)) { Adopt(players); LoadAudit(text2); try { File.Copy(text2, _path, overwrite: true); } catch (Exception ex2) { ReadOnly = true; PerPlayerSettingsPlugin.Log.LogWarning((object)("Loaded backup " + text2 + " but could not restore the primary file: " + ex2.Message)); } PerPlayerSettingsPlugin.Log.LogWarning((object)("Recovered " + _players.Count + " player record(s) from " + text2 + ".")); } else { if (error2 != null) { PerPlayerSettingsPlugin.Log.LogError((object)("Failed to parse backup " + text2 + " (" + error2.Message + "). Writes are blocked until recovery.")); } else if (flag) { PerPlayerSettingsPlugin.Log.LogError((object)"No usable backup was found. Writes are blocked until recovery."); } ReadOnly = flag || File.Exists(text2); } } private static bool TryLoadFile(string path, out Dictionary<string, PlayerData> players, out Exception error) { players = null; error = null; if (!File.Exists(path)) { return false; } try { Dictionary<string, object> obj = (MiniJson.Parse(File.ReadAllText(path)) as Dictionary<string, object>) ?? throw new FormatException("Root is not an object"); if (obj.TryGetValue("schemaVersion", out var value) && (!(value is long) || (long)value != 1)) { throw new UnsupportedSchemaException(); } if (!TryGetObject(obj, "players", out var value2)) { throw new FormatException("Missing 'players' object"); } players = new Dictionary<string, PlayerData>(StringComparer.Ordinal); foreach (KeyValuePair<string, object> item in value2) { if (!(item.Value is Dictionary<string, object> fields)) { throw new FormatException("Invalid player record"); } players[item.Key] = ParsePlayer(fields); } return true; } catch (Exception ex) { error = ex; return false; } } private void Adopt(Dictionary<string, PlayerData> players) { _players.Clear(); foreach (KeyValuePair<string, PlayerData> player in players) { _players[player.Key] = player.Value; } } private static PlayerData ParsePlayer(Dictionary<string, object> fields) { PlayerData playerData = new PlayerData(); if (fields.TryGetValue("name", out var value) && value != null) { if (!(value is string)) { throw new FormatException("Invalid player name"); } playerData.Name = (string)value; } if (fields.TryGetValue("preregistered", out var value2)) { if (!(value2 is bool)) { throw new FormatException("Invalid preregistration marker"); } playerData.Preregistered = (bool)value2; if (playerData.Preregistered && string.IsNullOrWhiteSpace(playerData.Name)) { throw new FormatException("Preregistered player name is empty"); } } if (!TryGetObject(fields, "settings", out var value3)) { throw new FormatException("Invalid player settings"); } foreach (KeyValuePair<string, object> item in value3) { if (!(item.Value is string)) { throw new FormatException("Invalid setting value"); } playerData.Settings[item.Key] = (string)item.Value; } return playerData; } private static bool TryGetObject(Dictionary<string, object> parent, string key, out Dictionary<string, object> value) { value = null; if (!parent.TryGetValue(key, out var value2)) { return false; } value = value2 as Dictionary<string, object>; return value != null; } public void MarkDirty() { HasUnsavedChanges = true; } public void BeginBatch() { _batching = true; } public void EndBatch() { _batching = false; } private Dictionary<string, object> PlayersObject() { Dictionary<string, object> dictionary = new Dictionary<string, object>(); foreach (KeyValuePair<string, PlayerData> item in _players.OrderBy<KeyValuePair<string, PlayerData>, string>((KeyValuePair<string, PlayerData> pair) => pair.Key, StringComparer.Ordinal)) { Dictionary<string, object> dictionary2 = new Dictionary<string, object>(); dictionary2["name"] = item.Value.Name ?? ""; if (item.Value.Preregistered) { dictionary2["preregistered"] = true; } Dictionary<string, object> dictionary3 = new Dictionary<string, object>(); foreach (KeyValuePair<string, string> item2 in item.Value.Settings.OrderBy<KeyValuePair<string, string>, string>((KeyValuePair<string, string> pair) => pair.Key, StringComparer.Ordinal)) { dictionary3[item2.Key] = item2.Value; } dictionary2["settings"] = dictionary3; dictionary[item.Key] = dictionary2; } return dictionary; } public string Snapshot() { return MiniJson.Serialize(PlayersObject()); } private static string Hash(string text) { using SHA256 sHA = SHA256.Create(); return Convert.ToBase64String(sHA.ComputeHash(Encoding.UTF8.GetBytes(text))); } public void RecordAudit(string before, string actor, string command) { string text = Snapshot(); if (!(before == text)) { Dictionary<string, object> dictionary = new Dictionary<string, object>(); dictionary["id"] = Guid.NewGuid().ToString("N"); dictionary["utc"] = DateTime.UtcNow.ToString("O"); dictionary["actor"] = actor; dictionary["command"] = ((command.Length > 256) ? command.Substring(0, 256) : command); dictionary["before"] = before; dictionary["afterHash"] = Hash(text); _audit.Add(dictionary); while (_audit.Count > 32 || _audit.Sum((Dictionary<string, object> item) => ((string)item["before"]).Length) > 2097152) { _audit.RemoveAt(0); } MarkDirty(); } } public bool Rollback(string id, out string error) { error = null; Dictionary<string, object> dictionary = _audit.FirstOrDefault((Dictionary<string, object> item) => string.Equals((string)item["id"], id, StringComparison.Ordinal)); if (ReadOnly || dictionary == null) { error = "Audit entry not found or storage is read-only."; return false; } if ((string)dictionary["afterHash"] != Hash(Snapshot())) { error = "Values changed after this operation; rollback would overwrite newer work."; return false; } if (!(MiniJson.Parse((string)dictionary["before"]) is Dictionary<string, object> dictionary2)) { error = "Audit snapshot is invalid."; return false; } Dictionary<string, PlayerData> dictionary3 = new Dictionary<string, PlayerData>(StringComparer.Ordinal); foreach (KeyValuePair<string, object> item in dictionary2) { if (!(item.Value is Dictionary<string, object> dictionary4) || !TryGetObject(dictionary4, "settings", out var _)) { error = "Audit snapshot is invalid."; return false; } try { dictionary3[item.Key] = ParsePlayer(dictionary4); } catch (FormatException) { error = "Audit snapshot is invalid."; return false; } } Adopt(dictionary3); MarkDirty(); return true; } private void LoadAudit(string path) { try { if (!(MiniJson.Parse(File.ReadAllText(path)) is Dictionary<string, object> dictionary) || !dictionary.TryGetValue("audit", out var value) || !(value is List<object>)) { return; } foreach (object item in ((List<object>)value).Take(32)) { Dictionary<string, object> entry = item as Dictionary<string, object>; if (entry != null && !new string[6] { "id", "utc", "actor", "command", "before", "afterHash" }.Any((string key) => !entry.ContainsKey(key) || !(entry[key] is string))) { if (((string)entry["before"]).Length <= 2097152) { _audit.Add(entry); } while (_audit.Sum((Dictionary<string, object> item) => ((string)item["before"]).Length) > 2097152) { _audit.RemoveAt(0); } } } } catch (Exception ex) { PerPlayerSettingsPlugin.Log.LogWarning((object)("Audit history could not be read: " + ex.Message)); } } public bool Save() { MarkDirty(); if (ReadOnly) { LastSaveError = "Storage recovery is required before writes are allowed."; return false; } if (_batching) { return false; } string text = null; try { string value = MiniJson.Serialize(new Dictionary<string, object> { ["schemaVersion"] = 1L, ["players"] = PlayersObject(), ["audit"] = _audit.Cast<object>().ToList() }); string directoryName = Path.GetDirectoryName(_path); if (!string.IsNullOrEmpty(directoryName)) { Directory.CreateDirectory(directoryName); } text = _path + ".tmp-" + Guid.NewGuid().ToString("N"); using (FileStream fileStream = new FileStream(text, FileMode.CreateNew, FileAccess.Write, FileShare.None)) { using StreamWriter streamWriter = new StreamWriter(fileStream, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); streamWriter.Write(value); streamWriter.Flush(); fileStream.Flush(flushToDisk: true); } if (File.Exists(_path)) { File.Replace(text, _path, _path + ".bak", ignoreMetadataErrors: true); } else { File.Move(text, _path); } text = null; HasUnsavedChanges = false; LastSaveError = null; return true; } catch (Exception ex) { PerPlayerSettingsPlugin.Log.LogError((object)("Failed to save " + _path + ": " + ex.Message)); LastSaveError = ex.Message; return false; } finally { if (text != null) { try { File.Delete(text); } catch { } } } } public PlayerData Get(string id) { if (!_players.TryGetValue(id, out var value)) { return null; } return value; } public PlayerData GetOrCreate(string id) { if (!_players.TryGetValue(id, out var value)) { value = new PlayerData(); _players[id] = value; } return value; } public bool HasSettings(string id) { PlayerData playerData = Get(id); if (playerData != null) { return playerData.Settings.Count > 0; } return false; } public string FindPreregistered(string name) { string text = null; foreach (KeyValuePair<string, PlayerData> player in _players) { if (player.Value.Preregistered && string.Equals(player.Value.Name, name, StringComparison.OrdinalIgnoreCase)) { if (text != null) { return null; } text = player.Key; } } return text; } public string GetOrCreatePreregistered(string name) { if (ReadOnly) { throw new InvalidOperationException("Player storage is read-only."); } if (string.IsNullOrWhiteSpace(name)) { throw new ArgumentException("A preregistered player needs a name.", "name"); } string text = FindPreregistered(name); if (text != null) { return text; } if (_players.Values.Any((PlayerData data) => data.Preregistered && string.Equals(data.Name, name, StringComparison.OrdinalIgnoreCase))) { throw new InvalidOperationException("Multiple preregistrations have this name; remove the ambiguous records by ID first."); } string text2 = "preregistered/" + Guid.NewGuid().ToString("N"); _players.Add(text2, new PlayerData { Name = name, Preregistered = true }); MarkDirty(); return text2; } public bool MergePreregistered(string accountId, string playerName) { if (ReadOnly || string.IsNullOrEmpty(accountId) || string.IsNullOrWhiteSpace(playerName)) { return false; } string text = FindPreregistered(playerName); if (text == null || text == accountId) { return false; } PlayerData playerData = _players[text]; if (playerData.Settings.Count == 0) { return false; } PlayerData orCreate = GetOrCreate(accountId); if (orCreate.Preregistered) { return false; } foreach (KeyValuePair<string, string> setting in playerData.Settings) { if (!orCreate.Settings.ContainsKey(setting.Key)) { orCreate.Settings[setting.Key] = setting.Value; } } if (string.IsNullOrEmpty(orCreate.Name)) { orCreate.Name = playerName; } _players.Remove(text); MarkDirty(); return true; } public bool Remove(string id) { return _players.Remove(id); } public string FindByName(string name) { foreach (KeyValuePair<string, PlayerData> player in _players) { if (string.Equals(player.Value.Name, name, StringComparison.OrdinalIgnoreCase)) { return player.Key; } } return null; } } [BepInPlugin("jg224.PerPlayerSettings", "PerPlayerSettings", "0.5.0")] [BepInDependency("com.jg224.modcore", "0.5.0")] public class PerPlayerSettingsPlugin : BaseUnityPlugin { public const string Guid = "jg224.PerPlayerSettings"; public const string Name = "PerPlayerSettings"; public const string Version = "0.5.0"; public const string ModCoreGuid = "com.jg224.modcore"; public const int ProtocolVersion = 2; public static readonly ModuleId ModuleId = new ModuleId("perplayersettings"); internal static PerPlayerSettingsPlugin Instance; internal static ManualLogSource Log; internal static ICoreServices Core; private readonly List<IDisposable> _coreRegistrations = new List<IDisposable>(); private bool _shutDown; private ConfigEntry<bool> _enabled; private ConfigEntry<bool> _autoCapture; private ConfigEntry<float> _applyDelaySeconds; private ConfigEntry<float> _watchdogSeconds; private ConfigEntry<string> _syncedSettings; private ConfigEntry<string> _defaultSettings; private ConfigEntry<string> _dataFile; internal SettingRegistry Registry; internal PlayerStore Store; private readonly Dictionary<string, string> _defaults = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); internal const string RpcApply = "PPS.Apply"; internal const string RpcSet = "PPS.Set"; internal const string RpcQuery = "PPS.Query"; internal const string RpcAdmin = "PPS.Admin"; internal const string RpcMsg = "PPS.Msg"; internal const string RpcStatus = "PPS.Status"; private const string HostPlayerKey = "host"; private const int MaxAdminTokens = 128; private const int MaxRpcEntries = 2048; private const int MaxRpcStringLength = 4096; private ZRoutedRpc _registeredRpc; private readonly HashSet<long> _greetedPeers = new HashSet<long>(); private readonly Dictionary<long, float> _pendingPush = new Dictionary<long, float>(); private long _feedbackPeer; private readonly Dictionary<ConfigEntryBase, string> _hookedEntries = new Dictionary<ConfigEntryBase, string>(); private readonly HashSet<ConfigFile> _hookedFiles = new HashSet<ConfigFile>(); private bool _suppressAutoCapture; private float _hookScanTimer; private float _peerScanTimer; private float _watchdogTimer; private bool _hostApplied; private List<KeyValuePair<string, string>> _lastPayload; private ZNet _sessionZNet; private long _sessionServerUid; private readonly EditBudget _editBudget = new EditBudget(); private readonly HashSet<long> _pendingSavePeers = new HashSet<long>(); private readonly Dictionary<long, long> _pushSequences = new Dictionary<long, long>(); private readonly Dictionary<long, string> _applicationStatus = new Dictionary<long, string>(); private List<string> _adminFeedback; private readonly HashSet<long> _adminPushPeers = new HashSet<long>(); private float _saveDue; private float _saveStarted; private float _saveRetryNotBefore; private long _nextPushSequence; private string _clientApplicationStatus = "No settings received."; private int _lastAppliedCount; private int _lastRejectedCount; private string _lastApplyError; private void Awake() { //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Expected O, but got Unknown //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_0102: 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_0177: Expected O, but got Unknown //IL_01ab: Unknown result type (might be due to invalid IL or missing references) //IL_01b5: Expected O, but got Unknown //IL_02ac: Unknown result type (might be due to invalid IL or missing references) Instance = this; Log = ((BaseUnityPlugin)this).Logger; if (!ModCoreApi.IsAvailable) { throw new InvalidOperationException("ModCore did not initialize before PerPlayerSettings."); } Core = ModCoreApi.Services; SemanticVersion val = default(SemanticVersion); if (!SemanticVersion.TryParse("0.5.0", ref val)) { throw new InvalidOperationException("PerPlayerSettings has invalid release version metadata."); } _coreRegistrations.Add(Core.Modules.Register(new ModuleDescriptor(ModuleId, "jg224.PerPlayerSettings", "PerPlayerSettings", val, 2, (ModuleSide)3, (ModuleRequirement)4, 0uL, 1, 1))); _coreRegistrations.Add(Core.Namespaces.Register(ModuleId, (NamespaceKind)6, "PPS.", 1, Array.Empty<string>())); _coreRegistrations.Add(RoutedRpcIngress.Register(ModuleId, new string[6] { "PPS.Apply", "PPS.Set", "PPS.Query", "PPS.Admin", "PPS.Msg", "PPS.Status" })); _coreRegistrations.Add(Core.Namespaces.Register(ModuleId, (NamespaceKind)4, "jg224.PerPlayerSettings", 1, Array.Empty<string>())); _enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("1. General", "Enabled", true, "Master switch for the whole plugin."); _applyDelaySeconds = ((BaseUnityPlugin)this).Config.Bind<float>("1. General", "ApplyDelaySeconds", 2f, new ConfigDescription("Seconds after a player joins before their settings are pushed to them. Small delay so client mod loading settles first.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 30f), Array.Empty<object>())); _watchdogSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("1. General", "WatchdogSeconds", 10f, new ConfigDescription("How often (seconds) the client re-asserts the stored values while playing. Managed changes made through the config manager are captured automatically; unmanaged or rejected ones get reverted. 0 disables.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 120f), Array.Empty<object>())); _autoCapture = ((BaseUnityPlugin)this).Config.Bind<bool>("1. General", "AutoCapturePlayerChanges", true, "Server side: when a player changes a managed, player-settable setting through their own config manager (F1) during gameplay, save it as their personal value automatically. false = players must use /ppset explicitly, and config-manager edits get reverted."); _syncedSettings = ((BaseUnityPlugin)this).Config.Bind<string>("2. Settings registry", "SyncedSettings", "", "Managed settings, one per line: alias|ModGuid|Section|Key|playersCanSetOwn.\nEmpty by default - add settings with: perplayer add <player> <ModGuid> | <Setting name> = <Value>\nAliases are internal only - players and admins use the plain setting names. playersCanSetOwn: true = the player may change it themselves."); _defaultSettings = ((BaseUnityPlugin)this).Config.Bind<string>("2. Settings registry", "DefaultSettings", "", "Optional server defaults for players with no stored value, one per line: alias=value. Example: separatedodge=true. Player's own stored value always wins over these."); _dataFile = ((BaseUnityPlugin)this).Config.Bind<string>("3. Storage", "PlayerDataFile", "jg224.PerPlayerSettings.players.json", "File name (inside BepInEx/config) where the server stores per-player values."); Registry = SettingRegistry.Parse(_syncedSettings.Value); ParseDefaults(_defaultSettings.Value); Store = new PlayerStore(Path.Combine(Paths.ConfigPath, _dataFile.Value)); Store.Load(); RegisterCommands(); Core.Modules.SetState(ModuleId, (ModuleRuntimeState)2, "Required server-authoritative per-player settings protocol ready."); Log.LogInfo((object)("PerPlayerSettings 0.5.0 loaded. Managing " + Registry.Defs.Count + " setting(s).")); } private void ParseDefaults(string text) { _defaults.Clear(); if (string.IsNullOrEmpty(text)) { return; } string[] array = text.Split(new char[1] { '\n' }, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < array.Length; i++) { string text2 = array[i].Trim().TrimEnd(new char[1] { '\r' }); if (text2.Length != 0 && !text2.StartsWith("#", StringComparison.Ordinal)) { int num = text2.IndexOf('='); if (num <= 0) { Log.LogWarning((object)("Ignoring malformed DefaultSettings line: " + text2)); } else { _defaults[text2.Substring(0, num).Trim()] = text2.Substring(num + 1).Trim(); } } } } private void RegisterCommands() { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Expected O, but got Unknown //IL_0030: Expected O, but got Unknown //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Expected O, but got Unknown //IL_0061: Expected O, but got Unknown //IL_005c: Unknown result type (might be due to invalid IL or missing references) new ConsoleCommand("ppset", "set your own per-player settings. Examples: /ppset Keyboard Dodge Button = Mouse2 ; /ppset Keyboard Dodge Jump + Block = false ; /ppset (shows your current settings)", (ConsoleEvent)delegate(ConsoleEventArgs args) { CmdPlayerSet(args); }, false, false, false, false, false, false, new ConsoleOptionsFetcher(PpsetOptions), true, false, false); new ConsoleCommand("perplayer", "manage per-player settings (admin). Examples: perplayer whitelist ZenDragon.ZenCombat | Keyboard Dodge Button ; perplayer add Tiny ZenDragon.ZenCombat | Keyboard Dodge Button = CapsLock ; perplayer Tiny ; perplayer list ; perplayer mods ; perplayer entries <ModGuid> [filter] ; perplayer remove ZenDragon.ZenCombat | Keyboard Dodge Button ; perplayer reload", (ConsoleEvent)delegate(ConsoleEventArgs args) { CmdAdmin(args); }, false, false, false, false, false, false, new ConsoleOptionsFetcher(PerplayerOptions), true, false, false); } private List<string> PpsetOptions() { List<string> list = new List<string>(); if (Registry != null) { foreach (SettingDef def in Registry.Defs) { if (!list.Contains(def.Key)) { list.Add(def.Key); } } } return list; } private List<string> PerplayerOptions() { return new List<string> { "whitelist <ModGuid> | <Setting name> [admin]", "add <player> <ModGuid> | <Setting name> = <Value>", "remove <ModGuid> | <Setting name>", "set <player> \"<Setting>\" <value>", "clear <player> [\"<Setting>\"]", "list", "<player>", "mods", "entries <ModGuid> [filter]", "reload" }; } private void CmdPlayerSet(ConsoleEventArgs args) { //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Expected O, but got Unknown //IL_0271: Unknown result type (might be due to invalid IL or missing references) //IL_0278: Expected O, but got Unknown if (_shutDown) { return; } if (!_enabled.Value) { Feedback(args.Context, "PerPlayerSettings is disabled on this machine."); return; } IList<string> args2 = args.Args; if ((Object)(object)ZNet.instance == (Object)null) { Feedback(args.Context, "Not in a world yet."); return; } StringBuilder stringBuilder = new StringBuilder(); for (int i = 1; i < args2.Count; i++) { if (i > 1) { stringBuilder.Append(' '); } stringBuilder.Append(args2[i]); } string text = stringBuilder.ToString(); if (ZNet.instance.IsServer()) { if (ZNet.instance.IsDedicated()) { Feedback(args.Context, "This is the dedicated server console - use: perplayer set <player> \"<Setting>\" <value>"); } else if (args2.Count < 2) { Feedback(args.Context, "Usage: /ppset <Setting name> = <Value> (or just /ppset to see your settings)"); } else { HandleHostSet(text, args); } return; } ZNetPeer serverPeer = ZNet.instance.GetServerPeer(); if (serverPeer == null) { Feedback(args.Context, "Not connected to a server."); return; } if (args2.Count < 2) { ZPackage val = new ZPackage(); ZRoutedRpc.instance.InvokeRoutedRPC(serverPeer.m_uid, "PPS.Query", new object[1] { val }); Feedback(args.Context, "Requesting your stored settings from the server..."); return; } int num = text.IndexOf('='); string text2; string text3; if (num >= 0) { text2 = text.Substring(0, num).Trim().Trim(new char[1] { '"' }); text3 = text.Substring(num + 1).Trim().Trim(new char[1] { '"' }); } else { if (args2.Count != 3) { Feedback(args.Context, "Usage: /ppset <Setting name> = <Value> (or just /ppset to see your settings)"); return; } text2 = args2[1]; text3 = args2[2]; } if (text2.Length == 0 || text3.Length == 0) { Feedback(args.Context, "Usage: /ppset <Setting name> = <Value>"); return; } if (!ResolveSetting(text2, out var def, out var error)) { Feedback(args.Context, error + ". Use /ppset alone to see your settings."); return; } if (!def.PlayerSettable) { Feedback(args.Context, "'" + def.Key + "' can only be changed by a server admin."); return; } string normalized; string text4 = Registry.ValidateOnly(def, text3, out normalized); if (text4 != null) { Feedback(args.Context, "'" + def.Key + "': " + text4); return; } ZPackage val2 = new ZPackage(); val2.Write(def.Alias); val2.Write(text3); val2.Write(0); ZRoutedRpc.instance.InvokeRoutedRPC(serverPeer.m_uid, "PPS.Set", new object[1] { val2 }); } private void HandleHostSet(string joined, ConsoleEventArgs args) { if (_shutDown) { return; } int num = joined.IndexOf('='); string name; string rawValue; if (num >= 0) { name = joined.Substring(0, num).Trim().Trim(new char[1] { '"' }); rawValue = joined.Substring(num + 1).Trim().Trim(new char[1] { '"' }); } else { int num2 = joined.LastIndexOf(' '); if (num2 <= 0 || num2 >= joined.Length - 1) { Feedback(args.Context, "Usage: /ppset <Setting name> = <Value>"); return; } name = joined.Substring(0, num2).Trim(); rawValue = joined.Substring(num2 + 1).Trim(); } if (!ResolveSetting(name, out var def, out var error)) { Feedback(args.Context, error); return; } if (!def.PlayerSettable) { Feedback(args.Context, "'" + def.Key + "' can only be changed by a server admin."); return; } string normalized; string text = Registry.ValidateOnly(def, rawValue, out normalized); if (text != null) { Feedback(args.Context, "'" + def.Key + "': " + text); return; } PlayerData orCreate = Store.GetOrCreate("host"); if (Store.ReadOnly) { Feedback(args.Context, "Rejected: the server player store needs recovery."); return; } orCreate.Name = "host"; orCreate.Settings[def.Alias] = normalized; bool flag = Store.Save(); _suppressAutoCapture = true; string text2; try { text2 = Registry.TryApply(def, normalized, silent: false); } finally { _suppressAutoCapture = false; } Feedback(args.Context, (!flag) ? ("Pending persistence; retrying. " + ((text2 == null) ? "Applied locally." : text2)) : ((text2 == null) ? ("Saved " + def.ModGuid + " | \"" + def.Key + " = " + normalized + "\" (applied now)") : ("Saved server-side, but not applied locally: " + text2))); } private void CmdAdmin(ConsoleEventArgs args) { //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Expected O, but got Unknown if (_shutDown) { return; } if (!_enabled.Value) { Feedback(args.Context, "PerPlayerSettings is disabled."); } else if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { ZNetPeer val = (((Object)(object)ZNet.instance != (Object)null) ? ZNet.instance.GetServerPeer() : null); if (val != null && ZRoutedRpc.instance != null) { IList<string> args2 = args.Args; ZPackage val2 = new ZPackage(); val2.Write(args2.Count - 1); for (int i = 1; i < args2.Count; i++) { val2.Write(args2[i]); } ZRoutedRpc.instance.InvokeRoutedRPC(val.m_uid, "PPS.Admin", new object[1] { val2 }); } else { Feedback(args.Context, "perplayer runs on the server; connect to a server first."); } } else { RunAdminCore(args.Args, args.Context, 0L); } } private void RunAdminCore(IList<string> a, Terminal context, long feedbackPeer) { if (_shutDown) { return; } string before = Store.Snapshot(); string actor = ((feedbackPeer == 0L) ? "server console/host" : ResolvePeerId(ZNet.instance.GetPeer(feedbackPeer))); _adminFeedback = new List<string>(); Store.BeginBatch(); try { RunAdminCommand(a, context, feedbackPeer); } finally { Store.RecordAudit(before, actor, JoinArgs(a, 1, a.Count)); Store.EndBatch(); if (Store.HasUnsavedChanges && !Store.Save()) { _saveDue = (_saveRetryNotBefore = Time.unscaledTime + 5f); } List<string> adminFeedback = _adminFeedback; _adminFeedback = null; foreach (long adminPushPeer in _adminPushPeers) { if (Store.HasUnsavedChanges) { _pendingSavePeers.Add(adminPushPeer); } PushToPeer(adminPushPeer); } _adminPushPeers.Clear(); _feedbackPeer = feedbackPeer; foreach (string item in adminFeedback) { Feedback(context, item); } if (Store.HasUnsavedChanges) { Feedback(context, "Pending persistence; retrying: " + Store.LastSaveError); } _feedbackPeer = 0L; } } private void RunAdminCommand(IList<string> a, Terminal context, long feedbackPeer) { if (_shutDown) { return; } _feedbackPeer = feedbackPeer; try { string text = ((a.Count > 1) ? a[1].ToLowerInvariant() : "help"); switch (text) { case "audit": foreach (string auditDescription in Store.AuditDescriptions) { Feedback(context, auditDescription); } Feedback(context, "Rollback a value change with perplayer rollback <auditID>. Newer edits are never overwritten."); return; case "rollback": { string error = null; if (a.Count != 3 || !Store.Rollback(a[2], out error)) { Feedback(context, (a.Count != 3) ? "Use perplayer rollback <auditID>." : error); return; } PushAllToOnline(); if (!ZNet.instance.IsDedicated()) { ApplyPairs(BuildPairs("host"), silent: false); } Feedback(context, "Restored the audited player values; rollback is itself audited."); return; } case "status": Feedback(context, Store.ReadOnly ? "Storage is read-only: restore a valid store/backup and restart." : (Store.HasUnsavedChanges ? ("Persistence pending; retrying: " + Store.LastSaveError) : "All player values are stored on disk.")); { foreach (KeyValuePair<long, string> item in _applicationStatus) { Feedback(context, "Peer " + item.Key + ": " + item.Value); } return; } } if (Store.ReadOnly) { switch (text) { case "set": case "add": case "clear": Feedback(context, "Rejected: restore the player store before changing values."); return; } } switch (text) { case "help": Feedback(context, "perplayer add <player> <ModGuid> | <Setting name> = <Value> (quotes optional)"); Feedback(context, "perplayer <player> (show that player's settings; same as get)"); Feedback(context, "perplayer list / get <player> / set <player> \"<Setting>\" <value> / clear <player> [\"<Setting>\"]"); Feedback(context, "perplayer whitelist <ModGuid> | <Setting name> [admin] (manage it per player)"); Feedback(context, "perplayer remove <ModGuid> | <Setting name> (stop managing it)"); Feedback(context, "perplayer reload / mods / entries <ModGuid> [filter]"); Feedback(context, "<player> is a stored id, stored name, or the name of an online player."); return; case "list": { int num = 0; foreach (KeyValuePair<string, PlayerData> item2 in Store.All) { PlayerData value = item2.Value; StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append(item2.Key).Append(" (").Append(string.IsNullOrEmpty(value.Name) ? "?" : value.Name) .Append("): "); bool flag = true; foreach (KeyValuePair<string, string> setting in value.Settings) { if (!flag) { stringBuilder.Append(", "); } flag = false; stringBuilder.Append(setting.Key).Append("=").Append(setting.Value); } if (flag) { stringBuilder.Append("(no settings)"); } Feedback(context, stringBuilder.ToString()); num++; } Feedback(context, num + " player(s) stored. Managed settings: " + Registry.Keys()); return; } case "mods": Feedback(context, "Loaded plugins (GUID - Name):"); foreach (KeyValuePair<string, PluginInfo> pluginInfo in Chainloader.PluginInfos) { string text2 = ((pluginInfo.Value != null && pluginInfo.Value.Metadata != null) ? pluginInfo.Value.Metadata.Name : "?"); Feedback(context, " " + pluginInfo.Key + " - " + text2); } Feedback(context, "Mods installed only on clients are not listed here; add those from their cfg file."); return; case "entries": { if (a.Count < 3) { Feedback(context, "Usage: perplayer entries <ModGuid> [filter text]"); return; } BaseUnityPlugin val2 = FindPlugin(a[2]); if ((Object)(object)val2 == (Object)null) { Feedback(context, "No loaded plugin with GUID '" + a[2] + "'. See: perplayer mods"); return; } string text3 = ((a.Count > 3) ? StripOuterQuotes(JoinArgs(a, 3, a.Count)) : null); int num2 = 0; foreach (KeyValuePair<ConfigDefinition, ConfigEntryBase> item3 in (IEnumerable<KeyValuePair<ConfigDefinition, ConfigEntryBase>>)val2.Config) { if (text3 == null || item3.Key.Section.IndexOf(text3, StringComparison.OrdinalIgnoreCase) >= 0 || item3.Key.Key.IndexOf(text3, StringComparison.OrdinalIgnoreCase) >= 0) { string text4 = ((item3.Value.SettingType != null) ? item3.Value.SettingType.Name : "?"); string text5 = ((item3.Value.BoxedValue != null) ? item3.Value.BoxedValue.ToString() : "?"); Feedback(context, " " + item3.Key.Section + " | " + item3.Key.Key + " | " + text4 + " | " + text5); num2++; } } Feedback(context, num2 + " entr" + ((num2 == 1) ? "y" : "ies") + ". Quick-add one with:"); Feedback(context, " perplayer add <player> " + a[2] + " | \"<Key> = <Value>\""); return; } case "reload": ((BaseUnityPlugin)this).Config.Reload(); Registry = SettingRegistry.Parse(_syncedSettings.Value); ParseDefaults(_defaultSettings.Value); PushAllToOnline(); Feedback(context, "Reloaded config from disk. Managed settings: " + Registry.Keys()); return; case "whitelist": case "manage": { if (a.Count < 3) { Feedback(context, "Usage: perplayer whitelist <ModGuid> | <Setting name> [admin]"); return; } if (!TryParseGuidKey(a, 2, out var modGuid, out var keyName, out var adminOnly)) { Feedback(context, "Usage: perplayer whitelist <ModGuid> | <Setting name> [admin]"); return; } BaseUnityPlugin val = FindPlugin(modGuid); if ((Object)(object)val == (Object)null) { Feedback(context, "No loaded plugin '" + modGuid + "'. See: perplayer mods"); return; } List<string> list = SectionsForKey(val, keyName); if (list.Count == 0) { Feedback(context, "'" + modGuid + "' has no setting named '" + keyName + "'. Browse with: perplayer entries " + modGuid); return; } if (list.Count > 1) { Feedback(context, "'" + keyName + "' exists in several sections (" + string.Join(", ", list.ToArray()) + "). Whitelist a specific one via SyncedSettings in jg224.PerPlayerSettings.cfg, then: perplayer reload"); return; } SettingDef settingDef = Registry.FindByKey(modGuid, list[0], keyName); if (settingDef == null) { settingDef = new SettingDef(); settingDef.ModGuid = modGuid; settingDef.Section = list[0]; settingDef.Key = keyName; settingDef.PlayerSettable = !adminOnly; settingDef.Alias = UniqueAlias(keyName); Registry.Add(settingDef); SaveRegistryToConfig(); } else if (settingDef.PlayerSettable == adminOnly) { settingDef.PlayerSettable = !adminOnly; SaveRegistryToConfig(); } PushAllToOnline(); Feedback(context, "Whitelisted: " + modGuid + " | " + keyName + " [" + list[0] + "]" + (settingDef.PlayerSettable ? " - players set their own via F1 or /ppset" : " - admin-only (players cannot change it)")); return; } } string text6 = ((a.Count > 2) ? a[2] : null); string id = ((text6 != null) ? ResolveTargetId(text6) : null); switch (text) { case "add": { bool flag2 = false; for (int j = 2; j < a.Count; j++) { if (a[j].IndexOf('=') >= 0) { flag2 = true; break; } } if (flag2) { CmdAdminQuickAdd(a, context); break; } if (a.Count < 3) { Feedback(context, "Usage: perplayer add <player> <ModGuid> | \"<Key> = <Value>\""); Feedback(context, " or perplayer add <alias> <ModGuid> \"<Section>\" \"<Key>\" [true|false]"); break; } SettingDef def4; if (a[2].IndexOf('|') >= 0) { if (!TryParseDefLine(a[2], out def4, out var error6)) { Feedback(context, error6); break; } } else { List<string> list3 = Retokenize(a, 2); if (list3.Count < 4 || list3.Count > 5) { Feedback(context, "Positional form needs: <alias> <ModGuid> \"<Section>\" \"<Key>\" [true|false]"); break; } def4 = new SettingDef(); def4.Alias = list3[0]; def4.ModGuid = list3[1]; def4.Section = list3[2]; def4.Key = list3[3]; def4.PlayerSettable = true; if (list3.Count == 5 && !bool.TryParse(list3[4], out def4.PlayerSettable)) { Feedback(context, "playersCanSetOwn must be true or false."); break; } if (def4.Alias.Length == 0 || def4.ModGuid.Length == 0 || def4.Key.Length == 0) { Feedback(context, "alias, ModGuid and Key cannot be empty."); break; } } if (!Registry.Add(def4)) { Feedback(context, "Alias '" + def4.Alias + "' already exists."); break; } SaveRegistryToConfig(); ConfigEntryBase val3 = Registry.ResolveEntry(def4); PushAllToOnline(); Feedback(context, (val3 != null) ? ("Added '" + def4.Alias + "' -> " + def4.ModGuid + " (" + val3.SettingType.Name + "). Pushed to all online players.") : ("Added '" + def4.Alias + "' -> " + def4.ModGuid + " (target mod not loaded on this machine; values validated on clients). Pushed to all online players.")); break; } case "remove": { if (a.Count < 3) { Feedback(context, "Usage: perplayer remove <ModGuid> | <Setting name>"); break; } if (a[2] == "|" || (Object)(object)FindPlugin(a[2]) != (Object)null) { if (!TryParseGuidKey(a, 2, out var modGuid2, out var keyName2, out var _)) { Feedback(context, "Usage: perplayer remove <ModGuid> | <Setting name>"); break; } List<SettingDef> list2 = Registry.FindByGuidKey(modGuid2, keyName2); if (list2.Count == 0) { Feedback(context, "Nothing whitelisted for " + modGuid2 + " | " + keyName2 + ". Managed: " + Registry.Keys()); break; } foreach (SettingDef item4 in list2) { Registry.Remove(item4.Alias); } SaveRegistryToConfig(); PushAllToOnline(); Feedback(context, "Removed from whitelist: " + modGuid2 + " | " + keyName2 + ". Stored player values are kept but no longer pushed."); break; } StringBuilder stringBuilder2 = new StringBuilder(); for (int i = 2; i < a.Count; i++) { if (!(a[i] == "|")) { if (stringBuilder2.Length > 0) { stringBuilder2.Append(' '); } stringBuilder2.Append(a[i]); } } if (!ResolveSetting(stringBuilder2.ToString(), out var def, out var _) || !Registry.Remove(def.Alias)) { Feedback(context, "No managed setting '" + stringBuilder2?.ToString() + "'. Managed: " + Registry.Keys()); break; } SaveRegistryToConfig(); PushAllToOnline(); Feedback(context, "Removed '" + def.Key + "' from the whitelist. Stored player values are kept but no longer pushed."); break; } case "set": { if (a.Count < 5) { Feedback(context, "Usage: perplayer set <player> \"<Setting>\" <value>"); break; } if (!TryParseSettingAndValue(a, 3, out var def2, out var rawValue, out var error3)) { Feedback(context, error3 + ". Managed: " + Registry.Keys()); break; } string normalized; string text7 = Registry.ValidateOnly(def2, rawValue, out normalized); if (text7 != null) { Feedback(context, "'" + def2.Alias + "': " + text7); break; } if (!TryResolveWriteTarget(text6, out id, out var error4)) { Feedback(context, error4); break; } PlayerData orCreate = Store.GetOrCreate(id); if (string.IsNullOrEmpty(orCreate.Name)) { orCreate.Name = text6; } orCreate.Settings[def2.Alias] = normalized; Store.Save(); long? num3 = FindOnlineUid(id, text6); if (num3.HasValue) { PushToPeer(num3.Value); Feedback(context, "Saved " + def2.ModGuid + " | \"" + def2.Key + " = " + normalized + "\" for " + text6 + " and pushed it to them."); } else { Feedback(context, "Saved " + def2.ModGuid + " | \"" + def2.Key + " = " + normalized + "\" for " + text6 + " (not online; applies when they join)."); } break; } case "get": if (a.Count < 3) { Feedback(context, "Usage: perplayer get <player>"); } else { ShowPlayer(a[2], ResolveTargetId(a[2]), context); } break; case "clear": { if (a.Count < 3) { Feedback(context, "Usage: perplayer clear <player> [\"<Setting>\"]"); break; } if (id == null) { Feedback(context, "No stored player matching '" + text6 + "'."); break; } PlayerData playerData = Store.Get(id); if (playerData == null) { Feedback(context, "Nothing stored for " + text6 + "."); break; } if (a.Count >= 4) { string text8 = StripOuterQuotes(JoinArgs(a, 3, a.Count)); SettingDef def3; string error5; string key = (ResolveSetting(text8, out def3, out error5) ? def3.Alias : text8); if (!playerData.Settings.Remove(key)) { Feedback(context, "Nothing stored for '" + text8 + "' on " + text6 + "."); break; } Feedback(context, "Cleared " + text8 + " for " + text6 + ". Server default (if any) applies next join."); } else { playerData.Settings.Clear(); Feedback(context, "Cleared all stored settings for " + text6 + "."); } Store.Save(); long? num4 = FindOnlineUid(id, text6); if (num4.HasValue) { PushToPeer(num4.Value); } break; } default: if (a.Count >= 2) { ShowPlayer(a[1], ResolveTargetId(a[1]), context); } else { Feedback(context, "Unknown subcommand '" + text + "'. See: perplayer help"); } break; } } finally { _feedbackPeer = 0L; } } private void RpcAdminHandler(long sender, ZPackage pkg) { if (_shutDown || !_enabled.Value || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { return; } ZNetPeer peer = ZNet.instance.GetPeer(sender); if (peer == null) { return; } bool flag = false; try { flag = ZNet.instance.IsAdmin(peer.m_socket.GetHostName()); } catch (Exception) { flag = false; } if (!flag) { SendMsg(sender, "perplayer: admins only."); return; } try { int num = pkg.ReadInt(); if (num < 0 || num > 128) { SendMsg(sender, "perplayer: rejected malformed command."); Log.LogWarning((object)("Rejected PPS.Admin with " + num + " token(s) from " + sender + ".")); return; } List<string> list = new List<string>(); list.Add("perplayer"); for (int i = 0; i < num; i++) { string text = pkg.ReadString(); if (text == null || text.Length > 4096) { SendMsg(sender, "perplayer: rejected malformed command."); return; } list.Add(text); } RunAdminCore(list, null, sender); } catch (Exception ex2) { Log.LogWarning((object)("Rejected malformed PPS.Admin from " + sender + ": " + ex2.Message)); SendMsg(sender, "perplayer: rejected malformed command."); } } private void CmdAdminQuickAdd(IList<string> a, Terminal context) { if (_shutDown) { return; } List<string> list = new List<string>(); for (int i = 2; i < a.Count; i++) { if (a[i] != "|") { list.Add(a[i]); } } if (list.Count < 3) { Feedback(context, "Usage: perplayer add <player> <ModGuid> | <Setting name> = <Value> (quotes optional)"); return; } string text = list[0]; string text2 = list[1]; StringBuilder stringBuilder = new StringBuilder(); for (int j = 2; j < list.Count; j++) { if (j > 2) { stringBuilder.Append(' '); } stringBuilder.Append(list[j]); } string text3 = stringBuilder.ToString(); int num = text3.IndexOf('='); if (num <= 0 || num >= text3.Length - 1) { Feedback(context, "The last part must be \"<Key> = <Value>\"."); return; } string text4 = text3.Substring(0, num).Trim().Trim(new char[1] { '"' }); string rawValue = text3.Substring(num + 1).Trim().Trim(new char[1] { '"' }); BaseUnityPlugin val = FindPlugin(text2); if ((Object)(object)val == (Object)null) { Feedback(context, "No loaded plugin '" + text2 + "'. See: perplayer mods"); return; } List<string> list2 = new List<string>(); foreach (KeyValuePair<ConfigDefinition, ConfigEntryBase> item in (IEnumerable<KeyValuePair<ConfigDefinition, ConfigEntryBase>>)val.Config) { if (string.Equals(item.Key.Key, text4, StringComparison.OrdinalIgnoreCase) && !list2.Contains(item.Key.Section)) { list2.Add(item.Key.Section); } } if (list2.Count == 0) { Feedback(context, "'" + text2 + "' has no setting named '" + text4 + "'. Browse with: perplayer entries " + text2); return; } if (list2.Count > 1) { Feedback(context, "'" + text4 + "' exists in several sections (" + string.Join(", ", list2.ToArray()) + "). Add a specific one via SyncedSettings in jg224.PerPlayerSettings.cfg, then: perplayer reload"); return; } SettingDef settingDef = Registry.FindByKey(text2, list2[0], text4); bool flag = false; if (settingDef == null) { settingDef = new SettingDef(); settingDef.ModGuid = text2; settingDef.Section = list2[0]; settingDef.Key = text4; settingDef.PlayerSettable = true; settingDef.Alias = UniqueAlias(text4); Registry.Add(settingDef); SaveRegistryToConfig(); flag = true; } string normalized; string text5 = Registry.ValidateOnly(settingDef, rawValue, out normalized); if (text5 != null) { if (flag) { Registry.Remove(settingDef.Alias); SaveRegistryToConfig(); } Feedback(context, "'" + text4 + "': " + text5); return; } if (!TryResolveWriteTarget(text, out var id, out var error)) { Feedback(context, error); return; } PlayerData orCreate = Store.GetOrCreate(id); if (string.IsNullOrEmpty(orCreate.Name)) { orCreate.Name = text; } orCreate.Settings[settingDef.Alias] = normalized; Store.Save(); if (flag) { PushAllToOnline(); } long? num2 = FindOnlineUid(id, text); if (num2.HasValue) { PushToPeer(num2.Value); Feedback(context, "Saved for " + text + ": " + text2 + " | \"" + text4 + " = " + normalized + "\"" + (flag ? " (newly managed)" : "") + " and pushed to them."); } else { Feedback(context, "Saved for " + text + ": " + text2 + " | \"" + text4 + " = " + normalized + "\"" + (flag ? " (newly managed)" : "") + ". Not online; applies when they join."); } } private void ShowPlayer(string playerArg, string id, Terminal context) { if (playerArg == null || id == null) { Feedback(context, "No stored player matching '" + (playerArg ?? "?") + "'."); return; } PlayerData playerData = Store.Get(id); if (playerData == null || playerData.Settings.Count == 0) { Feedback(context, playerArg + " has no stored settings (defaults, if any, still apply)."); return; } Feedback(context, playerArg + " (" + id + "):"); foreach (KeyValuePair<string, string> setting in playerData.Settings) { SettingDef settingDef = Registry.Find(setting.Key); if (settingDef != null) { Feedback(context, " " + settingDef.ModGuid + " | \"" + settingDef.Key + " = " + setting.Value + "\" [" + settingDef.Section + "]"); } else { Feedback(context, " (unregistered) " + setting.Key + " = " + setting.Value); } } } private string UniqueAlias(string keyName) { StringBuilder stringBuilder = new StringBuilder(); string text = keyName.ToLowerInvariant(); foreach (char c in text) { if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9')) { stringBuilder.Append(c); } } string text2 = ((stringBuilder.Length > 0) ? stringBuilder.ToString() : "setting"); string text3 = text2; int num = 2; while (Registry.Find(text3) != null) { text3 = text2 + num; num++; } return text3; } private string ResolveTargetId(string playerArg) { if (playerArg.StartsWith("name:", StringComparison.OrdinalIgnoreCase)) { return Store.FindPreregistered(playerArg.Substring(5).Trim()); } if (Store.Get(playerArg) != null) { return playerArg; } string text = Store.FindByName(playerArg); if (text != null) { return text; } ZNetPeer val = FindOnlinePeer(playerArg); if (val == null) { return null; } return ResolvePeerId(val); } private bool TryResolveWriteTarget(string playerArg, out string id, out string error) { id = null; error = null; if (!playerArg.StartsWith("name:", StringComparison.OrdinalIgnoreCase)) { id = ResolveTargetId(playerArg) ?? playerArg; return true; } string text = playerArg.Substring(5).Trim(); if (text.Length == 0) { error = "Use name:<player> with a nonempty display name for explicit offline preregistration."; return false; } try { id = Store.GetOrCreatePreregistered(text); return true; } catch (InvalidOperationException ex) { error = ex.Message; return false; } } private long? FindOnlineUid(string id, string playerArg) { ZNetPeer val = FindOnlinePeer(playerArg); if (val == null) { foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (peer != null && ResolvePeerId(peer) == id) { return peer.m_uid; } } return null; } return val.m_uid; } private ZNetPeer FindOnlinePeer(string playerArg) { foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (peer != null) { if (string.Equals(peer.m_playerName, playerArg, StringComparison.OrdinalIgnoreCase)) { return peer; } if (ResolvePeerId(peer) == playerArg) { return peer; } } } return null; } private static bool TryParseGuidKey(IList<string> a, int start, out string modGuid, out string keyName, out bool adminOnly) { modGuid = null; keyName = null; adminOnly = false; List<string> list = new List<string>(); for (int i = start; i < a.Count; i++) { if (a[i] != "|") { list.Add(a[i]); } } if (list.Count >= 2) { string a2 = list[list.Count - 1]; if (string.Equals(a2, "admin", StringComparison.OrdinalIgnoreCase) || string.Equals(a2, "false", StringComparison.OrdinalIgnoreCase)) { adminOnly = true; list.RemoveAt(list.Count - 1); } } if (list.Count < 2) { return false; } modGuid = list[0]; StringBuilder stringBuilder = new StringBuilder(); for (int j = 1; j < list.Count; j++) { if (j > 1) { stringBuilder.Append(' '); } stringBuilder.Append(list[j]); } keyName = stringBuilder.ToString().Trim().Trim(new char[1] { '"' }); return keyName.Length > 0; } private bool TryParseSettingAndValue(IList<string> args, int start, out SettingDef def, out string rawValue, out string error) { def = null; rawValue = null; error = null; if (args == null || start < 0 || start >= args.Count - 1) { error = "Expected <Setting> = <Value>"; return false; } string text = JoinArgs(args, start, args.Count); int num = text.IndexOf('='); if (num >= 0) { string name = StripOuterQuotes(text.Substring(0, num)); rawValue = StripOuterQuotes(text.Substring(num + 1)); if (rawValue.Length == 0) { error = "Setting value cannot be empty"; return false; } return ResolveSetting(name, out def, out error); } string text2 = null; int num2 = args.Count - 1; while (num2 > start) { string name2 = StripOuterQuotes(JoinArgs(args, start, num2)); if (!ResolveSetting(name2, out var def2, out var error2)) { text2 = error2; num2--; continue; } string text3 = StripOuterQuotes(JoinArgs(args, num2, args.Count)); if (text3.Length == 0) { error = "Setting value cannot be empty"; return false; } def = def2; rawValue = text3; return true; } error = text2 ?? "Unknown setting"; return false; } private static string JoinArgs(IList<string> args, int start, int end) { StringBuilder stringBuilder = new StringBuilder(); for (int i = start; i < end && i < args.Count; i++) { if (stringBuilder.Length > 0) { stringBuilder.Append(' '); } stringBuilder.Append(args[i]); } return stringBuilder.ToString(); } private static string StripOuterQuotes(string text) { string text2 = (text ?? "").Trim(); if (text2.Length >= 2 && ((text2[0] == '"' && text2[text2.Length - 1] == '"') || (text2[0] == '\'' && text2[text2.Length - 1] == '\''))) { text2 = text2.Substring(1, text2.Length - 2).Trim(); } return text2; } private static List<string> Retokenize(IList<string> args, int start) { string text = JoinArgs(args, start, args.Count); List<string> list = new List<string>(); StringBuilder stringBuilder = new StringBuilder(); char c = '\0'; foreach (char c2 in text) { if (c != 0) { if (c2 == c) { c = '\0'; } else { stringBuilder.Append(c2); } } else if (c2 == '"' || c2 == '\'') { c = c2; } else if (char.IsWhiteSpace(c2)) { if (stringBuilder.Length > 0) { list.Add(stringBuilder.ToString()); stringBuilder.Length = 0; } } else { stringBuilder.Append(c2); } } if (c != 0) { return new List<string>(); } if (stringBuilder.Length > 0) { list.Add(stringBuilder.ToString()); } return list; } private static List<string> SectionsForKey(BaseUnityPlugin plugin, string keyName) { List<string> list = new List<string>(); foreach (KeyValuePair<ConfigDefinition, ConfigEntryBase> item in (IEnumerable<KeyValuePair<ConfigDefinition, ConfigEntryBase>>)plugin.Config) { if (string.Equals(item.Key.Key, keyName, StringComparison.OrdinalIgnoreCase) && !list.Contains(item.Key.Section)) { list.Add(item.Key.Section); } } return list; } private static BaseUnityPlugin FindPlugin(string modGuid) { foreach (KeyValuePair<string, PluginInfo> pluginInfo in Chainloader.PluginInfos) { if (string.Equals(pluginInfo.Key, modGuid, StringComparison.OrdinalIgnoreCase)) { return (pluginInfo.Value != null) ? pluginInfo.Value.Instance : null; } } return null; } private static bool TryParseDefLine(string line, out SettingDef def, out string error) { def = null; error = null; string[] array = (line ?? "").Split(new char[1] { '|' }); if (array.Length < 4) { error = "Need alias|ModGuid|Section|Key[|playersCanSetOwn] (quote the whole thing)."; return false; } SettingDef settingDef = new SettingDef(); settingDef.Alias = array[0].Trim(); settingDef.ModGuid = array[1].Trim(); settingDef.Section = array[2].Trim(); settingDef.Key = array[3].Trim(); settingDef.PlayerSettable = true; if (array.Length >= 5 && !bool.TryParse(array[4].Trim(), out settingDef.PlayerSettable)) { error = "playersCanSetOwn must be true or false."; return false; } if (settingDef.Alias.Length == 0 || settingDef.ModGuid.Length == 0 || settingDef.Key.Length == 0) { error = "alias, ModGuid and Key cannot be empty."; return false; } def = settingDef; return true; } private void SaveRegistryToConfig() { List<string> list = Registry.ToLines(); _syncedSettings.Value = ((list.Count > 0) ? string.Join("\n", list.ToArray()) : ""); ((BaseUnityPlugin)this).Config.Save(); } private void Feedback(Terminal context, string message) { if (_adminFeedback != null) { _adminFeedback.Add(message); return; } if (Store != null && Store.HasUnsavedChanges && message.StartsWith("Saved", StringComparison.Ordinal)) { message = "Pending persistence (retrying)" + message.Substring(5); } if ((Object)(object)context != (Object)null) { context.AddString("[PPS] " + message); } if (_feedbackPeer != 0L) { SendMsg(_feedbackPeer, message); } else { ShowToast(message); } } private static void ShowToast(string message) { try { if ((Object)(object)ZNet.instance != (Object)null && !ZNet.instance.IsDedicated() && (Object)(object)MessageHud.instance != (Object)null) { MessageHud.instance.ShowMessage((MessageType)1, message, 0, (Sprite)null, false, true); } } catch (Exception) { } } private void Update() { if (_shutDown || !_enabled.Value) { return; } ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null) { if (_sessionZNet != null) { ResetNetworkSessionState(); } _sessionZNet = null; _sessionServerUid = 0L; _hostApplied = false; return; } long num = ((!instance.IsServer()) ? instance.GetServerPeer() : null)?.m_uid ?? 0; bool num2 = _sessionZNet != instance; bool flag = !instance.IsServer() && _sessionServerUid != 0L && num == 0; bool flag2 = !instance.IsServer() && _sessionServerUid != 0L && num != 0L && _sessionServerUid != num; if (num2 || flag || flag2) { ResetNetworkSessionState(); _sessionZNet = instance; } _sessionServerUid = num; EnsureRpcRegistered(); if (ZRoutedRpc.instance != null) { _hookScanTimer -= Time.deltaTime; if (_hookScanTimer <= 0f) { _hookScanTimer = 5f; EnsureHooks(); } ServerUpdate(); ClientWatchdog(); } } private void ResetNetworkSessionState() { _lastPayload = null; _watchdogTimer = 0f; _greetedPeers.Clear(); _pendingPush.Clear(); _editBudget.Clear(); _pendingSavePeers.Clear(); _adminPushPeers.Clear(); _pushSequences.Clear(); _applicationStatus.Clear(); _adminFeedback = null; _saveDue = (_saveStarted = (_saveRetryNotBefore = 0f)); _clientApplicationStatus = "No settings received."; _feedbackPeer = 0L; _hookedEntries.Clear(); Registry = SettingRegistry.Parse(_syncedSettings.Value); } private void EnsureHooks() { if (Registry == null) { return; } foreach (SettingDef def in Registry.Defs) { ConfigEntryBase val = Registry.ResolveEntry(def); if (val != null) { if (!_hookedEntries.ContainsKey(val)) { _hookedEntries[val] = def.Alias; } ConfigFile val2 = OwnerConfig(def.ModGuid); if (val2 != null && !_hookedFiles.Contains(val2)) { _hookedFiles.Add(val2); val2.SettingChanged += OnConfigFileSettingChanged; } } } } private ConfigFile OwnerConfig(string modGuid) { foreach (KeyValuePair<string, PluginInfo> pluginInfo in Chainloader.PluginInfos) { if (string.Equals(pluginInfo.Key, modGuid, StringComparison.OrdinalIgnoreCase)) { return (pluginInfo.Value != null && (Object)(object)pluginInfo.Value.Instance != (Object)null) ? pluginInfo.Value.Instance.Config : null; } } return null; } private void OnConfigFileSettingChanged(object sender, SettingChangedEventArgs e) { //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Expected O, but got Unknown if (_shutDown || _suppressAutoCapture || !_enabled.Value || !_autoCapture.Value) { return; } ConfigEntryBase val = ((e != null) ? e.ChangedSetting : null); if (val == null || val.BoxedValue == null || !_hookedEntries.TryGetValue(val, out var value)) { return; } SettingDef settingDef = Registry.Find(value); if (settingDef == null || !settingDef.PlayerSettable || (Object)(object)ZNet.instance == (Object)null) { return; } string text = Convert.ToString(val.BoxedValue, CultureInfo.InvariantCulture); if (ZNet.instance.IsServer()) { if (!ZNet.instance.IsDedicated()) { if (Store.ReadOnly) { ShowToast("PerPlayer: rejected local change; server storage needs recovery."); return; } PlayerData orCreate = Store.GetOrCreate("host"); orCreate.Name = "host"; orCreate.Settings[settingDef.Alias] = text; ScheduleSave(0L); } return; } ZNetPeer serverPeer = ZNet.instance.GetServerPeer(); if (serverPeer != null) { _watchdogTimer = Math.Max(_watchdogTimer, 3f); ZPackage val2 = new ZPackage(); val2.Write(settingDef.Alias); val2.Write(text); val2.Write(1); ZRoutedRpc.instance.InvokeRoutedRPC(serverPeer.m_uid, "PPS.Set", new object[1] { val2 }); } } private void EnsureRpcRegistered() { if (!_shutDown) { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null && _registeredRpc != instance) { instance.Register<ZPackage>("PPS.Apply", (Action<long, ZPackage>)RpcApplyHandler); instance.Register<ZPackage>("PPS.Set", (Action<long, ZPackage>)RpcSetHandler); instance.Register<ZPackage>("PPS.Query", (Action<long, ZPackage>)RpcQueryHandler); instance.Register<ZPackage>("PPS.Admin", (Action<long, ZPackage>)RpcAdminHandler); instance.Register<ZPackage>("PPS.Msg", (Action<long, ZPackage>)RpcMsgHandler); instance.Register<ZPackage>("PPS.Status", (Action<long, ZPackage>)RpcStatusHandler); _registeredRpc = instance; _greetedPeers.Clear(); _pendingPush.Clear(); Log.LogInfo((object)"Routed RPCs registered."); } } } private void ServerUpdate() { if (_shutDown || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { return; } if (Store.HasUnsavedChanges && !Store.ReadOnly && Time.unscaledTime >= _saveDue) { bool flag = Store.Save(); _saveRetryNotBefore = (flag ? 0f : (Time.unscaledTime + 5f)); _saveDue = _saveRetryNotBefore; _saveStarted = 0f; foreach (long pendingSavePeer in _pendingSavePeers) { if (pendingSavePeer == 0L) { ShowToast(flag ? "PerPlayer: stored your changes." : "PerPlayer: persistence failed; retrying."); } else if (ZNet.instance.GetPeer(pendingSavePeer) != null) { SendMsg(pendingSavePeer, flag ? "Stored on server; awaiting client application." : "Pending persistence; server will retry."); if (flag) { PushToPeer(pendingSavePeer); } } } if (flag) { _pendingSavePeers.Clear(); } } if (!ZNet.instance.IsDedicated() && !_hostApplied) { _hostApplied = true; List<KeyValuePair<string, string>> list = BuildPairs("host"); if (list.Count > 0) { ApplyPairs(list, silent: false); } } _peerScanTimer -= Time.deltaTime; if (_peerScanTimer <= 0f) { _peerScanTimer = 1f; HashSet<long> currentUids = new HashSet<long>(); foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (peer == null) { continue; } currentUids.Add(peer.m_uid); string text = ResolvePeerId(peer); if (!Store.ReadOnly && !string.IsNullOrEmpty(peer.m_playerName)) { PlayerData orCreate = Store.GetOrCreate(text); if (!string.Equals(orCreate.Name, peer.m_playerName, StringComparison.Ordinal)) { orCreate.Name = peer.m_playerName; ScheduleSave(0L); } if (MergePreregistered(text, peer.m_playerName)) { _pendingPush[peer.m_uid] = Time.time + 0.5f; } } if (_greetedPeers.Add(peer.m_uid) && (Registry.Defs.Count > 0 || Store.HasSettings(text) || _defaults.Count > 0)) { _pendingPush[peer.m_uid] = Time.time + _applyDelaySeconds.Value; } } _greetedPeers.RemoveWhere((long uid) => !currentUids.Contains(uid)); _editBudget.Retain(currentUids); foreach (long item in new List<long>(_applicationStatus.Keys)) { if (!currentUids.Contains(item)) { _applicationStatus.Remove(item); _pushSequences.Remove(item); } } _pendingSavePeers.RemoveWhere((long uid) => uid != 0L && !currentUids.Contains(uid)); } if (_pendingPush.Count == 0) { return; } List<long> list2 = null; foreach (KeyValuePair<long, float> item2 in _pendingPush) { if (!(item2.Value > Time.time)) { if (list2 == null) { list2 = new List<long>(); } list2.Add(item2.Key); } } if (list2 == null) { return; } foreach (long item3 in list2) { _pendingPush.Remove(item3); PushToPeer(item3); } } private bool MergePreregistered(string realId, string playerName) { if (!Store.MergePreregistered(realId, playerName)) { return false; } ScheduleSave(0L); Log.LogInfo((object)("Applied explicitly pre-registered settings for '" + playerName + "' to id " + realId + ".")); return true; } private void ClientWatchdog() { if (!_shutDown && _lastPayload != null && !(_watchdogSeconds.Value <= 0f) && !((Object)(object)ZNet.instance == (Object)null) && !ZNet.instance.IsServer()) { _watchdogTimer -= Time.deltaTime; if (!(_watchdogTimer > 0f)) { _watchdogTimer = _watchdogSeconds.Value; ApplyPairs(_lastPayload, silent: true); } } } private void RpcSetHandler(long sender, ZPackage pkg) { if (_shutDown || !_enabled.Value || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { return; } ZNetPeer peer = ZNet.instance.GetPeer(sender); if (peer == null || !_editBudget.Accept(sender, Time.unscaledTime) || pkg == null || pkg.Size() > 32768) { return; } if (Store.ReadOnly) { SendMsg(sender, "Rejected: server storage needs recovery."); return; } string text; string text2; int num; try { text = pkg.ReadString(); text2 = pkg.ReadString(); num = pkg.ReadInt(); if (pkg.GetPos() != pkg.Size()) { throw new FormatException("unexpected trailing data"); } if (text == null || text2 == null || text.Length > 4096 || text2.Length > 4096 || (num != 0 && num != 1)) { throw new FormatException("invalid alias, value, or source"); } } catch (Exception ex) { Log.LogWarning((object)("Rejected malformed PPS.Set from " + sender + ": " + ex.Message)); SendMsg(sender, "PerPlayer: rejected malformed setting update."); return; } SettingDef settingDef = Registry.Find(text); if (settingDef == null) { SendMsg(sender, "'" + text + "' is not a known setting on this server."); return; } if (!settingDef.PlayerSettable) { SendMsg(sender, "'" + settingDef.Alias + "' can only be changed by a server admin."); return; } string normalized; string text3 = Registry.ValidateOnly(settingDef, text2, out normalized); if (text3 != null) { SendMsg(sender, "'" + settingDef.Alias + "': " + text3); return; } if (num == 1 && !_autoCapture.Value) { SendMsg(sender, "Auto-captured changes are disabled on this server. Use /ppset <Setting> = <Value> instead."); PushToPeer(sender); return; } string text4 = ResolvePeerId(peer); PlayerData orCreate = Store.GetOrCreate(text4); if (orCreate.Settings.TryGetValue(settingDef.Alias, out var value) && value == normalized) { if (num == 0) { SendMsg(sender, Store.HasUnsavedChanges ? "Unchanged; persistence pending." : "Unchanged; value is stored."); } return; } if (!string.IsNullOrEmpty(peer.m_playerName)) { orCreate.Name = peer.m_playerName; } orCreate.Settings[settingDef.Alias] = normalized; ScheduleSave(sender); if (num == 0) { SendMsg(sender, "Queued for storage; server will confirm persistence and application separately."); } Log.LogInfo((object)("Player " + (string.IsNullOrEmpty(orCreate.Name) ? text4 : orCreate.Name) + " set " + settingDef.Alias + " = " + normalized)); } private void ScheduleSave(long peer) { Store.MarkDirty(); _pendingSavePeers.Add(peer); float unscaledTime = Time.unscaledTime; if (_saveStarted == 0f) { _saveStarted = unscaledTime; } _saveDue = Math.Max(_saveRetryNotBefore, Math.Min(unscaledTime + 0.5f, _saveStarted + 2f)); } private void RpcStatusHandler(long sender, ZPackage pkg) { if (_shutDown || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || ZNet.instance.GetPeer(sender) == null || pkg == null || pkg.Size() > 2048) { return; } try { long num = pkg.ReadLong(); int num2 = pkg.ReadInt(); int num3 = pkg.ReadInt(); string text = pkg.ReadString(); if (_pushSequences.TryGetValue(sender, out var value) && num == value && num2 >= 0 && num3 >= 0 && num2 + num3 <= 2048 && text.Length <= 256 && pkg.GetPos() == pkg.Size()) { _applicationStatus[sender] = "Client reports " + num2 + " applied, " + num3 + " rejected" + ((num3 > 0) ? (": " + text) : "."); _pushSequences.Remove(sender); } } catch (Exception ex) { Log.LogWarning((object)("Rejected application status: " + ex.Message)); } } private void RpcQueryHandler(long sender, ZPackage pkg) { if (_shutDown || !_enabled.Value || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { return; } ZNetPeer peer = ZNet.instance.GetPeer(sender); if (peer != null) { string text = ResolvePeerId(peer); PlayerData playerData = Store.Get(text); PushToPeer(sender); if (playerData == null || playerData.Settings.Count == 0) { SendMsg(sender, (_defaults.Count > 0) ? ("You have no personal settings stored. Server defaults: " + DescribePairsFriendly(BuildPairs(text))) : "You have no personal settings stored."); } else { SendMsg(sender, "Your settings: " + DescribePairsFriendly(BuildPairs(text))); } } } private void RpcApplyHandler(long sender, ZPackage pkg) { //IL_0210: Unknown result type (might be due to invalid IL or missing references) //IL_0217: Expected O, but got Unknown if (_shutDown || !_enabled.Value || (Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer()) { return; } ZNetPeer serverPeer = ZNet.instance.GetServerPeer(); if (serverPeer == null || sender != serverPeer.m_uid) { Log.LogWarning((object)("Ignored PPS.Apply from non-server sender " + sender)); return; } try { int num = pkg.ReadInt(); if (num < 0 || num > 2048) { throw new FormatException("invalid registry count " + num); } List<string> list = new List<string>(); for (int i = 0; i < num; i++) { string text = pkg.ReadString(); if (text == null || text.Length > 4096) { throw new FormatException("registry line is too long"); } list.Add(text); } SettingRegistry registry = SettingRegistry.Parse(string.Join("\n", list.ToArray())); int num2 = pkg.ReadInt(); if (num2 < 0 || num2 > 2048) { throw new FormatException("invalid value count " + num2); } List<KeyValuePair<string, string>> list2 = new List<KeyValuePair<string, string>>(); for (int j = 0; j < num2; j++) { string text2 = pkg.ReadString(); string text3 = pkg.ReadString(); if (text2 == null || text3 == null || text2.Length > 4096 || text3.Length > 4096) { throw new FormatException("setting alias or value is too long"); } list2.Add(new KeyValuePair<string, string>(text2, text3)); } long num3 = pkg.ReadLong(); bool flag = pkg.ReadBool(); if (pkg.GetPos() != pkg.Size()) { throw new FormatException("unexpected snapshot data"); } Registry = registry; _hookedEntries.Clear(); EnsureHooks(); Log.LogInfo((object)("Adopted server settings registry: " + Registry.Defs.Count + " setting(s).")); _lastPayload = list2; _watchdogTimer = _watchdogSeconds.Value; ApplyPairs(list2, silent: false); ZPackage val = new ZPackage(); val.Write(num3); val.Write(_lastAppliedCount); val.Write(_lastRejectedCount); val.Write(_lastApplyError ?? ""); ZRoutedRpc.instance.InvokeRoutedRPC(sender, "PPS.Status", new object[1] { val }); _clientApplicationStatus = (flag ? "Stored on server. " : "Server persistence pending. ") + _lastAppliedCount + " applied; " + _lastRejectedCount + " rejected."; ShowToast(_clientApplicationStatus); } catch (Exception ex) { Log.LogWarning((object)("Rejected malformed PPS.Apply from server: " + ex.Message)); } } private void RpcMsgHandler(long sender, ZPackage pkg) { if (_shutDown || !_enabled.Value || (Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer()) { return; } ZNetPeer serverPeer = ZNet.instance.GetServerPeer(); if (serverPeer == null || sender != serverPeer.m_uid) { Log.LogWarning((object)("Ignored PPS.Msg from non-server sender " + sender)); return; } try { string text = pkg.ReadString(); if (text == null || text.Length > 4096) { throw new FormatException("message is too long"); } ShowToast(text); } catch (Exception ex) { Log.LogWarning((object)("Rejected malformed PPS.Msg from server: " + ex.Message)); } } private List<KeyValuePair<string, string>> BuildPairs(string playerId) { Dictionary<string, string> dictionary = new Dictionary<string, string>(_defaults, StringComparer.OrdinalIgnoreCase); PlayerData playerData = Store.Get(playerId); if (playerData != null) { foreach (KeyValuePair<string, string> setting in playerData.Settings) { dictionary[setting.Key] = setting.Value; } } List<KeyValuePair<string, string>> list = new List<KeyValuePair<string, string>>(); foreach (KeyValuePair<string, string> item in dictionary) { SettingDef settingDef = Registry.Find(item.Key); if (settingDef != null) { list.Add(new KeyValuePair<string, string>(settingDef.Alias, item.Value)); } } return list; } private void PushToPeer(long uid) { //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Expected O, but got Unknown if (_adminFeedback != null) { _adminPushPeers.Add(uid); } else { if ((Object)(object)ZNet.instance == (Object)null) { return; } ZNetPeer peer = ZNet.instance.GetPeer(uid); if (peer == null) { return; } List<KeyValuePair<string, string>> list = BuildPairs(ResolvePeerId(peer)); ZPackage val = new ZPackage(); List<string> list2 = Registry.ToLines(); if (list2.Count > 2048 || list.Count > 2048) { Log.LogError((object)("Cannot push settings snapshot: registry/value count exceeds " + 2048 + ".")); return; } val.Write(list2.Count); foreach (string item in list2) { val.Write(item); } val.Write(list.Count); foreach (KeyValuePair<string, string> item2 in list) { val.Write(item2.Key); val.Write(item2.Value); } long num = ++_nextPushSequence; _pushSequences[uid] = num; _applicationStatus[uid] = (Store.HasUnsavedChanges ? "Persistence pending; awaiting client result." : "Stored; awaiting client result."); val.Write(num); val.Write(!Store.HasUnsavedChanges); ZRoutedRpc.instance.InvokeRoutedRPC(uid, "PPS.Apply", new object[1] { val }); } } private void PushAllToOnline() { if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { return; } foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (peer != null) { PushToPeer(peer.m_uid); } } } private void SendMsg(long uid, string message) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Expected O, but got Unknown if (message == null) { message = ""; } if (message.Length > 4096) { message = message.Substring(0, 4096); } ZPackage val = new ZPackage(); val.Write(message); ZRoutedRpc.instance.InvokeRoutedRPC(uid, "PPS.Msg", new object[1] { val }); } private void ApplyPairs(List<KeyValuePair<string, string>> pairs, bool silent) { if (_shutDown) { return; } int num = 0; string text = null; _suppressAutoCapture = true; try { foreach (KeyValuePair<string, string> pair in pairs) { SettingDef settingDef = Registry.Find(pair.Key); if (settingDef != null) { string text2 = Registry.TryApply(settingDef, pair.Value, silent); if (text2 == null) { num++; } else if (text == null) { text = settingDef.Alias + ": " + text2; } } } } finally { _suppressAutoCapture = false; } _lastAppliedCount = num; _lastRejectedCount = pairs.Count - num; _lastApplyError = ((text == null) ? "" : text.Substring(0, Math.Min(text.Length, 256))); if (!silent) { if (num > 0 && text == null) { ShowToast("PerPlayer: " + num + " setting(s) applied"); } else if (num > 0) { ShowToast("PerPlayer: " + num + " applied; failed: " + text); } else if (text != null) { ShowToast("PerPlayer: " + text); } } } private string DescribePairsFriendly(List<KeyValuePair<string, string>> pairs) { StringBuilder stringBuilder = new StringBuilder(); int num = 0; foreach (KeyValuePair<string, string> pair in pairs) { SettingDef settingDef = Registry.Find(pair.Key); if (settingDef != null) { if (num > 0) { stringBuilder.Append(", "); } stringBuilder.Append(settingDef.ModGuid).Append(" | ").Append(settingDef.Key) .Append(" = ") .Append(pair.Value); num++; } } if (stringBuilder.Length <= 0) { return "(none)"; } return stringBuilder.ToString(); } private bool ResolveSetting(string name, out SettingDef def, out string error) { def = null; error = null; if (string.IsNullOrEmpty(name)) { error = "Empty setting name"; return false; } def = Registry.Find(name); if (def != null) { return true; } List<SettingDef> list = Registry.FindAllByKey(name); if (list.Count == 1) { def = list[0]; return true; } if (list.Count > 1) { StringBuilder stringBuilder = new StringBuilder("'" + name + "' exists in several mods: "); for (int i = 0; i < list.Count; i++) { if (i > 0) { stringBuilder.Append(", "); } stringBuilder.Append(list[i].ModGuid); } error = stringBuilder.ToString(); return false; } error = "No managed setting '" + name + "'"; return false; } private static string DescribePairs(List<KeyValuePair<string, string>> pairs) { StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < pairs.Count; i++) { if (i > 0) { stringBuilder.Append(", "); } stringBuilder.Append(pairs[i].Key).Append("=").Append(pairs[i].Value); } if (stringBuilder.Length <= 0) { return "(none)"; } return stringBuilder.ToString(); } internal static string EndpointId(ZNetPeer peer) { try { if (peer != null && peer.m_socket != null) { string name = ((object)peer.m_socket).GetType().Name; bool flag = string.Equals(name, "ZSocket", StringComparison.Ordinal) || string.Equals(name, "ZSocket2", StringComparison.Ordinal); if (!flag) { string hostName = peer.m_socket.GetHostName(); if (!string.IsNullOrEmpty(hostName) && !string.Equals(hostName, "None", StringComparison.OrdinalIgnoreCase)) { return hostName; } } string endPointString = peer.m_socket.GetEndPointString(); if (!string.IsNullOrEmpty(endPointString) && !string.Equals(endPointString, "None", StringComparison.OrdinalIgnoreCase)) { return flag ? ("endpoint/" + endPointString) : endPointString; } } } catch (Exception) { } if (peer == null) { return "?"; } return peer.m_uid.ToString(); } private static string LegacyEndpointId(ZNetPeer peer) { try { if (peer != null && peer.m_socket != null) { string endPointString = peer.m_socket.GetEndPointString(); if (!string.IsNullOrEmpty(endPointString)) { int num = endPointString.IndexOf(':'); return (num > 0) ? endPointString.Substring(0, num) : endPointString; } } } catch (Exception) { } if (peer == null) { return "?"; } return peer.m_uid.ToString(); } private string ResolvePeerId(ZNetPeer peer) { string text = EndpointId(peer); string text2 = LegacyEndpointId(peer); if (Store.ReadOnly) { return text; } if (string.Equals(text, text2, StringComparison.Ordinal) || !text2.StartsWith("playfab/", StringComparison.OrdinalIgnoreCase)) { return text; } PlayerData playerData = Store.Get(text2); if (playerData == null) { return text; } PlayerData orCreate = Store.GetOrCreate(text); if (string.IsNullOrEmpty(orCreate.Name)) { orCreate.Name = playerData.Name; } foreach (KeyValuePair<string, string> setting in playerData.Settings) { if (!orCreate.Settings.ContainsKey(setting.Key)) { orCreate.Settings[setting.Key] = setting.Value; } } Store.Remove(text2); Store.Save(); Log.LogInfo((object)("Migrated player settings key '" + text2 + "' -> '" + text + "'.")); return text; } private void OnDestroy() { //IL_00d1: Unknown result type (might be due to invalid IL or missing references) if (_shutDown) { return; } _shutDown = true; foreach (ConfigFile hookedFile in _hookedFiles) { hookedFile.SettingChanged -= OnConfigFileSettingChanged; } _hookedFiles.Clear(); _hookedEntries.Clear(); for (int num = _coreRegistrations.Count - 1; num >= 0; num--) { try { _coreRegistrations[num].Dispose(); } catch (Exception ex) { ManualLogSource logger = ((BaseUnityPlugin)this).Logger; if (logger != null) { logger.LogWarning((object)("Registration cleanup failed: " + ex.Message)); } } } _coreRegistrations.Clear(); if (Core != null) { Core.Metrics.RemoveOwner(ModuleId); } Core = null; } } public class SettingDef { public string Alias; public string ModGuid; public string Section; public string Key; public bool PlayerSettable; } public class SettingRegistry { private const int MaxValueLength = 4096; private readonly List<SettingDef> _defs = new List<SettingDef>(); public IList<SettingDef> Defs => _defs; public static SettingRegistry Parse(string configText) { SettingRegistry settingRegistry = new SettingRegistry(); if (string.IsNullOrEmpty(configText)) { return settingRegistry; } string[] array = configText.Split(new char[1] { '\n' }, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim().TrimEnd(new char[1] { '\r' }); if (text.Length == 0 || text.StartsWith("#", StringComparison.Ordinal)) { continue; } string[] array2 = text.Split(new char[1] { '|' }); if (array2.Length < 4) { PerPlayerSettingsPlugin.Log.LogWarning((object)("Ignoring malformed SyncedSettings line (need alias|ModGuid|Section|Key[|playersCanSetOwn]): " + text)); continue; } SettingDef settingDef = new SettingDef(); settingDef.Alias = array2[0].Trim(); settingDef.ModGuid = array2[1].Trim(); settingDef.Section = array2[2].Trim(); settingDef.Key = array2[3].Trim(); settingDef.PlayerSettable = true; if (array2.Length >= 5 && !bool.TryParse(array2[4].Trim(), out settingDef.PlayerSettable)) { PerPlayerSettingsPlugin.Log.LogWarning((object)("Ignoring SyncedSettings line with invalid playersCanSetOwn value: " + text)); } else if (settingDef.Alias.Length == 0 || settingDef.ModGuid.Length == 0 || settingDef.Key.Length == 0) { PerPlayerSettingsPlugin.Log.LogWarning((object)("Ignoring incomplete SyncedSettings line: " + text)); } else if (settingRegistry.Find(settingDef.Alias) != null) { PerPlayerSettingsPlugin.Log.LogWarning((object)("Ignoring duplicate alias '" + settingDef.Alias + "'.")); } else { settingRegistry._defs.Add(settingDef); } } return settingRegistry; } public SettingDef Find(string alias) { foreach (SettingDef def in _defs) { if (string.Equals(def.Alias, alias, StringComparison.OrdinalIgnoreCase)) { return def; } } return null; } public SettingDef FindByKey(string modGuid, string section, string key) { foreach (SettingDef def in _defs) { if (string.Equals(def.ModGuid, modGuid, StringComparison.OrdinalIgnoreCase) && string.Equals(def.Section, section, StringComparison.OrdinalIgnoreCase) && string.Equals(def.Key, key, StringComparison.OrdinalIgnoreCase)) { return def; } } return null; } public List<SettingDef> FindAllByKey(string key) { List<SettingDef> list = new List<SettingDef>(); foreach (SettingDef def in _defs) { if (string.Equals(def.Key, key, StringComparison.OrdinalIgnoreCase)) { list.Add(def); } } return list; } public string Keys() { StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < _defs.Count; i++) { if (i > 0) { stringBuilder.Append(", "); } stringBuilder.Append(_defs[i].Key).Append(" (").Append(_defs[i].ModGuid) .Append(")"); if (!_defs[i].PlayerSettable) { stringBuilder.Append(" [admin only]"); } } if (stringBuilder.Length <= 0) { return "(none configured)"; } return stringBuilder.ToString(); } public List<SettingDef> FindByGuidKey(string modGuid, string key) { List<SettingDef> list = new List<SettingDef>(); foreach (SettingDef def in _defs) { if (string.Equals(def.ModGuid, modGuid, StringComparison.OrdinalIgnoreCase) && string.Equals(def.Key, key, StringComparison.OrdinalIgnoreCase)) { list.Add(def); } } return list; } public bool Add(SettingDef def) { if (def == null || Find(def.Alias) != null) { return false; } _defs.Add(def); return true; } public bool Remove(string alias) { SettingDef settingDef = Find(alias); if (settingDef != null) { return _defs.Remove(settingDef); } return false; } public List<string> ToLines() { List<string> list = new List<string>(); foreach (SettingDef def in _defs) { list.Add(def.Alias + "|" + def.ModGuid + "|" + def.Section + "|" + def.Key + "|" + (def.PlayerSettable ? "true" : "false")); } return list; } public string Aliases() { StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < _defs.Count; i++) { if (i > 0) { stringBuilder.Append(", "); } stringBuilder.Append(_defs[i].Alias); if (!_defs[i].PlayerSettable) { stringBuilder.Append(" (admin only)"); } } if (stringBuilder.Length <= 0) { return "(none configured)"; } return stringBuilder.ToString(); } public ConfigEntryBase ResolveEntry(SettingDef def) { if (def == null || Chainloader.PluginInfos == null) { return null; } BaseUnityPlugin val = null; foreach (KeyValuePair<string, PluginInfo> pluginInfo in Chainloader.PluginInfos) { if (string.Equals(pluginInfo.Key, def.ModGuid, StringComparison.OrdinalIgnoreCase)) { val = ((pluginInfo.Value != null) ? pluginInfo.Value.Instance : null); break; } } if ((Object)(object)val == (Object)null || val.Config == null) { return null; } foreach (KeyValuePair<ConfigDefinition, ConfigEntryBase> item in (IEnumerable<KeyValuePair<ConfigDefinition, ConfigEntryBase>>)val.Config) { if (string.Equals(item.Key.Section, def.Section, StringComparison.OrdinalIgnoreCase) && string.Equals(item.Key.Key, def.Key, StringComparison.OrdinalIgnoreCase)) { return item.Value; } } return null; } public static string TryParseValue(Type settingType, string raw, out object boxed) { //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_0137: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Unknown result type (might be due to invalid IL or missing references) //IL_0176: Unknown result type (might be due to invalid IL or missing references) //IL_01ad: Unknown result type (might be due to invalid IL or missing references) //IL_01b5: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Unknown result type (might be due to invalid IL or missing references) //IL_01a1: Unknown result type (might be due to invalid IL or missing references) boxed = null; if (settingType == null) { return "unknown setting type"; } if (settingType == typeof(string)) { boxed = raw; return null; } if (settingType.IsEnum) { try { boxed = Enum.Parse(settingType, raw, ignoreCase: true); if (!Attribute.IsDefined(settingType, typeof(FlagsAttribute)) && !Enum.IsDefined(settingType, boxed)) { boxed = null; return "'" + raw + "' is not a defined " + settingType.Name + " value"; } return null; } catch (ArgumentException) { return "'" + raw + "' is not a valid " + settingType.Name + " (example: " + Enum.GetNames(settingType)[0] + ")"; } } if (settingType == typeof(KeyboardShortcut)) { string text = raw.Trim(); if (text.Length == 0 || string.Equals(text, "None", StringComparison.OrdinalIgnoreCase)) { boxed = KeyboardShortcut.Empty; return null; } string[] array = text.Split(new char[1] { '+' }); KeyCode val = (KeyCode)0; List<KeyCode> list = new List<KeyCode>(); for (int i = 0; i < array.Length; i++) { if (!Enum.TryParse<KeyCode>(array[i].Trim(), ignoreCase: true, out KeyCode result)) { return "'" + array[i].Trim() + "' is not a valid KeyCode"; } if (i == 0) { val = result; } else { list.Add(result); } } try { boxed = ((list.Count > 0) ? ((object)new KeyboardShortcut(val, list.ToArray())) : ((object)new KeyboardShortcut(val, Array.Empty<KeyCode>()))); return null; } catch (ArgumentException ex2) { return ex2.Message; } } if (settingType == typeof(bool)) { if (bool.TryParse(raw, out var result2)) { boxed = result2; return null; } return "expected true or false"; } if (settingType == typeof(int)) { if (int.TryParse(raw, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result3)) { boxed = result3; return null; } return "expected a whole number"; } if (settingType == typeof(long)) { if (long.TryParse(raw, NumberStyles.Integer, CultureInfo.Inva