Decompiled source of Quickstart v0.2.1

Quickstart.dll

Decompiled an hour ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using CG.GameLoopStateMachine;
using CG.GameLoopStateMachine.GameStates;
using CG.Profile;
using Client.Utils;
using Gameplay.Hub;
using Gameplay.Quests;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Photon.Pun;
using PlatformAbstractionLayer;
using ResourceAssets;
using ToolClasses;
using UI;
using UI.Core.Audio;
using UnityEngine;
using UnityEngine.UIElements;
using VoidManager;
using VoidManager.CustomGUI;
using VoidManager.MPModChecks;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("Quickstart")]
[assembly: AssemblyDescription("Adds a Quickstart option to the main menu that drops you straight into a solo pilgrimage on a chosen ship loadout, skipping the hub entirely.")]
[assembly: AssemblyProduct("Quickstart")]
[assembly: AssemblyCompany("Jack")]
[assembly: AssemblyFileVersion("0.2.1.0")]
[assembly: ComVisible(false)]
[assembly: AssemblyMetadata("ModCategories", "Quality of Life, Client-Side")]
[assembly: AssemblyMetadata("MPType", "Client")]
[assembly: AssemblyMetadata("ProgressionFlag", "enabled")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("0.2.1.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;
		}
	}
}
internal static class MenuKit
{
	private static readonly Dictionary<string, bool> foldouts = new Dictionary<string, bool>();

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

	public static void Header(string text)
	{
		GUILayout.Space(10f);
		GUILayout.Label("<b>" + text + "</b>", Array.Empty<GUILayoutOption>());
	}

	public static void Rule()
	{
		//IL_0023: Unknown result type (might be due to invalid IL or missing references)
		//IL_0028: Unknown result type (might be due to invalid IL or missing references)
		//IL_002d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0042: Unknown result type (might be due to invalid IL or missing references)
		//IL_0056: Unknown result type (might be due to invalid IL or missing references)
		GUILayout.Space(6f);
		Rect rect = GUILayoutUtility.GetRect(1f, 1f, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) });
		Color color = GUI.color;
		GUI.color = new Color(1f, 1f, 1f, 0.25f);
		GUI.DrawTexture(rect, (Texture)(object)Texture2D.whiteTexture);
		GUI.color = color;
		GUILayout.Space(6f);
	}

	public static bool Foldout(string id, string label)
	{
		foldouts.TryGetValue(id, out var value);
		if (GUILayout.Button((value ? "[-] " : "[+] ") + label, Array.Empty<GUILayoutOption>()))
		{
			value = !value;
			foldouts[id] = value;
		}
		return value;
	}

	public static void DevZone(string id, Action body)
	{
		if (!Configs.IsDebugMode)
		{
			return;
		}
		GUILayout.Space(12f);
		Rule();
		if (!Foldout(id + ".dev", "Developer tools"))
		{
			return;
		}
		GUILayout.BeginVertical(GUI.skin.box, Array.Empty<GUILayoutOption>());
		try
		{
			body();
		}
		catch (Exception ex)
		{
			GUILayout.Label("Dev section failed: " + ex.Message + " (see log)", Array.Empty<GUILayoutOption>());
			if (loggedFailures.Add(id))
			{
				Debug.LogError((object)("[MenuKit] dev zone '" + id + "' threw: " + ex));
			}
		}
		finally
		{
			GUILayout.EndVertical();
		}
	}
}
internal static class ModFlagPolicy
{
	internal const bool FlagsSession = true;

	internal const MultiplayerType MPType = (MultiplayerType)20;

	internal const SessionModificationEffect NetworkSpawnedEffect = (SessionModificationEffect)2;

	internal const SessionModificationEffect RequiredEffect = (SessionModificationEffect)94;

	internal const string BuildTag = "enabled";
}
internal static class StatContrib
{
	internal enum Kind
	{
		Additive,
		Multiplicative,
		Override
	}

	private const string SlotKey = "jack.voidcrew.statcontrib.v1";

	internal static ManualLogSource Log;

	private static object[] Root()
	{
		object[] array = AppDomain.CurrentDomain.GetData("jack.voidcrew.statcontrib.v1") as object[];
		if (array == null)
		{
			array = new object[2]
			{
				new object(),
				new Dictionary<object, object>()
			};
			AppDomain.CurrentDomain.SetData("jack.voidcrew.statcontrib.v1", array);
		}
		return array;
	}

	internal static bool Set(object target, string field, string ownerId, Func<float> getter, Action<float> setter, Kind kind, float value, Func<bool> alive = null)
	{
		if (target == null || string.IsNullOrEmpty(field) || string.IsNullOrEmpty(ownerId) || getter == null || setter == null)
		{
			Error("StatContrib.Set called with a null argument (field '" + field + "', owner '" + ownerId + "') - ignored.");
			return false;
		}
		if (float.IsNaN(value) || float.IsInfinity(value))
		{
			Error("StatContrib.Set: owner '" + ownerId + "' offered a non-finite value for '" + field + "' - ignored.");
			return false;
		}
		try
		{
			object[] array = Root();
			lock (array[0])
			{
				Dictionary<object, object> dictionary = (Dictionary<object, object>)array[1];
				object value2;
				Dictionary<string, object> dictionary2 = (Dictionary<string, object>)(dictionary.TryGetValue(target, out value2) ? ((Dictionary<string, object>)value2) : (dictionary[target] = new Dictionary<string, object>()));
				object[] array2;
				if (dictionary2.TryGetValue(field, out value2))
				{
					array2 = (object[])value2;
				}
				else
				{
					array2 = (object[])(dictionary2[field] = new object[4]
					{
						getter(),
						getter,
						setter,
						new List<object[]>()
					});
					Info("captured base " + ((float)array2[0]).ToString("0.####") + " for " + Describe(target, field));
				}
				List<object[]> list = (List<object[]>)array2[3];
				object[] array3 = null;
				for (int i = 0; i < list.Count; i++)
				{
					if ((string)list[i][0] == ownerId)
					{
						array3 = list[i];
						break;
					}
				}
				if (array3 == null)
				{
					array3 = new object[4] { ownerId, null, null, null };
					list.Add(array3);
				}
				array3[1] = alive;
				array3[2] = (int)kind;
				array3[3] = value;
				Recompute(dictionary2, field, array2);
				SweepDeadOwners(dictionary, target, field);
			}
			return true;
		}
		catch (Exception ex)
		{
			Error("StatContrib.Set('" + field + "', '" + ownerId + "') threw: " + ex);
			return false;
		}
	}

	internal static void Remove(object target, string field, string ownerId)
	{
		if (target == null || string.IsNullOrEmpty(field) || string.IsNullOrEmpty(ownerId))
		{
			return;
		}
		try
		{
			object[] array = Root();
			lock (array[0])
			{
				Dictionary<object, object> dictionary = (Dictionary<object, object>)array[1];
				if (!dictionary.TryGetValue(target, out var value))
				{
					return;
				}
				Dictionary<string, object> dictionary2 = (Dictionary<string, object>)value;
				if (!dictionary2.TryGetValue(field, out value))
				{
					return;
				}
				object[] array2 = (object[])value;
				List<object[]> list = (List<object[]>)array2[3];
				bool flag = false;
				for (int num = list.Count - 1; num >= 0; num--)
				{
					if ((string)list[num][0] == ownerId)
					{
						list.RemoveAt(num);
						flag = true;
					}
				}
				if (flag)
				{
					Recompute(dictionary2, field, array2);
					if (!dictionary2.ContainsKey(field) && dictionary2.Count == 0)
					{
						dictionary.Remove(target);
					}
					SweepDeadOwners(dictionary, target, field);
				}
			}
		}
		catch (Exception ex)
		{
			Error("StatContrib.Remove('" + field + "', '" + ownerId + "') threw: " + ex);
		}
	}

	internal static string DescribeField(object target, string field)
	{
		try
		{
			object[] array = Root();
			lock (array[0])
			{
				Dictionary<object, object> dictionary = (Dictionary<object, object>)array[1];
				if (target == null || !dictionary.TryGetValue(target, out var value))
				{
					return null;
				}
				if (!((Dictionary<string, object>)value).TryGetValue(field, out value))
				{
					return null;
				}
				object[] array2 = (object[])value;
				List<object[]> list = (List<object[]>)array2[3];
				string text = field + ": base " + ((float)array2[0]).ToString("0.####");
				for (int i = 0; i < list.Count; i++)
				{
					text = text + ", " + (string)list[i][0] + " " + ((Kind)(int)list[i][2]/*cast due to .constrained prefix*/).ToString() + " " + ((float)list[i][3]).ToString("0.####");
				}
				return text;
			}
		}
		catch
		{
			return null;
		}
	}

	private static void Recompute(Dictionary<string, object> byField, string field, object[] entry)
	{
		List<object[]> list = (List<object[]>)entry[3];
		for (int num = list.Count - 1; num >= 0; num--)
		{
			Func<bool> func = list[num][1] as Func<bool>;
			bool flag = true;
			if (func != null)
			{
				try
				{
					flag = func();
				}
				catch
				{
					flag = false;
				}
			}
			if (!flag)
			{
				Info("dropping contribution '" + (string)list[num][0] + "' to '" + field + "' - its owner is gone.");
				list.RemoveAt(num);
			}
		}
		float num2 = (float)entry[0];
		Action<float> setter = (Action<float>)entry[2];
		if (list.Count == 0)
		{
			byField.Remove(field);
			TryWrite(setter, num2, field, "base restore");
			return;
		}
		float num3 = num2;
		float num4 = 1f;
		object[] array = null;
		for (int i = 0; i < list.Count; i++)
		{
			float num5 = (float)list[i][3];
			switch ((Kind)(int)list[i][2])
			{
			case Kind.Additive:
				num3 += num5;
				break;
			case Kind.Multiplicative:
				num4 *= num5;
				break;
			case Kind.Override:
				array = list[i];
				break;
			}
		}
		float num6;
		if (array != null)
		{
			num6 = (float)array[3];
			if (list.Count > 1)
			{
				Info("override '" + (string)array[0] + "' wins '" + field + "' over " + (list.Count - 1) + " other contribution(s).");
			}
		}
		else
		{
			num6 = num3 * num4;
		}
		if (float.IsNaN(num6) || float.IsInfinity(num6))
		{
			Error("composed value for '" + field + "' is non-finite; writing the base back instead. " + DescribeEntry(field, entry));
			num6 = num2;
		}
		TryWrite(setter, num6, field, "recompute");
	}

	private static void SweepDeadOwners(Dictionary<object, object> entries, object exceptTarget, string exceptField)
	{
		List<object> list = null;
		foreach (KeyValuePair<object, object> entry in entries)
		{
			Dictionary<string, object> dictionary = (Dictionary<string, object>)entry.Value;
			List<string> list2 = null;
			foreach (KeyValuePair<string, object> item in dictionary)
			{
				if (entry.Key == exceptTarget && item.Key == exceptField)
				{
					continue;
				}
				List<object[]> list3 = (List<object[]>)((object[])item.Value)[3];
				for (int i = 0; i < list3.Count; i++)
				{
					Func<bool> func = list3[i][1] as Func<bool>;
					bool flag = true;
					if (func != null)
					{
						try
						{
							flag = func();
						}
						catch
						{
							flag = false;
						}
					}
					if (!flag)
					{
						List<string> obj2 = list2 ?? new List<string>();
						list2 = obj2;
						obj2.Add(item.Key);
						break;
					}
				}
			}
			if (list2 != null)
			{
				for (int j = 0; j < list2.Count; j++)
				{
					Recompute(dictionary, list2[j], (object[])dictionary[list2[j]]);
				}
			}
			if (dictionary.Count == 0)
			{
				List<object> obj3 = list ?? new List<object>();
				list = obj3;
				obj3.Add(entry.Key);
			}
		}
		if (list != null)
		{
			for (int k = 0; k < list.Count; k++)
			{
				entries.Remove(list[k]);
			}
		}
	}

	private static void TryWrite(Action<float> setter, float value, string field, string why)
	{
		try
		{
			setter(value);
		}
		catch (Exception ex)
		{
			Info("write to '" + field + "' (" + why + ") failed: " + ex.Message);
		}
	}

	private static string Describe(object target, string field)
	{
		string text;
		try
		{
			text = target.GetType().Name;
		}
		catch
		{
			text = "<target>";
		}
		return text + "." + field;
	}

	private static string DescribeEntry(string field, object[] entry)
	{
		List<object[]> list = (List<object[]>)entry[3];
		string text = "base " + ((float)entry[0]).ToString("0.####") + ";";
		for (int i = 0; i < list.Count; i++)
		{
			text = text + " " + (string)list[i][0] + "=" + ((Kind)(int)list[i][2]/*cast due to .constrained prefix*/).ToString() + ":" + ((float)list[i][3]).ToString("0.####");
		}
		return text;
	}

	private static void Info(string m)
	{
		if (Log != null)
		{
			Log.LogInfo((object)("StatContrib: " + m));
		}
	}

	private static void Error(string m)
	{
		if (Log != null)
		{
			Log.LogError((object)("StatContrib: " + m));
		}
	}
}
internal interface ITuningKnob
{
	string Name { get; }

	string Help { get; }

	bool IsInert { get; }

	void WriteJson(JObject o);

	void ReadJson(JObject o);

	string Describe();

	void DrawMenuRow();

	string BakeSnippet();
}
internal sealed class TuningFile
{
	private readonly string _guid;

	private readonly string _tag;

	private readonly string _readme;

	private readonly ManualLogSource _log;

	private readonly List<ITuningKnob> _order = new List<ITuningKnob>();

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

	private readonly List<Action> _reloadHandlers = new List<Action>();

	private DateTime _lastWrite = DateTime.MinValue;

	private JObject _baseline;

	private JObject _previousBaseline;

	internal string LastBakePath = "<bake not run>";

	private string _menuSignature;

	internal string Status { get; private set; } = "not loaded";

	internal string Path { get; private set; }

	internal string BaselinePath { get; private set; }

	internal IList<ITuningKnob> Knobs => _order;

	internal bool AllInert
	{
		get
		{
			for (int i = 0; i < _order.Count; i++)
			{
				if (!_order[i].IsInert)
				{
					return false;
				}
			}
			return true;
		}
	}

	internal TuningFile(string pluginGuid, string logTag, string readme, ManualLogSource log)
	{
		_guid = pluginGuid;
		_tag = logTag;
		_readme = readme;
		_log = log;
	}

	internal T Add<T>(T knob) where T : ITuningKnob
	{
		if (knob == null)
		{
			return default(T);
		}
		if (_byName.ContainsKey(knob.Name))
		{
			Warn("duplicate tuning knob '" + knob.Name + "' ignored. Two declarations of one name means one of them is silently dead.");
			return knob;
		}
		_byName[knob.Name] = knob;
		_order.Add(knob);
		return knob;
	}

	internal ITuningKnob Find(string name)
	{
		if (!_byName.TryGetValue(name, out var value))
		{
			return null;
		}
		return value;
	}

	internal void OnReload(Action handler)
	{
		if (handler != null && !_reloadHandlers.Contains(handler))
		{
			_reloadHandlers.Add(handler);
		}
	}

	internal void Initialise(string configDir)
	{
		try
		{
			Path = System.IO.Path.Combine(configDir, _guid + ".tuning.json");
			BaselinePath = System.IO.Path.Combine(configDir, _guid + ".tuning.baseline.json");
			_baseline = BuildKnobObject();
			_previousBaseline = ReadPreviousBaseline();
			WriteBaseline();
			if (!File.Exists(Path))
			{
				Save("created at the declared defaults");
			}
			else
			{
				Load();
			}
		}
		catch (Exception e)
		{
			Status = "FAILED to initialise: " + Brief(e);
			Error(Status);
		}
	}

	internal bool PollForChanges()
	{
		if (string.IsNullOrEmpty(Path))
		{
			return false;
		}
		try
		{
			if (!File.Exists(Path))
			{
				return false;
			}
			if (File.GetLastWriteTimeUtc(Path) <= _lastWrite)
			{
				return false;
			}
			Load();
			Info("tuning reloaded - " + Status);
			FireReloadHandlers();
			return true;
		}
		catch (Exception e)
		{
			Warn("tuning poll threw: " + Brief(e));
			return false;
		}
	}

	private void FireReloadHandlers()
	{
		for (int i = 0; i < _reloadHandlers.Count; i++)
		{
			try
			{
				_reloadHandlers[i]();
			}
			catch (Exception ex)
			{
				Error("a tuning re-apply handler threw: " + ex);
			}
		}
	}

	private void Load()
	{
		string text;
		try
		{
			text = File.ReadAllText(Path);
		}
		catch (Exception e)
		{
			Status = "could not read the file (" + Brief(e) + "); previous values stand";
			Error(Status);
			return;
		}
		_lastWrite = File.GetLastWriteTimeUtc(Path);
		JObject val;
		try
		{
			val = JObject.Parse(text);
		}
		catch (Exception e2)
		{
			Status = "MALFORMED tuning file (" + Brief(e2) + "); previous values stand";
			Error(Status + ". Fix " + Path + " - nothing was applied from it.");
			return;
		}
		JToken obj = val["knobs"];
		JObject val2 = (JObject)(object)((obj is JObject) ? obj : null);
		if (val2 == null)
		{
			Status = "tuning file has no 'knobs' object; every knob left at its declared default";
			Warn(Status);
			return;
		}
		int num = 0;
		int num2 = 0;
		int num3 = 0;
		int num4 = 0;
		foreach (KeyValuePair<string, JToken> item in val2)
		{
			if (!_byName.TryGetValue(item.Key, out var value))
			{
				num2++;
				Warn("tuning file names unknown knob '" + item.Key + "' - ignored. This is a typo or a rename, not a tuning value.");
				continue;
			}
			JToken value2 = item.Value;
			JObject val3 = (JObject)(object)((value2 is JObject) ? value2 : null);
			if (val3 == null)
			{
				continue;
			}
			if (FollowsNewDeclaration(item.Key, val3))
			{
				num4++;
				num++;
				continue;
			}
			try
			{
				value.ReadJson(val3);
			}
			catch (Exception e3)
			{
				Warn("knob '" + item.Key + "' could not be read: " + Brief(e3));
			}
			num++;
			if (!value.IsInert)
			{
				num3++;
			}
		}
		Status = num + " knob(s) read, " + num3 + " active" + ((num2 > 0) ? (", " + num2 + " unknown name(s) ignored") : "") + ((num4 > 0) ? (", " + num4 + " untouched knob(s) moved to this build's new declared value") : "");
		if (num3 == 0)
		{
			Status += " - every knob inert";
		}
		int count = DriftReport().Count;
		if (count > 0)
		{
			Status = Status + "; " + count + " differ(s) from source";
		}
		if (num2 > 0 || num < _order.Count || num4 > 0)
		{
			string status = Status;
			Save("rewritten to match this version's knobs (" + (_order.Count - num) + " added, " + num2 + " stale name(s) dropped)");
			Status = status + "; file rewritten to match this version's knobs";
		}
	}

	private JObject ReadPreviousBaseline()
	{
		try
		{
			if (!File.Exists(BaselinePath))
			{
				return null;
			}
			JToken obj = JObject.Parse(File.ReadAllText(BaselinePath))["knobs"];
			return (JObject)(object)((obj is JObject) ? obj : null);
		}
		catch
		{
			return null;
		}
	}

	private bool FollowsNewDeclaration(string name, JObject fromFile)
	{
		if (_previousBaseline == null || _baseline == null)
		{
			return false;
		}
		JToken obj = _previousBaseline[name];
		JObject val = (JObject)(object)((obj is JObject) ? obj : null);
		JToken obj2 = _baseline[name];
		JObject val2 = (JObject)(object)((obj2 is JObject) ? obj2 : null);
		if (val == null || val2 == null)
		{
			return false;
		}
		if (JToken.DeepEquals((JToken)(object)val, (JToken)(object)val2))
		{
			return false;
		}
		return JToken.DeepEquals((JToken)(object)val, (JToken)(object)fromFile);
	}

	internal void Save(string why)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0006: Expected O, but got Unknown
		try
		{
			JObject val = new JObject();
			val["_readme"] = JToken.op_Implicit(_readme + "  The '_' field in each knob is a comment and is ignored. Saved changes are picked up within a second - no restart. This file is NOT shipped: values dialled in here reach nobody else until they are written back into the mod's source declarations.");
			val["knobs"] = (JToken)(object)BuildKnobObject();
			EnsureDir(Path);
			File.WriteAllText(Path, ((JToken)val).ToString((Formatting)1, Array.Empty<JsonConverter>()));
			_lastWrite = File.GetLastWriteTimeUtc(Path);
			Status = why;
			Info("tuning file " + why + " -> " + Path);
		}
		catch (Exception e)
		{
			Status = "could not write the tuning file: " + Brief(e);
			Error(Status);
		}
	}

	private JObject BuildKnobObject()
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0006: Expected O, but got Unknown
		//IL_0017: Unknown result type (might be due to invalid IL or missing references)
		//IL_001d: Expected O, but got Unknown
		JObject val = new JObject();
		for (int i = 0; i < _order.Count; i++)
		{
			ITuningKnob tuningKnob = _order[i];
			JObject val2 = new JObject();
			val2["_"] = JToken.op_Implicit(tuningKnob.Help);
			tuningKnob.WriteJson(val2);
			val[tuningKnob.Name] = (JToken)(object)val2;
		}
		return val;
	}

	private void WriteBaseline()
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0006: Expected O, but got Unknown
		try
		{
			JObject val = new JObject();
			val["_readme"] = JToken.op_Implicit("Generated every launch. These are the knob values as DECLARED IN SOURCE, which is what a build handed to anyone else runs at. Diff the sibling .tuning.json against this to see which dialled-in numbers would be lost by shipping. Do not edit; edit the source declarations and relaunch.");
			val["knobs"] = (JToken)(object)_baseline;
			EnsureDir(BaselinePath);
			File.WriteAllText(BaselinePath, ((JToken)val).ToString((Formatting)1, Array.Empty<JsonConverter>()));
		}
		catch (Exception e)
		{
			Warn("could not write the tuning baseline (" + Brief(e) + "). The release drift check has nothing to compare against for this mod.");
		}
	}

	internal List<string> DriftReport()
	{
		//IL_0024: Unknown result type (might be due to invalid IL or missing references)
		//IL_002a: Expected O, but got Unknown
		List<string> list = new List<string>();
		if (_baseline == null)
		{
			return list;
		}
		for (int i = 0; i < _order.Count; i++)
		{
			ITuningKnob tuningKnob = _order[i];
			JObject val = new JObject();
			tuningKnob.WriteJson(val);
			JToken obj = _baseline[tuningKnob.Name];
			JObject val2 = (JObject)(object)((obj is JObject) ? obj : null);
			if (val2 == null)
			{
				continue;
			}
			foreach (KeyValuePair<string, JToken> item in val)
			{
				JToken val3 = val2[item.Key];
				if (val3 != null && !JToken.DeepEquals(val3, item.Value))
				{
					list.Add(tuningKnob.Name + "." + item.Key + ": source " + ((object)val3)?.ToString() + " -> live " + (object)item.Value);
				}
			}
		}
		return list;
	}

	internal string WriteBakeSnippet(string outputDir)
	{
		try
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.AppendLine("// " + _tag + " - tuning bake, taken from the live tuning file.");
			stringBuilder.AppendLine("//");
			stringBuilder.AppendLine("// Paste over the knob declarations. After this the declared defaults");
			stringBuilder.AppendLine("// and the tuning file agree, the drift check is clean, and a build");
			stringBuilder.AppendLine("// handed to anyone else plays at the numbers you dialled in.");
			stringBuilder.AppendLine("//");
			stringBuilder.AppendLine("// Source : " + (Path ?? "<none>"));
			stringBuilder.AppendLine("// State  : " + Status);
			stringBuilder.AppendLine();
			for (int i = 0; i < _order.Count; i++)
			{
				stringBuilder.AppendLine(_order[i].BakeSnippet());
			}
			List<string> list = DriftReport();
			stringBuilder.AppendLine();
			if (list.Count == 0)
			{
				stringBuilder.AppendLine("// Nothing has drifted - source already matches the tuning file.");
			}
			else
			{
				stringBuilder.AppendLine("// Differs from source (" + list.Count + "):");
				for (int j = 0; j < list.Count; j++)
				{
					stringBuilder.AppendLine("//   " + list[j]);
				}
			}
			string text = System.IO.Path.Combine(outputDir, _guid + ".tuning.bake.cs.txt");
			EnsureDir(text);
			File.WriteAllText(text, stringBuilder.ToString());
			Info("tuning bake snippet -> " + text);
			return text;
		}
		catch (Exception ex)
		{
			Error("tuning bake snippet threw: " + ex);
			return "<failed: " + Brief(ex) + ">";
		}
	}

	internal void DrawMenu()
	{
		try
		{
			GUILayout.Label("Tuning : " + Status, Array.Empty<GUILayoutOption>());
			GUILayout.Label("  " + (Path ?? "<not initialised>"), Array.Empty<GUILayoutOption>());
			for (int i = 0; i < _order.Count; i++)
			{
				_order[i].DrawMenuRow();
			}
			string text = MenuSignature();
			if (_menuSignature == null)
			{
				_menuSignature = text;
			}
			else if (text != _menuSignature)
			{
				_menuSignature = text;
				FireReloadHandlers();
			}
			List<string> list = DriftReport();
			GUILayout.Space(6f);
			if (list.Count == 0)
			{
				GUILayout.Label("Source and tuning file agree - safe to package.", Array.Empty<GUILayoutOption>());
			}
			else
			{
				GUILayout.Label("NOT IN SOURCE (" + list.Count + ") - a build sent to anyone else", Array.Empty<GUILayoutOption>());
				GUILayout.Label("would run at the source values, not these:", Array.Empty<GUILayoutOption>());
				for (int j = 0; j < list.Count; j++)
				{
					GUILayout.Label("    " + list[j], Array.Empty<GUILayoutOption>());
				}
			}
			GUILayout.Space(4f);
			if (GUILayout.Button("Save these values to the tuning file", Array.Empty<GUILayoutOption>()))
			{
				Save("saved from the F5 menu");
			}
			if (GUILayout.Button("Reload the tuning file now", Array.Empty<GUILayoutOption>()))
			{
				_lastWrite = DateTime.MinValue;
				PollForChanges();
			}
			if (GUILayout.Button("Write the bake snippet (the release step)", Array.Empty<GUILayoutOption>()))
			{
				LastBakePath = WriteBakeSnippet(Paths.ConfigPath);
			}
			GUILayout.Label("  " + LastBakePath, Array.Empty<GUILayoutOption>());
		}
		catch (Exception e)
		{
			GUILayout.Label("Tuning menu failed to draw: " + Brief(e), Array.Empty<GUILayoutOption>());
		}
	}

	private string MenuSignature()
	{
		//IL_000a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0010: Expected O, but got Unknown
		StringBuilder stringBuilder = new StringBuilder();
		for (int i = 0; i < _order.Count; i++)
		{
			JObject val = new JObject();
			_order[i].WriteJson(val);
			stringBuilder.Append(((JToken)val).ToString((Formatting)0, Array.Empty<JsonConverter>()));
		}
		return stringBuilder.ToString();
	}

	private static void EnsureDir(string file)
	{
		string directoryName = System.IO.Path.GetDirectoryName(file);
		if (!string.IsNullOrEmpty(directoryName) && !Directory.Exists(directoryName))
		{
			Directory.CreateDirectory(directoryName);
		}
	}

	private static string Brief(Exception e)
	{
		if (e != null)
		{
			return e.GetType().Name + ": " + e.Message;
		}
		return "<null>";
	}

	private void Info(string m)
	{
		if (_log != null)
		{
			_log.LogInfo((object)(_tag + ": " + m));
		}
	}

	private void Warn(string m)
	{
		if (_log != null)
		{
			_log.LogWarning((object)(_tag + ": " + m));
		}
	}

	private void Error(string m)
	{
		if (_log != null)
		{
			_log.LogError((object)(_tag + ": " + m));
		}
	}

	internal static string F(float v)
	{
		return v.ToString("0.####", CultureInfo.InvariantCulture);
	}
}
internal sealed class TuningScalar : ITuningKnob
{
	public float Value;

	public readonly float Declared;

	public readonly float Min;

	public readonly float Max;

	public readonly float Sentinel;

	private readonly NumericField _field = new NumericField();

	public string Name { get; private set; }

	public string Help { get; private set; }

	public bool IsInert
	{
		get
		{
			if (!float.IsNaN(Sentinel) && Nearly(Value, Sentinel))
			{
				return true;
			}
			return Nearly(Value, Declared);
		}
	}

	public bool IsSentinel
	{
		get
		{
			if (!float.IsNaN(Sentinel))
			{
				return Nearly(Value, Sentinel);
			}
			return false;
		}
	}

	internal TuningScalar(string name, string help, float declared, float min, float max, float sentinel = float.NaN)
	{
		Name = name;
		Help = help;
		Value = declared;
		Declared = declared;
		Min = min;
		Max = max;
		Sentinel = sentinel;
	}

	public void WriteJson(JObject o)
	{
		o["value"] = JToken.op_Implicit(Value);
	}

	public void ReadJson(JObject o)
	{
		JToken val = o["value"];
		if (val == null)
		{
			return;
		}
		try
		{
			float num = Extensions.Value<float>((IEnumerable<JToken>)val);
			if (!float.IsNaN(num) && !float.IsInfinity(num))
			{
				Value = num;
			}
		}
		catch
		{
		}
	}

	public string Describe()
	{
		if (!IsSentinel)
		{
			return TuningFile.F(Value);
		}
		return "keep the game's own value";
	}

	public void DrawMenuRow()
	{
		GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
		GUILayout.Label(Name + "  " + Describe(), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(280f) });
		Value = _field.Draw(Value);
		Value = GUILayout.HorizontalSlider(Value, Min, Max, Array.Empty<GUILayoutOption>());
		GUILayout.EndHorizontal();
	}

	public string BakeSnippet()
	{
		string text = (float.IsNaN(Sentinel) ? "" : (", " + TuningFile.F(Sentinel) + "f"));
		return "Scalar(\"" + Name + "\", \"" + Help.Replace("\"", "\\\"") + "\", " + TuningFile.F(Value) + "f, " + TuningFile.F(Min) + "f, " + TuningFile.F(Max) + "f" + text + ");";
	}

	private static bool Nearly(float a, float b)
	{
		return Math.Abs(a - b) < 0.0001f;
	}
}
internal sealed class TuningString : ITuningKnob
{
	public string Value;

	public readonly string Declared;

	public string Name { get; private set; }

	public string Help { get; private set; }

	public bool HasValue => !string.IsNullOrEmpty(Value);

	public bool IsInert => string.Equals(Value, Declared, StringComparison.Ordinal);

	internal TuningString(string name, string help, string declared)
	{
		Name = name;
		Help = help;
		Value = declared ?? "";
		Declared = declared ?? "";
	}

	public void WriteJson(JObject o)
	{
		o["text"] = JToken.op_Implicit(Value ?? "");
	}

	public void ReadJson(JObject o)
	{
		JToken val = o["text"];
		if (val == null)
		{
			return;
		}
		try
		{
			string text = Extensions.Value<string>((IEnumerable<JToken>)val);
			Value = (text ?? "").Trim();
		}
		catch
		{
		}
	}

	public string Describe()
	{
		if (!HasValue)
		{
			return "the game's own choice";
		}
		return Value;
	}

	public void DrawMenuRow()
	{
		GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
		GUILayout.Label(Name, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(280f) });
		Value = GUILayout.TextField(Value ?? "", Array.Empty<GUILayoutOption>()) ?? "";
		GUILayout.EndHorizontal();
	}

	public string BakeSnippet()
	{
		return "Text(\"" + Name + "\", \"" + Help.Replace("\"", "\\\"") + "\", \"" + (Value ?? "").Replace("\\", "\\\\").Replace("\"", "\\\"") + "\");";
	}
}
internal sealed class NumericField
{
	private string _text;

	private float _synced = float.NaN;

	internal float Draw(float value, float width = 64f, bool integer = false)
	{
		if (_text == null || value != _synced)
		{
			_text = (integer ? ((int)Math.Round(value)).ToString(CultureInfo.InvariantCulture) : TuningFile.F(value));
			_synced = value;
		}
		string text = GUILayout.TextField(_text, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(width) });
		if (text == _text)
		{
			return value;
		}
		_text = text;
		if (!float.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
		{
			return value;
		}
		if (float.IsNaN(result) || float.IsInfinity(result))
		{
			return value;
		}
		if (integer)
		{
			result = (float)Math.Round(result);
		}
		_synced = result;
		return result;
	}
}
internal sealed class TuningToggle : ITuningKnob
{
	public bool Value;

	public readonly bool Declared;

	public string Name { get; private set; }

	public string Help { get; private set; }

	public bool IsInert => Value == Declared;

	internal TuningToggle(string name, string help, bool declared)
	{
		Name = name;
		Help = help;
		Value = declared;
		Declared = declared;
	}

	public void WriteJson(JObject o)
	{
		o["on"] = JToken.op_Implicit(Value);
	}

	public void ReadJson(JObject o)
	{
		JToken val = o["on"];
		if (val == null)
		{
			return;
		}
		try
		{
			Value = Extensions.Value<bool>((IEnumerable<JToken>)val);
		}
		catch
		{
		}
	}

	public string Describe()
	{
		if (!Value)
		{
			return "off";
		}
		return "on";
	}

	public void DrawMenuRow()
	{
		Value = GUILayout.Toggle(Value, "  " + Name + " - " + Help, Array.Empty<GUILayoutOption>());
	}

	public string BakeSnippet()
	{
		return "Toggle(\"" + Name + "\", \"" + Help.Replace("\"", "\\\"") + "\", " + (Value ? "true" : "false") + ");";
	}
}
namespace VoidCrewQuickstart
{
	internal static class MenuInjection
	{
		[HarmonyPatch(typeof(MainMenu), "StartAsTitleMenu")]
		internal static class Patch_MainMenu_StartAsTitleMenu
		{
			[HarmonyPostfix]
			private static void Postfix(MainMenu __instance)
			{
				try
				{
					Inject(Traverse.Create((object)__instance).Field("_mainmenuRoot").GetValue<VisualElement>());
				}
				catch (Exception ex)
				{
					Plugin.Log.LogError((object)("Quickstart: injection at StartAsTitleMenu failed: " + ex));
				}
			}
		}

		[HarmonyPatch(typeof(TitleMenu), "OnEnter")]
		internal static class Patch_TitleMenu_OnEnter
		{
			[HarmonyPostfix]
			private static void Postfix(TitleMenu __instance)
			{
				try
				{
					UIDocument value = Traverse.Create((object)__instance).Field("UIDoc").GetValue<UIDocument>();
					if (!((Object)(object)value == (Object)null))
					{
						Inject(value.rootVisualElement);
					}
				}
				catch (Exception ex)
				{
					Plugin.Log.LogError((object)("Quickstart: injection at TitleMenu.OnEnter failed: " + ex));
				}
			}
		}

		private const string PICKING_IGNORE = "picking-mode-setter-ignore";

		private static bool _loggedInject;

		private static MethodInfo[] _gameReadyChecks;

		private static bool _loggedReadyFailure;

		public static void Inject(VisualElement root)
		{
			if (root == null || !QuickstartConfig.Enabled.Value)
			{
				return;
			}
			Button val = UQueryExtensions.Q<Button>(root, "button_quickstart", (string)null);
			if (val != null)
			{
				MenuLayout.Apply(root, val);
				return;
			}
			Button val2 = UQueryExtensions.Q<Button>(root, "button_solo", (string)null);
			if (val2 == null)
			{
				Plugin.Log.LogWarning((object)"Quickstart: 'button_solo' not found in the title menu - the menu UXML changed. No button injected.");
				return;
			}
			VisualElement parent = ((VisualElement)val2).parent;
			if (parent == null)
			{
				Plugin.Log.LogWarning((object)"Quickstart: solo button has no parent; not injecting.");
				return;
			}
			Button btn = BuildButton(val2, QuickstartConfig.ButtonLabel.Value);
			int num = parent.IndexOf((VisualElement)(object)val2);
			if (!QuickstartConfig.PlaceAboveSoloPlay.Value)
			{
				num++;
			}
			parent.Insert(num, (VisualElement)(object)btn);
			try
			{
				UIAudioProvider.AddSounds((VisualElement)(object)btn, "button");
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Quickstart: could not attach menu sounds: " + ex.Message));
			}
			UItoolkitExtensionMethods.AddClickedEvent(btn, (Action)OnClicked);
			((VisualElement)btn).schedule.Execute((Action)delegate
			{
				UpdateAvailability(btn);
			}).Every(250L);
			UpdateAvailability(btn);
			MenuLayout.Apply(root, btn);
			if (!_loggedInject)
			{
				_loggedInject = true;
				Plugin.Log.LogInfo((object)("Quickstart: button injected " + (QuickstartConfig.PlaceAboveSoloPlay.Value ? "above" : "below") + " Solo Play."));
			}
		}

		private static Button BuildButton(Button template, string label)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Expected O, but got Unknown
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Expected O, but got Unknown
			Button val = new Button();
			((VisualElement)val).name = "button_quickstart";
			foreach (string @class in ((VisualElement)template).GetClasses())
			{
				((VisualElement)val).AddToClassList(@class);
			}
			((VisualElement)val).AddToClassList("picking-mode-setter-ignore");
			((VisualElement)val).pickingMode = (PickingMode)0;
			Label val2 = UQueryExtensions.Q<Label>((VisualElement)(object)template, (string)null, (string)null);
			if (val2 != null)
			{
				Label val3 = new Label(label);
				((VisualElement)val3).name = ((VisualElement)val2).name;
				foreach (string class2 in ((VisualElement)val2).GetClasses())
				{
					((VisualElement)val3).AddToClassList(class2);
				}
				((VisualElement)val3).AddToClassList("picking-mode-setter-ignore");
				((VisualElement)val3).pickingMode = (PickingMode)1;
				((VisualElement)val).Add((VisualElement)(object)val3);
			}
			else
			{
				((TextElement)val).text = label;
			}
			return val;
		}

		private static void UpdateAvailability(Button btn)
		{
			try
			{
				bool enabled = (((Focusable)btn).focusable = IsReadyToLaunch() && !QuickstartLauncher.InFlight);
				((VisualElement)btn).SetEnabled(enabled);
			}
			catch
			{
			}
		}

		internal static bool IsReadyToLaunch()
		{
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Invalid comparison between Unknown and I4
			try
			{
				if (_gameReadyChecks == null)
				{
					ResolveReadyChecks();
				}
				if (_gameReadyChecks != null && _gameReadyChecks.Length == 3)
				{
					bool num = (bool)_gameReadyChecks[0].Invoke(null, null);
					bool flag = (bool)_gameReadyChecks[1].Invoke(null, null);
					bool flag2 = (bool)_gameReadyChecks[2].Invoke(null, null);
					return num && flag && !flag2 && VoiceReadiness.IsReady();
				}
				return (PhotonNetwork.OfflineMode || PhotonNetwork.InLobby || (int)PhotonNetwork.NetworkClientState == 4) && VoiceReadiness.IsReady();
			}
			catch (Exception ex)
			{
				if (!_loggedReadyFailure)
				{
					_loggedReadyFailure = true;
					Plugin.Log.LogWarning((object)("Quickstart: readiness check failed, treating the button as available: " + ex.Message));
				}
				return true;
			}
		}

		private static void ResolveReadyChecks()
		{
			MethodInfo methodInfo = AccessTools.Method(typeof(MainMenu), "IsConnectedToLobby", (Type[])null, (Type[])null);
			MethodInfo methodInfo2 = AccessTools.Method(typeof(MainMenu), "IsConnectedToUnityCloud", (Type[])null, (Type[])null);
			MethodInfo methodInfo3 = AccessTools.Method(typeof(MainMenu), "IsLocalProfileBeingReset", (Type[])null, (Type[])null);
			if (methodInfo == null || methodInfo2 == null || methodInfo3 == null)
			{
				Plugin.Log.LogWarning((object)"Quickstart: MainMenu's readiness helpers moved; falling back to a Photon-only check.");
				_gameReadyChecks = new MethodInfo[0];
				return;
			}
			_gameReadyChecks = new MethodInfo[3] { methodInfo, methodInfo2, methodInfo3 };
		}

		private static void OnClicked()
		{
			try
			{
				QuickstartLauncher.Launch();
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Quickstart: launch threw: " + ex));
				QuickstartLauncher.Status = "failed: " + ex.Message;
			}
		}
	}
	internal static class MenuLayout
	{
		private sealed class ButtonMetrics
		{
			public bool Captured;

			public float BaseMarginTop;

			public float BaseMarginBottom;
		}

		internal static readonly int TUTORIAL_HIDE_RANK = 30;

		internal static readonly bool APPLY_SHIFT = true;

		internal static readonly float STACK_RECENTRE_FACTOR = 0.5f;

		internal static readonly float EXTRA_SHIFT_UP_PX = 0f;

		internal static readonly float INJECTED_BUTTON_MARGIN_SCALE = 1f;

		internal static readonly string SHIFT_TARGET_NAME = "";

		internal static string Status = "not applied yet";

		internal static float MeasuredSlotPx = -1f;

		internal static float AppliedShiftPx;

		private static bool _loggedApply;

		private static IVisualElementScheduledItem _shiftItem;

		internal const string LogbookFinding = "The Logbook cannot be opened from the title menu. MainMenu.OnLogbookButtonClicked is wired only in StartAsEscapeMenu (button_logbook), and its body is ClientGame.Current.ModelEventBus.OnLocalToggleLogbookRequest.Publish() plus Singleton<ToggleInGameMenu>.Instance.CloseFromLogBook(). ClientGame.Current is assigned in ClientGame.Awake, which is a session-scene object, so it is null in the title menu. The receiving end is worse: UI.Codex.LogBookManager subscribes to that channel in Start via the same unguarded ClientGame.Current, and its Toggle() and SetTerminalState() dereference LocalPlayer.I - it exists only inside a run. So the prescribed fallback applies: hide the Tutorial button at max rank and leave no gap.";

		public static void Apply(VisualElement root, Button injected)
		{
			bool tutorialHidden = ApplyProgressionBranch(root);
			ApplyShift(root, injected, tutorialHidden);
		}

		private static bool ApplyProgressionBranch(VisualElement root)
		{
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			Button val = null;
			try
			{
				val = UQueryExtensions.Q<Button>(root, "button_awakening", (string)null);
			}
			catch
			{
			}
			if (val == null)
			{
				Status = "'button_awakening' not found - progression branch skipped";
				return false;
			}
			if (!TryGetRank(out var rank, out var why))
			{
				Status = "rank unreadable (" + why + ") - Tutorial button left alone";
				return false;
			}
			bool flag = rank >= TUTORIAL_HIDE_RANK;
			try
			{
				if (flag)
				{
					((VisualElement)val).style.display = StyleEnum<DisplayStyle>.op_Implicit((DisplayStyle)1);
					((VisualElement)val).SetEnabled(false);
					((Focusable)val).focusable = false;
				}
				else
				{
					((VisualElement)val).style.display = StyleEnum<DisplayStyle>.op_Implicit((StyleKeyword)1);
					((VisualElement)val).SetEnabled(true);
				}
			}
			catch (Exception ex)
			{
				Status = "could not set the Tutorial button's display: " + ex.Message;
				Plugin.Log.LogWarning((object)("Quickstart: " + Status));
				return false;
			}
			string[] obj2 = new string[7]
			{
				"rank ",
				rank.ToString(),
				" -> Tutorial button ",
				flag ? "hidden" : "shown",
				" (threshold ",
				null,
				null
			};
			int tUTORIAL_HIDE_RANK = TUTORIAL_HIDE_RANK;
			obj2[5] = tUTORIAL_HIDE_RANK.ToString();
			obj2[6] = ")";
			Status = string.Concat(obj2);
			return flag;
		}

		internal static bool TryGetRank(out int rank, out string why)
		{
			rank = -1;
			why = null;
			try
			{
				PlayerProfile instance = PlayerProfile.Instance;
				if (instance == null)
				{
					why = "PlayerProfile.Instance is null";
					return false;
				}
				IPlayerProfileData profile = instance.Profile;
				if (profile == null)
				{
					why = "PlayerProfile.Instance.Profile is null";
					return false;
				}
				rank = profile.Rank;
				return true;
			}
			catch (Exception ex)
			{
				why = ex.GetType().Name + ": " + ex.Message;
				return false;
			}
		}

		private static void ApplyShift(VisualElement root, Button injected, bool tutorialHidden)
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			if (injected == null)
			{
				return;
			}
			VisualElement val = ResolveShiftTarget(root, injected);
			if (val == null)
			{
				return;
			}
			try
			{
				val.style.translate = StyleTranslate.op_Implicit(new Translate(Length.op_Implicit(0f), Length.op_Implicit(0f)));
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Quickstart: could not reset the menu translate: " + ex.Message));
				return;
			}
			AppliedShiftPx = 0f;
			if (APPLY_SHIFT)
			{
				int num = 1 - (tutorialHidden ? 1 : 0);
				if (num == 0 && EXTRA_SHIFT_UP_PX == 0f)
				{
					MeasuredSlotPx = -1f;
				}
				else
				{
					ScheduleMeasureAndShift(val, injected, num);
				}
			}
		}

		private static VisualElement ResolveShiftTarget(VisualElement root, Button injected)
		{
			if (!string.IsNullOrEmpty(SHIFT_TARGET_NAME))
			{
				VisualElement val = null;
				try
				{
					val = UQueryExtensions.Q<VisualElement>(root, SHIFT_TARGET_NAME, (string)null);
				}
				catch
				{
				}
				if (val != null)
				{
					return val;
				}
				Plugin.Log.LogWarning((object)("Quickstart: SHIFT_TARGET_NAME '" + SHIFT_TARGET_NAME + "' is not in the title menu; using the button's parent."));
			}
			return ((VisualElement)injected).parent;
		}

		private static void ScheduleMeasureAndShift(VisualElement stack, Button injected, int netAdded)
		{
			Pause(_shiftItem);
			int[] attempts = new int[1];
			IVisualElementScheduledItem[] item = (IVisualElementScheduledItem[])(object)new IVisualElementScheduledItem[1];
			item[0] = ((VisualElement)injected).schedule.Execute((Action)delegate
			{
				//IL_0017: Unknown result type (might be due to invalid IL or missing references)
				//IL_001c: Unknown result type (might be due to invalid IL or missing references)
				//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
				//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
				//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
				//IL_00da: Unknown result type (might be due to invalid IL or missing references)
				try
				{
					attempts[0]++;
					Rect layout = ((VisualElement)injected).layout;
					float height = ((Rect)(ref layout)).height;
					if (float.IsNaN(height) || height <= 0f)
					{
						if (attempts[0] >= 40)
						{
							Pause(item[0]);
							Plugin.Log.LogWarning((object)"Quickstart: the injected button never resolved a height; menu left unshifted.");
						}
					}
					else
					{
						ButtonMetrics buttonMetrics = CaptureMetrics(injected);
						ApplyMarginScale(injected, buttonMetrics);
						float num = (MeasuredSlotPx = height + buttonMetrics.BaseMarginTop * INJECTED_BUTTON_MARGIN_SCALE + buttonMetrics.BaseMarginBottom * INJECTED_BUTTON_MARGIN_SCALE);
						float num2 = (AppliedShiftPx = STACK_RECENTRE_FACTOR * num * (float)netAdded + EXTRA_SHIFT_UP_PX);
						stack.style.translate = StyleTranslate.op_Implicit(new Translate(Length.op_Implicit(0f), Length.op_Implicit(0f - num2)));
						Pause(item[0]);
						if (!_loggedApply)
						{
							_loggedApply = true;
							Plugin.Log.LogDebug((object)("Quickstart: menu shifted up " + num2.ToString("0.##") + " reference px (slot " + num.ToString("0.##") + " px, net " + netAdded + " button(s) added). " + Status));
						}
					}
				}
				catch (Exception ex)
				{
					Pause(item[0]);
					Plugin.Log.LogWarning((object)("Quickstart: layout pass threw, menu left unshifted: " + ex));
				}
			}).Every(100L);
			_shiftItem = item[0];
		}

		private static void Pause(IVisualElementScheduledItem item)
		{
			try
			{
				if (item != null)
				{
					item.Pause();
				}
			}
			catch
			{
			}
		}

		private static ButtonMetrics CaptureMetrics(Button injected)
		{
			ButtonMetrics buttonMetrics = ((VisualElement)injected).userData as ButtonMetrics;
			if (buttonMetrics == null)
			{
				buttonMetrics = (ButtonMetrics)(((VisualElement)injected).userData = new ButtonMetrics());
			}
			if (!buttonMetrics.Captured)
			{
				buttonMetrics.BaseMarginTop = Sane(((VisualElement)injected).resolvedStyle.marginTop);
				buttonMetrics.BaseMarginBottom = Sane(((VisualElement)injected).resolvedStyle.marginBottom);
				buttonMetrics.Captured = true;
			}
			return buttonMetrics;
		}

		private static void ApplyMarginScale(Button injected, ButtonMetrics metrics)
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			if (INJECTED_BUTTON_MARGIN_SCALE != 1f)
			{
				((VisualElement)injected).style.marginTop = StyleLength.op_Implicit(metrics.BaseMarginTop * INJECTED_BUTTON_MARGIN_SCALE);
				((VisualElement)injected).style.marginBottom = StyleLength.op_Implicit(metrics.BaseMarginBottom * INJECTED_BUTTON_MARGIN_SCALE);
			}
		}

		private static float Sane(float v)
		{
			if (!float.IsNaN(v) && !float.IsInfinity(v))
			{
				return v;
			}
			return 0f;
		}
	}
	internal static class MenuTreeDump
	{
		private const int MAX_DEPTH = 40;

		internal static string LastResult = "";

		public static string Write()
		{
			if (!TryGetRoot(out var root, out var source))
			{
				throw new InvalidOperationException("the title menu is not up (TitleMenu.Instance / UIDoc is null). Open the main menu, then press this.");
			}
			StringBuilder stringBuilder = new StringBuilder();
			WriteHeader(stringBuilder, root, source);
			stringBuilder.AppendLine("visual tree");
			stringBuilder.AppendLine(new string('-', 100));
			int num = Walk(stringBuilder, root, 0);
			stringBuilder.AppendLine();
			stringBuilder.AppendLine(num + " elements.");
			stringBuilder.AppendLine();
			WriteButtonSummary(stringBuilder, root);
			string text = Path.Combine(Paths.BepInExRootPath, "Quickstart");
			Directory.CreateDirectory(text);
			string text2 = Path.Combine(text, "menu-tree.txt");
			File.WriteAllText(text2, stringBuilder.ToString(), new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
			return text2;
		}

		private static bool TryGetRoot(out VisualElement root, out string source)
		{
			root = null;
			source = null;
			try
			{
				TitleMenu instance = TitleMenu.Instance;
				if ((Object)(object)instance == (Object)null || (Object)(object)instance.UIDoc == (Object)null)
				{
					return false;
				}
				root = instance.UIDoc.rootVisualElement;
				source = "TitleMenu.Instance.UIDoc.rootVisualElement";
				return root != null;
			}
			catch
			{
				return false;
			}
		}

		private static void WriteHeader(StringBuilder sb, VisualElement root, string source)
		{
			sb.AppendLine("Quickstart main-menu visual tree dump");
			sb.AppendLine("v0.2.1   " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
			sb.AppendLine(new string('=', 100));
			sb.AppendLine("root      : " + source);
			sb.AppendLine("screen    : " + Screen.width + "x" + Screen.height + "   aspect " + Ratio(Screen.width, Screen.height) + "   panel scale (Screen.height/1080) = " + F((float)Screen.height / 1080f));
			sb.AppendLine("reference : root resolvedStyle " + F(root.resolvedStyle.width) + " x " + F(root.resolvedStyle.height) + " px");
			sb.AppendLine("            (layout is computed in reference px; the usable reference");
			sb.AppendLine("             WIDTH moves with aspect ratio, the height is always 1080)");
			if (MenuLayout.TryGetRank(out var rank, out var why))
			{
				string[] obj = new string[7]
				{
					"rank      : ",
					rank.ToString(),
					"   (hide threshold ",
					null,
					null,
					null,
					null
				};
				int tUTORIAL_HIDE_RANK = MenuLayout.TUTORIAL_HIDE_RANK;
				obj[3] = tUTORIAL_HIDE_RANK.ToString();
				obj[4] = " -> Tutorial ";
				obj[5] = ((rank >= MenuLayout.TUTORIAL_HIDE_RANK) ? "hidden" : "shown");
				obj[6] = ")";
				sb.AppendLine(string.Concat(obj));
			}
			else
			{
				sb.AppendLine("rank      : unreadable - " + why);
			}
			sb.AppendLine("layout    : " + MenuLayout.Status);
			string[] obj2 = new string[8] { "            APPLY_SHIFT=", null, null, null, null, null, null, null };
			bool aPPLY_SHIFT = MenuLayout.APPLY_SHIFT;
			obj2[1] = aPPLY_SHIFT.ToString();
			obj2[2] = "  STACK_RECENTRE_FACTOR=";
			obj2[3] = F(MenuLayout.STACK_RECENTRE_FACTOR);
			obj2[4] = "  EXTRA_SHIFT_UP_PX=";
			obj2[5] = F(MenuLayout.EXTRA_SHIFT_UP_PX);
			obj2[6] = "  INJECTED_BUTTON_MARGIN_SCALE=";
			obj2[7] = F(MenuLayout.INJECTED_BUTTON_MARGIN_SCALE);
			sb.AppendLine(string.Concat(obj2));
			sb.AppendLine("            measured slot = " + F(MenuLayout.MeasuredSlotPx) + " px, applied shift = " + F(MenuLayout.AppliedShiftPx) + " px");
			sb.AppendLine();
			sb.AppendLine("per element: #name <Type>  [classes]  pick=  disp=  vis=  focus=  enabled=");
			sb.AppendLine("             world=(x,y,w,h)  layout=(x,y,w,h)  pos=  translate=");
			sb.AppendLine("             margin=(t,r,b,l)  padding=(t,r,b,l)  inset=(t,r,b,l)");
			sb.AppendLine("             flex=(dir,grow,shrink)  size=(w,h)");
			sb.AppendLine();
		}

		private static int Walk(StringBuilder sb, VisualElement ve, int depth)
		{
			if (ve == null)
			{
				return 0;
			}
			string value = new string(' ', depth * 2);
			sb.Append(value).AppendLine(Headline(ve));
			sb.Append(value).Append("   ").AppendLine(Box(ve));
			sb.Append(value).Append("   ").AppendLine(Spacing(ve));
			sb.Append(value).Append("   ").AppendLine(Flex(ve));
			int num = 1;
			if (depth >= 40)
			{
				sb.Append(value).AppendLine("   ... depth limit reached, not descending");
				return num;
			}
			for (int i = 0; i < ve.childCount; i++)
			{
				num += Walk(sb, ve[i], depth + 1);
			}
			return num;
		}

		private static string Headline(VisualElement ve)
		{
			//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append('#').Append(string.IsNullOrEmpty(ve.name) ? "(unnamed)" : ve.name);
			stringBuilder.Append(" <").Append(((object)ve).GetType().Name).Append('>');
			string text = Classes(ve);
			if (text.Length > 0)
			{
				stringBuilder.Append("  [").Append(text).Append(']');
			}
			stringBuilder.Append("  pick=").Append(ve.pickingMode);
			stringBuilder.Append("  disp=").Append(Safe(() => ((object)ve.resolvedStyle.display/*cast due to .constrained prefix*/).ToString()));
			stringBuilder.Append("  vis=").Append(Safe(() => ((object)ve.resolvedStyle.visibility/*cast due to .constrained prefix*/).ToString()));
			stringBuilder.Append("  focus=").Append(((Focusable)ve).focusable);
			stringBuilder.Append("  enabled=").Append(ve.enabledInHierarchy);
			string value = TextOf(ve);
			if (!string.IsNullOrEmpty(value))
			{
				stringBuilder.Append("  text=\"").Append(value).Append('"');
			}
			return stringBuilder.ToString();
		}

		private static string Box(VisualElement ve)
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			Rect worldBound = ve.worldBound;
			Rect layout = ve.layout;
			return "world=(" + F(((Rect)(ref worldBound)).x) + "," + F(((Rect)(ref worldBound)).y) + "," + F(((Rect)(ref worldBound)).width) + "," + F(((Rect)(ref worldBound)).height) + ")  layout=(" + F(((Rect)(ref layout)).x) + "," + F(((Rect)(ref layout)).y) + "," + F(((Rect)(ref layout)).width) + "," + F(((Rect)(ref layout)).height) + ")  pos=" + Safe(() => ((object)ve.resolvedStyle.position/*cast due to .constrained prefix*/).ToString()) + "  translate=" + Safe(() => ((object)ve.resolvedStyle.translate/*cast due to .constrained prefix*/).ToString());
		}

		private static string Spacing(VisualElement ve)
		{
			IResolvedStyle resolvedStyle = ve.resolvedStyle;
			return "margin=(" + F(resolvedStyle.marginTop) + "," + F(resolvedStyle.marginRight) + "," + F(resolvedStyle.marginBottom) + "," + F(resolvedStyle.marginLeft) + ")  padding=(" + F(resolvedStyle.paddingTop) + "," + F(resolvedStyle.paddingRight) + "," + F(resolvedStyle.paddingBottom) + "," + F(resolvedStyle.paddingLeft) + ")  inset=(" + F(resolvedStyle.top) + "," + F(resolvedStyle.right) + "," + F(resolvedStyle.bottom) + "," + F(resolvedStyle.left) + ")";
		}

		private static string Flex(VisualElement ve)
		{
			IResolvedStyle s = ve.resolvedStyle;
			return "flex=(" + Safe(() => ((object)s.flexDirection/*cast due to .constrained prefix*/).ToString()) + "," + F(s.flexGrow) + "," + F(s.flexShrink) + ")  align=(" + Safe(() => ((object)s.alignItems/*cast due to .constrained prefix*/).ToString()) + "," + Safe(() => ((object)s.justifyContent/*cast due to .constrained prefix*/).ToString()) + ")  size=(" + F(s.width) + "," + F(s.height) + ")";
		}

		private static void WriteButtonSummary(StringBuilder sb, VisualElement root)
		{
			//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0116: Unknown result type (might be due to invalid IL or missing references)
			//IL_011b: Unknown result type (might be due to invalid IL or missing references)
			sb.AppendLine("button summary (tree order)");
			sb.AppendLine(new string('-', 100));
			List<Button> list = new List<Button>();
			Collect(root, list);
			if (list.Count == 0)
			{
				sb.AppendLine("  no Buttons found - the menu UXML changed.");
				return;
			}
			sb.AppendLine("  name                      parent                    y      h    mTop  mBot  disp");
			for (int i = 0; i < list.Count; i++)
			{
				Button b = list[i];
				StringBuilder stringBuilder = sb.Append("  ").Append(Pad(string.IsNullOrEmpty(((VisualElement)b).name) ? "(unnamed)" : ((VisualElement)b).name, 25)).Append(Pad((((VisualElement)b).parent == null) ? "(none)" : (string.IsNullOrEmpty(((VisualElement)b).parent.name) ? "(unnamed)" : ((VisualElement)b).parent.name), 25));
				Rect worldBound = ((VisualElement)b).worldBound;
				StringBuilder stringBuilder2 = stringBuilder.Append(Pad(F(((Rect)(ref worldBound)).y), 7));
				worldBound = ((VisualElement)b).worldBound;
				stringBuilder2.Append(Pad(F(((Rect)(ref worldBound)).height), 6)).Append(Pad(F(((VisualElement)b).resolvedStyle.marginTop), 6)).Append(Pad(F(((VisualElement)b).resolvedStyle.marginBottom), 6))
					.Append(Safe(() => ((object)((VisualElement)b).resolvedStyle.display/*cast due to .constrained prefix*/).ToString()))
					.AppendLine();
			}
			sb.AppendLine();
			sb.AppendLine("  Quickstart's button is 'button_quickstart'; the Tutorial button is 'button_awakening' and Solo Play is 'button_solo'.");
			sb.AppendLine("  The shift is applied to the injected button's parent unless");
			sb.AppendLine("  MenuLayout.SHIFT_TARGET_NAME names something else.");
		}

		private static void Collect(VisualElement ve, List<Button> into)
		{
			if (ve != null)
			{
				Button val = (Button)(object)((ve is Button) ? ve : null);
				if (val != null)
				{
					into.Add(val);
				}
				for (int i = 0; i < ve.childCount; i++)
				{
					Collect(ve[i], into);
				}
			}
		}

		private static string Classes(VisualElement ve)
		{
			StringBuilder stringBuilder = new StringBuilder();
			try
			{
				foreach (string @class in ve.GetClasses())
				{
					if (stringBuilder.Length > 0)
					{
						stringBuilder.Append(' ');
					}
					stringBuilder.Append(@class);
				}
			}
			catch
			{
			}
			return stringBuilder.ToString();
		}

		private static string TextOf(VisualElement ve)
		{
			try
			{
				TextElement val = (TextElement)(object)((ve is TextElement) ? ve : null);
				if (val == null)
				{
					return null;
				}
				string text = val.text;
				if (string.IsNullOrEmpty(text))
				{
					return null;
				}
				return text.Replace("\n", "\\n");
			}
			catch
			{
				return null;
			}
		}

		private static string Safe(Func<string> f)
		{
			try
			{
				return f();
			}
			catch
			{
				return "?";
			}
		}

		private static string F(float v)
		{
			if (float.IsNaN(v))
			{
				return "NaN";
			}
			if (float.IsInfinity(v))
			{
				return "inf";
			}
			return v.ToString("0.##", CultureInfo.InvariantCulture);
		}

		private static string Ratio(int w, int h)
		{
			if (h <= 0)
			{
				return "?";
			}
			return ((float)w / (float)h).ToString("0.###", CultureInfo.InvariantCulture) + ":1";
		}

		private static string Pad(string s, int width)
		{
			if (s == null)
			{
				s = "";
			}
			if (s.Length >= width)
			{
				return s + " ";
			}
			return s + new string(' ', width - s.Length);
		}
	}
	[BepInPlugin("jack.voidcrew.quickstart", "Quickstart", "0.2.1")]
	[BepInProcess("Void Crew.exe")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class Plugin : BaseUnityPlugin
	{
		internal static ManualLogSource Log;

		private void Awake()
		{
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Expected O, but got Unknown
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			QuickstartConfig.Bind(((BaseUnityPlugin)this).Config);
			if (!QuickstartConfig.Enabled.Value)
			{
				Log.LogWarning((object)"Quickstart: disabled via config ([General] Enabled = false). No patches applied.");
				return;
			}
			Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), "jack.voidcrew.quickstart");
			GameObject val = new GameObject("Quickstart.Driver");
			Object.DontDestroyOnLoad((Object)val);
			((Object)val).hideFlags = (HideFlags)61;
			QuickstartHubDriver.Attach(val);
			Log.LogInfo((object)"Quickstart v0.2.1 loaded.");
		}
	}
	public class QuickstartVoidPlugin : VoidPlugin
	{
		public override MultiplayerType MPType => (MultiplayerType)8;

		public override string Author => "Jack";

		public override string Description => "Adds a Quickstart option to the main menu that drops you straight into a solo pilgrimage on a chosen ship loadout, skipping the hub entirely.";

		public override string ThunderstoreID => "Jack/Quickstart";
	}
	public class QuickstartSettingsMenu : ModSettingsMenu
	{
		private static string _lastDump = "";

		public override string Name()
		{
			return "Quickstart";
		}

		public override void Draw()
		{
			GUILayout.Label("Status: " + QuickstartLauncher.Status, Array.Empty<GUILayoutOption>());
			GUILayout.Label(VoiceReadiness.Describe(), Array.Empty<GUILayoutOption>());
			GUILayout.Space(6f);
			GUILayout.Label("Quest:   " + QuickstartConfig.QuestName.Value, Array.Empty<GUILayoutOption>());
			GUILayout.Label("Ship:    " + QuickstartConfig.ShipName.Value, Array.Empty<GUILayoutOption>());
			GUILayout.Label("Loadout: " + QuickstartConfig.LoadoutName.Value, Array.Empty<GUILayoutOption>());
			GUILayout.Label("Skip hub: " + QuickstartConfig.SkipHub.Value + "   Unlocked only: " + QuickstartConfig.UnlockedOnly.Value, Array.Empty<GUILayoutOption>());
			GUILayout.Space(8f);
			QuickstartConfig.SkipHub.Value = GUILayout.Toggle(QuickstartConfig.SkipHub.Value, "Skip the hub (off = load the station and auto-start from its terminals)", Array.Empty<GUILayoutOption>());
			QuickstartConfig.UnlockedOnly.Value = GUILayout.Toggle(QuickstartConfig.UnlockedOnly.Value, "Only consider unlocked ship loadouts", Array.Empty<GUILayoutOption>());
			QuickstartConfig.PlaceAboveSoloPlay.Value = GUILayout.Toggle(QuickstartConfig.PlaceAboveSoloPlay.Value, "Put the button above Solo Play (applies next time the menu is built)", Array.Empty<GUILayoutOption>());
			GUILayout.Space(8f);
			GUILayout.Label("Names and paths only - nothing is loaded, so this is safe to press:", Array.Empty<GUILayoutOption>());
			if (GUILayout.Button("Dump available quests / ship loadouts to the log", Array.Empty<GUILayoutOption>()))
			{
				try
				{
					_lastDump = QuickstartAssets.Dump();
					Plugin.Log.LogInfo((object)_lastDump);
				}
				catch (Exception ex)
				{
					_lastDump = "dump failed: " + ex.Message;
					Plugin.Log.LogError((object)("Quickstart: dump failed: " + ex));
				}
			}
			if (!string.IsNullOrEmpty(_lastDump))
			{
				GUILayout.Label("  (full text is in BepInEx/LogOutput.log)", Array.Empty<GUILayoutOption>());
				string[] array = _lastDump.Split(new char[1] { '\n' });
				int num = ((array.Length < 24) ? array.Length : 24);
				for (int i = 0; i < num; i++)
				{
					GUILayout.Label(array[i], Array.Empty<GUILayoutOption>());
				}
				if (array.Length > num)
				{
					GUILayout.Label("  ... " + (array.Length - num) + " more", Array.Empty<GUILayoutOption>());
				}
			}
			GUILayout.Space(8f);
			GUILayout.Label("Matching is case-insensitive over the asset FILENAME and RESOURCE PATH.", Array.Empty<GUILayoutOption>());
			GUILayout.Label("Display names are deliberately not used: reading one goes through Unity", Array.Empty<GUILayoutOption>());
			GUILayout.Label("Localization, which blocks, and doing it once per entry can freeze the game.", Array.Empty<GUILayoutOption>());
			GUILayout.Label("Copy an exact filename - or a guid - into", Array.Empty<GUILayoutOption>());
			GUILayout.Label("BepInEx/config/jack.voidcrew.quickstart.cfg.", Array.Empty<GUILayoutOption>());
			if (!string.IsNullOrEmpty(QuickstartConfig.QuestGuid.Value))
			{
				GUILayout.Label("QuestGuid pin: " + QuickstartConfig.QuestGuid.Value, Array.Empty<GUILayoutOption>());
			}
			if (!string.IsNullOrEmpty(QuickstartConfig.LoadoutGuid.Value))
			{
				GUILayout.Label("LoadoutGuid pin: " + QuickstartConfig.LoadoutGuid.Value, Array.Empty<GUILayoutOption>());
			}
			MenuKit.DevZone("quickstart", DrawMenuLayoutSection);
		}

		private static void DrawMenuLayoutSection()
		{
			GUILayout.Label("--- main menu layout ---", Array.Empty<GUILayoutOption>());
			if (MenuLayout.TryGetRank(out var rank, out var why))
			{
				string[] obj = new string[5]
				{
					"Player rank: ",
					rank.ToString(),
					"   (Tutorial button hidden at >= ",
					null,
					null
				};
				int tUTORIAL_HIDE_RANK = MenuLayout.TUTORIAL_HIDE_RANK;
				obj[3] = tUTORIAL_HIDE_RANK.ToString();
				obj[4] = ")";
				GUILayout.Label(string.Concat(obj), Array.Empty<GUILayoutOption>());
			}
			else
			{
				GUILayout.Label("Player rank: unreadable - " + why, Array.Empty<GUILayoutOption>());
			}
			GUILayout.Label("Layout: " + MenuLayout.Status, Array.Empty<GUILayoutOption>());
			GUILayout.Label("Measured button slot: " + MenuLayout.MeasuredSlotPx.ToString("0.##") + " px   applied shift: " + MenuLayout.AppliedShiftPx.ToString("0.##") + " px", Array.Empty<GUILayoutOption>());
			if (GUILayout.Button("Dump the main-menu visual tree (press this ON the main menu)", Array.Empty<GUILayoutOption>()))
			{
				try
				{
					string text = MenuTreeDump.Write();
					MenuTreeDump.LastResult = "written to " + text;
					Plugin.Log.LogInfo((object)("Quickstart: menu tree dumped to " + text));
				}
				catch (Exception ex)
				{
					MenuTreeDump.LastResult = "dump failed: " + ex.Message;
					Plugin.Log.LogWarning((object)("Quickstart: menu tree dump failed: " + ex));
				}
			}
			if (!string.IsNullOrEmpty(MenuTreeDump.LastResult))
			{
				GUILayout.Label("  " + MenuTreeDump.LastResult, Array.Empty<GUILayoutOption>());
			}
		}
	}
	internal static class QuickstartPluginInfo
	{
		public const string PLUGIN_GUID = "jack.voidcrew.quickstart";

		public const string PLUGIN_NAME = "Quickstart";

		public const string USERS_PLUGIN_NAME = "Quickstart";

		public const string PLUGIN_VERSION = "0.2.1";

		public const string PLUGIN_DESCRIPTION = "Adds a Quickstart option to the main menu that drops you straight into a solo pilgrimage on a chosen ship loadout, skipping the hub entirely.";

		public const string PLUGIN_AUTHORS = "Jack";

		public const string PLUGIN_THUNDERSTORE_ID = "Jack/Quickstart";

		public const string BUTTON_NAME = "button_quickstart";

		public const string SOLO_BUTTON_NAME = "button_solo";

		public const string TUTORIAL_BUTTON_NAME = "button_awakening";
	}
	internal static class QuickstartAssets
	{
		internal sealed class Candidate
		{
			public object Def;

			public GUIDUnion Guid;

			public string FileName = "";

			public string Path = "";

			public string Describe()
			{
				//IL_0033: Unknown result type (might be due to invalid IL or missing references)
				return "file='" + FileName + "' path='" + Path + "' guid=" + Hex(Guid);
			}
		}

		private static bool _autoDumpedQuests;

		private static bool _autoDumpedLoadouts;

		internal static string Hex(GUIDUnion g)
		{
			try
			{
				return ((GUIDUnion)(ref g)).AsHex();
			}
			catch
			{
				return "<?>";
			}
		}

		private static List<TDef> Descriptions<TDef>(List<TDef> runtime, List<TDef> serialized)
		{
			if (runtime != null && runtime.Count > 0)
			{
				return runtime;
			}
			if (serialized != null)
			{
				return serialized;
			}
			return new List<TDef>();
		}

		private static Candidate Describe(object def, GUIDUnion guid, ResourceAssetRef reference)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			Candidate candidate = new Candidate();
			candidate.Def = def;
			candidate.Guid = guid;
			try
			{
				candidate.Path = reference.Path ?? "";
			}
			catch
			{
			}
			try
			{
				candidate.FileName = reference.Filename ?? "";
			}
			catch
			{
			}
			return candidate;
		}

		private static int Score(string needle, string fileName, string path)
		{
			if (string.IsNullOrEmpty(needle))
			{
				return 1;
			}
			if (Eq(fileName, needle))
			{
				return 100;
			}
			if (HasSegment(path, needle))
			{
				return 80;
			}
			if (Has(fileName, needle))
			{
				return 50;
			}
			if (Has(path, needle))
			{
				return 30;
			}
			return 0;
		}

		private static bool Eq(string a, string b)
		{
			if (!string.IsNullOrEmpty(a))
			{
				return string.Equals(a, b, StringComparison.OrdinalIgnoreCase);
			}
			return false;
		}

		private static bool Has(string a, string b)
		{
			if (!string.IsNullOrEmpty(a))
			{
				return a.IndexOf(b, StringComparison.OrdinalIgnoreCase) >= 0;
			}
			return false;
		}

		private static bool HasSegment(string path, string needle)
		{
			if (string.IsNullOrEmpty(path))
			{
				return false;
			}
			string[] array = path.Split('/', '\\');
			for (int i = 0; i < array.Length; i++)
			{
				if (Eq(array[i], needle))
				{
					return true;
				}
			}
			return false;
		}

		private static bool GuidMatches(string configuredHex, GUIDUnion guid)
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			if (string.IsNullOrEmpty(configuredHex))
			{
				return false;
			}
			string b = configuredHex.Replace("-", "").Trim();
			return string.Equals(Hex(guid), b, StringComparison.OrdinalIgnoreCase);
		}

		internal static List<Candidate> QuestCandidates()
		{
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			List<Candidate> list = new List<Candidate>();
			QuestAssetContainer instance = ResourceAssetContainer<QuestAssetContainer, QuestAsset, QuestAssetDef>.Instance;
			if ((Object)(object)instance == (Object)null)
			{
				return list;
			}
			List<QuestAssetDef> list2 = Descriptions(((ResourceAssetContainer<QuestAssetContainer, QuestAsset, QuestAssetDef>)(object)instance).RuntimeDescriptions, ((ResourceAssetContainer<QuestAssetContainer, QuestAsset, QuestAssetDef>)(object)instance).AssetDescriptions);
			for (int i = 0; i < list2.Count; i++)
			{
				QuestAssetDef val = list2[i];
				if (val != null && !(((ResourceAssetDef<QuestAsset>)(object)val).Ref == (ResourceAssetRef)null))
				{
					list.Add(Describe(val, ((ResourceAssetDef<QuestAsset>)(object)val).AssetGuid, ((ResourceAssetDef<QuestAsset>)(object)val).Ref));
				}
			}
			return list;
		}

		public static bool TryResolveQuest(out QuestAssetDef def, out string error)
		{
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: Unknown result type (might be due to invalid IL or missing references)
			//IL_0106: Expected O, but got Unknown
			def = null;
			error = null;
			List<Candidate> list = QuestCandidates();
			if (list.Count == 0)
			{
				error = (((Object)(object)ResourceAssetContainer<QuestAssetContainer, QuestAsset, QuestAssetDef>.Instance == (Object)null) ? "the quest container is not loaded yet - try again in a few seconds" : "the quest container is empty");
				return false;
			}
			string value = QuickstartConfig.QuestGuid.Value;
			string value2 = QuickstartConfig.QuestName.Value;
			Candidate candidate = null;
			int num = 0;
			for (int i = 0; i < list.Count; i++)
			{
				Candidate candidate2 = list[i];
				if (GuidMatches(value, candidate2.Guid))
				{
					candidate = candidate2;
					num = int.MaxValue;
					break;
				}
				int num2 = Score(value2, candidate2.FileName, candidate2.Path);
				if (num2 > num)
				{
					num = num2;
					candidate = candidate2;
				}
			}
			if (candidate == null)
			{
				error = "no quest matched QuestName '" + value2 + "' among " + list.Count + " quests (the full list has been written to the log)";
				AutoDump(ref _autoDumpedQuests, "quests", list);
				return false;
			}
			def = (QuestAssetDef)candidate.Def;
			Plugin.Log.LogDebug((object)("Quickstart: quest -> " + candidate.Describe() + " (type " + PeekQuestType(def) + ")"));
			return true;
		}

		private static string PeekQuestType(QuestAssetDef def)
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Invalid comparison between Unknown and I4
			try
			{
				QuestAsset asset = ((ResourceAssetDef<QuestAsset>)(object)def).Asset;
				if ((Object)(object)asset == (Object)null)
				{
					return "asset not loadable from here";
				}
				if ((int)asset.QuestType != 4)
				{
					Plugin.Log.LogWarning((object)("Quickstart: the chosen quest is type " + ((object)Unsafe.As<QuestType, QuestType>(ref asset.QuestType)/*cast due to .constrained prefix*/).ToString() + ", not Pilgrimage. Launching it anyway; set Selection/QuestName if that is not what you wanted."));
				}
				return ((object)Unsafe.As<QuestType, QuestType>(ref asset.QuestType)/*cast due to .constrained prefix*/).ToString();
			}
			catch (Exception ex)
			{
				return "unreadable (" + ex.Message + ")";
			}
		}

		internal static List<Candidate> LoadoutCandidates(out HashSet<string> unlockedHex)
		{
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			unlockedHex = new HashSet<string>();
			List<Candidate> list = new List<Candidate>();
			ShipLoadoutDataContainer instance = ResourceAssetContainer<ShipLoadoutDataContainer, ShipLoadoutData, ShipLoadoutDataDef>.Instance;
			if ((Object)(object)instance == (Object)null)
			{
				return list;
			}
			try
			{
				List<ShipLoadoutDataDef> unlockedShipDefinitionsFromHighestRankPlayer = instance.GetUnlockedShipDefinitionsFromHighestRankPlayer();
				if (unlockedShipDefinitionsFromHighestRankPlayer != null)
				{
					for (int i = 0; i < unlockedShipDefinitionsFromHighestRankPlayer.Count; i++)
					{
						if (unlockedShipDefinitionsFromHighestRankPlayer[i] != null)
						{
							unlockedHex.Add(Hex(((ResourceAssetDef<ShipLoadoutData>)(object)unlockedShipDefinitionsFromHighestRankPlayer[i]).AssetGuid));
						}
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Quickstart: unlock lookup unavailable here (" + ex.Message + "); considering all loadouts."));
			}
			List<ShipLoadoutDataDef> list2 = Descriptions(((ResourceAssetContainer<ShipLoadoutDataContainer, ShipLoadoutData, ShipLoadoutDataDef>)(object)instance).RuntimeDescriptions, ((ResourceAssetContainer<ShipLoadoutDataContainer, ShipLoadoutData, ShipLoadoutDataDef>)(object)instance).AssetDescriptions);
			for (int j = 0; j < list2.Count; j++)
			{
				ShipLoadoutDataDef val = list2[j];
				if (val != null && !(((ResourceAssetDef<ShipLoadoutData>)(object)val).Ref == (ResourceAssetRef)null))
				{
					list.Add(Describe(val, ((ResourceAssetDef<ShipLoadoutData>)(object)val).AssetGuid, ((ResourceAssetDef<ShipLoadoutData>)(object)val).Ref));
				}
			}
			return list;
		}

		public static bool TryResolveLoadout(out ShipLoadoutDataDef def, out string error)
		{
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ae: Expected O, but got Unknown
			//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
			def = null;
			error = null;
			HashSet<string> unlockedHex;
			List<Candidate> list = LoadoutCandidates(out unlockedHex);
			if (list.Count == 0)
			{
				error = (((Object)(object)ResourceAssetContainer<ShipLoadoutDataContainer, ShipLoadoutData, ShipLoadoutDataDef>.Instance == (Object)null) ? "the ship loadout container is not loaded yet - try again in a few seconds" : "the ship loadout container is empty");
				return false;
			}
			string value = QuickstartConfig.LoadoutGuid.Value;
			string value2 = QuickstartConfig.ShipName.Value;
			string value3 = QuickstartConfig.LoadoutName.Value;
			bool flag = QuickstartConfig.UnlockedOnly.Value && unlockedHex.Count > 0;
			Candidate candidate = null;
			int num = 0;
			bool flag2 = false;
			for (int i = 0; i < list.Count; i++)
			{
				Candidate candidate2 = list[i];
				if (GuidMatches(value, candidate2.Guid))
				{
					candidate = candidate2;
					num = int.MaxValue;
					break;
				}
				if (flag && !unlockedHex.Contains(Hex(candidate2.Guid)))
				{
					continue;
				}
				int num2 = Score(value3, candidate2.FileName, candidate2.Path);
				if (num2 != 0)
				{
					int num3 = (string.IsNullOrEmpty(value2) ? 1 : Score(value2, "", candidate2.Path));
					int num4 = num2 * 1000 + num3 * 10;
					if (num4 > num)
					{
						num = num4;
						candidate = candidate2;
						flag2 = num3 > 1;
					}
				}
			}
			if (candidate == null)
			{
				error = "no loadout matched LoadoutName '" + value3 + "' among " + list.Count + " loadouts" + (flag ? " (unlocked only)" : "") + " (the full list has been written to the log)";
				AutoDump(ref _autoDumpedLoadouts, "ship loadouts", list);
				return false;
			}
			def = (ShipLoadoutDataDef)candidate.Def;
			string text = PeekShipName(def);
			Plugin.Log.LogDebug((object)("Quickstart: loadout -> " + candidate.Describe() + " (hull " + text + ")"));
			if (!string.IsNullOrEmpty(value2) && !flag2 && !Has(text, value2))
			{
				Plugin.Log.LogWarning((object)("Quickstart: could not confirm this loadout belongs to ship '" + value2 + "' - its path does not mention it and its hull reads as '" + text + "'. Launching anyway; set Selection/LoadoutGuid to pin an exact one."));
			}
			return true;
		}

		private static string PeekShipName(ShipLoadoutDataDef def)
		{
			try
			{
				ShipLoadoutData asset = ((ResourceAssetDef<ShipLoadoutData>)(object)def).Asset;
				if ((Object)(object)asset == (Object)null || asset.ShipLoadout == null)
				{
					return "asset not loadable from here";
				}
				ResourceAssetRef referenceShip = (ResourceAssetRef)(object)asset.ShipLoadout.ReferenceShip;
				if (referenceShip == (ResourceAssetRef)null || referenceShip.IsNull)
				{
					return "no reference ship";
				}
				return referenceShip.Filename;
			}
			catch (Exception ex)
			{
				return "unreadable (" + ex.Message + ")";
			}
		}

		private static void AutoDump(ref bool alreadyDone, string label, List<Candidate> pool)
		{
			if (!alreadyDone)
			{
				alreadyDone = true;
				StringBuilder stringBuilder = new StringBuilder();
				stringBuilder.AppendLine("Quickstart: nothing matched, here is every " + label + " the container holds (" + pool.Count + "):");
				Append(stringBuilder, pool);
				Plugin.Log.LogInfo((object)stringBuilder.ToString());
			}
		}

		private static void Append(StringBuilder sb, List<Candidate> pool)
		{
			for (int i = 0; i < pool.Count; i++)
			{
				sb.AppendLine("     " + pool[i].Describe());
			}
		}

		public static string Dump()
		{
			//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.AppendLine("=== Quickstart: available assets (names and paths only, nothing loaded) ===");
			try
			{
				List<Candidate> list = QuestCandidates();
				stringBuilder.AppendLine("-- quests (" + list.Count + ") --");
				Append(stringBuilder, list);
			}
			catch (Exception ex)
			{
				stringBuilder.AppendLine("  quest dump failed: " + ex);
			}
			try
			{
				HashSet<string> unlockedHex;
				List<Candidate> list2 = LoadoutCandidates(out unlockedHex);
				stringBuilder.AppendLine("-- ship loadouts (" + list2.Count + ", " + unlockedHex.Count + " reported unlocked) --");
				for (int i = 0; i < list2.Count; i++)
				{
					stringBuilder.AppendLine("     " + (unlockedHex.Contains(Hex(list2[i].Guid)) ? "[U] " : "[ ] ") + list2[i].Describe());
				}
			}
			catch (Exception ex2)
			{
				stringBuilder.AppendLine("  loadout dump failed: " + ex2);
			}
			stringBuilder.AppendLine("Paste an exact filename into Selection/QuestName or Selection/LoadoutName, or a guid into Selection/QuestGuid or Selection/LoadoutGuid.");
			return stringBuilder.ToString();
		}
	}
	internal static class QuickstartConfig
	{
		public static ConfigEntry<bool> Enabled;

		public static ConfigEntry<string> ButtonLabel;

		public static ConfigEntry<bool> PlaceAboveSoloPlay;

		public static ConfigEntry<string> QuestName;

		public static ConfigEntry<string> ShipName;

		public static ConfigEntry<string> LoadoutName;

		public static ConfigEntry<string> QuestGuid;

		public static ConfigEntry<string> LoadoutGuid;

		public static ConfigEntry<bool> UnlockedOnly;

		public static ConfigEntry<bool> SkipHub;

		public static ConfigEntry<float> VoiceReadyTimeout;

		public static ConfigEntry<string> RoomName;

		public static void Bind(ConfigFile cfg)
		{
			Enabled = cfg.Bind<bool>("General", "Enabled", true, "Master switch. False = no button injected, no patches do anything.");
			ButtonLabel = cfg.Bind<string>("Button", "Label", "QUICKSTART", "Text shown on the injected main-menu button.");
			PlaceAboveSoloPlay = cfg.Bind<bool>("Button", "PlaceAboveSoloPlay", false, "True puts Quickstart directly above Solo Play; false (default) puts it directly below.");
			QuestName = cfg.Bind<string>("Selection", "QuestName", "Pilgrimage", "Which quest to launch, matched against the quest asset's filename and resource path. If nothing matches, the log lists every quest available at the moment of failure - copy an exact name out of it. The mod settings menu's dump button prints the same list on demand.");
			ShipName = cfg.Bind<string>("Selection", "ShipName", "Frigate", "Preferred ship hull, matched against the loadout's resource path. This is a preference, not a filter - if no path mentions the hull, the best LoadoutName match still wins and the log says the hull could not be confirmed. Blank = ignore.");
			LoadoutName = cfg.Bind<string>("Selection", "LoadoutName", "Freedom", "Which loadout to launch with, matched against the loadout asset's filename and resource path.");
			QuestGuid = cfg.Bind<string>("Selection", "QuestGuid", "", "Escape hatch: a 32-char hex asset GUID that overrides QuestName completely. The dump in the F5 menu prints the guid of every quest.");
			LoadoutGuid = cfg.Bind<string>("Selection", "LoadoutGuid", "", "Escape hatch: a 32-char hex asset GUID that overrides ShipName/LoadoutName completely, and bypasses the unlocked-only check.");
			UnlockedOnly = cfg.Bind<bool>("Selection", "UnlockedOnly", true, "True (default) only considers loadouts the profile has actually unlocked, so Quickstart can never hand you a ship the hub terminal would refuse. The unlock list reads player rank, which is a session concept, so when it comes back empty in the main menu this is ignored rather than blocking the launch.");
			SkipHub = cfg.Bind<bool>("Launch", "SkipHub", true, "True (default) builds the mission session in the main menu and loads straight into the pilgrimage. False loads the hub as normal and then drives the hub's own ship-select / quest-select / start-countdown for you - slower, but it goes through exactly the code path the game uses, so it is the fallback if the direct route ever breaks.");
			VoiceReadyTimeout = cfg.Bind<float>("Launch", "VoiceReadyTimeoutSeconds", 20f, "The button stays disabled until the game's voice chat has finished its start-up login, since launching over it leaves in-game voice dead for the run. This is how long to wait before giving up and enabling the button anyway. 0 skips the wait entirely.");
			RoomName = cfg.Bind<string>("Launch", "RoomName", "Quickstart", "Photon room name for the solo session. Cosmetic; the room is private and capped at one player either way.");
		}
	}
	internal class QuickstartHubDriver : MonoBehaviour
	{
		private enum Step
		{
			Idle,
			WaitForHub,
			SelectShip,
			SelectQuest,
			StartQuest,
			Done
		}

		private static QuickstartHubDriver _instance;

		private static bool _armed;

		private Step _step;

		private float _nextActionTime;

		private float _giveUpTime;

		private bool _loggedFailure;

		public static void Arm()
		{
			_armed = true;
			if ((Object)(object)_instance != (Object)null)
			{
				_instance.Begin();
			}
		}

		public static void Attach(GameObject host)
		{
			if ((Object)(object)_instance == (Object)null)
			{
				_instance = host.AddComponent<QuickstartHubDriver>();
			}
		}

		private void Awake()
		{
			_instance = this;
			if (_armed)
			{
				Begin();
			}
		}

		private void Begin()
		{
			_step = Step.WaitForHub;
			_nextActionTime = 0f;
			_giveUpTime = Time.unscaledTime + 120f;
			_loggedFailure = false;
		}

		private void Update()
		{
			if (_step == Step.Idle || _step == Step.Done)
			{
				return;
			}
			try
			{
				if (Time.unscaledTime > _giveUpTime)
				{
					Finish("timed out waiting for the hub; you are in the station, start the run from the terminals as normal");
				}
				else if (!(Time.unscaledTime < _nextActionTime))
				{
					switch (_step)
					{
					case Step.WaitForHub:
						TickWaitForHub();
						break;
					case Step.SelectShip:
						TickSelectShip();
						break;
					case Step.SelectQuest:
						TickSelectQuest();
						break;
					case Step.StartQuest:
						TickStartQuest();
						break;
					}
				}
			}
			catch (Exception ex)
			{
				if (!_loggedFailure)
				{
					_loggedFailure = true;
					Plugin.Log.LogError((object)("Quickstart: hub auto-start failed at " + _step.ToString() + ": " + ex));
				}
				Finish("hub auto-start failed at " + _step.ToString() + " (see the log); the station is playable as normal");
			}
		}

		private void TickWaitForHub()
		{
			if (!((Object)(object)HubQuestManager.Instance == (Object)null) && !((Object)(object)HubShipManager.Instance == (Object)null) && PhotonNetwork.IsMasterClient && !((Object)(object)GameSessionManager.Instance == (Object)null) && GameSessionManager.HasActiveSession && GameSessionManager.InHub && Traverse.Create((object)HubQuestManager.Instance).Field("questGameSession").GetValue() != null)
			{
				_step = Step.SelectShip;
				_nextActionTime = Time.unscaledTime + 0.75f;
			}
		}

		private void TickSelectShip()
		{
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			ShipLoadoutDataDef pendingLoadout = QuickstartLauncher.PendingLoadout;
			if (pendingLoadout == null)
			{
				Finish("no loadout was pending");
				return;
			}
			HubShipManager instance = HubShipManager.Instance;
			if ((Object)(object)instance == (Object)null || !GameSessionManager.InHub)
			{
				Finish("left the hub before the ship could be selected");
				return;
			}
			GUIDUnion assetGuid = ((ResourceAssetDef<ShipLoadoutData>)(object)pendingLoadout).AssetGuid;
			instance.SelectShip(((GUIDUnion)(ref assetGuid)).AsIntArray());
			QuickstartLauncher.Status = "hub: selected " + QuickstartLauncher.Describe(pendingLoadout);
			Plugin.Log.LogDebug((object)("Quickstart: hub ship selected -> " + QuickstartLauncher.Describe(pendingLoadout)));
			_step = Step.SelectQuest;
			_nextActionTime = Time.unscaledTime + 0.75f;
		}

		private void TickSelectQuest()
		{
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			QuestAssetDef pendingQuest = QuickstartLauncher.PendingQuest;
			if (pendingQuest == null)
			{
				Finish("no quest was pending");
				return;
			}
			HubQuestManager instance = HubQuestManager.Instance;
			if ((Object)(object)instance == (Object)null || !GameSessionManager.InHub)
			{
				Finish("left the hub before the quest could be selected");
				return;
			}
			instance.SelectQuest(((ResourceAssetDef<QuestAsset>)(object)pendingQuest).AssetGuid);
			QuickstartLauncher.Status = "hub: selected " + QuickstartLauncher.Describe(pendingQuest);
			Plugin.Log.LogDebug((object)("Quickstart: hub quest selected -> " + QuickstartLauncher.Describe(pendingQuest)));
			_step = Step.StartQuest;
			_nextActionTime = Time.unscaledTime + 1f;
		}

		private void TickStartQuest()
		{
			HubQuestManager instance = HubQuestManager.Instance;
			if ((Object)(object)instance == (Object)null)
			{
				Finish("hub quest manager vanished");
				return;
			}
			if ((ResourceAssetRef)(object)instance.SelectedQuest == (ResourceAssetRef)null)
			{
				_nextActionTime = Time.unscaledTime + 0.5f;
				return;
			}
			Traverse obj = Traverse.Create((object)instance);
			int value = obj.Field("questSeed").GetValue<int>();
			int value2 = obj.Field("challengeSeed").GetValue<int>();
			instance.StartQuest(instance.SelectedQuest, (SessionStartType)1, value, value2);
			QuickstartLauncher.Status = "hub: launching pilgrimage";
			Plugin.Log.LogInfo((object)("Quickstart: hub StartQuest issued (seed " + value + ")."));
			_step = Step.Done;
			_armed = false;
		}

		private void Finish(string message)
		{
			QuickstartLauncher.Status = message;
			Plugin.Log.LogWarning((object)("Quickstart: " + message));
			_step = Step.Done;
			_armed = false;
		}
	}
	internal static class QuickstartLauncher
	{
		public static string Status = "idle";

		internal static QuestAssetDef PendingQuest;

		internal static ShipLoadoutDataDef PendingLoadout;

		public static bool InFlight { get; private set; }

		public static void Launch()
		{
			if (InFlight)
			{
				Plugin.Log.LogDebug((object)"Quickstart: launch already in progress, ignoring.");
				return;
			}
			if (!MenuInjection.IsReadyToLaunch())
			{
				Fail("not connected to the Photon lobby yet - give it a moment and try again");
				return;
			}
			if (!QuickstartAssets.TryResolveQuest(out var def, out var error))
			{
				Fail("could not pick a quest - " + error);
				return;
			}
			if (!QuickstartAssets.TryResolveLoadout(out var def2, out error))
			{
				Fail("could not pick a ship loadout - " + error);
				return;
			}
			PendingQuest = def;
			PendingLoadout = def2;
			Plugin.Log.LogInfo((object)("Quickstart: launching quest '" + Describe(def) + "' with loadout '" + Describe(def2) + "' (SkipHub=" + QuickstartConfig.SkipHub.Value + ")."));
			InFlight = true;
			Status = "creating solo room...";
			try
			{
				PunSingleton<PhotonService>.I.CreateRoom(false, false).Then((Action)OnRoomCreated).Catch((Action<Exception>)OnLaunchFailed);
			}
			catch (Exception ex)
			{
				InFlight = false;
				Fail("CreateRoom threw: " + ex.Message);
				Plugin.Log.LogError((object)("Quickstart: CreateRoom threw: " + ex));
			}
		}

		private static void OnRoomCreated()
		{
			try
			{
				try
				{
					PAL.Matchmaking.CreateLobby(false);
				}
				catch (Exception ex)
				{
					Plugin.Log.LogWarning((object)("Quickstart: platform CreateLobby failed (harmless for a private solo run): " + ex.Message));
				}
				PunSingleton<PhotonService>.I.SetCurrentRoomPlayerLimit(1);
				string value = QuickstartConfig.RoomName.Value;
				if (!string.IsNullOrEmpty(value))
				{
					PunSingleton<PhotonService>.I.SetCurrentRoomName(value);
				}
				if (QuickstartConfig.SkipHub.Value)
				{
					LaunchMissionDirectly();
				}
				else
				{
					LaunchViaHub();
				}
			}
			catch (Exception ex2)
			{
				InFlight = false;
				Fail("room setup failed: " + ex2.Message);
				Plugin.Log.LogError((object)("Quickstart: room setup failed: " + ex2));
			}
		}

		private static void OnLaunchFailed(Exception e)
		{
			InFlight = false;
			try
			{
				string text = ((e == null) ? "unknown error" : e.Message);
				Fail("could not create the solo room: " + text);
				Plugin.Log.LogError((object)("Quickstart: CreateRoom rejected: " + e));
			}
			catch
			{
			}
		}

		private static void LaunchMissionDirectly()
		{
			//IL_019c: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bc: Expected O, but got Unknown
			//IL_01f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f8: Unknown result type (might be due to invalid IL or missing references)
			Status = "building mission session...";
			GameSessionManager instance = GameSessionManager.Instance;
			if ((Object)(object)instance == (Object)null)
			{
				InFlight = false;
				Fail("GameSessionManager.Instance is null - cannot queue a session");
				return;
			}
			GameSessionData val = null;
			bool flag = false;
			try
			{
				ScriptableObjectRef val2 = (((Object)(object)((ResourceAssetDef<QuestAsset>)(object)PendingQuest).Asset == (Object)null) ? null : ((ResourceAssetDef<QuestAsset>)(object)PendingQuest).Asset.OverrideGameSessionData);
				if ((ResourceAssetRef)(object)val2 != (ResourceAssetRef)null && !((ResourceAssetRef)val2).IsNull)
				{
					ScriptableObject asset = ((ResourceAssetRef<ScriptableObjectContainer, ScriptableObject, ScriptableObjectDef>)(object)val2).Asset;
					val = (GameSessionData)(object)((asset is GameSessionData) ? asset : null);
					flag = (Object)(object)val != (Object)null;
					if (flag)
					{
						Plugin.Log.LogDebug((object)"Quickstart: this quest overrides the session template; using it and setting only Seed, exactly as HubQuestManager.StartQuest does.");
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Quickstart: could not read OverrideGameSessionData (" + ex.Message + "); using the default mission template."));
			}
			if ((Object)(object)val == (Object)null)
			{
				val = DataTable<DefaultAssetTable>.Instance.GameSessionMission;
			}
			if ((Object)(object)val == (Object)null || val.SessionData == null)
			{
				InFlight = false;
				Fail("DefaultAssetTable.GameSessionMission is missing - cannot build a session");
				return;
			}
			if (flag)
			{
				GameSession val3 = ObjectExtensions.DeepCopy<GameSession>(val.SessionData);
				val3.Seed = Random.Range(int.MinValue, int.MaxValue);
				instance.SetNextGameSession((PreservedGameSession)null);
				instance.SetNextGameSession(val3);
				Status = "loading quest (session overridden by the quest)...";
				((AbstractStateMachine<GameStateMachine>)(object)Singleton<GameStateMachine>.Instance).ChangeState<GSLoadingGame>();
				InFlight = false;
				return;
			}
			ShipLoadoutData asset2 = ((ResourceAssetDef<ShipLoadoutData>)(object)PendingLoadout).Asset;
			if ((Object)(object)asset2 == (Object)null || asset2.ShipLoadout == null)
			{
				InFlight = false;
				Fail("the chosen loadout ('" + Describe(PendingLoadout) + "') has no ShipLoadout data - pick a different one");
				return;
			}
			GameSession val4 = ObjectExtensions.DeepCopy<GameSession>(val.SessionData);
			val4.ToLoadShipData = asset2.ShipLoadout;
			val4.ToLoadShipGuid = ((ResourceAssetDef<ShipLoadoutData>)(object)PendingLoadout).AssetGuid;
			val4.SessionQuestAsset = new QuestAssetRef(((ResourceAssetDef<QuestAsset>)(object)PendingQuest).Ref);
			val4.StartingMutators = new List<GUIDUnion>();
			val4.Seed = Random.Range(int.MinValue, int.MaxValue);
			instance.SetNextGameSession((PreservedGameSession)null);
			instance.SetNextGameSession(val4);
			instance.PreviousSessionShipLoadout = ((ResourceAssetDef<ShipLoadoutData>)(object)PendingLoadout).AssetGuid;
			TrySetRoomProperties(val4);
			Status = "loading pilgrimage...";
			Plugin.Log.LogDebug((object)("Quickstart: session built (seed " + val4.Seed + "), entering GSLoadingGame."));
			((AbstractStateMachine<GameStateMachine>)(object)Singleton<GameStateMachine>.Instance).ChangeState<GSLoadingGame>();
			InFlight = false;
		}

		private static void TrySetRoomProperties(GameSession session)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				PunSingleton<PhotonService>.I.SetCurrentRoomInHub(false);
				PunSingleton<PhotonService>.I.SetCurrentRoomShip(((ResourceAssetDef<ShipLoadoutData>)(object)PendingLoadout).AssetGuid);
				QuestAsset asset = ((ResourceAssetDef<QuestAsset>)(object)PendingQuest).Asset;
				if ((Object)(object)asset != (Object)null)
				{
					PunSingleton<PhotonService>.I.SetCurrentRoomQuestType(asset.QuestType);
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Quickstart: could not publish room properties (cosmetic): " + ex.Message));
			}
		}

		private static void LaunchViaHub()
		{
			Status = "loading hub, will auto-start...";
			QuickstartHubDriver.Arm();
			GameSessionManager instance = GameSessionManager.Instance;
			if ((Object)(object)instance == (Object)null)
			{
				InFlight = false;
				Fail("GameSessionManager.Instance is null - cannot queue the hub session");
				return;
			}
			instance.SetNextGameSession((PreservedGameSession)null);
			instance.SetNextGameSession(GameSessionLoadPromise.LoadLobbyData());
			((AbstractStateMachine<GameStateMachine>)(object)Singleton<GameStateMachine>.Instance).ChangeState<GSLoadingGame>();
			InFlight = false;
		}

		private static void Fail(string message)
		{
			Status = "failed: " + message;
			Plugin.Log.LogWarning((object)("Quickstart: " + message));
		}

		internal static string Describe(QuestAssetDef def)
		{
			if (def == null)
			{
				return "<none>";
			}
			try
			{
				return (((ResourceAssetDef<QuestAsset>)(object)def).Ref != (ResourceAssetRef)null) ? ((ResourceAssetDef<QuestAsset>)(object)def).Ref.Filename : "<no ref>";
			}
			catch
			{
				return "<unreadable>";
			}
		}

		internal static string Describe(ShipLoadoutDataDef def)
		{
			if (def == null)
			{
				return "<none>";
			}
			try
			{
				return (((ResourceAssetDef<ShipLoadoutData>)(object)def).Ref != (ResourceAssetRef)null) ? ((ResourceAssetDef<ShipLoadoutData>)(object)def).Ref.Filename : "<no ref>";
			}
			catch
			{
				return "<unreadable>";
			}
		}
	}
	internal static class VoiceReadiness
	{
		private static float _waitStartedAt = -1f;

		private static bool _loggedTimeout;

		public static string Describe()
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				IVoipService instance = VoipService.Instance;
				if (instance == null)
				{
					return "voice: starting";
				}
				if (!(instance is VivoxAdapter))
				{
					return "voice: disabled";
				}
				return "voice: " + ((object)instance.Status/*cast due to .constrained prefix*/).ToString();
			}
			catch (Exception ex)
			{
				return "voice: ? (" + ex.Message + ")";
			}
		}

		public static bool IsReady()
		{
			try
			{
				float value = QuickstartConfig.VoiceReadyTimeout.Value;
				if (value <= 0f)
				{
					return true;
				}
				if (Settled())
				{
					_waitStartedAt = -1f;
					_loggedTimeout = false;
					return true;
				}
				if (_waitStartedAt < 0f)
				{
					_waitStartedAt = Time.realtimeSinceStartup;
				}
				if (Time.realtimeSinceStartup - _waitStartedAt < value)
				{
					return false;
				}
				if (!_loggedTimeout)
				{
					_loggedTimeout = true;
					Plugin.Log.LogWarning((object)("Quickstart: voice chat did not settle within " + value + "s (" + Describe() + "); allowing the launch anyway - in-game voice may not work this run."));
				}
				return true;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Quickstart: voice readiness check failed, ignoring it: " + ex.Message));
				return true;
			}
		}

		private static bool Settled()
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Invalid comparison between Unknown and I4
			IVoipService instance = VoipService.Instance;
			if (instance == null)
			{
				return false;
			}
			if (!(instance is VivoxAdapter))
			{
				return true;
			}
			return (int)instance.Status == 1;
		}
	}
}