Hash.dll

Decompiled 11 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Resources;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using HarmonyLib;
using Hash;
using Hash.Game;
using Hash.Terminal;
using Il2CppFishNet;
using Il2CppFishNet.Object;
using Il2CppInterop.Runtime;
using Il2CppInterop.Runtime.Injection;
using Il2CppInterop.Runtime.InteropTypes;
using Il2CppScheduleOne;
using Il2CppScheduleOne.AvatarFramework;
using Il2CppScheduleOne.AvatarFramework.Emotions;
using Il2CppScheduleOne.Core.Items.Framework;
using Il2CppScheduleOne.Cutscenes;
using Il2CppScheduleOne.DevUtilities;
using Il2CppScheduleOne.Employees;
using Il2CppScheduleOne.Interaction;
using Il2CppScheduleOne.ItemFramework;
using Il2CppScheduleOne.Map;
using Il2CppScheduleOne.NPCs;
using Il2CppScheduleOne.Persistence;
using Il2CppScheduleOne.PlayerScripts;
using Il2CppScheduleOne.Product;
using Il2CppScheduleOne.Product.Packaging;
using Il2CppScheduleOne.Property;
using Il2CppScheduleOne.Quests;
using Il2CppScheduleOne.UI;
using Il2CppScheduleOne.Variables;
using Il2CppScheduleOne.Vehicles;
using Il2CppSystem;
using Il2CppSystem.Collections.Generic;
using MelonLoader;
using MelonLoader.Preferences;
using MelonLoader.Utils;
using Microsoft.CodeAnalysis;
using Sideload.Api;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(Core), "hash", "1.0.2", "DooDesch", "https://github.com/DooDesch-Mods/ScheduleOne-Hash")]
[assembly: MelonGame("TVGS", "Schedule I")]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("Hash")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.2.0")]
[assembly: AssemblyInformationalVersion("1.0.2+eb6683bcfee3f2a30b362296fea41161889c5776")]
[assembly: AssemblyProduct("Hash")]
[assembly: AssemblyTitle("Hash")]
[assembly: NeutralResourcesLanguage("en-US")]
[assembly: AssemblyVersion("1.0.2.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 Sideload.Api
{
	public static class Apps
	{
		private static bool _bound;

		private static int _probeAttempts;

		private static readonly List<Action> _pending = new List<Action>();

		private static Action<string, string, string, string, Assembly> _registerApp;

		private static Action<string, string, Func<string, string, string>> _handle;

		private static Action<string, string, string> _emit;

		private static Action<string, string> _allowHost;

		private static Action<string, string> _declareOrientations;

		private static Action<string, int> _setBadge;

		private static Action<string, string, string> _notify;

		private static Action<string, string, string, float> _notifyFor;

		private static Func<string, bool> _isOnScreen;

		private static Action<string, string, byte[]> _setImage;

		private static Action<string, bool> _setIconHidden;

		private static Action<string, bool> _setAppOpen;

		private static Func<string, bool> _isAppOpen;

		private static Func<bool, bool> _setPhoneRaised;

		private static Func<bool> _isPhoneRaised;

		public static bool Available
		{
			get
			{
				EnsureBound();
				return _bound;
			}
		}

		internal static bool HasPhone
		{
			get
			{
				EnsureBound();
				return _setPhoneRaised != null;
			}
		}

		internal static bool HasOpen
		{
			get
			{
				EnsureBound();
				if (_setAppOpen != null)
				{
					return _setIconHidden != null;
				}
				return false;
			}
		}

		public static AppHandle Register(string id, string bundlePrefix, string title = null, string iconLabel = null, Assembly hostAssembly = null)
		{
			AppHandle result = new AppHandle(id);
			if (string.IsNullOrEmpty(id))
			{
				return result;
			}
			Assembly asm = hostAssembly ?? Assembly.GetCallingAssembly();
			string t = title;
			string il = iconLabel;
			string prefix = bundlePrefix;
			EnsureBound();
			if (_registerApp != null)
			{
				_registerApp(id, t, il, prefix, asm);
			}
			else
			{
				_pending.Add(delegate
				{
					_registerApp?.Invoke(id, t, il, prefix, asm);
				});
			}
			return result;
		}

		internal static void WhenBound(Action work)
		{
			if (work != null)
			{
				EnsureBound();
				if (_bound)
				{
					work();
				}
				else
				{
					_pending.Add(work);
				}
			}
		}

		internal static void HandleCall(string appId, string name, Func<string, string> handler)
		{
			if (_handle != null && handler != null)
			{
				_handle(appId, name, (string app, string argument) => handler(argument));
			}
		}

		internal static void EmitEvent(string appId, string name, string payload)
		{
			_emit?.Invoke(appId, name, payload);
		}

		internal static void AllowNetHost(string appId, string host)
		{
			_allowHost?.Invoke(appId, host);
		}

		internal static void Orient(string appId, string orientations)
		{
			_declareOrientations?.Invoke(appId, orientations);
		}

		internal static void Badge(string appId, int count)
		{
			_setBadge?.Invoke(appId, count);
		}

		internal static void Notify(string appId, string title, string subtitle, float seconds)
		{
			if (_notifyFor != null)
			{
				_notifyFor(appId, title, subtitle, seconds);
			}
			else if (_notify != null)
			{
				_notify(appId, title, subtitle);
			}
		}

		internal static bool OnScreen(string appId)
		{
			if (_isOnScreen != null)
			{
				return _isOnScreen(appId);
			}
			return false;
		}

		internal static void SetImage(string appId, string name, byte[] png)
		{
			_setImage?.Invoke(appId, name, png);
		}

		internal static void HideIcon(string appId, bool hidden)
		{
			_setIconHidden?.Invoke(appId, hidden);
		}

		internal static void OpenApp(string appId, bool open)
		{
			_setAppOpen?.Invoke(appId, open);
		}

		internal static bool AppIsOpen(string appId)
		{
			if (_isAppOpen != null)
			{
				return _isAppOpen(appId);
			}
			return false;
		}

		internal static bool RaisePhone(bool raised)
		{
			if (_setPhoneRaised != null)
			{
				return _setPhoneRaised(raised);
			}
			return false;
		}

		internal static bool PhoneIsRaised()
		{
			if (_isPhoneRaised != null)
			{
				return _isPhoneRaised();
			}
			return false;
		}

		private static void EnsureBound()
		{
			if (_bound)
			{
				return;
			}
			try
			{
				Type type = FindBridge(_probeAttempts++ % 30 == 0);
				if (type == null || (type.GetField("AbiVersion", BindingFlags.Static | BindingFlags.Public)?.GetValue(null) is int num && num < 1))
				{
					return;
				}
				_registerApp = Get<Action<string, string, string, string, Assembly>>(type, "RegisterApp");
				if (_registerApp == null)
				{
					return;
				}
				_handle = Get<Action<string, string, Func<string, string, string>>>(type, "Handle");
				_emit = Get<Action<string, string, string>>(type, "Emit");
				_allowHost = Get<Action<string, string>>(type, "AllowHost");
				_declareOrientations = Get<Action<string, string>>(type, "DeclareOrientations");
				_setBadge = Get<Action<string, int>>(type, "SetBadge");
				_notify = Get<Action<string, string, string>>(type, "Notify");
				_notifyFor = Get<Action<string, string, string, float>>(type, "NotifyFor");
				_isOnScreen = Get<Func<string, bool>>(type, "IsAppOnScreen");
				_setImage = Get<Action<string, string, byte[]>>(type, "SetImage");
				_setIconHidden = Get<Action<string, bool>>(type, "SetIconHidden");
				_setAppOpen = Get<Action<string, bool>>(type, "SetAppOpen");
				_isAppOpen = Get<Func<string, bool>>(type, "IsAppOpen");
				_setPhoneRaised = Get<Func<bool, bool>>(type, "SetPhoneRaised");
				_isPhoneRaised = Get<Func<bool>>(type, "IsPhoneRaised");
				_bound = true;
				for (int i = 0; i < _pending.Count; i++)
				{
					try
					{
						_pending[i]();
					}
					catch
					{
					}
				}
				_pending.Clear();
			}
			catch
			{
			}
		}

		private static T Get<T>(Type t, string field) where T : class
		{
			return t.GetField(field, BindingFlags.Static | BindingFlags.Public)?.GetValue(null) as T;
		}

		private static Type FindBridge(bool scan)
		{
			Type type = Type.GetType("Sideload.Bridge.SideloadBridge, Sideload", throwOnError: false);
			if (type != null || !scan)
			{
				return type;
			}
			Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
			foreach (Assembly assembly in assemblies)
			{
				try
				{
					type = assembly.GetType("Sideload.Bridge.SideloadBridge", throwOnError: false);
					if (type != null)
					{
						return type;
					}
				}
				catch
				{
				}
			}
			return null;
		}
	}
	public sealed class AppHandle
	{
		private readonly string _id;

		public string Id => _id;

		public bool IsOnScreen
		{
			get
			{
				if (Apps.Available)
				{
					return Apps.OnScreen(_id);
				}
				return false;
			}
		}

		public bool IsOpen
		{
			get
			{
				if (Apps.Available)
				{
					return Apps.AppIsOpen(_id);
				}
				return false;
			}
		}

		public static bool CanOpenProgrammatically
		{
			get
			{
				if (Apps.Available)
				{
					return Apps.HasOpen;
				}
				return false;
			}
		}

		internal AppHandle(string id)
		{
			_id = id ?? "";
		}

		public AppHandle OnCall(string name, Func<string, string> handler)
		{
			string id = _id;
			Apps.WhenBound(delegate
			{
				Apps.HandleCall(id, name, handler);
			});
			return this;
		}

		public void Emit(string name, string payload = "")
		{
			string id = _id;
			Apps.WhenBound(delegate
			{
				Apps.EmitEvent(id, name, payload);
			});
		}

		public AppHandle AllowHost(string host)
		{
			string id = _id;
			Apps.WhenBound(delegate
			{
				Apps.AllowNetHost(id, host);
			});
			return this;
		}

		public AppHandle Orientation(params string[] supported)
		{
			string id = _id;
			string list = ((supported == null) ? "" : string.Join(",", supported));
			Apps.WhenBound(delegate
			{
				Apps.Orient(id, list);
			});
			return this;
		}

		public AppHandle Badge(int count)
		{
			string id = _id;
			Apps.WhenBound(delegate
			{
				Apps.Badge(id, count);
			});
			return this;
		}

		public AppHandle Notify(string title, string subtitle = "", float seconds = 0f)
		{
			string id = _id;
			Apps.WhenBound(delegate
			{
				Apps.Notify(id, title, subtitle, seconds);
			});
			return this;
		}

		public AppHandle Image(string name, byte[] png)
		{
			string id = _id;
			Apps.WhenBound(delegate
			{
				Apps.SetImage(id, name, png);
			});
			return this;
		}

		public AppHandle Icon(bool visible)
		{
			Apps.HideIcon(_id, !visible);
			return this;
		}

		public AppHandle NoIcon()
		{
			string id = _id;
			Apps.WhenBound(delegate
			{
				Apps.HideIcon(id, hidden: true);
			});
			return this;
		}

		public AppHandle Open()
		{
			string id = _id;
			Apps.WhenBound(delegate
			{
				Apps.OpenApp(id, open: true);
			});
			return this;
		}

		public AppHandle Close()
		{
			string id = _id;
			Apps.WhenBound(delegate
			{
				Apps.OpenApp(id, open: false);
			});
			return this;
		}

		public bool Show()
		{
			if (!Apps.RaisePhone(raised: true))
			{
				return false;
			}
			Apps.OpenApp(_id, open: true);
			return true;
		}

		public AppHandle Hide()
		{
			string id = _id;
			Apps.WhenBound(delegate
			{
				Apps.OpenApp(id, open: false);
				Apps.RaisePhone(raised: false);
			});
			return this;
		}
	}
	public static class PhoneScreen
	{
		public static bool IsRaised
		{
			get
			{
				if (Apps.Available)
				{
					return Apps.PhoneIsRaised();
				}
				return false;
			}
		}

		public static bool Available
		{
			get
			{
				if (Apps.Available)
				{
					return Apps.HasPhone;
				}
				return false;
			}
		}

		public static bool Raise()
		{
			return Apps.RaisePhone(raised: true);
		}

		public static bool Lower()
		{
			return Apps.RaisePhone(raised: false);
		}
	}
}
namespace Hash
{
	public class Core : MelonMod
	{
		internal const string AppId = "hash";

		internal static Instance Log;

		private static MelonPreferences_Entry<bool> _hijack;

		private AppHandle _app;

		private Session _session;

		private CommandIndex _index;

		private ArgProviders _providers;

		private LogCapture _log;

		private CommandRunner _runner;

		private Store _store;

		private History _history;

		private Aliases _aliases;

		private Usage _usage;

		private WorldMarks _marks;

		private bool _wasOnScreen;

		private int _logsDrawnUpTo;

		private const float IconInterval = 0.5f;

		private float _iconCheckedAt;

		private int _toggledOnFrame = -1;

		public override void OnInitializeMelon()
		{
			Log = ((MelonBase)this).LoggerInstance;
			_hijack = MelonPreferences.CreateCategory("Hash", "hash").CreateEntry<bool>("HijackConsoleKey", true, "Console key opens the terminal", "ON (default): the key that opened the console now takes the phone out with hash on it. OFF: the vanilla console bar comes back and hash stays reachable only from code. Turn it off if another mod needs the vanilla bar.", false, false, (ValueValidator)null, (string)null);
			if (!PhoneScreen.Available)
			{
				Log.Error("[hash] needs Sideload 1.5.0 or newer - this one cannot take the phone out of the player's pocket, so the terminal could never be reached. Nothing was registered.");
				return;
			}
			Build();
			RegisterApp();
			Patch();
			Log.Msg("[hash] ready. Press the console key.");
		}

		private void Build()
		{
			_store = new Store();
			_log = new LogCapture();
			_providers = new ArgProviders();
			_index = new CommandIndex(_providers);
			_history = new History();
			_aliases = new Aliases();
			_usage = new Usage();
			_history.Load(_store);
			_aliases.Load(_store);
			_marks = new WorldMarks();
			_runner = new CommandRunner(_log);
			_session = new Session(_index, _runner, _usage, _history, _aliases, _marks);
			_runner.LogViewOpen = () => _session.Builtins.LogsOpen;
			_session.Builtins.UseFace(_store.Read(StoreScope.Global, "font"));
		}

		private void RegisterApp()
		{
			_app = Apps.Register("hash", "Hash.Assets.hash", "hash", "hash").Orientation("landscape").NoIcon()
				.OnCall("boot", (string _) => Boot())
				.OnCall("nav", Nav)
				.OnCall("run", Run)
				.OnCall("drain", (string _) => Drain())
				.OnCall("close", delegate
				{
					Toggle();
					return "";
				});
		}

		private void Patch()
		{
			ItemSourcePatch.Providers = _providers;
			ConsoleAwakePatch.OnAwake = delegate
			{
				DeclaredCommands.Apply();
				_index.MarkDirty();
			};
			ConsoleKeyPatch.OnOpen = Toggle;
			ConsoleKeyPatch.Enabled = _hijack.Value;
			try
			{
				((MelonBase)this).HarmonyInstance.PatchAll();
			}
			catch (Exception ex)
			{
				Log.Error("[hash] patching failed - the console key will open the vanilla bar: " + ex);
				ConsoleKeyPatch.Enabled = false;
			}
		}

		private bool Toggle()
		{
			if (_app == null)
			{
				return false;
			}
			int frameCount = Time.frameCount;
			if (frameCount == _toggledOnFrame)
			{
				return true;
			}
			_toggledOnFrame = frameCount;
			if (_app.IsOpen && PhoneScreen.IsRaised)
			{
				_app.Hide();
				_wasOnScreen = false;
				Persist();
				return true;
			}
			_index.MarkDirty();
			_providers.Invalidate();
			if (!_app.Show())
			{
				Log.Warning("[hash] the game would not take the phone out right now, so the terminal stayed shut.");
				return false;
			}
			_wasOnScreen = true;
			_app.Emit("shown");
			return true;
		}

		public override void OnUpdate()
		{
			_marks?.Tick();
			IconFollowsTheConsole();
			if (_wasOnScreen && _app != null && (!_app.IsOpen || !PhoneScreen.IsRaised))
			{
				_wasOnScreen = false;
				Persist();
			}
		}

		private void IconFollowsTheConsole()
		{
			if (_app != null && _runner != null)
			{
				float unscaledTime = Time.unscaledTime;
				if (!(unscaledTime - _iconCheckedAt < 0.5f))
				{
					_iconCheckedAt = unscaledTime;
					_app.Icon(_runner.CanRun);
				}
			}
		}

		public override void OnDeinitializeMelon()
		{
			Persist();
			_log?.Dispose();
		}

		private void Persist()
		{
			_history.Save(_store);
			_aliases.Save(_store);
			_usage.Save(_store);
			_store.Write(StoreScope.Global, "font", _session.Builtins.Face);
		}

		private string Boot()
		{
			_usage.Load(_store);
			_logsDrawnUpTo = _log.Ring.Count;
			if (_session.Transcript.Count == 0)
			{
				_session.Transcript.Add(_session.Banner(Identity()));
			}
			Json json = new Json();
			json.Str("session", _session.Locked ? "session:client" : "session:host");
			json.Num("commands", _session.CommandCount);
			json.Str("prompt", "hash $");
			MelonInfoAttribute info = ((MelonBase)this).Info;
			json.Str("version", ((info != null) ? info.Version : null) ?? "");
			json.Str("mark", Mark());
			json.Bool("locked", _session.Locked);
			json.Bool("live", _session.Builtins.LogsOpen);
			json.Str("font", _session.Builtins.Face);
			json.Raw("banner", Lines(_session.Transcript.Window()));
			return json.Done();
		}

		private string Identity()
		{
			MelonInfoAttribute info = ((MelonBase)this).Info;
			string text = ((info != null) ? info.Version : null) ?? "";
			string text2 = "";
			try
			{
				text2 = Application.version ?? "";
			}
			catch
			{
			}
			return "hash" + ((text.Length > 0) ? (" v" + text) : "") + ((text2.Length > 0) ? (" on Schedule I " + text2) : "");
		}

		private string Nav(string argument)
		{
			string line = Json.Field(argument, "line");
			string action = Json.Field(argument, "action");
			NavResult navResult = _session.Navigate(line, action);
			Json json = new Json();
			if (navResult.Line != null)
			{
				json.Str("line", navResult.Line);
			}
			json.Str("suggest", navResult.Suggest);
			json.Str("mark", Mark());
			json.Str("ghost", navResult.Ghost);
			return json.Done();
		}

		private string Drain()
		{
			bool flag = _wasOnScreen && _session.Builtins.LogsOpen;
			Json json = new Json();
			IReadOnlyList<OutputLine> lines;
			if (!flag)
			{
				IReadOnlyList<OutputLine> readOnlyList = Array.Empty<OutputLine>();
				lines = readOnlyList;
			}
			else
			{
				IReadOnlyList<OutputLine> readOnlyList = Fresh();
				lines = readOnlyList;
			}
			json.Raw("lines", Lines(lines));
			json.Bool("live", _session.Builtins.LogsOpen);
			json.Str("font", _session.Builtins.Face);
			return json.Done();
		}

		private string Mark()
		{
			Mark mark = _session.Marks.Resolve("#");
			if (!mark.Exists)
			{
				return "";
			}
			return "# " + mark.Id;
		}

		private string Run(string line)
		{
			RunResult runResult = _session.Run(line);
			if (!string.IsNullOrEmpty(runResult.Clipboard))
			{
				Clipboard.Put(runResult.Clipboard);
			}
			Json json = new Json();
			json.Raw("lines", Lines(WithLogs(runResult.Lines)));
			json.Num("commands", _session.CommandCount);
			json.Bool("cleared", runResult.Cleared);
			json.Str("mark", Mark());
			json.Bool("live", _session.Builtins.LogsOpen);
			json.Str("font", _session.Builtins.Face);
			return json.Done();
		}

		private IReadOnlyList<OutputLine> WithLogs(IReadOnlyList<OutputLine> ran)
		{
			if (!_session.Builtins.LogsOpen)
			{
				_logsDrawnUpTo = _log.Ring.Count;
				return ran;
			}
			List<OutputLine> list = new List<OutputLine>(ran);
			list.AddRange(Fresh());
			return list;
		}

		private List<OutputLine> Fresh()
		{
			List<OutputLine> list = new List<OutputLine>();
			string logsFilter = _session.Builtins.LogsFilter;
			for (int i = Math.Max(0, _logsDrawnUpTo); i < _log.Ring.Count; i++)
			{
				OutputLine outputLine = _log.Ring[i];
				if (logsFilter.Length <= 0 || outputLine.Text.IndexOf(logsFilter, StringComparison.OrdinalIgnoreCase) >= 0 || logsFilter.Equals(Kind(outputLine), StringComparison.OrdinalIgnoreCase))
				{
					_session.Push(outputLine);
					list.Add(outputLine);
				}
			}
			_logsDrawnUpTo = _log.Ring.Count;
			return list;
		}

		private static string Kind(OutputLine line)
		{
			return line.Kind switch
			{
				LineKind.Warn => "warn", 
				LineKind.Error => "error", 
				_ => "log", 
			};
		}

		private static string Lines(IReadOnlyList<OutputLine> lines)
		{
			StringBuilder stringBuilder = new StringBuilder("[");
			for (int i = 0; i < lines.Count; i++)
			{
				if (i > 0)
				{
					stringBuilder.Append(',');
				}
				Json json = new Json();
				json.Str("cls", Css(lines[i].Kind));
				json.Str("text", lines[i].Text);
				stringBuilder.Append(json.Done());
			}
			return stringBuilder.Append(']').ToString();
		}

		private static string Css(LineKind kind)
		{
			return kind switch
			{
				LineKind.Echo => "echo", 
				LineKind.Warn => "warn", 
				LineKind.Error => "err", 
				LineKind.Dim => "dim", 
				_ => "", 
			};
		}
	}
	internal sealed class Json
	{
		private readonly StringBuilder _sb = new StringBuilder("{");

		internal Json Str(string name, string value)
		{
			return Put(name, Quote(value));
		}

		internal Json Num(string name, int value)
		{
			return Put(name, value.ToString(CultureInfo.InvariantCulture));
		}

		internal Json Bool(string name, bool value)
		{
			return Put(name, value ? "true" : "false");
		}

		internal Json Raw(string name, string json)
		{
			return Put(name, json ?? "null");
		}

		internal string Done()
		{
			return _sb.Append('}').ToString();
		}

		private Json Put(string name, string value)
		{
			if (_sb.Length > 1)
			{
				_sb.Append(',');
			}
			_sb.Append(Quote(name)).Append(':').Append(value);
			return this;
		}

		internal static string Quote(string value)
		{
			if (value == null)
			{
				return "\"\"";
			}
			StringBuilder stringBuilder = new StringBuilder(value.Length + 2).Append('"');
			foreach (char c in value)
			{
				switch (c)
				{
				case '"':
					stringBuilder.Append("\\\"");
					continue;
				case '\\':
					stringBuilder.Append("\\\\");
					continue;
				case '\n':
					stringBuilder.Append("\\n");
					continue;
				case '\r':
					stringBuilder.Append("\\r");
					continue;
				case '\t':
					stringBuilder.Append("\\t");
					continue;
				}
				if (c < ' ')
				{
					StringBuilder stringBuilder2 = stringBuilder.Append("\\u");
					int num = c;
					stringBuilder2.Append(num.ToString("x4", CultureInfo.InvariantCulture));
				}
				else
				{
					stringBuilder.Append(c);
				}
			}
			return stringBuilder.Append('"').ToString();
		}

		internal static string Field(string json, string name)
		{
			if (string.IsNullOrEmpty(json) || string.IsNullOrEmpty(name))
			{
				return "";
			}
			string text = "\"" + name + "\"";
			int num = json.IndexOf(text, StringComparison.Ordinal);
			if (num < 0)
			{
				return "";
			}
			int num2 = json.IndexOf(':', num + text.Length);
			if (num2 < 0)
			{
				return "";
			}
			int i;
			for (i = num2 + 1; i < json.Length && char.IsWhiteSpace(json[i]); i++)
			{
			}
			if (i >= json.Length || json[i] != '"')
			{
				return "";
			}
			StringBuilder stringBuilder = new StringBuilder();
			for (i++; i < json.Length; i++)
			{
				char c = json[i];
				if (c == '\\' && i + 1 < json.Length)
				{
					char c2 = json[++i];
					switch (c2)
					{
					case 'n':
						stringBuilder.Append('\n');
						continue;
					case 'r':
						stringBuilder.Append('\r');
						continue;
					case 't':
						stringBuilder.Append('\t');
						continue;
					case 'u':
						if (i + 4 < json.Length)
						{
							if (int.TryParse(json.Substring(i + 1, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var result))
							{
								stringBuilder.Append((char)result);
								i += 4;
							}
							continue;
						}
						break;
					}
					stringBuilder.Append(c2);
				}
				else
				{
					if (c == '"')
					{
						break;
					}
					stringBuilder.Append(c);
				}
			}
			return stringBuilder.ToString();
		}
	}
}
namespace Hash.Terminal
{
	public sealed class Aliases
	{
		private const string FileName = "aliases.txt";

		private readonly Dictionary<string, string> _byName = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

		private bool _dirty;

		public int Count => _byName.Count;

		public IEnumerable<KeyValuePair<string, string>> All
		{
			get
			{
				List<string> list = new List<string>(_byName.Keys);
				list.Sort(StringComparer.OrdinalIgnoreCase);
				foreach (string item in list)
				{
					yield return new KeyValuePair<string, string>(item, _byName[item]);
				}
			}
		}

		public string Expand(string name)
		{
			if (name == null || !_byName.TryGetValue(name, out var value))
			{
				return null;
			}
			return value;
		}

		public bool TrySet(string name, string expansion, Func<string, bool> isRealCommand, out string error)
		{
			error = null;
			string text = name?.Trim();
			string value = expansion?.Trim();
			if (string.IsNullOrEmpty(text))
			{
				error = "an alias needs a name.";
				return false;
			}
			if (string.IsNullOrEmpty(value))
			{
				error = "an alias needs something to stand for.";
				return false;
			}
			if (text.IndexOf(' ') >= 0)
			{
				error = "an alias name cannot contain a space.";
				return false;
			}
			if (isRealCommand != null && isRealCommand(text))
			{
				error = "'" + text + "' is a real command; an alias would hide it.";
				return false;
			}
			_byName[text] = value;
			_dirty = true;
			return true;
		}

		public bool Remove(string name)
		{
			if (name == null || !_byName.Remove(name))
			{
				return false;
			}
			_dirty = true;
			return true;
		}

		public void Load(IStore store)
		{
			_byName.Clear();
			_dirty = false;
			string text = store?.Read(StoreScope.Global, "aliases.txt");
			if (string.IsNullOrEmpty(text))
			{
				return;
			}
			string[] array = text.Split('\n');
			for (int i = 0; i < array.Length; i++)
			{
				string text2 = array[i].Trim('\r');
				if (text2.Length != 0)
				{
					int num = text2.IndexOf('\t');
					if (num > 0 && num != text2.Length - 1)
					{
						_byName[text2.Substring(0, num)] = text2.Substring(num + 1);
					}
				}
			}
		}

		public void Save(IStore store)
		{
			if (!_dirty || store == null)
			{
				return;
			}
			StringBuilder stringBuilder = new StringBuilder();
			foreach (KeyValuePair<string, string> item in All)
			{
				stringBuilder.Append(item.Key).Append('\t').Append(item.Value)
					.Append('\n');
			}
			store.Write(StoreScope.Global, "aliases.txt", stringBuilder.ToString());
			_dirty = false;
		}
	}
	public sealed class Builtins
	{
		private readonly Suggestions _suggestions;

		private readonly ICommandCatalogue _catalogue;

		private readonly History _history;

		private readonly Aliases _aliases;

		private readonly Transcript _transcript;

		public const string Source = "hash";

		public static readonly IReadOnlyList<CommandInfo> Catalogue = new CommandInfo[11]
		{
			Own("help", "help [word|topic|all] [page]", "explain one command, list a topic, or search", "help all 2"),
			Own("clear", "clear", "empty the screen", "clear"),
			Own("history", "history [text]", "what you have run before, oldest first", "history give"),
			Own("alias", "alias [name] [command]", "make a short name for a command, or list them", "alias gk \"give ogkush 5\""),
			Own("unalias", "unalias <name>", "remove an alias", "unalias gk"),
			Own("grep", "grep <text>", "show only the lines on screen containing something", "grep ogkush"),
			Own("copy", "copy [text]", "put a line on the system clipboard - the last one, unless you name it", "copy ogkushseed"),
			Own("logs", "logs [filter|off]", "show what the game logs as it happens", "logs warn"),
			Own("raw", "raw <command>", "run the rest of the line as one command, semicolons and all", "raw bind t 'settime 1200'"),
			Own("repeat", "repeat <count> <command>", "run a command several times", "repeat 5 give ogkush 1"),
			Own("font", "font [mono|pixel]", "switch the typeface, or say which one is on", "font pixel")
		};

		public static readonly string[] Words = Vocabulary();

		public string PendingClipboard { get; private set; }

		public string Face { get; private set; } = "mono";

		public bool LogsOpen { get; private set; }

		public string LogsFilter { get; private set; } = "";

		public Builtins(Suggestions suggestions, ICommandCatalogue catalogue, History history, Aliases aliases, Transcript transcript)
		{
			_suggestions = suggestions;
			_catalogue = catalogue;
			_history = history;
			_aliases = aliases;
			_transcript = transcript;
		}

		private static CommandInfo Own(string word, string signature, string description, string usage)
		{
			return new CommandInfo(word, description, usage, signature, "hash", isVanilla: false);
		}

		private static string[] Vocabulary()
		{
			string[] array = new string[Catalogue.Count];
			for (int i = 0; i < Catalogue.Count; i++)
			{
				array[i] = Catalogue[i].Word;
			}
			return array;
		}

		public string TakeClipboard()
		{
			string pendingClipboard = PendingClipboard;
			PendingClipboard = null;
			return pendingClipboard;
		}

		public void UseFace(string face)
		{
			if (string.Equals(face, "pixel", StringComparison.OrdinalIgnoreCase))
			{
				Face = "pixel";
			}
			else if (string.Equals(face, "mono", StringComparison.OrdinalIgnoreCase))
			{
				Face = "mono";
			}
		}

		public bool TryRun(string line, out IReadOnlyList<OutputLine> output)
		{
			output = null;
			List<string> list = CommandLine.Tokenise(line);
			if (list.Count == 0)
			{
				return false;
			}
			string text = list[0].ToLowerInvariant();
			if (Array.IndexOf(Words, text) < 0)
			{
				return false;
			}
			if (_suggestions.IsCommand(text))
			{
				return false;
			}
			string text2 = ((line.Length > list[0].Length) ? line.Substring(list[0].Length).Trim() : "");
			List<OutputLine> list2 = new List<OutputLine>();
			switch (text)
			{
			case "help":
				Help(text2, list2);
				break;
			case "clear":
				_transcript.Clear();
				break;
			case "history":
				HistoryList(text2, list2);
				break;
			case "alias":
				Alias(text2, list, list2);
				break;
			case "unalias":
				Unalias(text2, list2);
				break;
			case "grep":
				Grep(text2, list2);
				break;
			case "copy":
				Copy(text2, list2);
				break;
			case "logs":
				Logs(text2, list2);
				break;
			case "font":
				Typeface(text2, list2);
				break;
			case "raw":
				list2.Add(OutputLine.Error("raw: no command after it"));
				UsageLine("raw", list2);
				break;
			case "repeat":
				list2.Add(OutputLine.Error("repeat: no count and no command"));
				UsageLine("repeat", list2);
				break;
			}
			output = list2;
			return true;
		}

		private void Help(string query, List<OutputLine> lines)
		{
			if (query.Length == 0)
			{
				Brief(lines);
				return;
			}
			if (query.StartsWith("all", StringComparison.OrdinalIgnoreCase))
			{
				int.TryParse(query.Substring(3).Trim(), out var result);
				Everything(Math.Max(1, result), lines);
				return;
			}
			if (HelpTopics.IsTopic(query))
			{
				OneTopic(query, lines);
				return;
			}
			CommandInfo commandInfo = _suggestions.Find(query);
			if (commandInfo != null)
			{
				Detail(commandInfo, lines);
				return;
			}
			List<CommandInfo> list = new List<CommandInfo>();
			foreach (CommandInfo command in _catalogue.Commands)
			{
				if (FuzzyMatcher.IsMatch(command.Word, query) || command.Description.IndexOf(query, StringComparison.OrdinalIgnoreCase) >= 0)
				{
					list.Add(command);
				}
			}
			if (list.Count == 0)
			{
				lines.Add(OutputLine.Warn("help: nothing matches '" + query + "'"));
				return;
			}
			lines.Add(OutputLine.Dim($"{list.Count} match '{query}':"));
			foreach (CommandInfo item in list)
			{
				lines.Add(OutputLine.Out(Row(item)));
			}
		}

		private void Brief(List<OutputLine> lines)
		{
			string[] common = HelpTopics.Common;
			foreach (string word in common)
			{
				CommandInfo commandInfo = _suggestions.Find(word);
				if (commandInfo != null)
				{
					lines.Add(OutputLine.Out(Row(commandInfo)));
				}
			}
			lines.Add(OutputLine.Out(""));
			Dictionary<string, List<string>> dictionary = Sort();
			List<string> list = new List<string>();
			(string, string[])[] groups = HelpTopics.Groups;
			for (int i = 0; i < groups.Length; i++)
			{
				string item = groups[i].Item1;
				if (dictionary.TryGetValue(item, out var value))
				{
					list.Add($"{item} {value.Count}");
				}
			}
			foreach (KeyValuePair<string, List<string>> item2 in dictionary)
			{
				if (!HelpTopics.IsTopic(item2.Key))
				{
					list.Add($"{item2.Key} {item2.Value.Count}");
				}
			}
			if (dictionary.TryGetValue("terminal", out var value2))
			{
				list.Add($"{"terminal"} {value2.Count}");
			}
			Wrap("topics", list, lines);
			lines.Add(OutputLine.Dim($"{Total()} commands. 'help <topic>' lists one, 'help <command>' explains one, " + "'help all' lists every one."));
		}

		private void OneTopic(string topic, List<OutputLine> lines)
		{
			if (!Sort().TryGetValue(topic, out var value) || value.Count == 0)
			{
				lines.Add(OutputLine.Warn("help: nothing is filed under '" + topic + "' in this game"));
				return;
			}
			value.Sort(StringComparer.OrdinalIgnoreCase);
			lines.Add(OutputLine.Dim($"{topic} - {value.Count} command(s)"));
			foreach (string item in value)
			{
				CommandInfo commandInfo = _suggestions.Find(item);
				if (commandInfo != null)
				{
					lines.Add(OutputLine.Out(Row(commandInfo)));
				}
			}
		}

		private void Everything(int page, List<OutputLine> lines)
		{
			Dictionary<string, List<string>> dictionary = Sort();
			List<List<OutputLine>> list = new List<List<OutputLine>>();
			(string, string[])[] groups = HelpTopics.Groups;
			for (int i = 0; i < groups.Length; i++)
			{
				string item = groups[i].Item1;
				if (dictionary.TryGetValue(item, out var value))
				{
					list.Add(TopicBlock(item, value));
				}
			}
			foreach (KeyValuePair<string, List<string>> item2 in dictionary)
			{
				if (!HelpTopics.IsTopic(item2.Key) && !(item2.Key == "other"))
				{
					list.Add(TopicBlock(item2.Key, item2.Value));
				}
			}
			if (dictionary.TryGetValue("terminal", out var value2))
			{
				list.Add(TopicBlock("terminal", value2));
			}
			if (dictionary.TryGetValue("other", out var value3))
			{
				list.Add(TopicBlock("other", value3));
			}
			List<List<OutputLine>> list2 = new List<List<OutputLine>>
			{
				new List<OutputLine>()
			};
			foreach (List<OutputLine> item3 in list)
			{
				List<OutputLine> list3 = list2[list2.Count - 1];
				if (list3.Count > 0 && list3.Count + item3.Count > 16)
				{
					list3 = new List<OutputLine>();
					list2.Add(list3);
				}
				list3.AddRange(item3);
			}
			if (page > list2.Count)
			{
				page = list2.Count;
			}
			lines.AddRange(list2[page - 1]);
			lines.Add(OutputLine.Dim((page < list2.Count) ? $"page {page} of {list2.Count} - 'help all {page + 1}' for the next." : $"{Total()} commands in all."));
		}

		private List<OutputLine> TopicBlock(string topic, List<string> words)
		{
			List<OutputLine> list = new List<OutputLine> { OutputLine.Dim(topic) };
			words.Sort(StringComparer.OrdinalIgnoreCase);
			foreach (string word in words)
			{
				CommandInfo commandInfo = _suggestions.Find(word);
				if (commandInfo != null)
				{
					list.Add(OutputLine.Out("  " + Row(commandInfo)));
				}
			}
			return list;
		}

		private static void Wrap(string label, List<string> words, List<OutputLine> lines)
		{
			if (words.Count == 0)
			{
				return;
			}
			StringBuilder stringBuilder = new StringBuilder(Pad(label, 10));
			int num = 10;
			foreach (string word in words)
			{
				if (num > 10 && num + 3 + word.Length > 96)
				{
					lines.Add(OutputLine.Out(stringBuilder.ToString()));
					stringBuilder = new StringBuilder(new string(' ', 10));
					num = 10;
				}
				if (num > 10)
				{
					stringBuilder.Append("   ");
					num += 3;
				}
				stringBuilder.Append(word);
				num += word.Length;
			}
			lines.Add(OutputLine.Out(stringBuilder.ToString()));
		}

		private Dictionary<string, List<string>> Sort()
		{
			Dictionary<string, List<string>> dictionary = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
			foreach (CommandInfo command in _catalogue.Commands)
			{
				Put(dictionary, command.IsVanilla ? HelpTopics.TopicOf(command.Word) : Mod(command.Source), command.Word);
			}
			foreach (CommandInfo item in Catalogue)
			{
				if (!_suggestions.IsCommand(item.Word))
				{
					Put(dictionary, "terminal", item.Word);
				}
			}
			return dictionary;
		}

		private static string Mod(string source)
		{
			if (string.IsNullOrEmpty(source))
			{
				return "other";
			}
			int num = source.LastIndexOf(" v", StringComparison.Ordinal);
			if (num <= 0)
			{
				return source;
			}
			return source.Substring(0, num);
		}

		private static void Put(Dictionary<string, List<string>> byTopic, string topic, string word)
		{
			if (!byTopic.TryGetValue(topic, out var value))
			{
				value = (byTopic[topic] = new List<string>());
			}
			value.Add(word);
		}

		private int Total()
		{
			int num = _catalogue.Commands.Count;
			foreach (CommandInfo item in Catalogue)
			{
				if (!_suggestions.IsCommand(item.Word))
				{
					num++;
				}
			}
			return num;
		}

		private static void UsageLine(string word, List<OutputLine> lines)
		{
			foreach (CommandInfo item in Catalogue)
			{
				if (string.Equals(item.Word, word, StringComparison.OrdinalIgnoreCase))
				{
					lines.Add(OutputLine.Dim("usage: " + item.Signature));
					break;
				}
			}
		}

		private static string Fragment(string description)
		{
			if (string.IsNullOrEmpty(description))
			{
				return "";
			}
			string text = description.TrimEnd();
			if (text.EndsWith(".", StringComparison.Ordinal) && !text.EndsWith("..", StringComparison.Ordinal))
			{
				text = text.Substring(0, text.Length - 1);
			}
			if (text.Length > 1 && char.IsUpper(text[0]) && !char.IsUpper(text[1]))
			{
				text = char.ToLowerInvariant(text[0]) + text.Substring(1);
			}
			return text;
		}

		private static void Detail(CommandInfo command, List<OutputLine> lines)
		{
			lines.Add(OutputLine.Out(command.Signature));
			if (command.Description.Length > 0)
			{
				lines.Add(OutputLine.Out("  " + Fragment(command.Description)));
			}
			if (command.Usage.Length > 0)
			{
				lines.Add(OutputLine.Dim("  e.g. " + command.Usage));
			}
			lines.Add(OutputLine.Dim("  " + command.Source));
		}

		private static string Row(CommandInfo command)
		{
			string value = ((command.Description.Length > 0) ? Fragment(command.Description) : command.Signature);
			return Pad(command.Word, 24) + Markup.Clip(value, 72);
		}

		private static IEnumerable<string> Grid(IReadOnlyList<CommandInfo> commands, IReadOnlyList<string> builtins)
		{
			List<string> words = new List<string>();
			foreach (CommandInfo command in commands)
			{
				words.Add(command.Word);
			}
			List<string> list = new List<string>(builtins);
			list.Sort(StringComparer.OrdinalIgnoreCase);
			words.AddRange(list);
			for (int i = 0; i < words.Count; i += 5)
			{
				StringBuilder stringBuilder = new StringBuilder();
				for (int j = 0; j < 5 && i + j < words.Count; j++)
				{
					stringBuilder.Append(Pad(words[i + j], 19));
				}
				yield return stringBuilder.ToString().TrimEnd();
			}
		}

		private void HistoryList(string query, List<OutputLine> lines)
		{
			List<string> list = _history.Search(query);
			if (list.Count == 0)
			{
				lines.Add(OutputLine.Dim((query.Length == 0) ? "history: nothing yet" : ("history: nothing matches '" + query + "'")));
				return;
			}
			for (int num = list.Count - 1; num >= 0; num--)
			{
				lines.Add(OutputLine.Out(Pad((list.Count - num).ToString(), 6) + list[num]));
			}
		}

		private void Alias(string rest, List<string> tokens, List<OutputLine> lines)
		{
			if (rest.Length == 0)
			{
				if (_aliases.Count != 0)
				{
					foreach (KeyValuePair<string, string> item in _aliases.All)
					{
						lines.Add(OutputLine.Out(Pad(item.Key, 16) + item.Value));
					}
					return;
				}
				lines.Add(OutputLine.Dim("no aliases yet - alias <name> <command> makes one"));
			}
			else if (tokens.Count < 3)
			{
				lines.Add(OutputLine.Error("alias: needs a name and a command"));
				UsageLine("alias", lines);
				lines.Add(OutputLine.Dim("       quote the command if it has spaces"));
			}
			else
			{
				string text = tokens[1];
				string text2 = rest.Substring(text.Length).Trim();
				if (!_aliases.TrySet(text, text2, _suggestions.IsKnown, out var error))
				{
					lines.Add(OutputLine.Error(error));
				}
				else
				{
					lines.Add(OutputLine.Out(text + " -> " + text2));
				}
			}
		}

		private void Unalias(string rest, List<OutputLine> lines)
		{
			if (rest.Length == 0)
			{
				lines.Add(OutputLine.Error("unalias: needs a name"));
				UsageLine("unalias", lines);
			}
			else
			{
				lines.Add(_aliases.Remove(rest) ? OutputLine.Out(rest + " removed.") : OutputLine.Warn("unalias: no alias called '" + rest + "'"));
			}
		}

		private void Grep(string pattern, List<OutputLine> lines)
		{
			if (pattern.Length == 0)
			{
				lines.Add(OutputLine.Error("grep: needs something to look for"));
				UsageLine("grep", lines);
				return;
			}
			List<OutputLine> list = new List<OutputLine>();
			foreach (OutputLine line in _transcript.Lines)
			{
				if (line.Text.IndexOf(pattern, StringComparison.OrdinalIgnoreCase) >= 0)
				{
					list.Add(line);
				}
			}
			if (list.Count == 0)
			{
				lines.Add(OutputLine.Dim("grep: nothing on screen matches '" + pattern + "'"));
				return;
			}
			lines.Add(OutputLine.Dim($"{list.Count} line(s) match '{pattern}':"));
			lines.AddRange(list);
		}

		private void Copy(string rest, List<OutputLine> lines)
		{
			string text = rest;
			if (text.Length == 0)
			{
				foreach (OutputLine item in _transcript.Recent(40))
				{
					if (item.Kind != LineKind.Dim && item.Kind != LineKind.Echo)
					{
						text = item.Text.Trim();
						break;
					}
				}
			}
			if (string.IsNullOrEmpty(text))
			{
				lines.Add(OutputLine.Warn("copy: nothing on screen to copy"));
				return;
			}
			PendingClipboard = text;
			lines.Add(OutputLine.Dim("Copied: " + text));
		}

		private void Typeface(string rest, List<OutputLine> lines)
		{
			string text = (rest ?? "").Trim().ToLowerInvariant();
			if (text.Length == 0)
			{
				lines.Add(OutputLine.Out("font: " + Face));
				lines.Add(OutputLine.Dim("mono   the machine's own monospaced font, easier to read"));
				lines.Add(OutputLine.Dim("pixel  the game's own face, smaller and denser"));
			}
			else if (text != "mono" && text != "pixel")
			{
				lines.Add(OutputLine.Error("font: '" + rest.Trim() + "' is not a face"));
				lines.Add(OutputLine.Dim("try 'mono' or 'pixel'"));
			}
			else
			{
				Face = text;
				lines.Add(OutputLine.Dim("font: " + Face));
			}
		}

		private void Logs(string rest, List<OutputLine> lines)
		{
			if (string.Equals(rest, "off", StringComparison.OrdinalIgnoreCase))
			{
				LogsOpen = false;
				LogsFilter = "";
				lines.Add(OutputLine.Dim("Log view off."));
			}
			else
			{
				LogsOpen = true;
				LogsFilter = rest;
				lines.Add(OutputLine.Dim((rest.Length == 0) ? "Log view on - everything the game logs. 'logs warn' filters, 'logs off' stops." : ("Log view on, filtered to '" + rest + "'. 'logs off' stops.")));
			}
		}

		internal static string Pad(string value, int width)
		{
			if (value == null)
			{
				value = "";
			}
			if (value.Length < width)
			{
				return value.PadRight(width);
			}
			return value + " ";
		}
	}
	public static class CommandLine
	{
		public readonly struct Plan
		{
			public IReadOnlyList<string> Commands { get; }

			public string Error { get; }

			public bool Failed => Error != null;

			internal Plan(IReadOnlyList<string> commands, string error)
			{
				Commands = commands ?? Array.Empty<string>();
				Error = error;
			}
		}

		public const string RawWord = "raw";

		public const string RepeatWord = "repeat";

		public const int MaxRepeat = 100;

		private static readonly (string Word, int OwnArguments, bool RunsNow)[] TakeACommand = new(string, int, bool)[4]
		{
			("raw", 0, true),
			("repeat", 1, true),
			("alias", 1, false),
			("bind", 1, false)
		};

		public static Plan Parse(string line, Func<string, string> expandAlias = null)
		{
			if (string.IsNullOrWhiteSpace(line))
			{
				return new Plan(Array.Empty<string>(), null);
			}
			string text = line.Trim();
			if (StartsWithWord(text, "raw", out var rest))
			{
				if (rest.Length != 0)
				{
					return new Plan(new string[1] { rest }, null);
				}
				return new Plan(null, "raw: no command after it\nusage: raw <command>");
			}
			if (!NeedsShell(text))
			{
				string text2 = ExpandFirstWord(text, expandAlias);
				return Split(text2, expandAlias, (object)text2 != text);
			}
			return Split(text, expandAlias, alreadyExpanded: false);
		}

		public static bool NeedsShell(string line)
		{
			if (line != null)
			{
				if (line.IndexOf('"') < 0)
				{
					return line.IndexOf(';') >= 0;
				}
				return true;
			}
			return false;
		}

		private static Plan Split(string line, Func<string, string> expandAlias, bool alreadyExpanded)
		{
			if (!TrySplitStatements(line, out var statements, out var error))
			{
				return new Plan(null, error);
			}
			List<string> list = new List<string>();
			foreach (string item in statements)
			{
				string text = item.Trim();
				if (text.Length == 0)
				{
					continue;
				}
				if (StartsWithWord(text, "raw", out var rest))
				{
					if (rest.Length == 0)
					{
						return new Plan(null, "raw: no command after it\nusage: raw <command>");
					}
					list.Add(rest);
					continue;
				}
				if (!alreadyExpanded)
				{
					text = ExpandFirstWord(text, expandAlias);
				}
				if (Expand(text, list, out error))
				{
					continue;
				}
				return new Plan(null, error);
			}
			return new Plan(list, null);
		}

		private static bool Expand(string statement, List<string> into, out string error)
		{
			error = null;
			if (!StartsWithWord(statement, "repeat", out var rest))
			{
				into.Add(statement);
				return true;
			}
			int num = rest.IndexOf(' ');
			string text = ((num < 0) ? rest : rest.Substring(0, num));
			string text2 = ((num < 0) ? "" : rest.Substring(num + 1).Trim());
			if (!int.TryParse(text, out var result))
			{
				error = "repeat: '" + text + "' is not a number\nusage: repeat <count> <command>";
				return false;
			}
			if (result < 1)
			{
				error = "repeat: the count has to be 1 or more\nusage: repeat <count> <command>";
				return false;
			}
			if (result > 100)
			{
				error = $"repeat: {100} is the most that can run in one frame";
				return false;
			}
			if (text2.Length == 0)
			{
				error = "repeat: no command after the count\nusage: repeat <count> <command>";
				return false;
			}
			if (StartsWithWord(text2, "raw", out var rest2))
			{
				if (rest2.Length == 0)
				{
					error = "raw: no command after it\nusage: raw <command>";
					return false;
				}
				text2 = rest2;
			}
			List<string> list = new List<string>();
			for (int i = 0; i < result; i++)
			{
				if (!Expand(text2, list, out error))
				{
					return false;
				}
				if (list.Count > 100)
				{
					error = $"repeat: that comes to more than {100} commands, and they all run in one frame";
					return false;
				}
			}
			into.AddRange(list);
			return true;
		}

		private static bool TrySplitStatements(string line, out List<string> statements, out string error)
		{
			statements = new List<string>();
			error = null;
			StringBuilder stringBuilder = new StringBuilder();
			bool flag = false;
			bool flag2 = true;
			for (int i = 0; i < line.Length; i++)
			{
				char c = line[i];
				if (flag2 && c != ' ')
				{
					flag2 = false;
					if (SuspendsSplitting(line.Substring(i)))
					{
						statements.Add(stringBuilder.Append(line.Substring(i)).ToString());
						return true;
					}
				}
				switch (c)
				{
				case '"':
					if (flag && i + 1 < line.Length && line[i + 1] == '"')
					{
						stringBuilder.Append('"');
						i++;
					}
					else
					{
						flag = !flag;
					}
					continue;
				case ';':
					if (!flag)
					{
						statements.Add(stringBuilder.ToString());
						stringBuilder.Clear();
						flag2 = true;
						continue;
					}
					break;
				}
				stringBuilder.Append(c);
			}
			if (flag)
			{
				error = "a quote was opened and never closed.";
				statements = null;
				return false;
			}
			statements.Add(stringBuilder.ToString());
			return true;
		}

		private static bool SuspendsSplitting(string rest)
		{
			if (StartsWithWord(rest, "raw", out var rest2))
			{
				return true;
			}
			if (!StartsWithWord(rest, "repeat", out var rest3))
			{
				return false;
			}
			int num = rest3.IndexOf(' ');
			if (num <= 0)
			{
				return false;
			}
			if (!int.TryParse(rest3.Substring(0, num), out var _))
			{
				return false;
			}
			return StartsWithWord(rest3.Substring(num + 1).TrimStart(), "raw", out rest2);
		}

		private static string ExpandFirstWord(string statement, Func<string, string> expandAlias)
		{
			if (expandAlias == null)
			{
				return statement;
			}
			int num = statement.IndexOf(' ');
			string arg = ((num < 0) ? statement : statement.Substring(0, num));
			string text = expandAlias(arg);
			if (string.IsNullOrEmpty(text))
			{
				return statement;
			}
			if (num >= 0)
			{
				return text + statement.Substring(num);
			}
			return text;
		}

		public static string Unwrap(string statement)
		{
			if (string.IsNullOrEmpty(statement))
			{
				return statement ?? "";
			}
			for (int i = 0; i < 4; i++)
			{
				string text = statement.TrimStart();
				int num = WrapperWidth(text);
				if (num <= 0)
				{
					return statement;
				}
				statement = text.Substring(num);
			}
			return statement;
		}

		public static int StoredCommandAt(string statement)
		{
			if (string.IsNullOrEmpty(statement))
			{
				return -1;
			}
			string line = statement.TrimStart();
			int at = 0;
			string a = ReadWord(line, ref at);
			(string, int, bool)[] takeACommand = TakeACommand;
			for (int i = 0; i < takeACommand.Length; i++)
			{
				(string, int, bool) tuple = takeACommand[i];
				var (b, num, _) = tuple;
				if (!tuple.Item3 && string.Equals(a, b, StringComparison.OrdinalIgnoreCase))
				{
					return num + 1;
				}
			}
			return -1;
		}

		private static int WrapperWidth(string statement)
		{
			int i = 0;
			string a = ReadWord(statement, ref i);
			int num = -1;
			(string, int, bool)[] takeACommand = TakeACommand;
			for (int j = 0; j < takeACommand.Length; j++)
			{
				var (b, num2, _) = takeACommand[j];
				if (string.Equals(a, b, StringComparison.OrdinalIgnoreCase))
				{
					num = num2;
				}
			}
			if (num < 0)
			{
				return 0;
			}
			for (int k = 0; k <= num; k++)
			{
				if (i >= statement.Length || statement[i] != ' ')
				{
					return 0;
				}
				for (; i < statement.Length && statement[i] == ' '; i++)
				{
				}
				if (k < num && ReadWord(statement, ref i).Length == 0)
				{
					return 0;
				}
			}
			if (i < statement.Length && (statement[i] == '"' || statement[i] == '\''))
			{
				i++;
			}
			return i;
		}

		private static string ReadWord(string line, ref int at)
		{
			int num = at;
			while (at < line.Length && line[at] != ' ')
			{
				at++;
			}
			return line.Substring(num, at - num);
		}

		public static bool StartsWithWord(string line, string word, out string rest)
		{
			rest = null;
			if (line == null || word == null)
			{
				return false;
			}
			if (!line.StartsWith(word, StringComparison.OrdinalIgnoreCase))
			{
				return false;
			}
			if (line.Length == word.Length)
			{
				rest = "";
				return true;
			}
			if (line[word.Length] != ' ')
			{
				return false;
			}
			rest = line.Substring(word.Length + 1).Trim();
			return true;
		}

		public static List<string> Tokenise(string command)
		{
			List<string> list = new List<string>();
			if (string.IsNullOrEmpty(command))
			{
				return list;
			}
			StringBuilder stringBuilder = new StringBuilder();
			bool flag = false;
			bool flag2 = false;
			foreach (char c in command)
			{
				switch (c)
				{
				case '"':
					flag = !flag;
					flag2 = true;
					continue;
				case ' ':
					if (!flag)
					{
						if (flag2)
						{
							list.Add(stringBuilder.ToString());
							stringBuilder.Clear();
							flag2 = false;
						}
						continue;
					}
					break;
				}
				stringBuilder.Append(c);
				flag2 = true;
			}
			if (flag2)
			{
				list.Add(stringBuilder.ToString());
			}
			return list;
		}

		public static void TokenAtCaret(string line, out int index, out string prefix)
		{
			index = 0;
			prefix = "";
			if (string.IsNullOrEmpty(line))
			{
				return;
			}
			bool flag = false;
			StringBuilder stringBuilder = new StringBuilder();
			bool flag2 = false;
			foreach (char c in line)
			{
				switch (c)
				{
				case '"':
					flag = !flag;
					flag2 = true;
					continue;
				case ' ':
					if (!flag)
					{
						if (flag2)
						{
							index++;
							stringBuilder.Clear();
							flag2 = false;
						}
						continue;
					}
					break;
				}
				stringBuilder.Append(c);
				flag2 = true;
			}
			prefix = stringBuilder.ToString();
		}

		public static string ReplaceTokenAtCaret(string line, string value, bool trailingSpace)
		{
			string text = ((value != null && value.IndexOf(' ') >= 0) ? ("\"" + value + "\"") : (value ?? ""));
			int length = line?.Length ?? 0;
			if (line != null)
			{
				bool flag = false;
				for (int num = line.Length - 1; num >= 0; num--)
				{
					if (line[num] == '"')
					{
						flag = !flag;
					}
					if (line[num] == ' ' && !flag)
					{
						length = num + 1;
						break;
					}
					if (num == 0)
					{
						length = 0;
					}
				}
			}
			return ((line == null) ? "" : line.Substring(0, length)) + text + (trailingSpace ? " " : "");
		}
	}
	public enum MatchKind
	{
		None,
		Subsequence,
		Substring,
		WordStart,
		Prefix,
		Exact
	}
	public readonly struct MatchResult
	{
		public static readonly MatchResult NoMatch = new MatchResult(MatchKind.None, int.MaxValue);

		public MatchKind Kind { get; }

		public int Offset { get; }

		public bool IsMatch => Kind != MatchKind.None;

		public int Score
		{
			get
			{
				if (Kind != MatchKind.None)
				{
					return (int)Kind * 1000 - Math.Min(Offset, 999);
				}
				return 0;
			}
		}

		internal MatchResult(MatchKind kind, int offset)
		{
			Kind = kind;
			Offset = offset;
		}
	}
	public static class FuzzyMatcher
	{
		private const int MinSubsequenceQuery = 2;

		private static readonly char[] WordSeparators = new char[6] { '_', '-', '.', ' ', '/', ':' };

		public static MatchResult Match(string candidate, string query)
		{
			if (string.IsNullOrEmpty(candidate))
			{
				return MatchResult.NoMatch;
			}
			if (string.IsNullOrEmpty(query))
			{
				return new MatchResult(MatchKind.Prefix, 0);
			}
			if (candidate.Equals(query, StringComparison.OrdinalIgnoreCase))
			{
				return new MatchResult(MatchKind.Exact, 0);
			}
			if (candidate.StartsWith(query, StringComparison.OrdinalIgnoreCase))
			{
				return new MatchResult(MatchKind.Prefix, 0);
			}
			int num = candidate.IndexOf(query, StringComparison.OrdinalIgnoreCase);
			if (num > 0)
			{
				return new MatchResult(IsWordStart(candidate, num) ? MatchKind.WordStart : MatchKind.Substring, num);
			}
			if (query.Length >= 2 && TryMatchSubsequence(candidate, query, out var firstIndex))
			{
				return new MatchResult(MatchKind.Subsequence, firstIndex);
			}
			return MatchResult.NoMatch;
		}

		public static bool IsMatch(string candidate, string query)
		{
			return Match(candidate, query).IsMatch;
		}

		private static bool IsWordStart(string candidate, int index)
		{
			if (index <= 0)
			{
				return true;
			}
			char c = candidate[index - 1];
			for (int i = 0; i < WordSeparators.Length; i++)
			{
				if (c == WordSeparators[i])
				{
					return true;
				}
			}
			if (char.IsLower(c))
			{
				return char.IsUpper(candidate[index]);
			}
			return false;
		}

		private static bool TryMatchSubsequence(string candidate, string query, out int firstIndex)
		{
			firstIndex = -1;
			int num = 0;
			for (int i = 0; i < candidate.Length; i++)
			{
				if (num >= query.Length)
				{
					break;
				}
				if (char.ToLowerInvariant(candidate[i]) == char.ToLowerInvariant(query[num]))
				{
					if (num == 0)
					{
						firstIndex = i;
					}
					num++;
				}
			}
			if (num < query.Length)
			{
				firstIndex = -1;
				return false;
			}
			return true;
		}
	}
	public static class HelpTopics
	{
		public const string Other = "other";

		public const string Terminal = "terminal";

		public static readonly (string Topic, string[] Words)[] Groups = new(string, string[])[13]
		{
			("items", new string[7] { "give", "setdiscovered", "setquality", "setquantity", "packageproduct", "clearinventory", "growplants" }),
			("money", new string[3] { "changecash", "changebalance", "addxp" }),
			("world", new string[8] { "settime", "setdayduration", "settimescale", "setweather", "triggerlightning", "triggerdistantthunder", "cleartrash", "forcesleep" }),
			("places", new string[4] { "teleport", "setowned", "setregionunlocked", "spawnvehicle" }),
			("people", new string[7] { "setunlocked", "setrelationship", "addemployee", "setemotion", "destroynpcs", "disablenpcs", "disablenpcasset" }),
			("police", new string[5] { "raisewanted", "lowerwanted", "clearwanted", "setlawintensity", "setpoliceignoreplayers" }),
			("player", new string[6] { "sethealth", "setstaminareserve", "setmovespeed", "setjumpforce", "setgravitymultiplier", "freecam" }),
			("quests", new string[5] { "setqueststate", "setquestentrystate", "setvar", "endtutorial", "playcutscene" }),
			("keys", new string[3] { "bind", "unbind", "clearbinds" }),
			("display", new string[5] { "hideui", "showfps", "hidefps", "enable", "disable" }),
			("graphics", new string[9] { "enableinstancing", "disableinstancing", "enableocclusionculling", "disableocclusionculling", "enablephysics", "disablephysics", "enableterrain", "disableterrain", "disablemeshes" }),
			("game", new string[2] { "save", "quit" }),
			("dev", new string[2] { "npcworkbench", "presentationworkbench" })
		};

		public static readonly string[] Common = new string[6] { "give", "teleport", "settime", "changecash", "spawnvehicle", "setweather" };

		private static readonly Dictionary<string, string> ByWord = Index();

		public static bool IsTopic(string name)
		{
			if (string.IsNullOrEmpty(name))
			{
				return false;
			}
			(string, string[])[] groups = Groups;
			for (int i = 0; i < groups.Length; i++)
			{
				if (string.Equals(groups[i].Item1, name, StringComparison.OrdinalIgnoreCase))
				{
					return true;
				}
			}
			return string.Equals(name, "terminal", StringComparison.OrdinalIgnoreCase);
		}

		public static string TopicOf(string word)
		{
			if (word == null || !ByWord.TryGetValue(word, out var value))
			{
				return "other";
			}
			return value;
		}

		private static Dictionary<string, string> Index()
		{
			Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
			(string, string[])[] groups = Groups;
			for (int i = 0; i < groups.Length; i++)
			{
				(string, string[]) tuple = groups[i];
				string item = tuple.Item1;
				string[] item2 = tuple.Item2;
				foreach (string key in item2)
				{
					dictionary[key] = item;
				}
			}
			return dictionary;
		}
	}
	public sealed class History
	{
		private const string FileName = "history.txt";

		public const int Capacity = 500;

		private readonly List<string> _lines = new List<string>();

		private bool _dirty;

		public IReadOnlyList<string> Lines => _lines;

		public int Count => _lines.Count;

		public void Add(string line)
		{
			string one = line?.Trim();
			if (!string.IsNullOrEmpty(one))
			{
				_lines.RemoveAll((string existing) => string.Equals(existing, one, StringComparison.Ordinal));
				_lines.Insert(0, one);
				if (_lines.Count > 500)
				{
					_lines.RemoveRange(500, _lines.Count - 500);
				}
				_dirty = true;
			}
		}

		public List<string> Search(string query)
		{
			List<string> list = new List<string>();
			if (string.IsNullOrEmpty(query))
			{
				list.AddRange(_lines);
				return list;
			}
			foreach (string line in _lines)
			{
				if (line.IndexOf(query, StringComparison.OrdinalIgnoreCase) >= 0)
				{
					list.Add(line);
				}
			}
			return list;
		}

		public string ReverseSearch(string query, int skip)
		{
			if (skip < 0)
			{
				return null;
			}
			List<string> list = Search(query);
			if (skip >= list.Count)
			{
				return null;
			}
			return list[skip];
		}

		public void Clear()
		{
			if (_lines.Count != 0)
			{
				_lines.Clear();
				_dirty = true;
			}
		}

		public void Load(IStore store)
		{
			_lines.Clear();
			_dirty = false;
			string text = store?.Read(StoreScope.Global, "history.txt");
			if (string.IsNullOrEmpty(text))
			{
				return;
			}
			string[] array = text.Split('\n');
			for (int i = 0; i < array.Length; i++)
			{
				string text2 = array[i].Trim('\r');
				if (text2.Length != 0)
				{
					_lines.Add(text2);
					if (_lines.Count >= 500)
					{
						break;
					}
				}
			}
		}

		public void Save(IStore store)
		{
			if (!_dirty || store == null)
			{
				return;
			}
			StringBuilder stringBuilder = new StringBuilder();
			foreach (string line in _lines)
			{
				stringBuilder.Append(line).Append('\n');
			}
			store.Write(StoreScope.Global, "history.txt", stringBuilder.ToString());
			_dirty = false;
		}
	}
	public readonly struct Expansion
	{
		public string Line { get; }

		public string Error { get; }

		public bool Failed => Error != null;

		internal Expansion(string line, string error)
		{
			Line = line ?? "";
			Error = error;
		}
	}
	public sealed class MarkExpansion
	{
		private readonly Marks _marks;

		private readonly ICommandCatalogue _catalogue;

		public MarkExpansion(Marks marks, ICommandCatalogue catalogue)
		{
			_marks = marks;
			_catalogue = catalogue;
		}

		public Expansion Apply(string statement)
		{
			if (string.IsNullOrEmpty(statement) || statement.IndexOf('#') < 0)
			{
				return new Expansion(statement, null);
			}
			List<string> list = CommandLine.Tokenise(statement);
			if (list.Count == 0)
			{
				return new Expansion(statement, null);
			}
			string text = list[0];
			StringBuilder stringBuilder = new StringBuilder(text);
			int num = CommandLine.StoredCommandAt(statement);
			for (int i = 1; i < list.Count; i++)
			{
				string text2 = list[i];
				stringBuilder.Append(' ');
				if (num >= 0 && i >= num)
				{
					stringBuilder.Append(Quote(text2));
					continue;
				}
				if (!Marks.IsWord(text2))
				{
					stringBuilder.Append(Quote(text2));
					continue;
				}
				Mark mark = _marks.Resolve(text2);
				int argIndex = i - 1;
				if (!mark.Exists)
				{
					return new Expansion(null, Unmarked(text2));
				}
				MarkKind wanted = _catalogue.KindOf(text, argIndex);
				if (!Fits(mark.Kind, wanted))
				{
					return new Expansion(null, Mismatch(text, text2, mark, wanted));
				}
				stringBuilder.Append(Quote(mark.Id));
			}
			return new Expansion(stringBuilder.ToString(), null);
		}

		private static bool Fits(MarkKind have, MarkKind wanted)
		{
			if (wanted == MarkKind.None)
			{
				return false;
			}
			if (have == MarkKind.Any || wanted == MarkKind.Any)
			{
				return true;
			}
			return have == wanted;
		}

		private static string Unmarked(string word)
		{
			if (!(word == "#"))
			{
				return word + ": nothing there right now";
			}
			return "#: nothing marked - look at something before opening the terminal";
		}

		private static string Mismatch(string command, string word, Mark mark, MarkKind wanted)
		{
			string text = $"{word}: marked {Mark.Word(mark.Kind)} '{mark.Id}'";
			if (wanted != MarkKind.None)
			{
				return text + $"\n{command} needs {AnA(Mark.Word(wanted))} there";
			}
			return text + "\n" + command + " does not take one there";
		}

		private static string AnA(string word)
		{
			if (word.Length == 0)
			{
				return word;
			}
			if (string.Equals(word, "npc", StringComparison.OrdinalIgnoreCase))
			{
				return "an " + word;
			}
			if ("aeiou".IndexOf(char.ToLowerInvariant(word[0])) < 0)
			{
				return "a " + word;
			}
			return "an " + word;
		}

		private static string Quote(string value)
		{
			if (value.IndexOf(' ') < 0)
			{
				return value;
			}
			return "\"" + value + "\"";
		}
	}
	public enum MarkKind
	{
		None,
		Npc,
		Vehicle,
		Property,
		Item,
		Any
	}
	public readonly struct Mark
	{
		public static readonly Mark None = new Mark(MarkKind.None, "", "");

		public MarkKind Kind { get; }

		public string Id { get; }

		public string Label { get; }

		public bool Exists
		{
			get
			{
				if (Kind != MarkKind.None)
				{
					return Id.Length > 0;
				}
				return false;
			}
		}

		public Mark(MarkKind kind, string id, string label)
		{
			Kind = kind;
			Id = id ?? "";
			Label = label ?? "";
		}

		public override string ToString()
		{
			if (!Exists)
			{
				return "nothing";
			}
			return Id + " (" + Word(Kind) + ")";
		}

		internal static string Word(MarkKind kind)
		{
			return kind switch
			{
				MarkKind.Npc => "npc", 
				MarkKind.Vehicle => "vehicle", 
				MarkKind.Property => "property", 
				MarkKind.Item => "item", 
				MarkKind.Any => "id", 
				_ => "nothing", 
			};
		}
	}
	public interface IMarks
	{
		Mark Looked { get; }

		Mark Hand { get; }

		Mark Here { get; }

		Mark Car { get; }

		Mark Home { get; }

		Mark Near { get; }
	}
	public sealed class Marks
	{
		public const char Sigil = '#';

		private readonly IMarks _world;

		private string _lastArgument = "";

		private string _lastPrinted = "";

		public static readonly string[] Words = new string[8] { "#", "#hand", "#here", "#last", "#it", "#car", "#home", "#near" };

		public Marks(IMarks world)
		{
			_world = world;
		}

		public Mark Resolve(string word)
		{
			if (string.IsNullOrEmpty(word) || word[0] != '#')
			{
				return Mark.None;
			}
			return word.ToLowerInvariant() switch
			{
				"#" => _world?.Looked ?? Mark.None, 
				"#hand" => _world?.Hand ?? Mark.None, 
				"#here" => _world?.Here ?? Mark.None, 
				"#car" => _world?.Car ?? Mark.None, 
				"#home" => _world?.Home ?? Mark.None, 
				"#near" => _world?.Near ?? Mark.None, 
				"#last" => Free(_lastArgument), 
				"#it" => Free(_lastPrinted), 
				_ => Mark.None, 
			};
		}

		public static bool IsWord(string word)
		{
			if (string.IsNullOrEmpty(word) || word[0] != '#')
			{
				return false;
			}
			string[] words = Words;
			for (int i = 0; i < words.Length; i++)
			{
				if (string.Equals(words[i], word, StringComparison.OrdinalIgnoreCase))
				{
					return true;
				}
			}
			return false;
		}

		public void Ran(string line)
		{
			List<string> list = CommandLine.Tokenise(line ?? "");
			_lastArgument = ((list.Count > 1) ? list[list.Count - 1] : "");
		}

		public void Printed(string text)
		{
			string text2 = (text ?? "").Trim();
			if (text2.Length != 0 && text2.IndexOf(' ') < 0)
			{
				_lastPrinted = text2;
			}
		}

		private static Mark Free(string id)
		{
			if (!string.IsNullOrEmpty(id))
			{
				return new Mark(MarkKind.Any, id, id);
			}
			return Mark.None;
		}
	}
	public static class Markup
	{
		private const int ValueColumn = 24;

		private const int SourceColumn = 30;

		private const int LineWidth = 88;

		public static string Suggestions(SuggestionSet set, int selected, int window, bool expanded)
		{
			if (set == null)
			{
				return "";
			}
			if (set.Command == null && !set.Any)
			{
				return "";
			}
			StringBuilder stringBuilder = new StringBuilder();
			if (set.Command != null)
			{
				Signature(stringBuilder, set.Command.Signature, set.ArgIndex);
				string value = ((set.Command.Description.Length > 0) ? set.Command.Description : "No description.");
				Line(stringBuilder, "desc", Clip(value, 88 - set.Command.Source.Length - 2) + "  " + set.Command.Source);
			}
			if (!expanded)
			{
				if (set.Any)
				{
					Line(stringBuilder, "ghost", Pad("tab  " + set.Rows[Math.Min(Math.Max(selected, 0), set.Rows.Count - 1)].Value, 32) + ((set.Rows.Count > 1) ? $"up/down  browse {set.Rows.Count}" : ""));
				}
				return stringBuilder.ToString();
			}
			if (!set.Any)
			{
				return stringBuilder.ToString();
			}
			int count = set.Rows.Count;
			int num = Math.Min(8, count);
			int num2 = Math.Max(0, Math.Min(window, count - num));
			int num3 = num2 + num;
			Line(stringBuilder, "rule", Rule(num2, num3, count));
			for (int i = num2; i < num3; i++)
			{
				Suggestion suggestion = set.Rows[i];
				bool flag = i == selected;
				StringBuilder stringBuilder2 = new StringBuilder();
				stringBuilder2.Append(flag ? "> " : "  ");
				stringBuilder2.Append(Pad(Clip(suggestion.Value, 24), 28));
				if (stringBuilder.Length > 0)
				{
					stringBuilder.Append("<br>");
				}
				Span(stringBuilder, flag ? "pick" : null, stringBuilder2.ToString());
				Span(stringBuilder, (suggestion.Kind == SuggestionKind.History) ? "src-history" : (suggestion.IsVanilla ? "src-game" : "src-mod"), suggestion.Source);
			}
			return stringBuilder.ToString();
		}

		private static void Signature(StringBuilder sb, string signature, int argIndex)
		{
			if (string.IsNullOrEmpty(signature))
			{
				return;
			}
			List<string> list = new List<string>(signature.Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries));
			int num = argIndex + 1;
			if (num <= 0 || num >= list.Count)
			{
				Span(sb, "sig", signature);
				return;
			}
			Span(sb, "sig", string.Join(" ", list.GetRange(0, num)) + " ");
			Span(sb, "cur", list[num]);
			if (num + 1 < list.Count)
			{
				Span(sb, "sig", " " + string.Join(" ", list.GetRange(num + 1, list.Count - num - 1)));
			}
		}

		private static string Rule(int first, int last, int count)
		{
			if (count <= 8)
			{
				return new string('-', 88);
			}
			string text = $" {first + 1}-{last} of {count} ";
			int count2 = Math.Max(4, 88 - text.Length);
			return new string('-', count2) + text;
		}

		public static string Transcript(IReadOnlyList<OutputLine> lines)
		{
			if (lines == null || lines.Count == 0)
			{
				return "";
			}
			StringBuilder stringBuilder = new StringBuilder();
			foreach (OutputLine line in lines)
			{
				if (stringBuilder.Length > 0)
				{
					stringBuilder.Append("<br>");
				}
				Span(stringBuilder, Class(line.Kind), line.Text);
			}
			return stringBuilder.ToString();
		}

		private static string Class(LineKind kind)
		{
			return kind switch
			{
				LineKind.Echo => "echo", 
				LineKind.Warn => "warn", 
				LineKind.Error => "err", 
				LineKind.Dim => "dim", 
				_ => null, 
			};
		}

		private static void Line(StringBuilder sb, string cls, string text)
		{
			if (sb.Length > 0)
			{
				sb.Append("<br>");
			}
			Span(sb, cls, text);
		}

		private static void Span(StringBuilder sb, string cls, string text)
		{
			if (!string.IsNullOrEmpty(text))
			{
				if (cls == null)
				{
					sb.Append(Escape(text));
				}
				else
				{
					sb.Append("<span class=\"").Append(cls).Append("\">")
						.Append(Escape(text))
						.Append("</span>");
				}
			}
		}

		public static string Escape(string text)
		{
			if (string.IsNullOrEmpty(text))
			{
				return "";
			}
			if (text.IndexOf('&') < 0 && text.IndexOf('<') < 0 && text.IndexOf('>') < 0)
			{
				return text;
			}
			return text.Replace("&", "&amp;").Replace("<", "&lt;").Replace(">", "&gt;");
		}

		internal static string Pad(string value, int width)
		{
			if (value == null)
			{
				value = "";
			}
			if (value.Length < width)
			{
				return value.PadRight(width);
			}
			return value + " ";
		}

		internal static string Clip(string value, int width)
		{
			if (value == null)
			{
				return "";
			}
			if (width < 4 || value.Length <= width)
			{
				return value;
			}
			return value.Substring(0, width - 2) + "..";
		}
	}
	public sealed class CommandInfo
	{
		public string Word { get; }

		public string Description { get; }

		public string Usage { get; }

		public string Signature { get; }

		public string Source { get; }

		public bool IsVanilla { get; }

		public CommandInfo(string word, string description, string usage, string signature, string source, bool isVanilla)
		{
			Word = word ?? "";
			Description = description ?? "";
			Usage = usage ?? "";
			Signature = signature ?? "";
			Source = source ?? "";
			IsVanilla = isVanilla;
		}
	}
	public readonly struct ArgValue
	{
		public string Value { get; }

		public string Source { get; }

		public bool IsVanilla { get; }

		public ArgValue(string value, string source, bool isVanilla)
		{
			Value = value ?? "";
			Source = source ?? "";
			IsVanilla = isVanilla;
		}
	}
	public enum LineKind
	{
		Echo,
		Out,
		Warn,
		Error,
		Dim
	}
	public readonly struct OutputLine
	{
		public LineKind Kind { get; }

		public string Text { get; }

		public OutputLine(LineKind kind, string text)
		{
			Kind = kind;
			Text = text ?? "";
		}

		public static OutputLine Echo(string text)
		{
			return new OutputLine(LineKind.Echo, text);
		}

		public static OutputLine Out(string text)
		{
			return new OutputLine(LineKind.Out, text);
		}

		public static OutputLine Warn(string text)
		{
			return new OutputLine(LineKind.Warn, text);
		}

		public static OutputLine Error(string text)
		{
			return new OutputLine(LineKind.Error, text);
		}

		public static OutputLine Dim(string text)
		{
			return new OutputLine(LineKind.Dim, text);
		}
	}
	public enum SuggestionKind
	{
		Command,
		Argument,
		History
	}
	public readonly struct Suggestion
	{
		public SuggestionKind Kind { get; }

		public string Value { get; }

		public string Source { get; }

		public bool IsVanilla { get; }

		public int Score { get; }

		public Suggestion(SuggestionKind kind, string value, string source, bool isVanilla, int score)
		{
			Kind = kind;
			Value = value ?? "";
			Source = source ?? "";
			IsVanilla = isVanilla;
			Score = score;
		}
	}
	public interface ICommandCatalogue
	{
		IReadOnlyList<CommandInfo> Commands { get; }

		IReadOnlyList<ArgValue> ValuesFor(string command, int argIndex);

		MarkKind KindOf(string command, int argIndex);

		bool Owns(string command, int argIndex);
	}
	public interface ICommandRunner
	{
		bool CanRun { get; }

		string RefusalReason { get; }

		IReadOnlyList<OutputLine> Run(string line);
	}
	public interface IStore
	{
		string Read(StoreScope scope, string name);

		void Write(StoreScope scope, string name, string content);
	}
	public enum StoreScope
	{
		Save,
		Global
	}
	public interface IClock
	{
		string Now { get; }
	}
	public sealed class NavResult
	{
		public string Line { get; internal set; }

		public string Suggest { get; internal set; } = "";

		public string Ghost { get; internal set; } = "";
	}
	public sealed class RunResult
	{
		public IReadOnlyList<OutputLine> Lines { get; internal set; } = Array.Empty<OutputLine>();

		public string Clipboard { get; internal set; }

		public bool Cleared { get; internal set; }
	}
	public sealed class Session
	{
		private readonly ICommandCatalogue _catalogue;

		private readonly ICommandRunner _runner;

		private readonly Suggestions _suggestions;

		private readonly Builtins _builtins;

		private readonly Transcript _transcript;

		private readonly History _history;

		private readonly Aliases _aliases;

		private readonly Usage _usage;

		private readonly Marks _marks;

		private readonly MarkExpansion _expansion;

		private SuggestionSet _current = SuggestionSet.Empty;

		private int _selected;

		private int _window;

		private bool _expanded;

		private int _searchSkip;

		private string _searchQuery = "";

		public Transcript Transcript => _transcript;

		public Marks Marks => _marks;

		public Builtins Builtins => _builtins;

		public bool Locked => !_runner.CanRun;

		public int CommandCount
		{
			get
			{
				int num = _catalogue.Commands.Count;
				foreach (CommandInfo item in Builtins.Catalogue)
				{
					if (!_suggestions.IsCommand(item.Word))
					{
						num++;
					}
				}
				return num;
			}
		}

		public Session(ICommandCatalogue catalogue, ICommandRunner runner, Usage usage, History history, Aliases aliases, IMarks marks = null)
		{
			_catalogue = catalogue;
			_runner = runner;
			_usage = usage;
			_history = history;
			_aliases = aliases;
			_marks = new Marks(marks);
			_expansion = new MarkExpansion(_marks, catalogue);
			_transcript = new Transcript();
			_suggestions = new Suggestions(catalogue, usage, history, aliases, _marks);
			_builtins = new Builtins(_suggestions, catalogue, history, aliases, _transcript);
		}

		public IReadOnlyList<OutputLine> Banner(string identity)
		{
			List<OutputLine> list = new List<OutputLine>();
			if (!string.IsNullOrEmpty(identity))
			{
				list.Add(OutputLine.Out(identity));
			}
			list.Add(OutputLine.Dim($"{CommandCount} commands loaded. Type 'help' for the list."));
			if (Locked)
			{
				list.Add(OutputLine.Error(_runner.RefusalReason + " Lookups still work."));
			}
			return list;
		}

		public NavResult Typed(string line)
		{
			_searchSkip = 0;
			if (string.IsNullOrWhiteSpace(line))
			{
				_expanded = false;
			}
			return Offer(line, resetSelection: true);
		}

		public NavResult Navigate(string line, string action)
		{
			return (action ?? "").ToLowerInvariant() switch
			{
				"typed" => Typed(line), 
				"accept" => Accept(line), 
				"up" => Move(line, -1), 
				"down" => Move(line, 1), 
				"pageup" => Move(line, -8), 
				"pagedown" => Move(line, 8), 
				"search" => ReverseSearch(line), 
				_ => Offer(line, resetSelection: false), 
			};
		}

		private NavResult Move(string line, int by)
		{
			if (_current.Rows.Count == 0)
			{
				_current = _suggestions.For(line);
				_selected = 0;
				_window = 0;
			}
			if (!_current.Any)
			{
				return Draw(line);
			}
			if (!_expanded)
			{
				_expanded = true;
				_selected = ((by < 0) ? (_current.Rows.Count - 1) : 0);
				ScrollToSelection();
				return Draw(line);
			}
			int count = _current.Rows.Count;
			_selected = ((_selected + by) % count + count) % count;
			ScrollToSelection();
			return Draw(line);
		}

		private NavResult Recall(string line)
		{
			_current = SuggestionSet.Empty;
			_selected = 0;
			_window = 0;
			_expanded = false;
			return new NavResult
			{
				Line = line
			};
		}

		private NavResult ReverseSearch(string line)
		{
			if (_searchSkip == 0)
			{
				_searchQuery = (line ?? "").Trim();
			}
			string text = _history.ReverseSearch(_searchQuery, _searchSkip);
			if (text == null)
			{
				_searchSkip = 0;
				return Draw(line);
			}
			_searchSkip++;
			return Recall(text);
		}

		private NavResult Accept(string line)
		{
			if (_current.Rows.Count == 0)
			{
				_current = _suggestions.For(line);
				_selected = 0;
				_window = 0;
			}
			if (!_current.Any)
			{
				return Draw(line);
			}
			Suggestion suggestion = _current.Rows[Math.Min(_selected, _current.Rows.Count - 1)];
			if (suggestion.Kind == SuggestionKind.History)
			{
				return Offer(suggestion.Value, resetSelection: true, suggestion.Value);
			}
			bool trailingSpace = suggestion.Kind == SuggestionKind.Command || MoreArgumentsAfter();
			string text = CommandLine.ReplaceTokenAtCaret(line, suggestion.Value, trailingSpace);
			return Offer(text, resetSelection: true, text);
		}

		private bool MoreArgumentsAfter()
		{
			if (_current.Command == null || _current.ArgIndex < 0)
			{
				return false;
			}
			return _current.ArgIndex + 1 < UsageExample.ArgumentCount(_current.Command.Signature);
		}

		public RunResult Run(string line)
		{
			RunResult runResult = new RunResult();
			List<OutputLine> list = new List<OutputLine>();
			string line2 = (line ?? "").Trim();
			if (line2.Length == 0)
			{
				runResult.Lines = list;
				return runResult;
			}
			_current = SuggestionSet.Empty;
			_searchSkip = 0;
			_expanded = false;
			if (!Expand(ref line2, list))
			{
				runResult.Lines = list;
				return runResult;
			}
			Echo(line2, list);
			_history.Add(line2);
			CommandLine.Plan plan = CommandLine.Parse(line2, _aliases.Expand);
			if (plan.Failed)
			{
				string[] array = plan.Error.Split('\n');
				Emit(OutputLine.Error(array[0]), list);
				for (int i = 1; i < array.Length; i++)
				{
					Emit(OutputLine.Dim(array[i]), list);
				}
				runResult.Lines = list;
				return runResult;
			}
			int count = _transcript.Count;
			List<string> list2 = new List<string>();
			foreach (string command in plan.Commands)
			{
				Expansion expansion = _expansion.Apply(command);
				if (expansion.Failed)
				{
					string[] array2 = expansion.Error.Split('\n');
					foreach (string text in array2)
					{
						Emit(OutputLine.Error(text), list);
					}
					runResult.Lines = list;
					return runResult;
				}
				list2.Add(expansion.Line);
			}
			_marks.Ran(line2);
			foreach (string item in list2)
			{
				RunOne(item, list);
			}
			runResult.Cleared = _transcript.Count < count;
			runResult.Lines = list;
			runResult.Clipboard = _builtins.TakeClipboard();
			return runResult;
		}

		private bool Expand(ref string line, List<OutputLine> lines)
		{
			if (line.Length < 2 || line[0] != '!')
			{
				return true;
			}
			string text = line.Substring(1);
			string text2;
			if (text == "!")
			{
				text2 = ((_history.Count > 0) ? _history.Lines[0] : null);
				if (text2 == null)
				{
					Emit(OutputLine.Warn("No previous command."), lines);
					return false;
				}
			}
			else
			{
				text2 = null;
				foreach (string line2 in _history.Lines)
				{
					if (line2.StartsWith(text, StringComparison.OrdinalIgnoreCase))
					{
						text2 = line2;
						break;
					}
				}
				if (text2 == null)
				{
					Emit(OutputLine.Warn("Nothing in history starts with '" + text + "'."), lines);
					return false;
				}
			}
			line = text2;
			return true;
		}

		private void RunOne(string command, List<OutputLine> lines)
		{
			if (_builtins.TryRun(command, out var output))
			{
				foreach (OutputLine item in output)
				{
					Emit(item, lines);
				}
				return;
			}
			if (Locked)
			{
				Emit(OutputLine.Error(_runner.RefusalReason), lines);
				return;
			}
			_usage.Record(CommandLine.Tokenise(command));
			foreach (OutputLine item2 in _runner.Run(command))
			{
				Emit(item2, lines);
			}
		}

		private void Emit(OutputLine line, List<OutputLine> into)
		{
			_transcript.Add(line);
			into.Add(line);
			if (line.Kind == LineKind.Out)
			{
				_marks.Printed(line.Text);
			}
		}

		private void Echo(string typed, List<OutputLine> lines)
		{
			Emit(OutputLine.Echo("$ " + typed), lines);
		}

		public void Push(OutputLine line)
		{
			_transcript.Add(line);
		}

		private NavResult Offer(string line, bool resetSelection, string forceLine = null)
		{
			_current = _suggestions.For(line);
			if (resetSelection)
			{
				_selected = 0;
			}
			if (_selected >= _current.Rows.Count)
			{
				_selected = 0;
			}
			if (resetSelection)
			{
				_window = 0;
			}
			ScrollToSelection();
			NavResult navResult = Draw(line);
			navResult.Line = forceLine;
			return navResult;
		}

		private void ScrollToSelection()
		{
			int count = _current.Rows.Count;
			int num = 8;
			if (count <= num)
			{
				_window = 0;
				return;
			}
			if (_selected < _window)
			{
				_window = _selected;
			}
			else if (_selected >= _window + num)
			{
				_window = _selected - num + 1;
			}
			if (_window > count - num)
			{
				_window = count - num;
			}
			if (_window < 0)
			{
				_window = 0;
			}
		}

		private NavResult Draw(string line)
		{
			return new NavResult
			{
				Suggest = Markup.Suggestions(_current, _selected, _window, _expanded),
				Ghost = Ghost(line)
			};
		}

		private string Ghost(string line)
		{
			if (!_current.Any)
			{
				return "";
			}
			Suggestion suggestion = _current.Rows[Math.Min(_selected, _current.Rows.Count - 1)];
			if (suggestion.Kind == SuggestionKind.History)
			{
				string text = line ?? "";
				if (text.Length == 0)
				{
					if (!_expanded)
					{
						return "";
					}
					return suggestion.Value;
				}
				if (!suggestion.Value.StartsWith(text, StringComparison.OrdinalIgnoreCase))
				{
					return "";
				}
				return suggestion.Value.Substring(text.Length);
			}
			string prefix = _current.Prefix;
			if (prefix.Length == 0 && !_expanded)
			{
				return "";
			}
			if (!suggestion.Value.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
			{
				return "";
			}
			return suggestion.Value.Substring(prefix.Length);
		}

		public string SuggestMarkup()
		{
			return Markup.Suggestions(_current, _selected, _window, _expanded);
		}
	}
	public sealed class SuggestionSet
	{
		public static readonly SuggestionSet Empty = new SuggestionSet(null, null, -1, "");

		public IReadOnlyList<Suggestion> Rows { get; }

		public CommandInfo Command { get; }

		public int ArgIndex { get; }

		public string Prefix { get; }

		public bool Any => Rows.Count > 0;

		internal SuggestionSet(IReadOnlyList<Suggestion> rows, CommandInfo command, int argIndex, string prefix)
		{
			Rows = rows ?? Array.Empty<Suggestion>();
			Command = command;
			ArgIndex = argIndex;
			Prefix = prefix ?? "";
		}
	}
	public sealed class Suggestions
	{
		public const int MaxRows = 8;

		public const int MaxMatches = 300;

		public const int MaxHistoryRows = 3;

		public const int MaxHistoryRowsAlone = 8;

		private readonly ICommandCatalogue _catalogue;

		private readonly Usage _usage;

		private readonly History _history;

		private readonly Aliases _aliases;

		private readonly Marks _marks;

		public Suggestions(ICommandCatalogue catalogue, Usage usage, History history, Aliases aliases, Marks marks = null)
		{
			_catalogue = catalogue;
			_usage = usage;
			_history = history;
			_aliases = aliases;
			_marks = marks;
		}

		public SuggestionSet For(string line)
		{
			if (line == null)
			{
				line = "";
			}
			string text = CommandLine.Unwrap(LastStatement(line));
			CommandLine.TokenAtCaret(text, out var index, out var prefix);
			if (index != 0)
			{
				return ForArgument(text, index, prefix);
			}
			return ForCommandWord(prefix, line);
		}

		private SuggestionSet ForCommandWord(string prefix, string wholeLine)
		{
			List<Suggestion> list = new List<Suggestion>();
			foreach (CommandInfo command in _catalogue.Commands)
			{
				MatchResult matchResult = FuzzyMatcher.Match(command.Word, prefix);
				if (matchResult.IsMatch)
				{
					list.Add(new Suggestion(SuggestionKind.Command, command.Word, command.Source, command.IsVanilla, matchResult.Score));
				}
			}
			foreach (CommandInfo item in Builtins.Catalogue)
			{
				if (InCatalogue(item.Word) == null)
				{
					MatchResult matchResult2 = FuzzyMatcher.Match(item.Word, prefix);
					if (matchResult2.IsMatch)
					{
						list.Add(new Suggestion(SuggestionKind.Command, item.Word, item.Source, isVanilla: false, matchResult2.Score));
					}
				}
			}
			foreach (KeyValuePair<string, string> item2 in _aliases.All)
			{
				MatchResult matchResult3 = FuzzyMatcher.Match(item2.Key, prefix);
				if (matchResult3.IsMatch)
				{
					list.Add(new Suggestion(SuggestionKind.Command, item2.Key, "Alias", isVanilla: false, matchResult3.Score));
				}
			}
			Usage.Order(list, (Suggestion s) => _usage.CommandCount(s.Value));
			Trim(list, 300);
			List<Suggestion> list2 = HistoryRows(wholeLine, (prefix.Length == 0) ? 8 : 3);
			list2.Reverse();
			list.AddRange(list2);
			return new SuggestionSet(list, null, -1, prefix);
		}

		private SuggestionSet ForArgument(string statement, int tokenIndex, string prefix)
		{
			List<string> list = CommandLine.Tokenise(statement);
			string word = ((list.Count > 0) ? list[0] : "");
			int argIndex = tokenIndex - 1;
			Resolve(ref word, ref argIndex);
			CommandInfo commandInfo = Find(word);
			List<Suggestion> list2 = new List<Suggestion>();
			foreach (ArgValue item in Values(word, argIndex))
			{
				MatchResult matchResult = FuzzyMatcher.Match(item.Value, prefix);
				if (matchResult.IsMatch)
				{
					list2.Add(new Suggestion(SuggestionKind.Argument, item.Value, item.Source, item.IsVanilla, matchResult.Score));
				}
			}
			if (list2.Count == 0 && !_catalogue.Owns(word, argIndex) && commandInfo != null)
			{
				foreach (string item2 in UsageExample.Literals(commandInfo.Usage, commandInfo.Signature, argIndex))
				{
					MatchResult matchResult2 = FuzzyMatcher.Match(item2, prefix);
					if (matchResult2.IsMatch)
					{
						list2.Add(new Suggestion(SuggestionKind.Argument, item2, commandInfo.Source, commandInfo.IsVanilla, matchResult2.Score));
					}
				}
			}
			Usage.Order(list2, (Suggestion s) => _usage.ArgCount(word, s.Value));
			list2 = Usage.GroupBySupplier(list2);
			Trim(list2, 300);
			list2.InsertRange(0, ContextRows(word, argIndex, prefix));
			return new SuggestionSet(list2, commandInfo, argIndex, prefix);
		}

		private IEnumerable<ArgValue> Values(string word, int argIndex)
		{
			IReadOnlyList<ArgValue> readOnlyList = _catalogue.ValuesFor(word, argIndex);
			if (readOnlyList.Count > 0)
			{
				return readOnlyList;
			}
			if (argIndex != 0)
			{
				return readOnlyList;
			}
			return word.ToLowerInvariant() switch
			{
				"unalias" => Aliases(), 
				"help" => Topics(), 
				"logs" => Literals("off", "warn", "error", "log"), 
				_ => readOnlyList, 
			};
		}

		private IEnumerable<ArgValue> Aliases()
		{
			foreach (KeyValuePair<string, string> item in _aliases.All)
			{
				yield return new ArgValue(item.Key, "Alias", isVanilla: false);
			}
		}

		private static IEnumerable<ArgValue> Topics()
		{
			yield return new ArgValue("all", "hash", isVanilla: false);
			(string Topic, string[] Words)[] groups = HelpTopics.Groups;
			for (int i = 0; i < groups.Length; i++)
			{
				string item = groups[i].Topic;
				yield return new ArgValue(item, "hash", isVanilla: false);
			}
			yield return new ArgValue("terminal", "hash", isVanilla: false);
		}

		private static IEnumerable<ArgValue> Literals(params string[] values)
		{
			foreach (string value in values)
			{
				yield return new ArgValue(value, "hash", isVanilla: false);
			}
		}

		private void Resolve(ref string word, ref int argIndex)
		{
			string text = _aliases.Expand(word);
			if (!string.IsNullOrEmpty(text))
			{
				List<string> list = CommandLine.Tokenise(text);
				if (list.Count != 0)
				{
					word = list[0];
					argIndex += list.Count - 1;
				}
			}
		}

		private List<Suggestion> ContextRows(string command, int argIndex, string prefix)
		{
			List<Suggestion> list = new List<Suggestion>();
			if (_marks == null)
			{
				return list;
			}
			MarkKind markKind = _catalogue.KindOf(command, argIndex);
			if (markKind == MarkKind.None)
			{
				return list;
			}
			string[] words = Marks.Words;
			foreach (string text in words)
			{
				MatchResult matchResult = FuzzyMatcher.Match(text, prefix);
				if (matchResult.IsMatch)
				{
					Mark mark = _marks.Resolve(text);
					if (mark.Exists && (mark.Kind == MarkKind.Any || markKind == MarkKind.Any || mark.Kind == markKind))
					{
						list.Add(new Suggestion(SuggestionKind.Argument, text, mark.Id, isVanilla: false, matchResult.Score));
					}
				}
			}
			return list;
		}

		private List<Suggestion> HistoryRows(string line, int budget)
		{
			List<Suggestion> list = new List<Suggestion>();
			if (budget <= 0)
			{
				return list;
			}
			foreach (string item in _history.Search(line.Trim()))
			{
				if (!string.Equals(item, line.Trim(), StringComparison.Ordinal))
				{
					list.Add(new Suggestion(SuggestionKind.History, item, "History", isVanilla: false, 0));
					if (list.Count >= budget)
					{
						break;
					}
				}
			}
			return list;
		}

		public CommandInfo Find(string word)
		{
			return InCatalogue(word) ?? InBuiltins(word);
		}

		public bool IsCommand(string word)
		{
			return InCatalogue(word) != null;
		}

		public bool IsKnown(string word)
		{
			return Find(word) != null;
		}

		private CommandInfo InCatalogue(string word)
		{
			if (string.IsNullOrEmpty(word))
			{
				return null;
			}
			foreach (CommandInfo command in _catalogue.Commands)
			{
				if (string.Equals(command.Word, word, StringComparison.OrdinalIgnoreCase))
				{
					return command;
				}
			}
			return null;
		}

		private static CommandInfo InBuiltins(string word)
		{
			if (string.IsNullOrEmpty(word))
			{
				return null;
			}
			foreach (CommandInfo item in Builtins.Catalogue)
			{
				if (string.Equals(item.Word, word, StringComparison.OrdinalIgnoreCase))
				{
					return item;
				}
			}
			return null;
		}

		private static void Trim(List<Suggestion> rows, int max)
		{
			if (max < 0)
			{
				max = 0;
			}
			if (rows.Count > max)
			{
				rows.RemoveRange(max, rows.Count - max);
			}
		}

		private static string LastStatement(string line)
		{
			int num = -1;
			bool flag = false;
			for (int i = 0; i < line.Length; i++)
			{
				if (line[i] == '"')
				{
					flag = !flag;
				}
				else if (line[i] == ';' && !flag)
				{
					num = i;
				}
			}
			if (num >= 0)
			{
				return line.Substring(num + 1).TrimStart();
			}
			return line;
		}
	}
	public sealed class Transcript
	{
		public const int Kept = 2000;

		public const int Shown = 40;

		private readonly List<OutputLine> _lines = new List<OutputLine>();

		public IReadOnlyList<OutputLine> Lines => _lines;

		public int Count => _lines.Count;

		public void Add(OutputLine line)
		{
			_lines.Add(line);
			if (_lines.Count > 2256)
			{
				_lines.RemoveRange(0, _lines.Count - 2000);
			}
		}

		public void Add(IEnumerable<OutputLine> lines)
		{
			if (lines == null)
			{
				return;
			}
			foreach (OutputLine line in lines)
			{
				Add(line);
			}
		}

		public void Clear()
		{
			_lines.Clear();
		}

		public IEnumerable<OutputLine> Recent(int count)
		{
			int i = _lines.Count - 1;
			while (i >= 0 && count > 0)
			{
				yield return _lines[i];
				i--;
				count--;
			}
		}

		public List<OutputLine> Window()
		{
			int num = Math.Max(0, _lines.Count - 40);
			return _lines.GetRange(num, _lines.Count - num);
		}
	}
	public sealed class Usage
	{
		private const string FileName = "usage.txt";

		private const int CountedArgIndex = 0;

		private readonly Dictionary<string, int> _commands = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);

		private readonly Dictionary<string, int> _args = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);

		private bool _dirty;

		public void Record(IReadOnlyList<string> tokens)
		{
			if (tokens == null || tokens.Count == 0)
			{
				return;
			}
			string text = tokens[0];
			if (!string.IsNullOrWhiteSpace(text))
			{
				Bump(_commands, text);
				if (tokens.Count > 1)
				{
					Bump(_args, Key(text, tokens[1]));
				}
				_dirty = true;
			}
		}

		public int CommandCount(string word)
		{
			if (word == null || !_commands.TryGetValue(word, out var value))
			{
				return 0;
			}
			return value;
		}

		public int ArgCount(string command, string value)
		{
			if (command == null || value == null || !_args.TryGetValue(Key(command, value), out var value2))
			{
				return 0;
			}
			return value2;
		}

		public static void Order(List<Suggestion> candidates, Func<Suggestion, int> countOf)
		{
			if (candidates == null || candidates.Count < 2)
			{
				return;
			}
			candidates.Sort(delegate(Suggestion a, Suggestion b)
			{
				if (a.Score != b.Score)
				{
					return b.Score.CompareTo(a.Score);
				}
				int num = countOf(b).CompareTo(countOf(a));
				return (num != 0) ? num : string.Compare(a.Value, b.Value, StringComparison.OrdinalIgnoreCase);
			});
		}

		public static List<Suggestion> GroupBySupplier(List<Suggestion> ordered)
		{
			if (ordered == null || ordered.Count < 2)
			{
				return ordered ?? new List<Suggestion>();
			}
			List<Suggestion> list = new List<Suggestion>();
			List<Suggestion> list2 = new List<Suggestion>();
			SortedDictionary<string, List<Suggestion>> sortedDictionary = new SortedDictionary<string, List<Suggestion>>(StringComparer.OrdinalIgnoreCase);
			foreach (Suggestion item in ordered)
			{
				if (item.IsVanilla)
				{
					list.Add(item);
					continue;
				}
				if (string.IsNullOrEmpty(item.Source))
				{
					list2.Add(item);
					continue;
				}
				if (!sortedDictionary.TryGetValue(item.Source, out var value))
				{
					value = (sortedDictionary[item.Source] = new List<Suggestion>());
				}
				value.Add(item);
			}
			List<Suggestion> list4 = new List<Suggestion>(ordered.Count);
			list4.AddRange(list);
			foreach (KeyValuePair<string, List<Suggestion>> item2 in sortedDictionary)
			{
				list4.AddRange(item2.Value);
			}
			list4.AddRange(list2);
			return list4;
		}

		public void Load(IStore store)
		{
			_commands.Clear();
			_args.Clear();
			_dirty = false;
			string text = store?.Read(StoreScope.Save, "usage.txt");
			if (string.IsNullOrEmpty(text))
			{
				return;
			}
			string[] array = text.Split('\n');
			for (int i = 0; i < array.Length; i++)
			{
				string text2 = array[i].Trim('\r', ' ');
				if (text2.Length == 0)
				{
					continue;
				}
				int num = text2.LastIndexOf('\t');
				if (num > 0 && int.TryParse(text2.Substring(num + 1), out var result) && result > 0)
				{
					string text3 = text2.Substring(0, num);
					if (text3.IndexOf(' ') > 0)
					{
						_args[text3] = result;
					}
					else
					{
						_commands[text3] = result;
					}
				}
			}
		}

		public void Save(IStore store)
		{
			if (!_dirty || store == null)
			{
				return;
			}
			StringBuilder stringBuilder = new StringBuilder();
			foreach (KeyValuePair<string, int> command in _commands)
			{
				stringBuilder.Append(command.Key).Append('\t').Append(command.Value)
					.Append('\n');
			}
			foreach (KeyValuePair<string, int> arg in _args)
			{
				stringBuilder.Append(arg.Key).Append('\t').Append(arg.Value)
					.Append('\n');
			}
			store.Write(StoreScope.Save, "usage.txt", stringBuilder.ToString());
			_dirty = false;
		}

		private static string Key(string command, string value)
		{
			return command + " " + value;
		}

		private static void Bump(Dictionary<string, int> into, string key)
		{
			into.TryGetValue(key, out var value);
			into[key] = value + 1;
		}
	}
	public static class UsageExample
	{
		private static readonly Dictionary<string, string> Known = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
		{
			["give"] = "give <item> [quantity]",
			["packageproduct"] = "packageproduct <packaging>",
			["setdiscovered"] = "setdiscovered <product>",
			["teleport"] = "teleport <location|property|npc>",
			["spawnvehicle"] = "spawnvehicle <vehicle>",
			["setowned"] = "setowned <property|business>",
			["setunlocked"] = "setunlocked <npc>",
			["setrelationship"] = "setrelationship <npc> <value>",
			["addemployee"] = "addemployee <type> <property>",
			["setquality"] = "setquality <quality>",
			["setregionunlocked"] = "setregionunlocked <region>",
			["setqueststate"] = "setqueststate <quest> <state>",
			["setquestentrystate"] = "setquestentrystate <quest> <entry> <state>",
			["setvar"] = "setvar <variable> <value>",
			["bind"] = "bind <key> <command>",
			["unbind"] = "unbind <key>",
			["setpoliceignoreplayers"] = "setpoliceignoreplayers <true|false>",
			["setweather"] = "setweather <weather>",
			["triggerlightning"] = "triggerlightning [npc|player]",
			["settime"] = "settime <hhmm>",
			["changecash"] = "changecash <amount>",
			["changebalance"] = "changebalance <amount>",
			["addxp"] = "addxp <amount>",
			["setstaminareserve"] = "setstaminareserve <amount>",
			["setmovespeed"] = "setmovespeed <multiplier>",
			["setjumpforce"] = "setjumpforce <multiplier>",
			["setemotion"] = "setemotion <emotion>",
			["disable"] = "disable <label>",
			["enable"] = "enable <label>",
			["disablenpcasset"] = "disablenpcasset <asset|all>",
			["playcutscene"] = "playcutscene <cutscene>",
			["sethealth"] = "sethealth <amount>",
			["setquantity"] = "setquantity <amount>",
			["settimescale"] = "settimescale <scale>",
			["setgravitymultiplier"] = "setgravitymultiplier <multiplier>",
			["setdayduration"] = "setdayduration <minutes>",
			["setlawintensity"] = "setlawintensity <intensity>"
		};

		private static readonly HashSet<string> OptionalLast = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "give", "triggerlightning" };

		public static string Signature(string word, string example)
		{
			if (string.IsNullOrEmpty(word))
			{
				return "";
			}
			if (Known.TryGetValue(word, out var value))
			{
				return value;
			}
			if (!string.IsNullOrEmpty(example) && example.IndexOf('<') >= 0)
			{
				return Tighten(Collapse(FirstExample(example)));
			}
			return FromExample(word, example);
		}

		public static IEnumerable<string> Literals(string example, string signature, int argIndex)
		{
			string text = ((!string.IsNullOrEmpty(signature)) ? signature : example);
			if (string.IsNullOrEmpty(text) || argIndex < 0)
			{
				yield break;
			}
			List<string> list = Split(FirstExample(text));
			int num = argIndex + 1;
			if (num >= list.Count)
			{
				yield break;
			}
			string text2 = list[num].Trim('<', '>', '[', ']');
			if (text2.IndexOf('|') < 0)
			{
				yield break;
			}
			string[] array = text2.Split('|');
			for (int i = 0; i < array.Length; i++)
			{
				string text3 = array[i].Trim();
				if (text3.Length > 0)
				{
					yield return text3;
				}
			}
		}

		public static int ArgumentCount(string signature)
		{
			if (string.IsNullOrEmpty(signature))
			{
				return 0;
			}
			return Math.Max(0, Split(signature).Count - 1);
		}

		private static string FromExample(string word, string example)
		{
			List<string> list = Split(FirstExample(example));
			if (list.Count <= 1)
			{
				return word.ToLowerInvariant();
			}
			StringBuilder stringBuilder = new StringBuilder(word.ToLowerInvariant());
			for (int i = 1; i < list.Count; i++)
			{
				bool flag = i == list.Count - 1;
				bool flag2 = (flag && list.Count >= 3 && (LooksNumeric(list[i]) || LooksBoolean(list[i]))) || (flag && OptionalLast.Contains(word) && list.Count >= 3);
				st