Decompiled source of Lawspeaker v1.0.0

Lawspeaker.dll

Decompiled a week ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using System.Threading;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using MyMods.Web;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("Lawspeaker")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+7fab51023aedc8d4c3e815dac3ed9cafbe3c3417")]
[assembly: AssemblyProduct("Lawspeaker")]
[assembly: AssemblyTitle("Lawspeaker")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace MyMods.Web
{
	public static class WebNav
	{
		public sealed class Link
		{
			public readonly string Id;

			public readonly string Label;

			public readonly int Port;

			public string Url => "http://127.0.0.1:" + Port + "/";

			public Link(string id, string label, int port)
			{
				Id = id;
				Label = label;
				Port = port;
			}
		}

		public static readonly Link[] Links = new Link[19]
		{
			new Link("monsters", "Monsters", 8777),
			new Link("searoute", "SeaRoute", 8778),
			new Link("longboat", "Harpoon Longboat", 8779),
			new Link("tradewind", "TradeWind", 8780),
			new Link("safeharbor", "Safe Harbor", 8781),
			new Link("hub", "DFG Hub", 8782),
			new Link("vault", "Viking Vault", 8783),
			new Link("qolvault", "QoL Vault Buttons", 8784),
			new Link("qolinventory", "QoL Inventory Buttons", 8785),
			new Link("cacophony", "Cacophony", 8787),
			new Link("ominous", "Ominous", 8788),
			new Link("pebble", "Pebble", 8789),
			new Link("trolls", "Trolls", 8790),
			new Link("transporter", "Transporter", 8791),
			new Link("skytram", "Sky-Tram", 8792),
			new Link("dockfortress", "Dock Fortress", 8793),
			new Link("odinseye", "Odin's Eye", 8794),
			new Link("skyherd", "Skyherd", 8795),
			new Link("lawspeaker", "Lawspeaker", 8796)
		};

		public const string Style = "nav.top{position:sticky;top:0;z-index:20;font-family:Segoe UI,Arial,sans-serif;background:#0d1219;border-bottom:1px solid #2b3b4c;padding:6px 14px;display:flex;align-items:center;gap:6px;flex-wrap:wrap}\nnav.top .brand{color:#7f8fa0;font-size:11px;text-transform:uppercase;letter-spacing:.6px;margin-right:8px}\nnav.top a{color:#9fc3dd;text-decoration:none;font-size:13px;padding:4px 9px;border-radius:5px}\nnav.top a:hover{background:#1a2530}\nnav.top a.here{background:#3a2b4c;border:1px solid #55406b;color:#efe0ff}\n";

		public static string Html(string currentId)
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append("<nav class='top'><span class='brand'>MyMods</span>");
			Link[] links = Links;
			foreach (Link link in links)
			{
				stringBuilder.Append("<a class='");
				if (link.Id == currentId)
				{
					stringBuilder.Append("here");
				}
				stringBuilder.Append("' href='").Append(link.Url).Append("'>")
					.Append(link.Label)
					.Append("</a>");
			}
			stringBuilder.Append("</nav>");
			return stringBuilder.ToString();
		}

		public static Link Find(string id)
		{
			Link[] links = Links;
			foreach (Link link in links)
			{
				if (link.Id == id)
				{
					return link;
				}
			}
			return null;
		}
	}
}
namespace Lawspeaker
{
	public static class Authority
	{
		private sealed class Pending
		{
			public readonly ManualResetEventSlim Done = new ManualResetEventSlim(initialState: false);

			public readonly long Started = DateTime.UtcNow.Ticks;

			public bool Ok;

			public string Error;

			public bool Pushed;

			public bool Settled;
		}

		public sealed class Change
		{
			public string Id { get; set; }

			public string Guid { get; set; }

			public string Section { get; set; }

			public string Key { get; set; }

			public string Value { get; set; }

			public bool Push { get; set; }
		}

		public sealed class Reply
		{
			public string Id { get; set; }

			public bool Ok { get; set; }

			public string Error { get; set; }

			public bool Pushed { get; set; }

			public string Guid { get; set; }

			public string Section { get; set; }

			public string Key { get; set; }

			public string Value { get; set; }
		}

		public const string PushRpc = "Lawspeaker_Push";

		public const string ApplyRpc = "Lawspeaker_Apply";

		public const string ResultRpc = "Lawspeaker_Result";

		private static readonly JsonSerializerSettings JsonSettings = new JsonSerializerSettings
		{
			ContractResolver = (IContractResolver)new CamelCasePropertyNamesContractResolver()
		};

		private static readonly ConcurrentDictionary<string, Pending> Waiting = new ConcurrentDictionary<string, Pending>();

		public static string Serialize(object value)
		{
			return JsonConvert.SerializeObject(value, JsonSettings);
		}

		public static Change ReadChange(string json)
		{
			return Parse<Change>(json);
		}

		public static void Describe(Snapshot snap)
		{
			ZNet instance = ZNet.instance;
			snap.InWorld = (Object)(object)instance != (Object)null;
			snap.Dedicated = (Object)(object)instance != (Object)null && instance.IsCurrentServerDedicated();
			snap.CanPush = (Object)(object)instance != (Object)null && (instance.IsServer() || instance.LocalPlayerIsAdminOrHost());
			if ((Object)(object)instance == (Object)null)
			{
				snap.Role = "menu";
				snap.RoleLabel = "Not in a world. Changes stay on this computer.";
			}
			else if (instance.IsServer() && snap.Dedicated)
			{
				snap.Role = "host";
				snap.RoleLabel = "This is the dedicated server. Saving a shared setting updates everyone connected.";
			}
			else if (instance.IsServer())
			{
				snap.Role = "host";
				snap.RoleLabel = "You are hosting. Saving a shared setting updates everyone connected.";
			}
			else if (instance.LocalPlayerIsAdminOrHost())
			{
				snap.Role = "admin";
				snap.RoleLabel = "You are an admin on this server. You can push shared settings.";
			}
			else
			{
				snap.Role = "player";
				snap.RoleLabel = (snap.Dedicated ? "You are not an admin. Edits stay on this computer." : "You are not the host. Edits stay on this computer.");
			}
		}

		public static bool IsHost()
		{
			if ((Object)(object)ZNet.instance != (Object)null)
			{
				return ZNet.instance.IsServer();
			}
			return false;
		}

		public static void Prepare(string id)
		{
			Waiting[id] = new Pending();
		}

		public static void Begin(Change change)
		{
			if (change == null || string.IsNullOrEmpty(change.Id))
			{
				return;
			}
			if (!Waiting.TryGetValue(change.Id, out var value))
			{
				value = new Pending();
				Waiting[change.Id] = value;
			}
			bool flag = (value.Pushed = change.Push);
			string text = Catalog.Validate(change.Guid, change.Section, change.Key, change.Value, flag);
			if (text != null)
			{
				Finish(change.Id, ok: false, text);
				LawspeakerGui.Note(text, bad: true);
				return;
			}
			if (!flag || IsHost())
			{
				text = Catalog.Commit(change.Guid, change.Section, change.Key, change.Value);
				if (text != null)
				{
					Finish(change.Id, ok: false, text);
					LawspeakerGui.Note(text, bad: true);
					return;
				}
				if (flag)
				{
					Broadcast(change);
				}
				Note(change, ok: true, flag);
				LawspeakerGui.Note(flag ? "Saved for everyone." : "Saved on this computer.", bad: false);
				Finish(change.Id, ok: true, null);
				return;
			}
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null || !instance.LocalPlayerIsAdminOrHost())
			{
				Finish(change.Id, ok: false, "You are not a server admin.");
				LawspeakerGui.Note("You are not a server admin.", bad: true);
				return;
			}
			ZNetPeer serverPeer = instance.GetServerPeer();
			if (serverPeer == null || ZRoutedRpc.instance == null)
			{
				Finish(change.Id, ok: false, "Not connected to a server.");
				LawspeakerGui.Note("Not connected to a server.", bad: true);
			}
			else
			{
				LawspeakerGui.Note("Sending to the server…", bad: false);
				ZRoutedRpc.instance.InvokeRoutedRPC(serverPeer.m_uid, "Lawspeaker_Push", new object[1] { Serialize(change) });
			}
		}

		public static string Wait(string id, int milliseconds)
		{
			if (!Waiting.TryGetValue(id, out var value))
			{
				return Serialize(new Reply
				{
					Id = id,
					Ok = false,
					Error = "lost the request"
				});
			}
			bool num = value.Done.Wait(milliseconds);
			Reply reply = new Reply
			{
				Id = id
			};
			if (!num || !value.Settled)
			{
				Finish(id, ok: false, "The server did not answer. Lawspeaker has to be installed there too.");
				reply.Ok = false;
				reply.Error = "The server did not answer. Lawspeaker has to be installed there too.";
			}
			else
			{
				reply.Ok = value.Ok;
				reply.Error = value.Error;
				reply.Pushed = value.Ok && value.Pushed;
			}
			Waiting.TryRemove(id, out var _);
			return Serialize(reply);
		}

		public static void Expire()
		{
			long ticks = DateTime.UtcNow.AddSeconds(-8.0).Ticks;
			foreach (KeyValuePair<string, Pending> item in Waiting)
			{
				if (!item.Value.Settled && item.Value.Started < ticks)
				{
					Finish(item.Key, ok: false, "The server did not answer. Lawspeaker has to be installed there too.");
				}
			}
		}

		public static void OnPush(long sender, string payload)
		{
			if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer())
			{
				return;
			}
			Change change = Parse<Change>(payload);
			if (change == null)
			{
				ReplyTo(sender, new Reply
				{
					Ok = false,
					Error = "could not read that change"
				});
				return;
			}
			ZNetPeer peer = ZNet.instance.GetPeer(sender);
			string text = HostOf(peer);
			if (peer == null || string.IsNullOrEmpty(text) || !ZNet.instance.IsAdmin(text))
			{
				ReplyTo(sender, Failed(change, "You are not a server admin."));
				return;
			}
			string text2 = Catalog.Validate(change.Guid, change.Section, change.Key, change.Value, pushing: true);
			if (text2 == null)
			{
				text2 = Catalog.Commit(change.Guid, change.Section, change.Key, change.Value);
			}
			if (text2 != null)
			{
				ReplyTo(sender, Failed(change, text2));
				return;
			}
			Broadcast(change);
			Note(change, ok: true, pushed: true);
			ReplyTo(sender, new Reply
			{
				Id = change.Id,
				Ok = true,
				Pushed = true,
				Guid = change.Guid,
				Section = change.Section,
				Key = change.Key,
				Value = change.Value
			});
		}

		public static void OnApply(long sender, string payload)
		{
			if ((Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer())
			{
				return;
			}
			ZNetPeer peer = ZNet.instance.GetPeer(sender);
			if (peer != null && peer.m_server)
			{
				Change change = Parse<Change>(payload);
				if (change != null && Catalog.Validate(change.Guid, change.Section, change.Key, change.Value, pushing: true) == null)
				{
					Catalog.Commit(change.Guid, change.Section, change.Key, change.Value);
				}
			}
		}

		public static void OnResult(long sender, string payload)
		{
			if ((Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer())
			{
				return;
			}
			ZNetPeer peer = ZNet.instance.GetPeer(sender);
			if (peer == null || !peer.m_server)
			{
				return;
			}
			Reply reply = Parse<Reply>(payload);
			if (reply != null && !string.IsNullOrEmpty(reply.Id))
			{
				if (reply.Ok)
				{
					Catalog.Commit(reply.Guid, reply.Section, reply.Key, reply.Value);
				}
				Finish(reply.Id, reply.Ok, reply.Error);
				if (!reply.Ok)
				{
					LawspeakerGui.Note(string.IsNullOrEmpty(reply.Error) ? "The server refused that change." : reply.Error, bad: true);
				}
				else
				{
					LawspeakerGui.Note("Pushed to the server.", bad: false);
				}
			}
		}

		public static void Broadcast(Change change)
		{
			if (change != null && ZRoutedRpc.instance != null && !((Object)(object)ZNet.instance == (Object)null))
			{
				ZRoutedRpc.instance.InvokeRoutedRPC(0L, "Lawspeaker_Apply", new object[1] { Serialize(change) });
			}
		}

		private static void ReplyTo(long target, Reply reply)
		{
			if (ZRoutedRpc.instance != null && reply != null)
			{
				ZRoutedRpc.instance.InvokeRoutedRPC(target, "Lawspeaker_Result", new object[1] { Serialize(reply) });
			}
		}

		private static Reply Failed(Change change, string error)
		{
			return new Reply
			{
				Id = ((change != null) ? change.Id : ""),
				Ok = false,
				Error = error,
				Guid = ((change != null) ? change.Guid : ""),
				Section = ((change != null) ? change.Section : ""),
				Key = ((change != null) ? change.Key : "")
			};
		}

		private static void Finish(string id, bool ok, string error)
		{
			if (!string.IsNullOrEmpty(id) && Waiting.TryGetValue(id, out var value) && !value.Settled)
			{
				value.Settled = true;
				value.Ok = ok;
				value.Error = error;
				value.Done.Set();
			}
		}

		private static void Note(Change change, bool ok, bool pushed)
		{
			if (ok)
			{
				ManualLogSource log = LawspeakerPlugin.Log;
				if (log != null && change != null)
				{
					string text = Shown(change.Key, change.Value);
					log.LogInfo((object)("Lawspeaker: " + change.Guid + " [" + change.Section + "] " + change.Key + " = " + text + (pushed ? " (shared)" : " (this computer)")));
				}
			}
		}

		private static string Shown(string key, string value)
		{
			string text = (key ?? "").ToLowerInvariant();
			if (text.IndexOf("password", StringComparison.Ordinal) >= 0 || text.IndexOf("token", StringComparison.Ordinal) >= 0 || text.IndexOf("secret", StringComparison.Ordinal) >= 0)
			{
				return "(hidden)";
			}
			if (value != null && value.Length > 80)
			{
				return value.Substring(0, 80) + "...";
			}
			return value ?? "";
		}

		private static string HostOf(ZNetPeer peer)
		{
			if (peer == null || peer.m_rpc == null)
			{
				return null;
			}
			ISocket socket = peer.m_rpc.GetSocket();
			if (socket == null)
			{
				return null;
			}
			return socket.GetHostName();
		}

		private static T Parse<T>(string payload) where T : class
		{
			if (string.IsNullOrEmpty(payload) || payload.Length > 6000)
			{
				return null;
			}
			try
			{
				return JsonConvert.DeserializeObject<T>(payload, JsonSettings);
			}
			catch
			{
				return null;
			}
		}
	}
	public sealed class Snapshot
	{
		public string Role { get; set; }

		public string RoleLabel { get; set; }

		public bool CanPush { get; set; }

		public bool InWorld { get; set; }

		public bool Dedicated { get; set; }

		public List<PluginSlice> Plugins { get; set; }
	}
	public sealed class PluginSlice
	{
		public string Guid { get; set; }

		public string Name { get; set; }

		public string Version { get; set; }

		public List<EntrySlice> Entries { get; set; }
	}
	public sealed class EntrySlice
	{
		public string Section { get; set; }

		public string Key { get; set; }

		public string Type { get; set; }

		public string Kind { get; set; }

		public string Value { get; set; }

		public string DefaultValue { get; set; }

		public string Description { get; set; }

		public bool Local { get; set; }

		public string LocalReason { get; set; }

		public bool ReadOnly { get; set; }

		public string ReadOnlyReason { get; set; }

		public List<string> Choices { get; set; }

		public string Min { get; set; }

		public string Max { get; set; }

		public string Step { get; set; }
	}
	public static class Catalog
	{
		public static Snapshot Read()
		{
			Snapshot snapshot = new Snapshot();
			snapshot.Plugins = new List<PluginSlice>();
			Authority.Describe(snapshot);
			Dictionary<string, PluginInfo> pluginInfos = Chainloader.PluginInfos;
			if (pluginInfos == null)
			{
				return snapshot;
			}
			foreach (PluginInfo value in pluginInfos.Values)
			{
				if (value == null || (Object)(object)value.Instance == (Object)null || value.Metadata == null || value.Instance.Config == null)
				{
					continue;
				}
				List<ConfigEntryBase> list;
				try
				{
					list = EntriesOf(value.Instance.Config);
				}
				catch
				{
					continue;
				}
				if (list.Count == 0)
				{
					continue;
				}
				PluginSlice pluginSlice = new PluginSlice();
				pluginSlice.Guid = value.Metadata.GUID ?? "";
				pluginSlice.Name = (string.IsNullOrEmpty(value.Metadata.Name) ? pluginSlice.Guid : value.Metadata.Name);
				pluginSlice.Version = ((value.Metadata.Version == null) ? "" : value.Metadata.Version.ToString());
				pluginSlice.Entries = new List<EntrySlice>();
				for (int i = 0; i < list.Count; i++)
				{
					EntrySlice entrySlice = Describe(pluginSlice.Guid, list[i]);
					if (entrySlice != null)
					{
						pluginSlice.Entries.Add(entrySlice);
					}
				}
				pluginSlice.Entries.Sort(CompareEntries);
				if (pluginSlice.Entries.Count > 0)
				{
					snapshot.Plugins.Add(pluginSlice);
				}
			}
			snapshot.Plugins.Sort((PluginSlice a, PluginSlice b) => string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase));
			return snapshot;
		}

		public static string Validate(string pluginGuid, string section, string key, string value, bool pushing)
		{
			string text = SettingPolicy.Reject(pluginGuid, section, key, value);
			if (text != null)
			{
				return text;
			}
			string error;
			ConfigEntryBase val = Find(pluginGuid, section, key, out error);
			if (val == null)
			{
				return error;
			}
			string text2 = ReadOnlyReason(val);
			if (text2 != null)
			{
				return text2;
			}
			string text3 = SettingPolicy.LocalReason(pluginGuid, section, key, val.SettingType.Name);
			if (pushing && text3 != null)
			{
				return text3;
			}
			object converted;
			return TryConvert(val, value, out converted);
		}

		public static string Commit(string pluginGuid, string section, string key, string value)
		{
			string error;
			ConfigEntryBase val = Find(pluginGuid, section, key, out error);
			if (val == null)
			{
				return error;
			}
			object converted;
			string text = TryConvert(val, value, out converted);
			if (text != null)
			{
				return text;
			}
			try
			{
				if (!Same(val, converted))
				{
					val.BoxedValue = converted;
				}
			}
			catch (Exception ex)
			{
				return ex.Message;
			}
			return null;
		}

		private static EntrySlice Describe(string pluginGuid, ConfigEntryBase entry)
		{
			if (entry == null || entry.Definition == (ConfigDefinition)null || entry.SettingType == null)
			{
				return null;
			}
			EntrySlice entrySlice = new EntrySlice();
			entrySlice.Section = entry.Definition.Section ?? "";
			entrySlice.Key = entry.Definition.Key ?? "";
			entrySlice.Type = entry.SettingType.Name;
			entrySlice.Value = Show(entry.BoxedValue, entry.SettingType);
			entrySlice.DefaultValue = Show(entry.DefaultValue, entry.SettingType);
			entrySlice.Description = ((entry.Description != null) ? (entry.Description.Description ?? "") : "");
			entrySlice.LocalReason = SettingPolicy.LocalReason(pluginGuid, entrySlice.Section, entrySlice.Key, entrySlice.Type);
			entrySlice.Local = entrySlice.LocalReason != null;
			entrySlice.ReadOnlyReason = ReadOnlyReason(entry);
			entrySlice.ReadOnly = entrySlice.ReadOnlyReason != null;
			entrySlice.Choices = Choices(entry);
			entrySlice.Min = "";
			entrySlice.Max = "";
			Range(entry, entrySlice);
			entrySlice.Kind = KindOf(entry, entrySlice);
			entrySlice.Step = (IsIntegral(entry.SettingType) ? "1" : "any");
			return entrySlice;
		}

		private static string KindOf(ConfigEntryBase entry, EntrySlice row)
		{
			Type settingType = entry.SettingType;
			if (settingType == typeof(bool))
			{
				return "bool";
			}
			if (settingType.IsEnum || (row.Choices != null && row.Choices.Count > 0))
			{
				return "choice";
			}
			if (IsIntegral(settingType) || settingType == typeof(float) || settingType == typeof(double))
			{
				return "number";
			}
			return "text";
		}

		private static bool IsIntegral(Type type)
		{
			if (!(type == typeof(int)) && !(type == typeof(long)) && !(type == typeof(short)) && !(type == typeof(byte)) && !(type == typeof(uint)))
			{
				return type == typeof(ushort);
			}
			return true;
		}

		private static List<string> Choices(ConfigEntryBase entry)
		{
			List<string> list = new List<string>();
			AcceptableValueBase val = ((entry.Description != null) ? entry.Description.AcceptableValues : null);
			if (val != null)
			{
				PropertyInfo property = ((object)val).GetType().GetProperty("AcceptableValues");
				object obj = ((property != null) ? property.GetValue(val, null) : null);
				if (obj is IEnumerable enumerable && !(obj is string))
				{
					foreach (object item in enumerable)
					{
						list.Add(Show(item, entry.SettingType));
					}
				}
			}
			if (list.Count == 0 && entry.SettingType.IsEnum)
			{
				string[] names = Enum.GetNames(entry.SettingType);
				for (int i = 0; i < names.Length; i++)
				{
					list.Add(names[i]);
				}
			}
			return list;
		}

		private static void Range(ConfigEntryBase entry, EntrySlice row)
		{
			AcceptableValueBase val = ((entry.Description != null) ? entry.Description.AcceptableValues : null);
			if (val != null)
			{
				Type type = ((object)val).GetType();
				PropertyInfo property = type.GetProperty("MinValue");
				PropertyInfo property2 = type.GetProperty("MaxValue");
				if (!(property == null) && !(property2 == null))
				{
					row.Min = Show(property.GetValue(val, null), entry.SettingType);
					row.Max = Show(property2.GetValue(val, null), entry.SettingType);
				}
			}
		}

		private static string ReadOnlyReason(ConfigEntryBase entry)
		{
			if (!TomlTypeConverter.CanConvert(entry.SettingType))
			{
				return "this value cannot be edited as text";
			}
			object[] array = ((entry.Description != null) ? entry.Description.Tags : null);
			if (array == null)
			{
				return null;
			}
			bool flag = default(bool);
			foreach (object obj in array)
			{
				if (obj == null)
				{
					continue;
				}
				if (obj is BrowsableAttribute { Browsable: false })
				{
					return "the mod hid this setting";
				}
				object obj2 = null;
				PropertyInfo property = obj.GetType().GetProperty("ReadOnly");
				if (property != null)
				{
					obj2 = property.GetValue(obj, null);
				}
				else
				{
					FieldInfo field = obj.GetType().GetField("ReadOnly");
					if (field != null)
					{
						obj2 = field.GetValue(obj);
					}
				}
				int num;
				if (obj2 is bool)
				{
					flag = (bool)obj2;
					num = 1;
				}
				else
				{
					num = 0;
				}
				if (((uint)num & (flag ? 1u : 0u)) != 0)
				{
					return "the mod marked this setting read-only";
				}
			}
			return null;
		}

		private static string TryConvert(ConfigEntryBase entry, string text, out object converted)
		{
			converted = null;
			try
			{
				converted = TomlTypeConverter.ConvertToValue(text, entry.SettingType);
			}
			catch (Exception ex)
			{
				return "could not read that value (" + ex.Message + ")";
			}
			AcceptableValueBase val = ((entry.Description != null) ? entry.Description.AcceptableValues : null);
			if (val != null && !val.IsValid(converted))
			{
				return "that value is outside the range this setting allows";
			}
			return null;
		}

		private static bool Same(ConfigEntryBase entry, object converted)
		{
			string a = Show(entry.BoxedValue, entry.SettingType);
			string b = Show(converted, entry.SettingType);
			return string.Equals(a, b, StringComparison.Ordinal);
		}

		private static string Show(object value, Type type)
		{
			if (value == null || type == null)
			{
				return "";
			}
			try
			{
				if (TomlTypeConverter.CanConvert(type))
				{
					return TomlTypeConverter.ConvertToString(value, type) ?? "";
				}
			}
			catch
			{
			}
			return value.ToString();
		}

		private static ConfigEntryBase Find(string pluginGuid, string section, string key, out string error)
		{
			error = null;
			Dictionary<string, PluginInfo> pluginInfos = Chainloader.PluginInfos;
			if (pluginInfos == null || !pluginInfos.TryGetValue(pluginGuid, out var value) || value == null || (Object)(object)value.Instance == (Object)null)
			{
				error = "that mod is not loaded";
				return null;
			}
			ConfigFile config = value.Instance.Config;
			if (config == null)
			{
				error = "that mod has no config";
				return null;
			}
			List<ConfigEntryBase> list = EntriesOf(config);
			for (int i = 0; i < list.Count; i++)
			{
				ConfigEntryBase val = list[i];
				if (val != null && val.Definition != (ConfigDefinition)null && val.Definition.Section == section && val.Definition.Key == key)
				{
					return val;
				}
			}
			error = "that setting is not loaded";
			return null;
		}

		private static List<ConfigEntryBase> EntriesOf(ConfigFile file)
		{
			List<ConfigEntryBase> list = new List<ConfigEntryBase>();
			foreach (ConfigEntryBase value in ((IDictionary<ConfigDefinition, ConfigEntryBase>)file).Values)
			{
				if (value != null)
				{
					list.Add(value);
				}
			}
			return list;
		}

		private static int CompareEntries(EntrySlice a, EntrySlice b)
		{
			int num = string.Compare(a.Section, b.Section, StringComparison.OrdinalIgnoreCase);
			if (num != 0)
			{
				return num;
			}
			return string.Compare(a.Key, b.Key, StringComparison.OrdinalIgnoreCase);
		}
	}
	public static class LawspeakerGui
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static WindowFunction <0>__DrawWindow;
		}

		private const int WindowId = 4157;

		private static Rect s_rect;

		private static Vector2 s_plugins;

		private static Vector2 s_entries;

		private static string s_query = "";

		private static string s_selected = "";

		private static bool s_pushShared = true;

		private static bool s_pushInit;

		private static string s_status = "";

		private static bool s_statusBad;

		private static float s_statusUntil;

		private static Snapshot s_snap;

		private static float s_snapAt;

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

		private static readonly HashSet<string> s_dirty = new HashSet<string>();

		private static bool s_styles;

		private static GUIStyle s_window;

		private static GUIStyle s_title;

		private static GUIStyle s_muted;

		private static GUIStyle s_section;

		private static GUIStyle s_button;

		private static GUIStyle s_buttonOn;

		private static GUIStyle s_field;

		private static GUIStyle s_badge;

		private static GUIStyle s_badgeLocal;

		private static GUIStyle s_statusStyle;

		private static GUIStyle s_statusBadStyle;

		private static Texture2D s_obsidian;

		private static Texture2D s_panel;

		private static Texture2D s_gold;

		private static Texture2D s_line;

		private static Texture2D s_white;

		private static readonly Color Gold = new Color(1f, 0.843f, 0.376f, 1f);

		private static readonly Color Backdrop = new Color(0f, 0f, 0f, 0.8f);

		private static readonly Color Ink = new Color(0.902f, 0.882f, 0.839f, 1f);

		private static readonly Color Muted = new Color(0.604f, 0.639f, 0.678f, 1f);

		public static bool IsOpen { get; private set; }

		public static void Toggle()
		{
			IsOpen = !IsOpen;
			if (IsOpen && !s_pushInit)
			{
				ZNet instance = ZNet.instance;
				s_pushShared = (Object)(object)instance != (Object)null && (instance.IsServer() || instance.LocalPlayerIsAdminOrHost());
				s_pushInit = true;
			}
		}

		public static void Note(string text, bool bad)
		{
			s_status = text ?? "";
			s_statusBad = bad;
			s_statusUntil = Time.unscaledTime + 6f;
			s_snap = null;
		}

		public static void Shutdown()
		{
			IsOpen = false;
			Destroy(ref s_obsidian);
			Destroy(ref s_panel);
			Destroy(ref s_gold);
			Destroy(ref s_line);
			Destroy(ref s_white);
			s_styles = false;
		}

		public static void Draw()
		{
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b0: Expected O, but got Unknown
			if (!IsOpen)
			{
				return;
			}
			EnsureStyles();
			float num = Mathf.Min(1080f, (float)Screen.width - 40f);
			float num2 = Mathf.Min(700f, (float)Screen.height - 40f);
			if (((Rect)(ref s_rect)).width < 50f)
			{
				s_rect = new Rect(((float)Screen.width - num) * 0.5f, ((float)Screen.height - num2) * 0.5f, num, num2);
			}
			int depth = GUI.depth;
			GUI.depth = -50;
			try
			{
				DrawBackdrop();
				HoldMouse();
				Rect val = s_rect;
				object obj = <>O.<0>__DrawWindow;
				if (obj == null)
				{
					WindowFunction val2 = DrawWindow;
					<>O.<0>__DrawWindow = val2;
					obj = (object)val2;
				}
				s_rect = GUILayout.Window(4157, val, (WindowFunction)obj, GUIContent.none, s_window, Array.Empty<GUILayoutOption>());
			}
			finally
			{
				GUI.depth = depth;
			}
		}

		private static void DrawBackdrop()
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			Color color = GUI.color;
			GUI.color = Backdrop;
			GUI.DrawTexture(new Rect(0f, 0f, (float)Screen.width, (float)Screen.height), (Texture)(object)s_white);
			GUI.color = color;
		}

		private static void HoldMouse()
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Invalid comparison between Unknown and I4
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Invalid comparison between Unknown and I4
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			Event current = Event.current;
			if (current != null && (int)current.type != 8 && (int)current.type != 7 && current.isMouse && !((Rect)(ref s_rect)).Contains(current.mousePosition))
			{
				current.Use();
			}
		}

		private static void DrawWindow(int id)
		{
			//IL_01dd: Unknown result type (might be due to invalid IL or missing references)
			Snapshot snapshot = Current();
			string query = ((s_query == null) ? "" : s_query.Trim());
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			GUILayout.Label("Lawspeaker", s_title, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(false) });
			GUILayout.Label(snapshot.RoleLabel ?? "", s_muted, Array.Empty<GUILayoutOption>());
			if (GUILayout.Button("Close", s_button, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(72f) }))
			{
				IsOpen = false;
			}
			GUILayout.EndHorizontal();
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			GUI.enabled = snapshot.CanPush;
			if (snapshot.CanPush)
			{
				s_pushShared = GUILayout.Toggle(s_pushShared, " Push shared settings to the server", Array.Empty<GUILayoutOption>());
			}
			else
			{
				GUILayout.Toggle(false, " Push shared settings to the server", Array.Empty<GUILayoutOption>());
			}
			GUI.enabled = true;
			GUILayout.FlexibleSpace();
			GUILayout.Label("http://127.0.0.1:" + LawspeakerPlugin.Port + "/", s_muted, Array.Empty<GUILayoutOption>());
			GUILayout.EndHorizontal();
			if (Time.unscaledTime < s_statusUntil && !string.IsNullOrEmpty(s_status))
			{
				GUILayout.Label(s_status, s_statusBad ? s_statusBadStyle : s_statusStyle, Array.Empty<GUILayoutOption>());
			}
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			GUILayout.Label("Search", s_muted, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(52f) });
			s_query = GUILayout.TextField(s_query ?? "", s_field, Array.Empty<GUILayoutOption>());
			GUILayout.EndHorizontal();
			GUILayout.Space(6f);
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			DrawPlugins(snapshot, query);
			DrawEntries(snapshot, query);
			GUILayout.EndHorizontal();
			GUI.DragWindow(new Rect(0f, 0f, 10000f, 28f));
		}

		private static void DrawPlugins(Snapshot snap, string query)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			s_plugins = GUILayout.BeginScrollView(s_plugins, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(250f) });
			bool flag = false;
			string text = "";
			bool flag2 = false;
			for (int i = 0; i < snap.Plugins.Count; i++)
			{
				PluginSlice pluginSlice = snap.Plugins[i];
				if (PluginVisible(pluginSlice, query))
				{
					flag = true;
					if (text.Length == 0)
					{
						text = pluginSlice.Guid;
					}
					if (pluginSlice.Guid == s_selected)
					{
						flag2 = true;
					}
					int num = CountShown(pluginSlice, query);
					GUI.backgroundColor = ((pluginSlice.Guid == s_selected) ? Gold : Color.white);
					if (GUILayout.Button(pluginSlice.Name + "   " + num, (pluginSlice.Guid == s_selected) ? s_buttonOn : s_button, Array.Empty<GUILayoutOption>()))
					{
						s_selected = pluginSlice.Guid;
					}
					GUI.backgroundColor = Color.white;
				}
			}
			if (!flag)
			{
				GUILayout.Label("No settings match.", s_muted, Array.Empty<GUILayoutOption>());
			}
			else if (!flag2)
			{
				s_selected = text;
			}
			GUILayout.EndScrollView();
		}

		private static void DrawEntries(Snapshot snap, string query)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			s_entries = GUILayout.BeginScrollView(s_entries, Array.Empty<GUILayoutOption>());
			PluginSlice pluginSlice = FindSelected(snap);
			if (pluginSlice == null)
			{
				GUILayout.Label("Pick a mod. Only mods that are loaded have settings here.", s_muted, Array.Empty<GUILayoutOption>());
				GUILayout.EndScrollView();
				return;
			}
			GUILayout.Label(pluginSlice.Name + "   " + pluginSlice.Version, s_title, Array.Empty<GUILayoutOption>());
			GUILayout.Label(pluginSlice.Guid, s_muted, Array.Empty<GUILayoutOption>());
			string text = null;
			bool flag = NameHit(pluginSlice, query);
			for (int i = 0; i < pluginSlice.Entries.Count; i++)
			{
				EntrySlice entrySlice = pluginSlice.Entries[i];
				if (flag || EntryHit(entrySlice, query))
				{
					if (entrySlice.Section != text)
					{
						text = entrySlice.Section;
						GUILayout.Space(8f);
						GUILayout.Label(text, s_section, Array.Empty<GUILayoutOption>());
					}
					DrawEntry(pluginSlice, entrySlice, snap.CanPush);
				}
			}
			GUILayout.EndScrollView();
		}

		private static void DrawEntry(PluginSlice plugin, EntrySlice entry, bool canPush)
		{
			//IL_01da: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_022a: Unknown result type (might be due to invalid IL or missing references)
			string text = DraftId(plugin.Guid, entry.Section, entry.Key);
			if ((!s_draft.TryGetValue(text, out var value) || (!s_dirty.Contains(text) && value != entry.Value)) && !s_dirty.Contains(text))
			{
				value = entry.Value ?? "";
				s_draft[text] = value;
			}
			GUILayout.BeginVertical(GUI.skin.box, Array.Empty<GUILayoutOption>());
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			GUILayout.Label(entry.Key, s_title, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(false) });
			if (entry.Local)
			{
				GUILayout.Label("this computer", s_badgeLocal, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(false) });
			}
			else
			{
				GUILayout.Label("shared", s_badge, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(false) });
			}
			GUILayout.FlexibleSpace();
			GUILayout.Label(entry.Type ?? "", s_muted, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(false) });
			GUILayout.EndHorizontal();
			if (!string.IsNullOrEmpty(entry.Description))
			{
				GUILayout.Label(entry.Description, s_muted, Array.Empty<GUILayoutOption>());
			}
			if (entry.ReadOnly)
			{
				GUILayout.Label(entry.Value, s_field, Array.Empty<GUILayoutOption>());
				GUILayout.Label(entry.ReadOnlyReason ?? "read only", s_muted, Array.Empty<GUILayoutOption>());
				GUILayout.EndVertical();
				return;
			}
			bool flag = canPush && s_pushShared && !entry.Local;
			if (entry.Kind == "bool")
			{
				bool flag2 = string.Equals(entry.Value, "true", StringComparison.OrdinalIgnoreCase);
				string obj = (flag2 ? "On" : "Off");
				GUI.backgroundColor = (flag2 ? Gold : Color.white);
				if (GUILayout.Button(obj, s_button, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(88f) }))
				{
					Submit(plugin.Guid, entry.Section, entry.Key, flag2 ? "false" : "true", flag);
				}
				GUI.backgroundColor = Color.white;
			}
			else if (entry.Kind == "choice" && entry.Choices != null && entry.Choices.Count > 0 && entry.Choices.Count <= 16)
			{
				int num = entry.Choices.IndexOf(value);
				int num2 = GUILayout.SelectionGrid(num, entry.Choices.ToArray(), 2, s_button, Array.Empty<GUILayoutOption>());
				if (num2 >= 0 && num2 != num && num2 < entry.Choices.Count)
				{
					Submit(plugin.Guid, entry.Section, entry.Key, entry.Choices[num2], flag);
				}
			}
			else
			{
				GUI.SetNextControlName(text);
				string text2 = GUILayout.TextField(value ?? "", s_field, Array.Empty<GUILayoutOption>());
				if (text2 != value)
				{
					s_draft[text] = text2;
					s_dirty.Add(text);
					value = text2;
				}
				if (!string.IsNullOrEmpty(entry.Min) || !string.IsNullOrEmpty(entry.Max))
				{
					GUILayout.Label("Range " + entry.Min + " to " + entry.Max, s_muted, Array.Empty<GUILayoutOption>());
				}
				GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
				if (GUILayout.Button(flag ? "Save for everyone" : "Save here", s_button, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(160f) }))
				{
					Submit(plugin.Guid, entry.Section, entry.Key, value ?? "", flag);
					s_dirty.Remove(text);
				}
				if (GUILayout.Button("Reset", s_button, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(72f) }))
				{
					Submit(plugin.Guid, entry.Section, entry.Key, entry.DefaultValue ?? "", flag);
					s_draft[text] = entry.DefaultValue ?? "";
					s_dirty.Remove(text);
				}
				GUILayout.EndHorizontal();
			}
			if (entry.Local && !string.IsNullOrEmpty(entry.LocalReason))
			{
				GUILayout.Label(entry.LocalReason, s_muted, Array.Empty<GUILayoutOption>());
			}
			GUILayout.EndVertical();
		}

		private static void Submit(string guid, string section, string key, string value, bool push)
		{
			Authority.Change obj = new Authority.Change
			{
				Id = Guid.NewGuid().ToString("N"),
				Guid = guid,
				Section = section,
				Key = key,
				Value = (value ?? ""),
				Push = push
			};
			Authority.Prepare(obj.Id);
			Authority.Begin(obj);
			s_snap = null;
		}

		private static Snapshot Current()
		{
			if (s_snap == null || Time.unscaledTime - s_snapAt > 0.4f)
			{
				s_snap = Catalog.Read();
				s_snapAt = Time.unscaledTime;
			}
			return s_snap;
		}

		private static PluginSlice FindSelected(Snapshot snap)
		{
			for (int i = 0; i < snap.Plugins.Count; i++)
			{
				if (snap.Plugins[i].Guid == s_selected)
				{
					return snap.Plugins[i];
				}
			}
			if (snap.Plugins.Count <= 0)
			{
				return null;
			}
			return snap.Plugins[0];
		}

		private static bool PluginVisible(PluginSlice plugin, string query)
		{
			if (query.Length == 0)
			{
				return true;
			}
			if (NameHit(plugin, query))
			{
				return true;
			}
			for (int i = 0; i < plugin.Entries.Count; i++)
			{
				if (EntryHit(plugin.Entries[i], query))
				{
					return true;
				}
			}
			return false;
		}

		private static int CountShown(PluginSlice plugin, string query)
		{
			if (query.Length == 0 || NameHit(plugin, query))
			{
				return plugin.Entries.Count;
			}
			int num = 0;
			for (int i = 0; i < plugin.Entries.Count; i++)
			{
				if (EntryHit(plugin.Entries[i], query))
				{
					num++;
				}
			}
			return num;
		}

		private static bool NameHit(PluginSlice plugin, string query)
		{
			if (query.Length == 0)
			{
				return true;
			}
			if (!Contains(plugin.Name, query))
			{
				return Contains(plugin.Guid, query);
			}
			return true;
		}

		private static bool EntryHit(EntrySlice entry, string query)
		{
			if (!Contains(entry.Section, query) && !Contains(entry.Key, query))
			{
				return Contains(entry.Description, query);
			}
			return true;
		}

		private static bool Contains(string text, string query)
		{
			if (text != null)
			{
				return text.IndexOf(query, StringComparison.OrdinalIgnoreCase) >= 0;
			}
			return false;
		}

		private static string DraftId(string guid, string section, string key)
		{
			return guid + "\u001f" + section + "\u001f" + key;
		}

		private static void EnsureStyles()
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Expected O, but got Unknown
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e8: Expected O, but got Unknown
			//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fb: Expected O, but got Unknown
			//IL_0105: Unknown result type (might be due to invalid IL or missing references)
			//IL_010f: Expected O, but got Unknown
			//IL_0130: Unknown result type (might be due to invalid IL or missing references)
			//IL_0144: Unknown result type (might be due to invalid IL or missing references)
			//IL_014e: Expected O, but got Unknown
			//IL_016f: Unknown result type (might be due to invalid IL or missing references)
			//IL_017e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0188: Expected O, but got Unknown
			//IL_019e: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bc: Expected O, but got Unknown
			//IL_01d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_020e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0221: Unknown result type (might be due to invalid IL or missing references)
			//IL_022b: Expected O, but got Unknown
			//IL_0230: Unknown result type (might be due to invalid IL or missing references)
			//IL_023a: Expected O, but got Unknown
			//IL_0258: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_02bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c6: Expected O, but got Unknown
			//IL_02dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0304: Unknown result type (might be due to invalid IL or missing references)
			//IL_0317: Unknown result type (might be due to invalid IL or missing references)
			//IL_0321: Expected O, but got Unknown
			//IL_0326: Unknown result type (might be due to invalid IL or missing references)
			//IL_0330: Expected O, but got Unknown
			//IL_033a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0349: Unknown result type (might be due to invalid IL or missing references)
			//IL_0353: Expected O, but got Unknown
			//IL_0371: Unknown result type (might be due to invalid IL or missing references)
			//IL_0380: Unknown result type (might be due to invalid IL or missing references)
			//IL_038a: Expected O, but got Unknown
			//IL_0394: Unknown result type (might be due to invalid IL or missing references)
			//IL_03a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_03ad: Expected O, but got Unknown
			//IL_03cb: Unknown result type (might be due to invalid IL or missing references)
			if (!s_styles)
			{
				s_styles = true;
				s_white = Solid(Color.white);
				s_obsidian = Solid(new Color(0.086f, 0.102f, 0.125f, 0.98f));
				s_panel = Solid(new Color(0.114f, 0.133f, 0.165f, 1f));
				s_gold = Solid(Gold);
				s_line = Solid(new Color(0.169f, 0.192f, 0.231f, 1f));
				s_window = new GUIStyle(GUI.skin.window);
				s_window.normal.background = s_obsidian;
				s_window.onNormal.background = s_obsidian;
				s_window.padding = new RectOffset(14, 14, 12, 14);
				s_window.border = new RectOffset(0, 0, 0, 0);
				s_title = new GUIStyle(GUI.skin.label);
				s_title.fontSize = 16;
				s_title.fontStyle = (FontStyle)1;
				s_title.normal.textColor = Gold;
				s_muted = new GUIStyle(GUI.skin.label);
				s_muted.fontSize = 12;
				s_muted.wordWrap = true;
				s_muted.normal.textColor = Muted;
				s_section = new GUIStyle(s_title);
				s_section.fontSize = 13;
				s_section.normal.textColor = Ink;
				s_button = new GUIStyle(GUI.skin.button);
				s_button.fontSize = 13;
				s_button.normal.textColor = Ink;
				s_button.normal.background = s_panel;
				s_button.hover.background = s_line;
				s_button.hover.textColor = Gold;
				s_button.padding = new RectOffset(8, 8, 5, 5);
				s_buttonOn = new GUIStyle(s_button);
				s_buttonOn.normal.textColor = new Color(0.086f, 0.102f, 0.125f, 1f);
				s_buttonOn.normal.background = s_gold;
				s_buttonOn.hover.background = s_gold;
				s_buttonOn.hover.textColor = new Color(0.086f, 0.102f, 0.125f, 1f);
				s_field = new GUIStyle(GUI.skin.textField);
				s_field.fontSize = 13;
				s_field.normal.textColor = Ink;
				s_field.normal.background = s_panel;
				s_field.focused.textColor = Gold;
				s_field.padding = new RectOffset(6, 6, 4, 4);
				s_badge = new GUIStyle(s_muted);
				s_badge.normal.textColor = Gold;
				s_badgeLocal = new GUIStyle(s_muted);
				s_badgeLocal.normal.textColor = new Color(0.624f, 0.769f, 0.867f, 1f);
				s_statusStyle = new GUIStyle(s_muted);
				s_statusStyle.normal.textColor = Gold;
				s_statusBadStyle = new GUIStyle(s_muted);
				s_statusBadStyle.normal.textColor = new Color(0.878f, 0.478f, 0.478f, 1f);
			}
		}

		private static Texture2D Solid(Color color)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Expected O, but got Unknown
			Texture2D val = new Texture2D(1, 1);
			val.SetPixel(0, 0, color);
			val.Apply();
			return val;
		}

		private static void Destroy(ref Texture2D tex)
		{
			if ((Object)(object)tex != (Object)null)
			{
				Object.Destroy((Object)(object)tex);
				tex = null;
			}
		}
	}
	[BepInPlugin("com.dfg.lawspeaker", "Lawspeaker", "1.0.0")]
	public class LawspeakerPlugin : BaseUnityPlugin
	{
		private sealed class Slot<T>
		{
			public T Value;

			public Exception Error;
		}

		public const string PluginGUID = "com.dfg.lawspeaker";

		public const string PluginName = "Lawspeaker";

		public const string PluginVersion = "1.0.0";

		internal static ManualLogSource Log;

		internal static int Port = 8796;

		private static ConfigEntry<bool> s_enabled;

		private static ConfigEntry<KeyboardShortcut> s_openKey;

		private static ConfigEntry<bool> s_web;

		private static ConfigEntry<int> s_port;

		private readonly ConcurrentQueue<Action> m_main = new ConcurrentQueue<Action>();

		private Harmony m_harmony;

		private LawspeakerWeb m_web;

		private ZRoutedRpc m_rpc;

		private void Awake()
		{
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ec: Expected O, but got Unknown
			//IL_0143: Unknown result type (might be due to invalid IL or missing references)
			//IL_0148: Unknown result type (might be due to invalid IL or missing references)
			Log = ((BaseUnityPlugin)this).Logger;
			s_enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Turn Lawspeaker off. The in-game window, the web page, and server pushes all stop. A dedicated server has to leave this on for an admin's push to land.");
			s_openKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("General", "OpenKey", new KeyboardShortcut((KeyCode)289, Array.Empty<KeyCode>()), "Opens and closes the in-game editor.");
			s_web = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnableWebGui", true, "Serve the editor at http://127.0.0.1:<WebPort>/ while the game is running.");
			s_port = ((BaseUnityPlugin)this).Config.Bind<int>("General", "WebPort", 8796, "Port for the editor. A change applies the next time the game starts.");
			if (!s_enabled.Value)
			{
				Log.LogInfo((object)"Lawspeaker is disabled.");
				return;
			}
			Port = ((s_port.Value > 0) ? s_port.Value : 8796);
			m_harmony = new Harmony("com.dfg.lawspeaker");
			m_harmony.PatchAll(typeof(LawspeakerInput));
			if (s_web.Value)
			{
				m_web = new LawspeakerWeb(this, Port);
				m_web.Start();
			}
			Log.LogInfo((object)("Lawspeaker ready. In game: " + ((object)s_openKey.Value/*cast due to .constrained prefix*/).ToString() + ". Page: http://127.0.0.1:" + Port + "/"));
		}

		private void OnDestroy()
		{
			if (m_web != null)
			{
				m_web.Stop();
				m_web = null;
			}
			if (m_harmony != null)
			{
				m_harmony.UnpatchSelf();
				m_harmony = null;
			}
			LawspeakerGui.Shutdown();
		}

		private void Update()
		{
			if (s_enabled != null && s_enabled.Value)
			{
				Drain();
				HookRpc();
				Authority.Expire();
				Hotkey();
			}
		}

		private void OnGUI()
		{
			if (s_enabled != null && s_enabled.Value)
			{
				LawspeakerGui.Draw();
			}
		}

		public void Enqueue(Action action)
		{
			if (action != null)
			{
				m_main.Enqueue(action);
			}
		}

		public T Ask<T>(Func<T> work, T fallback)
		{
			if (work == null)
			{
				return fallback;
			}
			ManualResetEventSlim done = new ManualResetEventSlim(initialState: false);
			Slot<T> slot = new Slot<T>();
			m_main.Enqueue(delegate
			{
				try
				{
					slot.Value = work();
				}
				catch (Exception error)
				{
					slot.Error = error;
				}
				finally
				{
					done.Set();
				}
			});
			if (!done.Wait(TimeSpan.FromSeconds(3.0)))
			{
				Log.LogWarning((object)"Lawspeaker: the page gave up waiting on the game.");
				return fallback;
			}
			if (slot.Error != null)
			{
				Log.LogWarning((object)("Lawspeaker: " + slot.Error.Message));
				return fallback;
			}
			return slot.Value;
		}

		private void Drain()
		{
			Action result;
			while (m_main.TryDequeue(out result))
			{
				try
				{
					result();
				}
				catch (Exception ex)
				{
					Log.LogError((object)("Lawspeaker: " + ex));
				}
			}
		}

		private void HookRpc()
		{
			ZRoutedRpc instance = ZRoutedRpc.instance;
			if (instance != null && instance != m_rpc)
			{
				instance.Register<string>("Lawspeaker_Push", (Action<long, string>)Authority.OnPush);
				instance.Register<string>("Lawspeaker_Apply", (Action<long, string>)Authority.OnApply);
				instance.Register<string>("Lawspeaker_Result", (Action<long, string>)Authority.OnResult);
				m_rpc = instance;
				Log.LogInfo((object)"Lawspeaker: listening for admin pushes.");
			}
		}

		private void Hotkey()
		{
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)Player.m_localPlayer == (Object)null) && s_openKey != null && !Menu.IsVisible() && !Console.IsVisible() && !InventoryGui.IsVisible() && !TextInput.IsVisible() && !Minimap.InTextInput() && (!((Object)(object)Chat.instance != (Object)null) || !Chat.instance.IsChatDialogWindowVisible()))
			{
				KeyboardShortcut value = s_openKey.Value;
				if (((KeyboardShortcut)(ref value)).IsDown())
				{
					LawspeakerGui.Toggle();
				}
			}
		}
	}
	internal static class LawspeakerInput
	{
		[HarmonyPatch(typeof(Player), "TakeInput")]
		[HarmonyPrefix]
		public static bool PlayerTakeInput(ref bool __result)
		{
			if (!LawspeakerGui.IsOpen)
			{
				return true;
			}
			__result = false;
			return false;
		}

		[HarmonyPatch(typeof(PlayerController), "TakeInput")]
		[HarmonyPrefix]
		public static bool ControllerTakeInput(ref bool __result)
		{
			if (!LawspeakerGui.IsOpen)
			{
				return true;
			}
			__result = false;
			return false;
		}

		[HarmonyPatch(typeof(PlayerController), "InInventoryEtc")]
		[HarmonyPostfix]
		public static void InInventoryEtc(ref bool __result)
		{
			if (LawspeakerGui.IsOpen)
			{
				__result = true;
			}
		}

		[HarmonyPatch(typeof(GameCamera), "UpdateMouseCapture")]
		[HarmonyPostfix]
		public static void KeepCursorFree()
		{
			if (LawspeakerGui.IsOpen)
			{
				ZCursor.LockState = (CursorLockMode)0;
				ZCursor.Show();
			}
		}
	}
	public class LawspeakerWeb
	{
		private readonly LawspeakerPlugin m_plugin;

		private readonly int m_port;

		private HttpListener m_listener;

		private Thread m_thread;

		private volatile bool m_running;

		public LawspeakerWeb(LawspeakerPlugin plugin, int port)
		{
			m_plugin = plugin;
			m_port = port;
		}

		public void Start()
		{
			try
			{
				m_listener = new HttpListener();
				m_listener.Prefixes.Add("http://127.0.0.1:" + m_port + "/");
				m_listener.Start();
				m_running = true;
				m_thread = new Thread(Loop)
				{
					IsBackground = true,
					Name = "Lawspeaker"
				};
				m_thread.Start();
				LawspeakerPlugin.Log.LogInfo((object)("Lawspeaker: editor at http://127.0.0.1:" + m_port + "/"));
			}
			catch (Exception ex)
			{
				m_running = false;
				LawspeakerPlugin.Log.LogWarning((object)("Lawspeaker: the editor page did not start: " + ex.Message));
			}
		}

		public void Stop()
		{
			m_running = false;
			try
			{
				if (m_listener != null)
				{
					m_listener.Stop();
					m_listener.Close();
				}
			}
			catch
			{
			}
			m_listener = null;
		}

		private void Loop()
		{
			while (m_running && m_listener != null && m_listener.IsListening)
			{
				try
				{
					HttpListenerContext context = m_listener.GetContext();
					Handle(context);
				}
				catch
				{
				}
			}
		}

		private void Handle(HttpListenerContext context)
		{
			try
			{
				switch (context.Request.Url.AbsolutePath)
				{
				case "/":
				case "/index.html":
					Write(context, "text/html; charset=utf-8", Page.Html);
					return;
				case "/state":
				{
					string body = m_plugin.Ask(() => Authority.Serialize(Catalog.Read()), "{\"plugins\":[]}");
					Write(context, "application/json; charset=utf-8", body);
					return;
				}
				case "/apply":
					if (context.Request.HttpMethod == "POST")
					{
						Write(context, "application/json; charset=utf-8", Apply(ReadBody(context)));
						return;
					}
					break;
				}
				context.Response.StatusCode = 404;
				context.Response.Close();
			}
			catch (Exception ex)
			{
				try
				{
					context.Response.StatusCode = 500;
					Write(context, "text/plain; charset=utf-8", ex.Message);
				}
				catch
				{
					try
					{
						context.Response.Close();
					}
					catch
					{
					}
				}
			}
		}

		private string Apply(string body)
		{
			Authority.Change change = Authority.ReadChange(body);
			if (change == null || string.IsNullOrEmpty(change.Guid))
			{
				return Authority.Serialize(new Authority.Reply
				{
					Ok = false,
					Error = "could not read that change"
				});
			}
			change.Id = Guid.NewGuid().ToString("N");
			Authority.Prepare(change.Id);
			m_plugin.Enqueue(delegate
			{
				Authority.Begin(change);
			});
			return Authority.Wait(change.Id, 4000);
		}

		private static string ReadBody(HttpListenerContext context)
		{
			using StreamReader streamReader = new StreamReader(context.Request.InputStream, context.Request.ContentEncoding);
			char[] array = new char[6000];
			int val = streamReader.Read(array, 0, array.Length);
			return new string(array, 0, Math.Max(0, val));
		}

		private static void Write(HttpListenerContext context, string contentType, string body)
		{
			byte[] bytes = Encoding.UTF8.GetBytes(body ?? "");
			context.Response.ContentType = contentType;
			context.Response.ContentEncoding = Encoding.UTF8;
			context.Response.ContentLength64 = bytes.Length;
			context.Response.OutputStream.Write(bytes, 0, bytes.Length);
			context.Response.OutputStream.Close();
		}
	}
	public static class Page
	{
		private const string ResourceName = "Lawspeaker.Page.html";

		private static string s_html;

		public static string Html
		{
			get
			{
				if (s_html != null)
				{
					return s_html;
				}
				try
				{
					using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Lawspeaker.Page.html"))
					{
						if (stream == null)
						{
							throw new InvalidOperationException("missing embedded resource Lawspeaker.Page.html");
						}
						using StreamReader streamReader = new StreamReader(stream);
						s_html = streamReader.ReadToEnd();
					}
					s_html = s_html.Replace("{{NAV_STYLE}}", "nav.top{position:sticky;top:0;z-index:20;font-family:Segoe UI,Arial,sans-serif;background:#0d1219;border-bottom:1px solid #2b3b4c;padding:6px 14px;display:flex;align-items:center;gap:6px;flex-wrap:wrap}\nnav.top .brand{color:#7f8fa0;font-size:11px;text-transform:uppercase;letter-spacing:.6px;margin-right:8px}\nnav.top a{color:#9fc3dd;text-decoration:none;font-size:13px;padding:4px 9px;border-radius:5px}\nnav.top a:hover{background:#1a2530}\nnav.top a.here{background:#3a2b4c;border:1px solid #55406b;color:#efe0ff}\n").Replace("{{NAV}}", WebNav.Html("lawspeaker"));
				}
				catch (Exception ex)
				{
					if (LawspeakerPlugin.Log != null)
					{
						LawspeakerPlugin.Log.LogError((object)("Lawspeaker: the editor page could not be read: " + ex.Message));
					}
					s_html = "<!DOCTYPE html><html><body style='background:#161a20;color:#e6e1d6;font:14px Segoe UI,sans-serif;padding:24px'>The editor page could not be loaded. See the BepInEx log.</body></html>";
				}
				return s_html;
			}
		}
	}
	public static class SettingPolicy
	{
		public const string OwnGuid = "com.dfg.lawspeaker";

		public const string OwnVersion = "1.0.0";

		public const int MaxNameLength = 180;

		public const int MaxValueLength = 4000;

		public static string LocalReason(string pluginGuid, string section, string key, string typeName)
		{
			if (string.Equals(pluginGuid, "com.dfg.lawspeaker", StringComparison.OrdinalIgnoreCase))
			{
				return "Lawspeaker's own settings stay on this computer";
			}
			if (typeName == "KeyboardShortcut" || typeName == "KeyCode")
			{
				return "Key bindings stay on this computer";
			}
			if (LooksLikeWindow(section, key))
			{
				return "Window and web settings stay on this computer";
			}
			return null;
		}

		public static string Reject(string pluginGuid, string section, string key, string value)
		{
			string text = CheckName("plugin", pluginGuid);
			if (text != null)
			{
				return text;
			}
			text = CheckName("section", section);
			if (text != null)
			{
				return text;
			}
			text = CheckName("key", key);
			if (text != null)
			{
				return text;
			}
			if (value == null)
			{
				return "the value is missing";
			}
			if (value.Length > 4000)
			{
				return "the value is too long";
			}
			foreach (char c in value)
			{
				if (char.IsControl(c) && c != '\n' && c != '\r' && c != '\t')
				{
					return "the value contains a control character";
				}
			}
			return null;
		}

		private static string CheckName(string label, string text)
		{
			if (string.IsNullOrEmpty(text))
			{
				return "the " + label + " is missing";
			}
			if (text.Length > 180)
			{
				return "the " + label + " is too long";
			}
			foreach (char c in text)
			{
				if (c == '\0' || c == '\n' || c == '\r' || char.IsControl(c))
				{
					return "the " + label + " contains a control character";
				}
			}
			return null;
		}

		private static bool LooksLikeWindow(string section, string key)
		{
			string text = (section + " " + key).ToLowerInvariant();
			string[] array = new string[11]
			{
				"hotkey", "webport", "enableweb", "guikey", "fontpercent", "panelwidth", "panelleft", "paneltop", "buttonoffset", "buttonwidth",
				"buttonheight"
			};
			for (int i = 0; i < array.Length; i++)
			{
				if (text.IndexOf(array[i], StringComparison.Ordinal) >= 0)
				{
					return true;
				}
			}
			return false;
		}
	}
}