Decompiled source of StonePortalControl v1.0.4

dannanboi_StonePortalControl.dll

Decompiled 16 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using TMPro;
using UnityEngine;
using UnityEngine.SceneManagement;

[assembly: AssemblyTitle("Stone Portal Control")]
[assembly: AssemblyDescription("Server-controlled stone portal restrictions and scale")]
[assembly: AssemblyCompany("Dannan")]
[assembly: AssemblyFileVersion("1.0.4.0")]
[assembly: AssemblyInformationalVersion("1.0.4")]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: AssemblyVersion("1.0.4.0")]
namespace StonePortalControl;

internal static class ConfigStorage
{
	internal const string LegacyGuid = "se.dannan.stoneportalcontrol";

	internal const string LegacyFolder = "StonePortalControl";

	internal static string GetPath(string root, string guid)
	{
		return Path.Combine(root, guid + ".cfg");
	}

	internal static Settings Initialize(string root, string guid, string name, string version)
	{
		//IL_0114: Unknown result type (might be due to invalid IL or missing references)
		//IL_011e: Expected O, but got Unknown
		//IL_0119: Unknown result type (might be due to invalid IL or missing references)
		//IL_0120: Expected O, but got Unknown
		//IL_016f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0179: Expected O, but got Unknown
		//IL_01c7: Unknown result type (might be due to invalid IL or missing references)
		//IL_01d1: Expected O, but got Unknown
		//IL_0201: Unknown result type (might be due to invalid IL or missing references)
		//IL_020b: Expected O, but got Unknown
		//IL_029d: Unknown result type (might be due to invalid IL or missing references)
		//IL_02a7: Expected O, but got Unknown
		//IL_02cc: Unknown result type (might be due to invalid IL or missing references)
		//IL_02d6: Expected O, but got Unknown
		//IL_02fb: Unknown result type (might be due to invalid IL or missing references)
		//IL_0305: Expected O, but got Unknown
		//IL_032a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0334: Expected O, but got Unknown
		string path = GetPath(root, guid);
		Directory.CreateDirectory(Path.GetDirectoryName(path));
		if (!File.Exists(path))
		{
			string[] array = new string[3]
			{
				Path.Combine(root, "StonePortalControl", guid + ".cfg"),
				Path.Combine(root, "StonePortalControl", "se.dannan.stoneportalcontrol.cfg"),
				GetPath(root, "se.dannan.stoneportalcontrol")
			};
			string[] array2 = array;
			foreach (string text in array2)
			{
				if (text != path && File.Exists(text))
				{
					File.Move(text, path);
					break;
				}
			}
		}
		string text2 = ((!File.Exists(path)) ? null : ReadText(path));
		if (text2 != null && !Settings.ReadStartupEnabled(text2))
		{
			return new Settings(restrictions: true, 1f, showEnabledText: false, showScaleText: false, disableStonePortalRecipe: false, modEnabled: false);
		}
		Settings result = ((text2 == null) ? new Settings(restrictions: true, 1f) : Settings.Parse(text2, upgradeLegacyScale: true));
		ConfigFile val = new ConfigFile(path, false, new BepInPlugin(guid, name, version));
		val.SaveOnConfigSet = false;
		val.Bind<bool>("General", "Enabled", true, "Enable Stone Portal Control on this machine. Restart required. False skips patches, networking and runtime updates. A server requiring the mod will reject a disabled client.").Value = result.ModEnabled;
		val.Bind<bool>("Settings", "Enabled", true, "Obsolete; renamed to Item restrictions.");
		val.Remove(new ConfigDefinition("Settings", "Enabled"));
		val.Bind<bool>("Settings", "Item restrictions", true, "Block non-teleportable items through stone portals. Server-controlled in multiplayer; local settings apply offline.").Value = result.ItemRestrictions;
		val.Bind<float>("Settings", "Portal scale", 1f, new ConfigDescription("Stone portal size: 0.5 = 50%, 1 = 100%. Independent of Item restrictions. Server-controlled in multiplayer.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 1f), Array.Empty<object>())).Value = result.Scale;
		val.Bind<bool>("Settings", "Hide hover text", false, "Obsolete; migrated to the two show options.");
		val.Remove(new ConfigDefinition("Settings", "Hide hover text"));
		val.Bind<bool>("Settings", "Disable Stone Portal Recipe", false, "Hide the stone portal building recipe and prevent discovery/building. Remove it from loaded characters; other characters are updated on load. Server-controlled in multiplayer. Existing portals remain.").Value = result.DisableStonePortalRecipe;
		val.Bind<bool>("Settings", "Use custom recipe", false, "Use Custom recipe for stone portal construction. False ignores the recipe string and restores the original requirements. Disable Stone Portal Recipe takes precedence. Server-controlled; updates live.").Value = result.UseCustomRecipe;
		val.Bind<string>("Settings", "Custom recipe", "GreydwarfEye[10],Grausten[30],MoltenCore[2]", "Stone portal materials: Item1[amount],Item2[amount]. Use exact item prefab names and positive whole-number amounts. Default is the vanilla recipe. Ignored unless Use custom recipe is true. Server-controlled in multiplayer.").Value = result.CustomRecipe;
		val.Bind<bool>("Settings", "Show enabled text", true, "Obsolete; moved to UI.");
		val.Remove(new ConfigDefinition("Settings", "Show enabled text"));
		val.Bind<bool>("Settings", "Show scale text", true, "Obsolete; moved to UI.");
		val.Remove(new ConfigDefinition("Settings", "Show scale text"));
		val.Bind<bool>("UI", "Show enabled text", true, "Obsolete; renamed to Show item restrictions text.");
		val.Remove(new ConfigDefinition("UI", "Show enabled text"));
		val.Bind<bool>("UI", "Show scale text", true, "Obsolete; renamed to Show portal scale text.");
		val.Remove(new ConfigDefinition("UI", "Show scale text"));
		val.Bind<bool>("UI", "Show item restrictions text", false, "Show Item restrictions: Enabled or Item restrictions: Disabled below the vanilla portal text. Local only; never synchronized.").Value = result.ShowEnabledText;
		val.Bind<bool>("UI", "Show portal scale text", false, "Show Scale: xx% below the vanilla portal text. Local only; never synchronized.").Value = result.ShowScaleText;
		val.Save();
		return result;
	}

	internal static Settings Read(string path, bool upgradeLegacyScale = false)
	{
		return Settings.Parse(ReadText(path), upgradeLegacyScale);
	}

	private static string ReadText(string path)
	{
		if (new FileInfo(path).Length > 65536)
		{
			throw new FormatException("Config exceeds 64 KiB.");
		}
		return File.ReadAllText(path);
	}
}
[BepInPlugin("dannanboi.stoneportalcontrol", "Stone Portal Control", "1.0.4")]
public sealed class Plugin : BaseUnityPlugin
{
	[HarmonyPatch(typeof(ObjectDB), "Awake")]
	private static class RecipeDatabaseAwake
	{
		private static void Postfix()
		{
			Instance.RecipeAssetsReady();
		}
	}

	[HarmonyPatch(typeof(ObjectDB), "CopyOtherDB")]
	private static class RecipeDatabaseCopy
	{
		private static void Postfix()
		{
			Instance.RecipeAssetsReady();
		}
	}

	[HarmonyPatch(typeof(ZNetScene), "Awake")]
	private static class RecipeSceneAwake
	{
		private static void Postfix()
		{
			Instance.RefreshRecipeRules();
		}
	}

	private sealed class Connection
	{
		internal ZNetPeer Peer;

		internal string Version;

		internal float StartedAt;

		internal bool Rejected;

		internal float CloseAt;
	}

	[HarmonyPatch(typeof(ZNet), "OnNewConnection")]
	private static class NewConnection
	{
		[HarmonyPriority(800)]
		private static void Prefix(ZNet __instance, ZNetPeer peer)
		{
			Instance.RegisterConnection(__instance, peer);
		}
	}

	[HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")]
	private static class PeerInfo
	{
		[HarmonyPriority(800)]
		private static bool Prefix(ZRpc rpc)
		{
			return Instance.GatePeerInfo(rpc);
		}
	}

	[HarmonyPatch(typeof(FejdStartup), "ShowConnectError")]
	private static class ShowError
	{
		private static void Postfix(FejdStartup __instance)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Invalid comparison between Unknown and I4
			if (connectionError != null && (int)ZNet.GetConnectionStatus() == 3)
			{
				object? value = ErrorText.GetValue(__instance);
				TMP_Text val = (TMP_Text)((value is TMP_Text) ? value : null);
				if ((Object)(object)val != (Object)null)
				{
					val.text = connectionError;
				}
			}
		}
	}

	[HarmonyPatch(typeof(ZNet), "Awake")]
	private static class NetworkAwake
	{
		private static void Postfix(ZNet __instance)
		{
			Instance.BeginNetwork(__instance);
		}
	}

	private sealed class PortalState
	{
		internal TeleportWorld Portal;

		internal Vector3 OriginalScale;

		internal bool OriginalAllowAll;

		internal string HoverVanilla;

		internal string HoverStatus;

		internal string HoverCombined;
	}

	[HarmonyPatch(typeof(TeleportWorld), "Awake")]
	private static class PortalAwake
	{
		private static void Postfix(TeleportWorld __instance)
		{
			Instance.TrackPortal(__instance);
		}
	}

	[HarmonyPatch(typeof(ZNetView), "SetLocalScale")]
	private static class NetworkScaleChanged
	{
		private static void Postfix(ZNetView __instance)
		{
			TeleportWorld component = ((Component)__instance).GetComponent<TeleportWorld>();
			if (Object.op_Implicit((Object)(object)component))
			{
				Instance.PortalStarted(component);
			}
		}
	}

	[HarmonyPatch(typeof(TeleportWorld), "Teleport")]
	private static class PortalTeleport
	{
		[HarmonyPriority(800)]
		private static bool Prefix(TeleportWorld __instance, Player player)
		{
			Plugin instance = Instance;
			if (!IsStone(__instance))
			{
				return true;
			}
			instance.TrackPortal(__instance);
			if (Object.op_Implicit((Object)(object)instance.network) && !instance.network.IsServer() && !instance.settingsReady)
			{
				return false;
			}
			if (!instance.settingsReady || !instance.current.ItemRestrictions || !Object.op_Implicit((Object)(object)player))
			{
				return true;
			}
			AllowAll.SetValue(__instance, false);
			foreach (ItemData allItem in ((Humanoid)player).GetInventory().GetAllItems())
			{
				if (allItem.m_shared == null || allItem.m_shared.m_teleportable)
				{
					continue;
				}
				((Character)player).Message((MessageType)2, "$msg_noteleport", 0, (Sprite)null, false);
				return false;
			}
			return true;
		}
	}

	[HarmonyPatch(typeof(TeleportWorld), "GetHoverText")]
	private static class PortalHover
	{
		private static void Postfix(TeleportWorld __instance, ref string __result)
		{
			Plugin instance = Instance;
			if ((Object)(object)instance == (Object)null || !Object.op_Implicit((Object)(object)__instance))
			{
				return;
			}
			string text = instance.SettingsStatus();
			if (string.IsNullOrEmpty(text))
			{
				return;
			}
			if (!instance.portals.TryGetValue(__instance, out var value))
			{
				instance.TrackPortal(__instance);
				if (!instance.portals.TryGetValue(__instance, out value))
				{
					return;
				}
			}
			if (value.HoverCombined == null || value.HoverVanilla != __result || value.HoverStatus != text)
			{
				value.HoverVanilla = __result;
				value.HoverStatus = text;
				value.HoverCombined = __result + "\n" + text;
			}
			__result = value.HoverCombined;
		}
	}

	[HarmonyPatch(typeof(Player), "AddKnownPiece")]
	private static class RecipeDiscovery
	{
		private static bool Prefix(Player __instance, Piece __0)
		{
			if (!IsStonePiece(__0))
			{
				return true;
			}
			Instance.RememberStoneRecipe(__0);
			if (!Instance.BlockStoneRecipe)
			{
				return true;
			}
			Instance.PurgeKnownStoneRecipe(__instance);
			return false;
		}
	}

	[HarmonyPatch(typeof(Player), "Load")]
	private static class RecipeOnLoad
	{
		private static void Postfix(Player __instance)
		{
			Instance.PurgeKnownStoneRecipe(__instance);
		}
	}

	[HarmonyPatch(typeof(Player), "SetLocalPlayer")]
	private static class RecipeOnLocalPlayer
	{
		private static void Postfix()
		{
			Instance.RefreshRecipeRules();
		}
	}

	[HarmonyPatch(typeof(Player), "UpdateKnownRecipesList")]
	private static class RecipeKnownRefresh
	{
		private static void Prefix()
		{
			Instance.ApplyRecipeSettings();
		}

		private static void Postfix(Player __instance)
		{
			Instance.PurgeKnownStoneRecipe(__instance);
		}
	}

	[HarmonyPatch(typeof(PieceTable), "UpdateAvailable")]
	private static class RecipeBuildMenu
	{
		private static void Postfix(PieceTable __instance)
		{
			Instance.FilterStoneRecipe(__instance);
		}
	}

	[HarmonyPatch(typeof(Player), "TryPlacePiece")]
	private static class RecipePlacement
	{
		[HarmonyPriority(800)]
		private static bool Prefix(Piece __0, ref bool __result)
		{
			if (!IsStonePiece(__0))
			{
				return true;
			}
			if (!Instance.BlockStoneRecipe)
			{
				if (Instance.current.UseCustomRecipe && Instance.installedRecipe != null)
				{
					__0.m_resources = Instance.installedRecipe;
				}
				return true;
			}
			__result = false;
			return false;
		}
	}

	private static readonly FieldInfo PlacementGhost = AccessTools.Field(typeof(Player), "m_placementGhost");

	private Piece recipePrefab;

	private Requirement[] originalRecipe;

	private Requirement[] installedRecipe;

	private Requirement[] resolvedRecipe;

	private ObjectDB recipeDatabase;

	private string resolvedRecipeText;

	private string lastRecipeError;

	private ObjectDB failedRecipeDatabase;

	private int failedRecipeItemCount;

	private string failedRecipeText;

	private FormatException failedRecipeError;

	private bool customRecipeReady;

	private const string HelloRpc = "dannanboi.stoneportalcontrol.Hello";

	private const string SettingsRpc = "dannanboi.stoneportalcontrol.Settings";

	private const string RejectedRpc = "dannanboi.stoneportalcontrol.Rejected";

	private const int Protocol = 5;

	private static string connectionError;

	private static readonly FieldInfo ConnectionStatus = AccessTools.Field(typeof(ZNet), "m_connectionStatus");

	private static readonly FieldInfo ErrorText = AccessTools.Field(typeof(FejdStartup), "m_connectionFailedError");

	private readonly Dictionary<ZRpc, Connection> connections = new Dictionary<ZRpc, Connection>();

	private readonly List<ZRpc> removedConnections = new List<ZRpc>();

	public const string Guid = "dannanboi.stoneportalcontrol";

	public const string ModName = "Stone Portal Control";

	public const string PluginVersion = "1.0.4";

	public const string ModVersion = "1.0.4";

	internal static Plugin Instance;

	private Harmony harmony;

	private ZNet network;

	private bool authority;

	private bool settingsReady;

	private Settings current = new Settings(restrictions: false, 1f);

	private Settings localSettings = new Settings(restrictions: true, 1f);

	private string configPath;

	private FileInfo configInfo;

	private DateTime lastConfigWrite;

	private long lastConfigLength = -1L;

	private string lastConfigError;

	private string statusText;

	private float nextPoll;

	private float nextPeerCheck;

	private bool restartNotice;

	private static readonly FieldInfo AllowAll = AccessTools.Field(typeof(TeleportWorld), "m_allowAllItems");

	private readonly Dictionary<TeleportWorld, PortalState> portals = new Dictionary<TeleportWorld, PortalState>();

	private static readonly FieldInfo KnownRecipes = AccessTools.Field(typeof(Player), "m_knownRecipes");

	private static readonly FieldInfo AvailablePieces = AccessTools.Field(typeof(PieceTable), "m_availablePieces");

	private static readonly FieldInfo EnabledPieces = AccessTools.Field(typeof(PieceTable), "m_enabledPieces");

	private static readonly FieldInfo PiecesByCategory = AccessTools.Field(typeof(PieceTable), "m_availablePiecesByCategory");

	private static readonly MethodInfo UpdateKnown = AccessTools.Method(typeof(Player), "UpdateKnownRecipesList", Type.EmptyTypes, (Type[])null);

	private static readonly MethodInfo UpdateAvailable = AccessTools.Method(typeof(Player), "UpdateAvailablePiecesList", Type.EmptyTypes, (Type[])null);

	private static readonly MethodInfo SetupGhost = AccessTools.Method(typeof(Player), "SetupPlacementGhost", Type.EmptyTypes, (Type[])null);

	private static readonly Predicate<Piece> StonePiecePredicate = IsStonePiece;

	private string stoneRecipeName;

	private bool BlockStoneRecipe => !settingsReady || current.DisableStonePortalRecipe || (current.UseCustomRecipe && !customRecipeReady);

	private Requirement[] ResolveCustomRecipe(Settings value)
	{
		//IL_0137: Unknown result type (might be due to invalid IL or missing references)
		//IL_013e: Expected O, but got Unknown
		if (!value.UseCustomRecipe)
		{
			return null;
		}
		ObjectDB instance = ObjectDB.instance;
		if (!Object.op_Implicit((Object)(object)instance) || instance.m_items == null || instance.m_items.Count == 0)
		{
			return null;
		}
		if (HasFailedRecipe(value, instance))
		{
			throw failedRecipeError;
		}
		if (object.ReferenceEquals(recipeDatabase, instance) && resolvedRecipe != null && string.Equals(resolvedRecipeText, value.CustomRecipe, StringComparison.Ordinal))
		{
			return resolvedRecipe;
		}
		RecipeDefinition.Ingredient[] array = RecipeDefinition.Parse(value.CustomRecipe);
		Requirement[] array2 = (Requirement[])(object)new Requirement[array.Length];
		for (int i = 0; i < array.Length; i++)
		{
			GameObject itemPrefab = instance.GetItemPrefab(array[i].Prefab);
			ItemDrop val = ((!Object.op_Implicit((Object)(object)itemPrefab)) ? null : itemPrefab.GetComponent<ItemDrop>());
			if (!Object.op_Implicit((Object)(object)val))
			{
				failedRecipeDatabase = instance;
				failedRecipeItemCount = instance.m_items.Count;
				failedRecipeText = value.CustomRecipe;
				failedRecipeError = new FormatException("Custom recipe item is unavailable: " + array[i].Prefab + ". Use an exact item prefab name installed on the server and all clients.");
				throw failedRecipeError;
			}
			int num = i;
			Requirement val2 = new Requirement();
			val2.m_resItem = val;
			val2.m_amount = array[i].Amount;
			val2.m_amountPerLevel = 0;
			val2.m_recover = true;
			array2[num] = val2;
		}
		recipeDatabase = instance;
		resolvedRecipeText = value.CustomRecipe;
		resolvedRecipe = array2;
		failedRecipeDatabase = null;
		failedRecipeError = null;
		failedRecipeText = null;
		return array2;
	}

	private bool HasFailedRecipe(Settings value, ObjectDB db)
	{
		return Object.op_Implicit((Object)(object)db) && failedRecipeError != null && object.ReferenceEquals(failedRecipeDatabase, db) && db.m_items != null && db.m_items.Count == failedRecipeItemCount && string.Equals(failedRecipeText, value.CustomRecipe, StringComparison.Ordinal);
	}

	private void ReportRecipeFailure(string message)
	{
		customRecipeReady = false;
		if (!(lastRecipeError == message))
		{
			lastRecipeError = message;
			((BaseUnityPlugin)this).Logger.LogWarning((object)("Stone portal construction is blocked until the custom recipe can be applied. " + message));
		}
	}

	private void RestoreCustomRecipe()
	{
		if (Object.op_Implicit((Object)(object)recipePrefab) && object.ReferenceEquals(recipePrefab.m_resources, installedRecipe))
		{
			recipePrefab.m_resources = originalRecipe;
		}
		recipePrefab = null;
		originalRecipe = null;
		installedRecipe = null;
		customRecipeReady = false;
	}

	private void ApplyRecipeSettings()
	{
		if (!settingsReady || !current.UseCustomRecipe)
		{
			RestoreCustomRecipe();
			lastRecipeError = null;
			return;
		}
		try
		{
			if (HasFailedRecipe(current, ObjectDB.instance))
			{
				ReportRecipeFailure(failedRecipeError.Message);
				return;
			}
			Requirement[] array = ResolveCustomRecipe(current);
			GameObject val = ((!Object.op_Implicit((Object)(object)ZNetScene.instance)) ? null : ZNetScene.instance.GetPrefab("portal_stone"));
			Piece val2 = ((!Object.op_Implicit((Object)(object)val)) ? null : val.GetComponent<Piece>());
			if (array == null || !Object.op_Implicit((Object)(object)val2))
			{
				customRecipeReady = false;
				return;
			}
			if (!object.ReferenceEquals(recipePrefab, val2))
			{
				RestoreCustomRecipe();
				recipePrefab = val2;
				originalRecipe = val2.m_resources;
			}
			if (!object.ReferenceEquals(val2.m_resources, array))
			{
				val2.m_resources = array;
			}
			installedRecipe = array;
			customRecipeReady = true;
			lastRecipeError = null;
		}
		catch (Exception ex)
		{
			ReportRecipeFailure(ex.Message);
		}
	}

	private void UpdatePlacementRecipe(Player player)
	{
		object? value = PlacementGhost.GetValue(player);
		GameObject val = (GameObject)((value is GameObject) ? value : null);
		Piece val2 = ((!Object.op_Implicit((Object)(object)val)) ? null : val.GetComponent<Piece>());
		if (IsStonePiece(val2))
		{
			GameObject val3 = ((!Object.op_Implicit((Object)(object)ZNetScene.instance)) ? null : ZNetScene.instance.GetPrefab("portal_stone"));
			Piece val4 = ((!Object.op_Implicit((Object)(object)val3)) ? null : val3.GetComponent<Piece>());
			if (Object.op_Implicit((Object)(object)val4))
			{
				val2.m_resources = val4.m_resources;
			}
		}
	}

	private void RecipeAssetsReady()
	{
		recipeDatabase = null;
		resolvedRecipe = null;
		resolvedRecipeText = null;
		failedRecipeDatabase = null;
		failedRecipeText = null;
		failedRecipeError = null;
		if (authority)
		{
			lastConfigLength = -1L;
		}
		RefreshRecipeRules();
	}

	internal static bool VersionMatches(string received)
	{
		return string.Equals(received, "1.0.4", StringComparison.Ordinal);
	}

	private void RegisterConnection(ZNet net, ZNetPeer peer)
	{
		BeginNetwork(net);
		if (!net.IsServer() && peer.m_server)
		{
			connectionError = null;
			settingsReady = false;
			current = new Settings(restrictions: false, 1f);
			RefreshStatus();
		}
		Connection connection = new Connection();
		connection.Peer = peer;
		connection.StartedAt = Time.realtimeSinceStartup;
		Connection value = connection;
		connections[peer.m_rpc] = value;
		peer.m_rpc.Register<string>("dannanboi.stoneportalcontrol.Hello", (Action<ZRpc, string>)ReceiveHello);
		peer.m_rpc.Register<ZPackage>("dannanboi.stoneportalcontrol.Settings", (Action<ZRpc, ZPackage>)ReceiveSettings);
		peer.m_rpc.Register<string>("dannanboi.stoneportalcontrol.Rejected", (Action<ZRpc, string>)ReceiveRejection);
		peer.m_rpc.Invoke("dannanboi.stoneportalcontrol.Hello", new object[1] { "1.0.4" });
	}

	private bool IsServerConnection(ZRpc rpc)
	{
		Connection value;
		return Object.op_Implicit((Object)(object)network) && !network.IsServer() && connections.TryGetValue(rpc, out value) && value.Peer.m_server;
	}

	private void ReceiveHello(ZRpc rpc, string version)
	{
		if (!Object.op_Implicit((Object)(object)network) || !connections.TryGetValue(rpc, out var value) || value.Rejected || value.Version != null)
		{
			return;
		}
		value.Version = version ?? string.Empty;
		if (!VersionMatches(version))
		{
			if (network.IsServer())
			{
				Reject(rpc, value, Requirement(version));
			}
			else if (IsServerConnection(rpc))
			{
				FailClient("This server requires Stone Portal Control " + SafeVersion(version) + ". You have 1.0.4. Install the matching version to join.");
			}
		}
		else if (authority)
		{
			SendSettings(rpc);
		}
	}

	private static string SafeVersion(string value)
	{
		if (string.IsNullOrEmpty(value) || value.Length > 32)
		{
			return "(unknown version)";
		}
		foreach (char c in value)
		{
			if (!char.IsLetterOrDigit(c) && c != '.' && c != '-' && c != '+' && c != '_')
			{
				return "(unknown version)";
			}
		}
		return value;
	}

	private static string Requirement(string found)
	{
		return "This server requires Stone Portal Control 1.0.4. " + ((!string.IsNullOrEmpty(found)) ? ("Your version is " + SafeVersion(found) + ". ") : "The mod is missing. ") + "Install the matching version to join.";
	}

	private void Reject(ZRpc rpc, Connection connection, string message)
	{
		if (!connection.Rejected)
		{
			connection.Rejected = true;
			connection.CloseAt = Time.realtimeSinceStartup + 2f;
			((BaseUnityPlugin)this).Logger.LogWarning((object)message);
			bool flag = connection.Version != null;
			if (flag)
			{
				rpc.Invoke("dannanboi.stoneportalcontrol.Rejected", new object[1] { message });
			}
			rpc.Invoke("Error", new object[1] { (!flag) ? 5 : 3 });
		}
	}

	private void ReceiveRejection(ZRpc rpc, string message)
	{
		if (IsServerConnection(rpc) && !connections[rpc].Rejected)
		{
			if (string.IsNullOrEmpty(message) || message.Length > 512)
			{
				message = Requirement(null);
			}
			FailClient(message.Replace("<", string.Empty).Replace(">", string.Empty));
		}
	}

	private void FailClient(string message)
	{
		connectionError = message;
		settingsReady = false;
		RefreshStatus();
		foreach (Connection value in connections.Values)
		{
			value.Rejected = true;
			value.CloseAt = Time.realtimeSinceStartup;
		}
		ConnectionStatus.SetValue(null, (object)(ConnectionStatus)3);
		((BaseUnityPlugin)this).Logger.LogError((object)message);
	}

	private bool GatePeerInfo(ZRpc rpc)
	{
		if (!connections.TryGetValue(rpc, out var value))
		{
			if (Object.op_Implicit((Object)(object)network) && network.IsServer())
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)(Requirement(null) + " Connection registration was not verified."));
				rpc.Invoke("Error", new object[1] { 5 });
			}
			else
			{
				FailClient("The connection could not verify Stone Portal Control 1.0.4.");
			}
			return false;
		}
		if (value.Rejected)
		{
			return false;
		}
		if (VersionMatches(value.Version) && (network.IsServer() || settingsReady))
		{
			return true;
		}
		if (network.IsServer())
		{
			Reject(rpc, value, Requirement(value.Version));
		}
		else if (connectionError == null)
		{
			FailClient("The server must also run Stone Portal Control 1.0.4 and send its settings before you can join.");
		}
		return false;
	}

	private void SendSettings(ZRpc rpc)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0006: Expected O, but got Unknown
		ZPackage val = new ZPackage();
		val.Write(5);
		val.Write(current.ItemRestrictions);
		val.Write(current.Scale);
		val.Write(current.DisableStonePortalRecipe);
		val.Write(current.UseCustomRecipe);
		val.Write(current.CustomRecipe);
		rpc.Invoke("dannanboi.stoneportalcontrol.Settings", new object[1] { val });
	}

	private void BroadcastSettings()
	{
		if (!Object.op_Implicit((Object)(object)network) || !network.IsServer() || connections.Count == 0)
		{
			return;
		}
		foreach (KeyValuePair<ZRpc, Connection> connection in connections)
		{
			if (VersionMatches(connection.Value.Version) && !connection.Value.Rejected && connection.Key.IsConnected())
			{
				SendSettings(connection.Key);
			}
		}
	}

	private void ReceiveSettings(ZRpc rpc, ZPackage data)
	{
		if (!IsServerConnection(rpc))
		{
			return;
		}
		Connection connection = connections[rpc];
		if (!VersionMatches(connection.Version) || connection.Rejected)
		{
			return;
		}
		try
		{
			if (data.ReadInt() != 5)
			{
				throw new FormatException("Settings protocol mismatch.");
			}
			bool flag = data.ReadBool();
			float num = data.ReadSingle();
			bool flag2 = data.ReadBool();
			bool flag3 = data.ReadBool();
			string text = data.ReadString();
			if (!Settings.ValidScale(num))
			{
				throw new FormatException("Invalid portal scale from server.");
			}
			RecipeDefinition.Validate(text, flag3);
			ref Settings reference = ref localSettings;
			bool restrictions = flag;
			float scale = num;
			bool disableStonePortalRecipe = flag2;
			bool useCustomRecipe = flag3;
			Settings settings = reference.WithGameplayFrom(new Settings(restrictions, scale, showEnabledText: false, showScaleText: false, disableStonePortalRecipe, modEnabled: true, useCustomRecipe, text));
			if (settingsReady && current.SameGameplayAs(settings))
			{
				return;
			}
			ResolveCustomRecipe(settings);
			bool flag4 = !settingsReady || current.ItemRestrictions != flag || current.Scale != num;
			bool flag5 = !settingsReady || !current.SameActiveRecipeAs(settings);
			current = settings;
			settingsReady = true;
			if (flag4)
			{
				foreach (PortalState value in portals.Values)
				{
					ApplyPortal(value);
				}
			}
			if (flag5)
			{
				RefreshRecipeRules();
			}
			LogSettings();
		}
		catch (Exception ex)
		{
			FailClient("Stone Portal Control 1.0.4: " + ex.Message);
		}
	}

	private void PumpConnections(float now)
	{
		if (connections.Count == 0)
		{
			return;
		}
		removedConnections.Clear();
		bool flag = network.IsServer();
		foreach (KeyValuePair<ZRpc, Connection> connection in connections)
		{
			Connection value = connection.Value;
			if (!connection.Key.IsConnected())
			{
				removedConnections.Add(connection.Key);
				continue;
			}
			if (flag && !value.Rejected && value.Version == null && now - value.StartedAt > 30f)
			{
				Reject(connection.Key, value, Requirement(null));
			}
			else if (!flag && value.Peer.m_server && !settingsReady && connectionError == null && now - value.StartedAt > 30f)
			{
				FailClient("The server did not complete the Stone Portal Control 1.0.4 handshake.");
			}
			if (value.Rejected && now >= value.CloseAt)
			{
				network.Disconnect(value.Peer);
				removedConnections.Add(connection.Key);
			}
		}
		foreach (ZRpc removedConnection in removedConnections)
		{
			connections.Remove(removedConnection);
		}
		removedConnections.Clear();
	}

	private static void ValidateApi()
	{
		ValidateRecipeApi();
		if (AllowAll == null || AllowAll.FieldType != typeof(bool) || ConnectionStatus == null || ErrorText == null)
		{
			throw new MissingMemberException("This Valheim build is incompatible with Stone Portal Control.");
		}
		if (AccessTools.Method(typeof(ZNet), "OnNewConnection", new Type[1] { typeof(ZNetPeer) }, (Type[])null) == null || AccessTools.Method(typeof(ZNet), "RPC_PeerInfo", new Type[2]
		{
			typeof(ZRpc),
			typeof(ZPackage)
		}, (Type[])null) == null || AccessTools.Method(typeof(TeleportWorld), "Teleport", new Type[1] { typeof(Player) }, (Type[])null) == null)
		{
			throw new MissingMethodException("This Valheim build is incompatible with Stone Portal Control.");
		}
	}

	private void Awake()
	{
		//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e7: Expected O, but got Unknown
		((BaseUnityPlugin)this).Config.SaveOnConfigSet = false;
		configPath = ConfigStorage.GetPath(Paths.ConfigPath, "dannanboi.stoneportalcontrol");
		try
		{
			localSettings = ConfigStorage.Initialize(Paths.ConfigPath, "dannanboi.stoneportalcontrol", "Stone Portal Control", "1.0.4");
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)this).Logger.LogError((object)("Cannot initialize config; plugin disabled until the file is corrected and the game/server restarted: " + ex.Message));
			((Behaviour)this).enabled = false;
			return;
		}
		if (!localSettings.ModEnabled)
		{
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Stone Portal Control is disabled. Restart after enabling it in the config.");
			((Behaviour)this).enabled = false;
			return;
		}
		Instance = this;
		configInfo = new FileInfo(configPath);
		authority = true;
		current = localSettings;
		settingsReady = true;
		PollConfig();
		RefreshStatus();
		ValidateApi();
		harmony = new Harmony("dannanboi.stoneportalcontrol");
		harmony.PatchAll(typeof(Plugin).Assembly);
		((BaseUnityPlugin)this).Logger.LogInfo((object)"Stone Portal Control 1.0.4 loaded. Local settings apply offline; multiplayer settings are locked to the server.");
	}

	private void BeginNetwork(ZNet value)
	{
		if (!object.ReferenceEquals(network, value))
		{
			RestorePortals();
			RestoreCustomRecipe();
			connections.Clear();
			network = value;
			authority = !Object.op_Implicit((Object)(object)value) || value.IsServer();
			settingsReady = false;
			current = new Settings(restrictions: false, 1f);
			if (authority)
			{
				current = localSettings;
				settingsReady = true;
				lastConfigLength = -1L;
				PollConfig();
			}
			RefreshRecipeRules();
			LogSettings();
			nextPoll = 0f;
			nextPeerCheck = 0f;
		}
	}

	private void Update()
	{
		if (!object.ReferenceEquals(network, ZNet.instance))
		{
			BeginNetwork(ZNet.instance);
		}
		float realtimeSinceStartup = Time.realtimeSinceStartup;
		if (realtimeSinceStartup >= nextPoll)
		{
			nextPoll = realtimeSinceStartup + 1f;
			PollConfig();
		}
		if (Object.op_Implicit((Object)(object)network) && realtimeSinceStartup >= nextPeerCheck)
		{
			nextPeerCheck = realtimeSinceStartup + 1f;
			PumpConnections(realtimeSinceStartup);
		}
	}

	private void PollConfig()
	{
		if (configInfo == null)
		{
			return;
		}
		try
		{
			FileInfo fileInfo = configInfo;
			fileInfo.Refresh();
			if (!fileInfo.Exists)
			{
				throw new FileNotFoundException("Config file is missing.");
			}
			if (fileInfo.LastWriteTimeUtc == lastConfigWrite && fileInfo.Length == lastConfigLength)
			{
				return;
			}
			Settings value;
			try
			{
				value = ConfigStorage.Read(configPath);
				if (authority)
				{
					ResolveCustomRecipe(value);
				}
			}
			catch (FormatException)
			{
				lastConfigWrite = fileInfo.LastWriteTimeUtc;
				lastConfigLength = fileInfo.Length;
				throw;
			}
			lastConfigWrite = fileInfo.LastWriteTimeUtc;
			lastConfigLength = fileInfo.Length;
			lastConfigError = null;
			Settings settings = current;
			bool flag = localSettings.ShowEnabledText != value.ShowEnabledText || localSettings.ShowScaleText != value.ShowScaleText;
			localSettings = value;
			if (!value.ModEnabled && !restartNotice)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Enabled changed to false. Restart the game/server to disable the plugin completely.");
			}
			restartNotice = !value.ModEnabled;
			if (authority)
			{
				current = value;
			}
			else
			{
				current = value.WithGameplayFrom(current);
			}
			bool flag2 = authority && !settings.SameGameplayAs(current);
			if (flag2)
			{
				if (settings.ItemRestrictions != current.ItemRestrictions || settings.Scale != current.Scale)
				{
					foreach (PortalState value2 in portals.Values)
					{
						ApplyPortal(value2);
					}
				}
				if (!settings.SameActiveRecipeAs(current))
				{
					RefreshRecipeRules();
				}
				BroadcastSettings();
			}
			if (flag2 || flag)
			{
				LogSettings();
			}
		}
		catch (Exception ex2)
		{
			if (ex2 is IOException)
			{
				lastConfigLength = -1L;
			}
			if (!(lastConfigError == ex2.Message))
			{
				lastConfigError = ex2.Message;
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Config not applied; retaining previous values. " + ex2.Message));
			}
		}
	}

	private string SettingsStatus()
	{
		return statusText;
	}

	private void RefreshStatus()
	{
		statusText = string.Empty;
		if (settingsReady)
		{
			if (localSettings.ShowEnabledText)
			{
				statusText = "Item restrictions: " + ((!current.ItemRestrictions) ? "Disabled" : "Enabled");
			}
			if (localSettings.ShowScaleText)
			{
				string text = statusText;
				statusText = text + ((statusText.Length != 0) ? "\n" : string.Empty) + "Scale: " + (current.Scale * 100f).ToString("0.#", CultureInfo.InvariantCulture) + "%";
			}
		}
	}

	private void LogSettings()
	{
		RefreshStatus();
		((BaseUnityPlugin)this).Logger.LogInfo((object)("Item restrictions: " + ((!current.ItemRestrictions) ? "Disabled" : "Enabled") + "; Scale: " + (current.Scale * 100f).ToString("0.#", CultureInfo.InvariantCulture) + "%; Show item restrictions text=" + localSettings.ShowEnabledText + "; Show portal scale text=" + localSettings.ShowScaleText + "; Disable Stone Portal Recipe=" + current.DisableStonePortalRecipe + "; Use custom recipe=" + current.UseCustomRecipe + " (gameplay: " + ((!authority) ? "server locked" : "local settings") + "; UI: local)"));
	}

	private void OnDestroy()
	{
		if (harmony != null)
		{
			RestorePortals();
			RestoreCustomRecipe();
			harmony.UnpatchSelf();
			settingsReady = true;
			current = new Settings(restrictions: false, 1f);
			RefreshRecipeRules();
			if ((Object)(object)Instance == (Object)(object)this)
			{
				Instance = null;
			}
		}
	}

	private static bool IsStone(TeleportWorld portal)
	{
		if (!Object.op_Implicit((Object)(object)portal))
		{
			return false;
		}
		string name = ((Object)((Component)portal).gameObject).name;
		return name == "portal_stone" || name.StartsWith("portal_stone(", StringComparison.Ordinal);
	}

	private void TrackPortal(TeleportWorld portal)
	{
		//IL_002d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0032: Unknown result type (might be due to invalid IL or missing references)
		//IL_0054: Unknown result type (might be due to invalid IL or missing references)
		//IL_0059: Unknown result type (might be due to invalid IL or missing references)
		if (!Object.op_Implicit((Object)(object)portal) || portals.ContainsKey(portal) || !IsStone(portal))
		{
			return;
		}
		Scene scene = ((Component)portal).gameObject.scene;
		if (((Scene)(ref scene)).IsValid())
		{
			PortalState portalState = new PortalState();
			portalState.Portal = portal;
			portalState.OriginalScale = ((Component)portal).transform.localScale;
			portalState.OriginalAllowAll = (bool)AllowAll.GetValue(portal);
			PortalState portalState2 = portalState;
			portals.Add(portal, portalState2);
			PortalLifetime portalLifetime = ((Component)portal).GetComponent<PortalLifetime>();
			if (!Object.op_Implicit((Object)(object)portalLifetime))
			{
				portalLifetime = ((Component)portal).gameObject.AddComponent<PortalLifetime>();
			}
			portalLifetime.Initialize(portal);
			ApplyPortal(portalState2);
		}
	}

	private void ApplyPortal(PortalState state)
	{
		//IL_0012: Unknown result type (might be due to invalid IL or missing references)
		//IL_0037: Unknown result type (might be due to invalid IL or missing references)
		//IL_003c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0048: Unknown result type (might be due to invalid IL or missing references)
		//IL_004d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0063: Unknown result type (might be due to invalid IL or missing references)
		if (Object.op_Implicit((Object)(object)state.Portal))
		{
			Vector3 val = state.OriginalScale * ((!settingsReady) ? 1f : current.Scale);
			if (((Component)state.Portal).transform.localScale != val)
			{
				((Component)state.Portal).transform.localScale = val;
			}
			bool flag = (!settingsReady || !current.ItemRestrictions) && state.OriginalAllowAll;
			if ((bool)AllowAll.GetValue(state.Portal) != flag)
			{
				AllowAll.SetValue(state.Portal, flag);
			}
		}
	}

	internal void PortalStarted(TeleportWorld portal)
	{
		if (Object.op_Implicit((Object)(object)portal))
		{
			if (portals.TryGetValue(portal, out var value))
			{
				ApplyPortal(value);
			}
			else
			{
				TrackPortal(portal);
			}
		}
	}

	internal void UntrackPortal(TeleportWorld portal)
	{
		if (!object.ReferenceEquals(portal, null))
		{
			portals.Remove(portal);
		}
	}

	private void RestorePortals()
	{
		//IL_003f: Unknown result type (might be due to invalid IL or missing references)
		foreach (PortalState value in portals.Values)
		{
			if (Object.op_Implicit((Object)(object)value.Portal))
			{
				((Component)value.Portal).transform.localScale = value.OriginalScale;
				AllowAll.SetValue(value.Portal, value.OriginalAllowAll);
			}
		}
		portals.Clear();
	}

	private static bool IsStonePiece(Piece piece)
	{
		if (!Object.op_Implicit((Object)(object)piece))
		{
			return false;
		}
		string name = ((Object)((Component)piece).gameObject).name;
		return name == "portal_stone" || name.StartsWith("portal_stone(", StringComparison.Ordinal);
	}

	private void RememberStoneRecipe(Piece piece)
	{
		if (Object.op_Implicit((Object)(object)piece) && !string.IsNullOrEmpty(piece.m_name))
		{
			stoneRecipeName = piece.m_name;
		}
	}

	private void PurgeKnownStoneRecipe(Player player)
	{
		if (!Object.op_Implicit((Object)(object)player) || !BlockStoneRecipe)
		{
			return;
		}
		if (string.IsNullOrEmpty(stoneRecipeName) && Object.op_Implicit((Object)(object)ZNetScene.instance))
		{
			GameObject prefab = ZNetScene.instance.GetPrefab("portal_stone");
			if (Object.op_Implicit((Object)(object)prefab))
			{
				RememberStoneRecipe(prefab.GetComponent<Piece>());
			}
		}
		if (!string.IsNullOrEmpty(stoneRecipeName))
		{
			((HashSet<string>)KnownRecipes.GetValue(player)).Remove(stoneRecipeName);
		}
	}

	private void RefreshRecipeRules()
	{
		ApplyRecipeSettings();
		Player localPlayer = Player.m_localPlayer;
		if (Object.op_Implicit((Object)(object)localPlayer))
		{
			if (BlockStoneRecipe)
			{
				PurgeKnownStoneRecipe(localPlayer);
			}
			else
			{
				UpdateKnown.Invoke(localPlayer, null);
			}
			UpdateAvailable.Invoke(localPlayer, null);
			SetupGhost.Invoke(localPlayer, null);
			UpdatePlacementRecipe(localPlayer);
		}
	}

	private void FilterStoneRecipe(PieceTable table)
	{
		if (!BlockStoneRecipe)
		{
			return;
		}
		HashSet<Piece> hashSet = (HashSet<Piece>)AvailablePieces.GetValue(table);
		HashSet<Piece> hashSet2 = (HashSet<Piece>)EnabledPieces.GetValue(table);
		hashSet?.RemoveWhere(StonePiecePredicate);
		hashSet2?.RemoveWhere(StonePiecePredicate);
		List<List<Piece>> list = (List<List<Piece>>)PiecesByCategory.GetValue(table);
		if (list == null)
		{
			return;
		}
		foreach (List<Piece> item in list)
		{
			item?.RemoveAll(StonePiecePredicate);
		}
	}

	private static void ValidateRecipeApi()
	{
		if (KnownRecipes == null || KnownRecipes.FieldType != typeof(HashSet<string>) || AvailablePieces == null || AvailablePieces.FieldType != typeof(HashSet<Piece>) || EnabledPieces == null || EnabledPieces.FieldType != typeof(HashSet<Piece>) || PiecesByCategory == null || PiecesByCategory.FieldType != typeof(List<List<Piece>>) || UpdateKnown == null || UpdateAvailable == null || SetupGhost == null || PlacementGhost == null || PlacementGhost.FieldType != typeof(GameObject) || AccessTools.Method(typeof(ObjectDB), "Awake", Type.EmptyTypes, (Type[])null) == null || AccessTools.Method(typeof(ObjectDB), "CopyOtherDB", new Type[1] { typeof(ObjectDB) }, (Type[])null) == null || AccessTools.Method(typeof(ZNetScene), "Awake", Type.EmptyTypes, (Type[])null) == null)
		{
			throw new MissingMemberException("This Valheim build's recipe API is incompatible with Stone Portal Control.");
		}
	}
}
internal sealed class PortalLifetime : MonoBehaviour
{
	private TeleportWorld portal;

	internal void Initialize(TeleportWorld value)
	{
		portal = value;
	}

	private IEnumerator Start()
	{
		yield return null;
		if ((Object)(object)Plugin.Instance != (Object)null && Object.op_Implicit((Object)(object)portal))
		{
			Plugin.Instance.PortalStarted(portal);
		}
	}

	private void OnDestroy()
	{
		if ((Object)(object)Plugin.Instance != (Object)null)
		{
			Plugin.Instance.UntrackPortal(portal);
		}
	}
}
internal static class RecipeDefinition
{
	internal struct Ingredient
	{
		internal readonly string Prefab;

		internal readonly int Amount;

		internal Ingredient(string prefab, int amount)
		{
			Prefab = prefab;
			Amount = amount;
		}
	}

	internal const string Default = "GreydwarfEye[10],Grausten[30],MoltenCore[2]";

	internal const int MaxLength = 2048;

	internal const int MaxIngredients = 32;

	private static readonly char[] InvalidNameCharacters = new char[3] { '\r', '\n', '=' };

	internal static void Validate(string text, bool enabled)
	{
		if (text == null || text.Length > 2048)
		{
			throw new FormatException("Custom recipe must be at most " + 2048 + " characters.");
		}
		if (enabled)
		{
			Parse(text);
		}
	}

	internal static Ingredient[] Parse(string text)
	{
		if (string.IsNullOrWhiteSpace(text) || text.Length > 2048)
		{
			throw new FormatException("Custom recipe requires Item1[amount],Item2[amount] entries.");
		}
		string[] array = text.Split(',');
		if (array.Length > 32)
		{
			throw new FormatException("Custom recipe supports at most " + 32 + " ingredients.");
		}
		Ingredient[] array2 = new Ingredient[array.Length];
		HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
		for (int i = 0; i < array.Length; i++)
		{
			string text2 = array[i].Trim();
			int num = text2.IndexOf('[');
			if (num <= 0 || !text2.EndsWith("]", StringComparison.Ordinal) || text2.IndexOf('[', num + 1) >= 0 || text2.IndexOf(']') != text2.Length - 1)
			{
				throw new FormatException("Invalid custom recipe entry: " + text2 + ". Use Item[amount].");
			}
			string text3 = text2.Substring(0, num).Trim();
			if (text3.Length == 0 || text3.Length > 128 || text3.IndexOfAny(InvalidNameCharacters) >= 0 || !int.TryParse(text2.Substring(num + 1, text2.Length - num - 2).Trim(), NumberStyles.None, CultureInfo.InvariantCulture, out var result) || result < 1)
			{
				throw new FormatException("Custom recipe amounts must be positive whole numbers; use exact item prefab names.");
			}
			if (!hashSet.Add(text3))
			{
				throw new FormatException("Duplicate custom recipe item: " + text3);
			}
			ref Ingredient reference = ref array2[i];
			reference = new Ingredient(text3, result);
		}
		return array2;
	}
}
internal struct Settings
{
	internal readonly bool ItemRestrictions;

	internal readonly bool DisableStonePortalRecipe;

	internal readonly bool UseCustomRecipe;

	internal readonly string CustomRecipe;

	internal readonly float Scale;

	internal readonly bool ShowEnabledText;

	internal readonly bool ShowScaleText;

	internal readonly bool ModEnabled;

	internal const string DefaultFile = "[General]\nEnabled = true\n\n[Settings]\nItem restrictions = true\nPortal scale = 1\nDisable Stone Portal Recipe = false\nUse custom recipe = false\nCustom recipe = GreydwarfEye[10],Grausten[30],MoltenCore[2]\n\n[UI]\nShow item restrictions text = false\nShow portal scale text = false\n";

	internal Settings(bool restrictions, float scale, bool showEnabledText = false, bool showScaleText = false, bool disableStonePortalRecipe = false, bool modEnabled = true, bool useCustomRecipe = false, string customRecipe = "GreydwarfEye[10],Grausten[30],MoltenCore[2]")
	{
		ItemRestrictions = restrictions;
		Scale = scale;
		ShowEnabledText = showEnabledText;
		ShowScaleText = showScaleText;
		DisableStonePortalRecipe = disableStonePortalRecipe;
		ModEnabled = modEnabled;
		UseCustomRecipe = useCustomRecipe;
		CustomRecipe = customRecipe;
	}

	internal bool SameGameplayAs(Settings other)
	{
		return ItemRestrictions == other.ItemRestrictions && Scale == other.Scale && DisableStonePortalRecipe == other.DisableStonePortalRecipe && UseCustomRecipe == other.UseCustomRecipe && string.Equals(CustomRecipe, other.CustomRecipe, StringComparison.Ordinal);
	}

	internal bool SameActiveRecipeAs(Settings other)
	{
		return DisableStonePortalRecipe == other.DisableStonePortalRecipe && UseCustomRecipe == other.UseCustomRecipe && (!UseCustomRecipe || string.Equals(CustomRecipe, other.CustomRecipe, StringComparison.Ordinal));
	}

	internal bool SameAs(Settings other)
	{
		return SameGameplayAs(other) && ShowEnabledText == other.ShowEnabledText && ShowScaleText == other.ShowScaleText && ModEnabled == other.ModEnabled;
	}

	internal Settings WithGameplayFrom(Settings server)
	{
		return new Settings(server.ItemRestrictions, server.Scale, ShowEnabledText, ShowScaleText, server.DisableStonePortalRecipe, ModEnabled, server.UseCustomRecipe, server.CustomRecipe);
	}

	internal static bool ValidScale(float scale)
	{
		return !float.IsNaN(scale) && !float.IsInfinity(scale) && scale >= 0.5f && scale <= 1f;
	}

	internal static bool ReadStartupEnabled(string text)
	{
		bool result = true;
		bool flag = false;
		string text2 = string.Empty;
		using StringReader stringReader = new StringReader(text);
		string text3;
		while ((text3 = stringReader.ReadLine()) != null)
		{
			text3 = text3.Trim();
			if (text3.StartsWith("[") && text3.EndsWith("]"))
			{
				text2 = text3.Substring(1, text3.Length - 2).Trim();
				continue;
			}
			int num = text3.IndexOf('=');
			if (!(text2 != "General") && num >= 0 && !(text3.Substring(0, num).Trim() != "Enabled"))
			{
				if (flag || !bool.TryParse(text3.Substring(num + 1).Trim(), out result))
				{
					throw new FormatException("[General] Enabled must be one true/false value.");
				}
				flag = true;
			}
		}
		return result;
	}

	internal static Settings Parse(string text, bool upgradeLegacyScale = false)
	{
		Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.Ordinal);
		string text2 = string.Empty;
		using (StringReader stringReader = new StringReader(text))
		{
			string text3;
			while ((text3 = stringReader.ReadLine()) != null)
			{
				text3 = text3.Trim();
				if (text3.Length == 0 || text3.StartsWith("#") || text3.StartsWith(";"))
				{
					continue;
				}
				if (text3.StartsWith("[") && text3.EndsWith("]"))
				{
					text2 = text3.Substring(1, text3.Length - 2).Trim();
					continue;
				}
				int num = text3.IndexOf('=');
				if (num < 0)
				{
					throw new FormatException("Expected 'name = value'.");
				}
				string text4 = text3.Substring(0, num).Trim();
				string value = text3.Substring(num + 1).Trim();
				if (text2 == "General" && text4 == "Enabled")
				{
					goto IL_01ed;
				}
				if (text2 == "UI")
				{
					switch (text4)
					{
					case "Show item restrictions text":
					case "Show portal scale text":
					case "Show enabled text":
					case "Show scale text":
						goto IL_01ed;
					}
				}
				int num2;
				if (text2 == "Settings")
				{
					switch (text4)
					{
					default:
						num2 = ((text4 == "Hide hover text") ? 1 : 0);
						break;
					case "Item restrictions":
					case "Portal scale":
					case "Disable Stone Portal Recipe":
					case "Use custom recipe":
					case "Custom recipe":
					case "Enabled":
					case "Show enabled text":
					case "Show scale text":
						num2 = 1;
						break;
					}
				}
				else
				{
					num2 = 0;
				}
				goto IL_01ee;
				IL_01ed:
				num2 = 1;
				goto IL_01ee;
				IL_01ee:
				bool flag = (byte)num2 != 0;
				string text5 = text2 + "/" + text4;
				if (!flag || dictionary.ContainsKey(text5))
				{
					throw new FormatException("Unknown or duplicate setting: " + text5);
				}
				dictionary.Add(text5, value);
				if (text4 != "Portal scale" && text4 != "Custom recipe")
				{
					Boolean(value, text5);
				}
			}
		}
		if (!dictionary.TryGetValue("Settings/Item restrictions", out var value2) && !dictionary.TryGetValue("Settings/Enabled", out value2))
		{
			throw new FormatException("Item restrictions is required in [Settings].");
		}
		if (!dictionary.TryGetValue("Settings/Portal scale", out var value3))
		{
			throw new FormatException("Portal scale is required in [Settings].");
		}
		if (!float.TryParse(value3, NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
		{
			throw new FormatException("Portal scale must be from 0.5 to 1, using a decimal point.");
		}
		if (upgradeLegacyScale && result >= 0.1f && result < 0.5f)
		{
			result = 0.5f;
		}
		if (!ValidScale(result))
		{
			throw new FormatException("Portal scale must be from 0.5 to 1, using a decimal point.");
		}
		bool fallback = dictionary.ContainsKey("Settings/Hide hover text") && !GetBoolean(dictionary, "Settings/Hide hover text", fallback: false);
		bool boolean = GetBoolean(dictionary, "Settings/Use custom recipe", fallback: false);
		if (!dictionary.TryGetValue("Settings/Custom recipe", out var value4))
		{
			value4 = "GreydwarfEye[10],Grausten[30],MoltenCore[2]";
		}
		RecipeDefinition.Validate(value4, boolean);
		return new Settings(Boolean(value2, "Item restrictions"), result, GetBoolean(dictionary, "UI/Show item restrictions text", GetBoolean(dictionary, "UI/Show enabled text", GetBoolean(dictionary, "Settings/Show enabled text", fallback))), GetBoolean(dictionary, "UI/Show portal scale text", GetBoolean(dictionary, "UI/Show scale text", GetBoolean(dictionary, "Settings/Show scale text", fallback))), GetBoolean(dictionary, "Settings/Disable Stone Portal Recipe", fallback: false), GetBoolean(dictionary, "General/Enabled", fallback: true), boolean, value4);
	}

	private static bool Boolean(string value, string name)
	{
		if (!bool.TryParse(value, out var result))
		{
			throw new FormatException(name + " must be true or false.");
		}
		return result;
	}

	private static bool GetBoolean(Dictionary<string, string> values, string key, bool fallback)
	{
		string value;
		return (!values.TryGetValue(key, out value)) ? fallback : Boolean(value, key);
	}
}