Decompiled source of PerPlayerSettings v0.5.0

PerPlayerSettings.dll

Decompiled 8 hours ago
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