Decompiled source of SandboxTools v1.0.0

BepInEx\plugins\SandboxTools.dll

Decompiled 2 weeks ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyCompany("SandboxTools")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+5a7893f063ebc01dc2fdd45d41f21693bbde6510")]
[assembly: AssemblyProduct("SandboxTools")]
[assembly: AssemblyTitle("SandboxTools")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace SandboxTools
{
	internal static class DiscoverActions
	{
		internal static bool MuteUnlockMessages;

		public static void Run(ConsoleEventArgs args)
		{
			if (args.Length < 3)
			{
				throw new InvalidOperationException("Usage: sandbox discover all|materials|stations|recipes");
			}
			switch (args[2].ToLowerInvariant())
			{
			case "all":
				DiscoverAll(args);
				break;
			case "materials":
				DiscoverMaterialsOnly(args);
				break;
			case "stations":
				DiscoverStationsOnly(args);
				break;
			case "recipes":
				DiscoverRecipes(args);
				break;
			default:
				throw new InvalidOperationException("Unknown discover target '" + args[2] + "'. Use all, materials, stations, or recipes.");
			}
		}

		private static void DiscoverAll(ConsoleEventArgs args)
		{
			Player player = RequirePlayer();
			ObjectDB odb = RequireObjectDB();
			MuteUnlockMessages = true;
			try
			{
				int num = DiscoverMaterials(player, odb);
				int num2 = DiscoverStations(player, odb);
				GameAccess.RefreshPlayerLists(player);
				string msg = $"Discovered {num} materials, {num2} stations. Known recipes: {GameAccess.KnownRecipes(player).Count}.";
				Reply(args, player, msg);
			}
			finally
			{
				MuteUnlockMessages = false;
			}
		}

		private static void DiscoverMaterialsOnly(ConsoleEventArgs args)
		{
			Player player = RequirePlayer();
			ObjectDB odb = RequireObjectDB();
			MuteUnlockMessages = true;
			try
			{
				int num = DiscoverMaterials(player, odb);
				GameAccess.RefreshPlayerLists(player);
				string msg = $"Discovered {num} materials. Known materials: {GameAccess.KnownMaterials(player).Count}.";
				Reply(args, player, msg);
			}
			finally
			{
				MuteUnlockMessages = false;
			}
		}

		private static void DiscoverStationsOnly(ConsoleEventArgs args)
		{
			Player player = RequirePlayer();
			ObjectDB odb = RequireObjectDB();
			MuteUnlockMessages = true;
			try
			{
				int num = DiscoverStations(player, odb);
				GameAccess.RefreshPlayerLists(player);
				string msg = $"Discovered {num} stations. Known stations: {GameAccess.KnownStations(player).Count}.";
				Reply(args, player, msg);
			}
			finally
			{
				MuteUnlockMessages = false;
			}
		}

		private static void DiscoverRecipes(ConsoleEventArgs args)
		{
			Player player = RequirePlayer();
			ObjectDB odb = RequireObjectDB();
			MuteUnlockMessages = true;
			try
			{
				HashSet<string> hashSet = GameAccess.KnownRecipes(player);
				int count = hashSet.Count;
				int num = 0;
				foreach (Recipe item in GameAccess.Recipes(odb))
				{
					if (!((Object)(object)item == (Object)null) && Object.op_Implicit((Object)(object)item.m_item))
					{
						num++;
						AddKnownRecipe(player, item);
					}
				}
				GameAccess.RefreshPlayerLists(player);
				int num2 = hashSet.Count - count;
				string msg = $"Unlocked {num2} recipes ({num} scanned, {hashSet.Count} known).";
				Reply(args, player, msg);
			}
			finally
			{
				MuteUnlockMessages = false;
			}
		}

		private static int DiscoverMaterials(Player player, ObjectDB odb)
		{
			HashSet<string> hashSet = GameAccess.KnownMaterials(player);
			int count = hashSet.Count;
			foreach (GameObject item in GameAccess.Items(odb))
			{
				if (!Object.op_Implicit((Object)(object)item))
				{
					continue;
				}
				ItemDrop component = item.GetComponent<ItemDrop>();
				if (Object.op_Implicit((Object)(object)component) && component.m_itemData?.m_shared != null)
				{
					string name = component.m_itemData.m_shared.m_name;
					if (!string.IsNullOrEmpty(name))
					{
						hashSet.Add(name);
					}
				}
			}
			return hashSet.Count - count;
		}

		private static int DiscoverStations(Player player, ObjectDB odb)
		{
			HashSet<string> names = new HashSet<string>();
			foreach (Recipe item in GameAccess.Recipes(odb))
			{
				if (!((Object)(object)item == (Object)null))
				{
					AddStation(item.m_craftingStation);
				}
			}
			foreach (GameObject item2 in GameAccess.Items(odb))
			{
				if (!Object.op_Implicit((Object)(object)item2))
				{
					continue;
				}
				AddStation(item2.GetComponent<CraftingStation>());
				PieceTable val = item2.GetComponent<ItemDrop>()?.m_itemData?.m_shared?.m_buildPieces;
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				foreach (GameObject item3 in GameAccess.PieceTablePieces(val))
				{
					if (Object.op_Implicit((Object)(object)item3))
					{
						AddStation(item3.GetComponent<CraftingStation>());
						AddFromPiece(item3.GetComponent<Piece>());
					}
				}
			}
			foreach (GameObject item4 in GameAccess.BuildPieces(odb))
			{
				if (Object.op_Implicit((Object)(object)item4))
				{
					AddStation(item4.GetComponent<CraftingStation>());
					AddFromPiece(item4.GetComponent<Piece>());
				}
			}
			Dictionary<string, int> dictionary = GameAccess.KnownStations(player);
			foreach (string item5 in names)
			{
				if (dictionary.TryGetValue(item5, out var value))
				{
					if (value < 100)
					{
						dictionary[item5] = 100;
					}
				}
				else
				{
					dictionary.Add(item5, 100);
				}
			}
			return names.Count;
			void AddFromPiece(Piece? piece)
			{
				if (Object.op_Implicit((Object)(object)piece))
				{
					AddStation(piece.m_craftingStation);
				}
			}
			void AddStation(CraftingStation? station)
			{
				if (Object.op_Implicit((Object)(object)station) && !string.IsNullOrEmpty(station.m_name))
				{
					names.Add(station.m_name);
				}
			}
		}

		private static void AddKnownRecipe(Player player, Recipe recipe)
		{
			SharedData val = recipe.m_item?.m_itemData?.m_shared;
			if (val != null)
			{
				string name = val.m_name;
				if (!string.IsNullOrEmpty(name))
				{
					GameAccess.KnownRecipes(player).Add(name);
				}
			}
		}

		private static Player RequirePlayer()
		{
			Player localPlayer = Player.m_localPlayer;
			if (!Object.op_Implicit((Object)(object)localPlayer))
			{
				throw new InvalidOperationException("No player.");
			}
			return localPlayer;
		}

		private static ObjectDB RequireObjectDB()
		{
			ObjectDB instance = ObjectDB.instance;
			if (!Object.op_Implicit((Object)(object)instance))
			{
				throw new InvalidOperationException("ObjectDB not ready.");
			}
			return instance;
		}

		private static void Reply(ConsoleEventArgs args, Player player, string msg)
		{
			args.Context.AddString(msg);
			((Character)player).Message((MessageType)1, msg, 0, (Sprite)null, false);
		}
	}
	[HarmonyPatch(typeof(MessageHud), "QueueUnlockMsg")]
	internal static class MuteUnlockMessagesPatch
	{
		private static bool Prefix()
		{
			return !DiscoverActions.MuteUnlockMessages;
		}
	}
	internal static class ExportActions
	{
		public static void Run(ConsoleEventArgs args, ManualLogSource log)
		{
			ExportKind exportKind = ExportKind.All;
			if (args.Length >= 3)
			{
				string text = args[2].ToLowerInvariant();
				if (!(text == "items"))
				{
					if (!(text == "recipes"))
					{
						throw new InvalidOperationException("Usage: sandbox export [items|recipes]");
					}
					exportKind = ExportKind.Recipes;
				}
				else
				{
					exportKind = ExportKind.Items;
				}
			}
			ExportService.Run(log, exportKind);
			string text2 = ((exportKind == ExportKind.All) ? "full" : exportKind.ToString().ToLowerInvariant());
			args.Context.AddString("SandboxTools: " + text2 + " export complete.");
		}
	}
	public static class SandboxCommand
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static ConsoleOptionsFetcher <0>__AutocompleteOptions;
		}

		private static readonly string[] Groups = new string[4] { "discover", "skills", "export", "help" };

		private static readonly string[] DiscoverTargets = new string[4] { "all", "materials", "stations", "recipes" };

		private static readonly string[] ExportTargets = new string[2] { "items", "recipes" };

		private static readonly string[] SkillLevels = new string[3] { "100", "50", "0" };

		private const string Help = "sandbox <group> ...\n  discover all|materials|stations|recipes\n  skills [level=100]\n  export [items|recipes]";

		public static void Register(ManualLogSource log)
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Expected O, but got Unknown
			//IL_0047: 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)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Expected O, but got Unknown
			ConsoleEvent val = delegate(ConsoleEventArgs args)
			{
				try
				{
					Dispatch(args, log);
				}
				catch (InvalidOperationException ex)
				{
					args.Context.AddString(ex.Message);
				}
				catch (Exception ex2)
				{
					log.LogError((object)$"sandbox command failed: {ex2}");
					args.Context.AddString("sandbox failed: " + ex2.Message);
				}
			};
			object obj = <>O.<0>__AutocompleteOptions;
			if (obj == null)
			{
				ConsoleOptionsFetcher val2 = AutocompleteOptions;
				<>O.<0>__AutocompleteOptions = val2;
				obj = (object)val2;
			}
			new ConsoleCommand("sandbox", "SandboxTools cheat helpers. Try: sandbox", val, true, false, false, false, false, false, (ConsoleOptionsFetcher)obj, false, false, false);
		}

		private static void Dispatch(ConsoleEventArgs args, ManualLogSource log)
		{
			if (args.Length < 2)
			{
				args.Context.AddString("sandbox <group> ...\n  discover all|materials|stations|recipes\n  skills [level=100]\n  export [items|recipes]");
				return;
			}
			switch (args[1].ToLowerInvariant())
			{
			case "discover":
				DiscoverActions.Run(args);
				break;
			case "skills":
				SkillsActions.Run(args);
				break;
			case "export":
				ExportActions.Run(args, log);
				break;
			case "help":
			case "?":
				args.Context.AddString("sandbox <group> ...\n  discover all|materials|stations|recipes\n  skills [level=100]\n  export [items|recipes]");
				break;
			default:
				throw new InvalidOperationException("Unknown sandbox group '" + args[1] + "'.\nsandbox <group> ...\n  discover all|materials|stations|recipes\n  skills [level=100]\n  export [items|recipes]");
			}
		}

		private static List<string> AutocompleteOptions()
		{
			string text = ReadConsoleInput().TrimStart(Array.Empty<char>());
			string[] array = SplitKeepingTrailing(text);
			if (array.Length <= 1)
			{
				return Groups.ToList();
			}
			if (array.Length == 2 && !text.EndsWith(" "))
			{
				return FilterPrefix(Groups, array[1]);
			}
			if (array.Length == 2 && text.EndsWith(" "))
			{
				return OptionsForGroup(array[1]).ToList();
			}
			if (array.Length >= 3)
			{
				string prefix = (text.EndsWith(" ") ? "" : array[^1]);
				return FilterPrefix(OptionsForGroup(array[1]), prefix);
			}
			return Groups.ToList();
		}

		private static string[] OptionsForGroup(string group)
		{
			return group.ToLowerInvariant() switch
			{
				"discover" => DiscoverTargets, 
				"export" => ExportTargets, 
				"skills" => SkillLevels, 
				_ => Array.Empty<string>(), 
			};
		}

		private static List<string> FilterPrefix(IEnumerable<string> options, string prefix)
		{
			if (string.IsNullOrEmpty(prefix))
			{
				return options.ToList();
			}
			return options.Where((string o) => o.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)).ToList();
		}

		private static string[] SplitKeepingTrailing(string input)
		{
			if (string.IsNullOrEmpty(input))
			{
				return Array.Empty<string>();
			}
			List<string> list = new List<string>();
			int num = 0;
			for (int i = 0; i < input.Length; i++)
			{
				if (input[i] == ' ')
				{
					list.Add(input.Substring(num, i - num));
					num = i + 1;
				}
			}
			list.Add(input.Substring(num));
			return list.ToArray();
		}

		private static string ReadConsoleInput()
		{
			try
			{
				if (Object.op_Implicit((Object)(object)Console.instance))
				{
					string text = ReadTerminalInput((Terminal)(object)Console.instance);
					if (!string.IsNullOrEmpty(text))
					{
						return text;
					}
				}
			}
			catch
			{
			}
			try
			{
				if (Object.op_Implicit((Object)(object)Chat.instance))
				{
					string text2 = ReadTerminalInput((Terminal)(object)Chat.instance);
					if (!string.IsNullOrEmpty(text2))
					{
						return text2;
					}
				}
			}
			catch
			{
			}
			return "";
		}

		private static string? ReadTerminalInput(Terminal terminal)
		{
			object value = Traverse.Create((object)terminal).Field("m_input").GetValue();
			if (value == null)
			{
				return null;
			}
			return Traverse.Create(value).Property("text", (object[])null).GetValue<string>() ?? Traverse.Create(value).Field("m_Text").GetValue<string>();
		}
	}
	internal static class SkillsActions
	{
		public static void Run(ConsoleEventArgs args)
		{
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Invalid comparison between Unknown and I4
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			Player localPlayer = Player.m_localPlayer;
			if (!Object.op_Implicit((Object)(object)localPlayer))
			{
				throw new InvalidOperationException("No player.");
			}
			float result = 100f;
			if (args.Length >= 3 && !float.TryParse(args[2], out result))
			{
				throw new InvalidOperationException("Usage: sandbox skills [level=100]");
			}
			if (result < 0f || result > 100f)
			{
				throw new InvalidOperationException($"Level must be between 0 and {100f}.");
			}
			Skills skills = ((Character)localPlayer).GetSkills();
			int num = 0;
			foreach (SkillType item in GameAccess.AllSkillTypes())
			{
				if ((int)item != 0 && (int)item != 999 && GameAccess.IsSkillValid(item))
				{
					Skill skill = GameAccess.GetSkill(skills, item);
					if (skill != null)
					{
						GameAccess.SetSkillLevel(skill, result);
						num++;
					}
				}
			}
			string text = $"Set {num} skills to {result}.";
			args.Context.AddString(text);
			((Character)localPlayer).Message((MessageType)1, text, 0, (Sprite)null, false);
		}
	}
	internal static class BiomeGuess
	{
		private static readonly (string Needle, string Biome)[] NameHints = new(string, string)[61]
		{
			("ashland", "Ashlands"),
			("asksvin", "Ashlands"),
			("flametal", "Ashlands"),
			("charred", "Ashlands"),
			("fallen", "Ashlands"),
			("mistland", "Mistlands"),
			("carapace", "Mistlands"),
			("seeker", "Mistlands"),
			("gjall", "Mistlands"),
			("mage", "Mistlands"),
			("eitr", "Mistlands"),
			("softtissue", "Mistlands"),
			("blackmetal", "Plains"),
			("lox", "Plains"),
			("flax", "Plains"),
			("barley", "Plains"),
			("tar", "Plains"),
			("deathsquito", "Plains"),
			("padded", "Plains"),
			("silver", "Mountain"),
			("wolf", "Mountain"),
			("fenring", "Mountain"),
			("freeze", "Mountain"),
			("obsidian", "Mountain"),
			("onion", "Mountain"),
			("crystal", "Mountain"),
			("iron", "Swamp"),
			("ancientbark", "Swamp"),
			("guck", "Swamp"),
			("chain", "Swamp"),
			("bloodbag", "Swamp"),
			("entrails", "Swamp"),
			("root", "Swamp"),
			("ooze", "Swamp"),
			("bronze", "BlackForest"),
			("troll", "BlackForest"),
			("copper", "BlackForest"),
			("tin", "BlackForest"),
			("thistle", "BlackForest"),
			("blueberry", "BlackForest"),
			("surtling", "BlackForest"),
			("antler", "BlackForest"),
			("deer", "Meadows"),
			("boar", "Meadows"),
			("neck", "Meadows"),
			("flint", "Meadows"),
			("raspberry", "Meadows"),
			("mushroom", "Meadows"),
			("honey", "Meadows"),
			("leatherscraps", "Meadows"),
			("wood", "Meadows"),
			("stone", "Meadows"),
			("queen", "Mistlands"),
			("goblin", "Plains"),
			("yagluth", "Plains"),
			("moder", "Mountain"),
			("bonemass", "Swamp"),
			("elder", "BlackForest"),
			("eikthyr", "Meadows"),
			("fader", "Ashlands"),
			("deepnorth", "DeepNorth")
		};

		private static readonly Dictionary<string, string> StationBiomes = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
		{
			{ "piece_workbench", "Meadows" },
			{ "piece_cauldron", "Meadows" },
			{ "forge", "BlackForest" },
			{ "piece_artisanstation", "Mistlands" },
			{ "blackforge", "Mistlands" },
			{ "piece_magetable", "Mistlands" },
			{ "piece_oven", "Plains" },
			{ "piece_spinningwheel", "Plains" },
			{ "piece_stonecutter", "Mountain" },
			{ "piece_meadcauldron", "Meadows" },
			{ "galdrtable", "Mistlands" }
		};

		public static string Guess(string internalName, string itemName, string? stationInternal, string? stationName, IEnumerable<string> materialInternalNames)
		{
			foreach (string materialInternalName in materialInternalNames)
			{
				string text = FromText(materialInternalName);
				if (text != "Unknown")
				{
					return text;
				}
			}
			string text2 = FromStation(stationInternal, stationName);
			if (text2 != "Unknown")
			{
				return text2;
			}
			string text3 = FromText(internalName);
			if (text3 != "Unknown")
			{
				return text3;
			}
			return FromText(itemName);
		}

		private static string FromStation(string? stationInternal, string? stationName)
		{
			if (!string.IsNullOrEmpty(stationInternal))
			{
				foreach (KeyValuePair<string, string> stationBiome in StationBiomes)
				{
					if (stationInternal.IndexOf(stationBiome.Key, StringComparison.OrdinalIgnoreCase) >= 0)
					{
						return stationBiome.Value;
					}
				}
			}
			return FromText(stationInternal + " " + stationName);
		}

		private static string FromText(string? text)
		{
			if (string.IsNullOrEmpty(text))
			{
				return "Unknown";
			}
			string text2 = text.ToLowerInvariant();
			for (int i = 0; i < NameHints.Length; i++)
			{
				if (text2.IndexOf(NameHints[i].Needle, StringComparison.Ordinal) >= 0)
				{
					return NameHints[i].Biome;
				}
			}
			if (text2.IndexOf("ashland", StringComparison.Ordinal) >= 0)
			{
				return "Ashlands";
			}
			if (text2.IndexOf("mistland", StringComparison.Ordinal) >= 0)
			{
				return "Mistlands";
			}
			if (text2.IndexOf("blackforest", StringComparison.Ordinal) >= 0)
			{
				return "BlackForest";
			}
			if (text2.IndexOf("deepnorth", StringComparison.Ordinal) >= 0)
			{
				return "DeepNorth";
			}
			if (text2.IndexOf("meadow", StringComparison.Ordinal) >= 0)
			{
				return "Meadows";
			}
			if (text2.IndexOf("swamp", StringComparison.Ordinal) >= 0)
			{
				return "Swamp";
			}
			if (text2.IndexOf("mountain", StringComparison.Ordinal) >= 0)
			{
				return "Mountain";
			}
			if (text2.IndexOf("plain", StringComparison.Ordinal) >= 0)
			{
				return "Plains";
			}
			return "Unknown";
		}
	}
	internal static class Csv
	{
		public static string Escape(string? value)
		{
			if (value == null)
			{
				value = "";
			}
			if (value.IndexOfAny(new char[4] { ',', '"', '\r', '\n' }) < 0)
			{
				return value;
			}
			return "\"" + value.Replace("\"", "\"\"") + "\"";
		}

		public static void Write(string path, IReadOnlyList<string> headers, IEnumerable<IReadOnlyList<string>> rows)
		{
			string directoryName = Path.GetDirectoryName(path);
			if (!string.IsNullOrEmpty(directoryName))
			{
				Directory.CreateDirectory(directoryName);
			}
			StringBuilder stringBuilder = new StringBuilder();
			WriteLine(stringBuilder, headers);
			foreach (IReadOnlyList<string> row in rows)
			{
				WriteLine(stringBuilder, row);
			}
			UTF8Encoding encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: true);
			File.WriteAllText(path, stringBuilder.ToString(), encoding);
		}

		private static void WriteLine(StringBuilder sb, IReadOnlyList<string> cells)
		{
			for (int i = 0; i < cells.Count; i++)
			{
				if (i > 0)
				{
					sb.Append(',');
				}
				sb.Append(Escape(cells[i]));
			}
			sb.Append("\r\n");
		}
	}
	internal enum ExportKind
	{
		All,
		Items,
		Recipes
	}
	internal static class ExportService
	{
		private readonly struct Flags
		{
			public readonly bool IsFood;

			public readonly bool IsWeapon;

			public readonly bool IsArmor;

			public readonly bool IsShield;

			public readonly bool IsTool;

			public readonly bool IsMaterial;

			public readonly bool IsPlaceable;

			public Flags(bool isFood, bool isWeapon, bool isArmor, bool isShield, bool isTool, bool isMaterial, bool isPlaceable)
			{
				IsFood = isFood;
				IsWeapon = isWeapon;
				IsArmor = isArmor;
				IsShield = isShield;
				IsTool = isTool;
				IsMaterial = isMaterial;
				IsPlaceable = isPlaceable;
			}
		}

		private sealed class ItemRow
		{
			public string ItemName = "";

			public string InternalName = "";

			public string ItemType = "";

			public string Biome = "Unknown";

			public float Weight;

			public int MaxStackSize;

			public float Durability;

			public float FoodHealth;

			public float FoodStamina;

			public float FoodEitr;

			public float FoodDuration;

			public float FoodRegen;

			public float Armor;

			public float BlockPower;

			public float ParryBonus;

			public string Damage = "0";

			public bool IsFood;

			public bool IsWeapon;

			public bool IsArmor;

			public bool IsShield;

			public bool IsTool;

			public bool IsMaterial;

			public bool IsPlaceable;

			public bool HasRecipe;
		}

		private sealed class RecipeRow
		{
			public string ItemName = "";

			public string InternalName = "";

			public string ItemType = "";

			public string Biome = "Unknown";

			public string CraftingStation = "";

			public int MinStationLevel;

			public int AmountProduced;

			public float Weight;

			public int MaxStackSize;

			public float Durability;

			public float FoodHealth;

			public float FoodStamina;

			public float FoodEitr;

			public float FoodDuration;

			public float FoodRegen;

			public bool IsFood;

			public bool IsWeapon;

			public bool IsArmor;

			public bool IsShield;

			public bool IsTool;

			public bool IsMaterial;

			public bool IsPlaceable;

			public bool IsCraftable;

			public List<(string Name, int Amount)> Materials = new List<(string, int)>();
		}

		private static readonly string[] ItemHeaders = new string[24]
		{
			"ItemName", "InternalName", "ItemType", "Biome", "Weight", "MaxStackSize", "Durability", "FoodHealth", "FoodStamina", "FoodEitr",
			"FoodDuration", "FoodRegen", "Armor", "BlockPower", "ParryBonus", "Damage", "IsFood", "IsWeapon", "IsArmor", "IsShield",
			"IsTool", "IsMaterial", "IsPlaceable", "HasRecipe"
		};

		private static readonly string[] RecipeFixedHeaders = new string[23]
		{
			"ItemName", "InternalName", "ItemType", "Biome", "CraftingStation", "MinStationLevel", "AmountProduced", "Weight", "MaxStackSize", "Durability",
			"FoodHealth", "FoodStamina", "FoodEitr", "FoodDuration", "FoodRegen", "IsFood", "IsWeapon", "IsArmor", "IsShield", "IsTool",
			"IsMaterial", "IsPlaceable", "IsCraftable"
		};

		public static void Run(ManualLogSource log, ExportKind kind = ExportKind.All)
		{
			Stopwatch stopwatch = Stopwatch.StartNew();
			ObjectDB instance = ObjectDB.instance;
			if (!Object.op_Implicit((Object)(object)instance))
			{
				throw new InvalidOperationException("ObjectDB not ready.");
			}
			if (!Object.op_Implicit((Object)(object)ZNetScene.instance))
			{
				throw new InvalidOperationException("ZNetScene not ready.");
			}
			HashSet<string> craftable = BuildCraftableSet(instance);
			Dictionary<string, Recipe> recipeByItem = IndexRecipes(instance);
			List<ItemRow> source = CollectItems(instance, craftable, recipeByItem);
			List<RecipeRow> source2 = CollectRecipes(instance, craftable);
			source = source.OrderBy<ItemRow, string>((ItemRow i) => i.InternalName, StringComparer.OrdinalIgnoreCase).ToList();
			source2 = source2.OrderBy<RecipeRow, string>((RecipeRow r) => r.InternalName, StringComparer.OrdinalIgnoreCase).ToList();
			string text = Path.Combine(Paths.BepInExRootPath, "exports");
			Directory.CreateDirectory(text);
			bool num = kind == ExportKind.All || kind == ExportKind.Items;
			bool flag = kind == ExportKind.All || kind == ExportKind.Recipes;
			if (num)
			{
				WriteItems(Path.Combine(text, "items.csv"), source);
				WriteFiltered(text, source);
			}
			if (flag)
			{
				WriteRecipes(Path.Combine(text, "recipes.csv"), source2);
			}
			stopwatch.Stop();
			LogSummary(log, source, source2, stopwatch.Elapsed, text, kind);
		}

		private static HashSet<string> BuildCraftableSet(ObjectDB odb)
		{
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			foreach (Recipe item in GameAccess.Recipes(odb))
			{
				if (!((Object)(object)item == (Object)null) && Object.op_Implicit((Object)(object)item.m_item))
				{
					hashSet.Add(PrefabName(((Component)item.m_item).gameObject));
				}
			}
			return hashSet;
		}

		private static Dictionary<string, Recipe> IndexRecipes(ObjectDB odb)
		{
			Dictionary<string, Recipe> dictionary = new Dictionary<string, Recipe>(StringComparer.OrdinalIgnoreCase);
			foreach (Recipe item in GameAccess.Recipes(odb))
			{
				if (!((Object)(object)item == (Object)null) && Object.op_Implicit((Object)(object)item.m_item))
				{
					string key = PrefabName(((Component)item.m_item).gameObject);
					if (!dictionary.ContainsKey(key))
					{
						dictionary[key] = item;
					}
				}
			}
			return dictionary;
		}

		private static List<ItemRow> CollectItems(ObjectDB odb, HashSet<string> craftable, Dictionary<string, Recipe> recipeByItem)
		{
			HashSet<string> seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			List<ItemRow> rows = new List<ItemRow>();
			foreach (GameObject item in GameAccess.Items(odb))
			{
				TryAdd(item);
			}
			ZNetScene instance = ZNetScene.instance;
			if (Object.op_Implicit((Object)(object)instance))
			{
				foreach (GameObject item2 in GameAccess.Prefabs(instance))
				{
					TryAdd(item2);
				}
			}
			return rows;
			void TryAdd(GameObject? go)
			{
				if (Object.op_Implicit((Object)(object)go))
				{
					ItemDrop component = go.GetComponent<ItemDrop>();
					if (Object.op_Implicit((Object)(object)component) && component.m_itemData?.m_shared != null)
					{
						string text = PrefabName(go);
						if (seen.Add(text))
						{
							recipeByItem.TryGetValue(text, out Recipe value);
							rows.Add(BuildItemRow(component, text, craftable.Contains(text), value));
						}
					}
				}
			}
		}

		private static List<RecipeRow> CollectRecipes(ObjectDB odb, HashSet<string> craftable)
		{
			List<RecipeRow> list = new List<RecipeRow>();
			foreach (Recipe item2 in GameAccess.Recipes(odb))
			{
				if ((Object)(object)item2 == (Object)null || !Object.op_Implicit((Object)(object)item2.m_item))
				{
					continue;
				}
				ItemDrop item = item2.m_item;
				if (item.m_itemData?.m_shared == null)
				{
					continue;
				}
				string text = PrefabName(((Component)item).gameObject);
				SharedData shared = item.m_itemData.m_shared;
				string itemName = Localize(shared.m_name);
				Flags flags = Classify(shared);
				CraftingStation craftingStation = item2.m_craftingStation;
				string text2 = (Object.op_Implicit((Object)(object)craftingStation) ? PrefabName(((Component)craftingStation).gameObject) : "");
				string text3 = (Object.op_Implicit((Object)(object)craftingStation) ? Localize(craftingStation.m_name) : "");
				List<(string, int)> list2 = new List<(string, int)>();
				if (item2.m_resources != null)
				{
					Requirement[] resources = item2.m_resources;
					foreach (Requirement val in resources)
					{
						if (val != null && Object.op_Implicit((Object)(object)val.m_resItem))
						{
							string text4 = PrefabName(((Component)val.m_resItem).gameObject);
							if (!string.IsNullOrEmpty(text4))
							{
								list2.Add((text4, val.m_amount));
							}
						}
					}
				}
				string biome = BiomeGuess.Guess(text, itemName, text2, text3, list2.Select<(string, int), string>(((string Name, int Amount) m) => m.Name));
				list.Add(new RecipeRow
				{
					ItemName = itemName,
					InternalName = text,
					ItemType = ((object)Unsafe.As<ItemType, ItemType>(ref shared.m_itemType)/*cast due to .constrained prefix*/).ToString(),
					Biome = biome,
					CraftingStation = (string.IsNullOrEmpty(text3) ? text2 : text3),
					MinStationLevel = item2.m_minStationLevel,
					AmountProduced = item2.m_amount,
					Weight = shared.m_weight,
					MaxStackSize = shared.m_maxStackSize,
					Durability = (shared.m_useDurability ? shared.m_maxDurability : 0f),
					FoodHealth = shared.m_food,
					FoodStamina = shared.m_foodStamina,
					FoodEitr = shared.m_foodEitr,
					FoodDuration = shared.m_foodBurnTime,
					FoodRegen = shared.m_foodRegen,
					IsFood = flags.IsFood,
					IsWeapon = flags.IsWeapon,
					IsArmor = flags.IsArmor,
					IsShield = flags.IsShield,
					IsTool = flags.IsTool,
					IsMaterial = flags.IsMaterial,
					IsPlaceable = flags.IsPlaceable,
					IsCraftable = craftable.Contains(text),
					Materials = list2
				});
			}
			return list;
		}

		private static ItemRow BuildItemRow(ItemDrop drop, string internalName, bool hasRecipe, Recipe? recipe)
		{
			//IL_018c: Unknown result type (might be due to invalid IL or missing references)
			SharedData shared = drop.m_itemData.m_shared;
			string itemName = Localize(shared.m_name);
			Flags flags = Classify(shared);
			string stationInternal = null;
			string stationName = null;
			IEnumerable<string> materialInternalNames = Array.Empty<string>();
			if ((Object)(object)recipe != (Object)null)
			{
				CraftingStation craftingStation = recipe.m_craftingStation;
				if (Object.op_Implicit((Object)(object)craftingStation))
				{
					stationInternal = PrefabName(((Component)craftingStation).gameObject);
					stationName = Localize(craftingStation.m_name);
				}
				if (recipe.m_resources != null)
				{
					materialInternalNames = from r in recipe.m_resources
						where r != null && Object.op_Implicit((Object)(object)r.m_resItem)
						select PrefabName(((Component)r.m_resItem).gameObject);
				}
			}
			return new ItemRow
			{
				ItemName = itemName,
				InternalName = internalName,
				ItemType = ((object)Unsafe.As<ItemType, ItemType>(ref shared.m_itemType)/*cast due to .constrained prefix*/).ToString(),
				Biome = BiomeGuess.Guess(internalName, itemName, stationInternal, stationName, materialInternalNames),
				Weight = shared.m_weight,
				MaxStackSize = shared.m_maxStackSize,
				Durability = (shared.m_useDurability ? shared.m_maxDurability : 0f),
				FoodHealth = shared.m_food,
				FoodStamina = shared.m_foodStamina,
				FoodEitr = shared.m_foodEitr,
				FoodDuration = shared.m_foodBurnTime,
				FoodRegen = shared.m_foodRegen,
				Armor = shared.m_armor,
				BlockPower = shared.m_blockPower,
				ParryBonus = shared.m_timedBlockBonus,
				Damage = FormatDamage(shared.m_damages),
				IsFood = flags.IsFood,
				IsWeapon = flags.IsWeapon,
				IsArmor = flags.IsArmor,
				IsShield = flags.IsShield,
				IsTool = flags.IsTool,
				IsMaterial = flags.IsMaterial,
				IsPlaceable = flags.IsPlaceable,
				HasRecipe = hasRecipe
			};
		}

		private static Flags Classify(SharedData shared)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: Invalid comparison between Unknown and I4
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Invalid comparison between Unknown and I4
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Invalid comparison between Unknown and I4
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Invalid comparison between Unknown and I4
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Invalid comparison between Unknown and I4
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Invalid comparison between Unknown and I4
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Invalid comparison between Unknown and I4
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Invalid comparison between Unknown and I4
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Invalid comparison between Unknown and I4
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Invalid comparison between Unknown and I4
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Invalid comparison between Unknown and I4
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Invalid comparison between Unknown and I4
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Invalid comparison between Unknown and I4
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Invalid comparison between Unknown and I4
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Invalid comparison between Unknown and I4
			ItemType itemType = shared.m_itemType;
			bool isFood = (int)itemType == 2 && (shared.m_food > 0f || shared.m_foodStamina > 0f || shared.m_foodEitr > 0f);
			bool isWeapon = (int)itemType == 3 || (int)itemType == 14 || (int)itemType == 22 || (int)itemType == 4 || (int)itemType == 15 || (int)itemType == 20;
			bool isArmor = (int)itemType == 6 || (int)itemType == 7 || (int)itemType == 11 || (int)itemType == 17 || (int)itemType == 12;
			bool isShield = (int)itemType == 5;
			bool isTool = (int)itemType == 19;
			bool isMaterial = (int)itemType == 1;
			bool isPlaceable = (Object)(object)shared.m_buildPieces != (Object)null;
			return new Flags(isFood, isWeapon, isArmor, isShield, isTool, isMaterial, isPlaceable);
		}

		private static string FormatDamage(DamageTypes dmg)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: 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_0026: 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_0034: 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)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0109: 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)
			//IL_012d: Unknown result type (might be due to invalid IL or missing references)
			float num;
			try
			{
				num = ((DamageTypes)(ref dmg)).GetTotalDamage();
			}
			catch
			{
				num = dmg.m_damage + dmg.m_blunt + dmg.m_slash + dmg.m_pierce + dmg.m_chop + dmg.m_pickaxe + dmg.m_fire + dmg.m_frost + dmg.m_lightning + dmg.m_poison + dmg.m_spirit;
			}
			if (num <= 0f)
			{
				return "0";
			}
			List<string> parts = new List<string>();
			Add("slash", dmg.m_slash);
			Add("blunt", dmg.m_blunt);
			Add("pierce", dmg.m_pierce);
			Add("chop", dmg.m_chop);
			Add("pickaxe", dmg.m_pickaxe);
			Add("fire", dmg.m_fire);
			Add("frost", dmg.m_frost);
			Add("lightning", dmg.m_lightning);
			Add("poison", dmg.m_poison);
			Add("spirit", dmg.m_spirit);
			Add("damage", dmg.m_damage);
			if (parts.Count != 0)
			{
				return F(num) + " (" + string.Join(" ", parts) + ")";
			}
			return F(num);
			void Add(string label, float v)
			{
				if (v > 0f)
				{
					parts.Add(label + ":" + F(v));
				}
			}
		}

		private static void WriteItems(string path, List<ItemRow> items)
		{
			List<IReadOnlyList<string>> rows = ((IEnumerable<ItemRow>)items).Select((Func<ItemRow, IReadOnlyList<string>>)((ItemRow i) => new string[24]
			{
				i.ItemName,
				i.InternalName,
				i.ItemType,
				i.Biome,
				F(i.Weight),
				i.MaxStackSize.ToString(CultureInfo.InvariantCulture),
				F(i.Durability),
				F(i.FoodHealth),
				F(i.FoodStamina),
				F(i.FoodEitr),
				F(i.FoodDuration),
				F(i.FoodRegen),
				F(i.Armor),
				F(i.BlockPower),
				F(i.ParryBonus),
				i.Damage,
				Bool(i.IsFood),
				Bool(i.IsWeapon),
				Bool(i.IsArmor),
				Bool(i.IsShield),
				Bool(i.IsTool),
				Bool(i.IsMaterial),
				Bool(i.IsPlaceable),
				Bool(i.HasRecipe)
			})).ToList();
			Csv.Write(path, ItemHeaders, rows);
		}

		private static void WriteRecipes(string path, List<RecipeRow> recipes)
		{
			int num = 0;
			foreach (RecipeRow recipe in recipes)
			{
				if (recipe.Materials.Count > num)
				{
					num = recipe.Materials.Count;
				}
			}
			List<string> list = new List<string>(RecipeFixedHeaders);
			for (int i = 1; i <= num; i++)
			{
				list.Add($"Material{i}");
				list.Add($"Material{i}Amount");
			}
			List<IReadOnlyList<string>> list2 = new List<IReadOnlyList<string>>(recipes.Count);
			foreach (RecipeRow recipe2 in recipes)
			{
				List<string> list3 = new List<string>(list.Count)
				{
					recipe2.ItemName,
					recipe2.InternalName,
					recipe2.ItemType,
					recipe2.Biome,
					recipe2.CraftingStation,
					recipe2.MinStationLevel.ToString(CultureInfo.InvariantCulture),
					recipe2.AmountProduced.ToString(CultureInfo.InvariantCulture),
					F(recipe2.Weight),
					recipe2.MaxStackSize.ToString(CultureInfo.InvariantCulture),
					F(recipe2.Durability),
					F(recipe2.FoodHealth),
					F(recipe2.FoodStamina),
					F(recipe2.FoodEitr),
					F(recipe2.FoodDuration),
					F(recipe2.FoodRegen),
					Bool(recipe2.IsFood),
					Bool(recipe2.IsWeapon),
					Bool(recipe2.IsArmor),
					Bool(recipe2.IsShield),
					Bool(recipe2.IsTool),
					Bool(recipe2.IsMaterial),
					Bool(recipe2.IsPlaceable),
					Bool(recipe2.IsCraftable)
				};
				for (int j = 0; j < num; j++)
				{
					if (j < recipe2.Materials.Count)
					{
						list3.Add(recipe2.Materials[j].Name);
						list3.Add(recipe2.Materials[j].Amount.ToString(CultureInfo.InvariantCulture));
					}
					else
					{
						list3.Add("");
						list3.Add("");
					}
				}
				list2.Add(list3);
			}
			Csv.Write(path, list, list2);
		}

		private static void WriteFiltered(string exportDir, List<ItemRow> items)
		{
			WriteSubset(Path.Combine(exportDir, "foods.csv"), (from i in items
				where i.IsFood
				orderby i.FoodHealth + i.FoodStamina + i.FoodEitr descending
				select i).ThenBy<ItemRow, string>((ItemRow i) => i.InternalName, StringComparer.OrdinalIgnoreCase));
			WriteSubset(Path.Combine(exportDir, "weapons.csv"), items.Where((ItemRow i) => i.IsWeapon).OrderBy<ItemRow, string>((ItemRow i) => i.InternalName, StringComparer.OrdinalIgnoreCase));
			WriteSubset(Path.Combine(exportDir, "armor.csv"), items.Where((ItemRow i) => i.IsArmor).OrderBy<ItemRow, string>((ItemRow i) => i.InternalName, StringComparer.OrdinalIgnoreCase));
			WriteSubset(Path.Combine(exportDir, "materials.csv"), items.Where((ItemRow i) => i.IsMaterial).OrderBy<ItemRow, string>((ItemRow i) => i.InternalName, StringComparer.OrdinalIgnoreCase));
		}

		private static void WriteSubset(string path, IEnumerable<ItemRow> items)
		{
			WriteItems(path, items.ToList());
		}

		private static void LogSummary(ManualLogSource log, List<ItemRow> items, List<RecipeRow> recipes, TimeSpan elapsed, string exportDir, ExportKind kind)
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.AppendLine($"SandboxTools {kind.ToString().ToLowerInvariant()} export finished in {elapsed.TotalMilliseconds:0} ms -> {exportDir}");
			if (kind == ExportKind.All || kind == ExportKind.Items)
			{
				stringBuilder.AppendLine($"Total Items: {items.Count}");
				stringBuilder.AppendLine($"Food Count: {items.Count((ItemRow i) => i.IsFood)}");
				stringBuilder.AppendLine($"Weapon Count: {items.Count((ItemRow i) => i.IsWeapon)}");
				stringBuilder.AppendLine($"Armor Count: {items.Count((ItemRow i) => i.IsArmor)}");
				stringBuilder.AppendLine($"Shield Count: {items.Count((ItemRow i) => i.IsShield)}");
				stringBuilder.AppendLine($"Tool Count: {items.Count((ItemRow i) => i.IsTool)}");
				stringBuilder.AppendLine($"Material Count: {items.Count((ItemRow i) => i.IsMaterial)}");
				stringBuilder.AppendLine($"Placeable Count: {items.Count((ItemRow i) => i.IsPlaceable)}");
			}
			if (kind == ExportKind.All || kind == ExportKind.Recipes)
			{
				stringBuilder.AppendLine($"Total Recipes: {recipes.Count}");
			}
			log.LogInfo((object)stringBuilder.ToString().TrimEnd(Array.Empty<char>()));
		}

		private static string Localize(string? token)
		{
			if (string.IsNullOrEmpty(token))
			{
				return "";
			}
			try
			{
				Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
				for (int i = 0; i < assemblies.Length; i++)
				{
					Type type = assemblies[i].GetType("Localization");
					if (type == null)
					{
						continue;
					}
					object obj = type.GetProperty("instance", BindingFlags.Static | BindingFlags.Public)?.GetValue(null);
					if (obj == null)
					{
						continue;
					}
					MethodInfo method = type.GetMethod("Localize", new Type[1] { typeof(string) });
					if (!(method == null))
					{
						string text = method.Invoke(obj, new object[1] { token }) as string;
						if (!string.IsNullOrEmpty(text))
						{
							return text;
						}
					}
				}
			}
			catch
			{
			}
			return token ?? "";
		}

		private static string PrefabName(GameObject go)
		{
			string name = ((Object)go).name;
			if (name.EndsWith("(Clone)", StringComparison.Ordinal))
			{
				return name.Substring(0, name.Length - "(Clone)".Length).TrimEnd(Array.Empty<char>());
			}
			return name;
		}

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

		private static string Bool(bool v)
		{
			if (!v)
			{
				return "FALSE";
			}
			return "TRUE";
		}
	}
	internal static class GameAccess
	{
		public static List<Recipe> Recipes(ObjectDB odb)
		{
			return RequireField<List<Recipe>>(odb, "m_recipes");
		}

		public static List<GameObject> Items(ObjectDB odb)
		{
			return RequireField<List<GameObject>>(odb, "m_items");
		}

		public static IEnumerable<GameObject> BuildPieces(ObjectDB odb)
		{
			return EnumerateGameObjects(odb, "m_buildPieces");
		}

		public static IEnumerable<GameObject> PieceTablePieces(PieceTable table)
		{
			return EnumerateGameObjects(table, "m_pieces");
		}

		public static List<GameObject> Prefabs(ZNetScene scene)
		{
			List<GameObject> list = Field<List<GameObject>>(scene, "m_prefabs");
			if (list != null)
			{
				return list;
			}
			Dictionary<int, GameObject> dictionary = Field<Dictionary<int, GameObject>>(scene, "m_namedPrefabs");
			if (dictionary == null)
			{
				return new List<GameObject>();
			}
			List<GameObject> list2 = new List<GameObject>(dictionary.Count);
			foreach (KeyValuePair<int, GameObject> item in dictionary)
			{
				if (Object.op_Implicit((Object)(object)item.Value))
				{
					list2.Add(item.Value);
				}
			}
			return list2;
		}

		public static HashSet<string> KnownRecipes(Player player)
		{
			return RequireField<HashSet<string>>(player, "m_knownRecipes");
		}

		public static HashSet<string> KnownMaterials(Player player)
		{
			return RequireField<HashSet<string>>(player, "m_knownMaterial");
		}

		public static Dictionary<string, int> KnownStations(Player player)
		{
			return RequireField<Dictionary<string, int>>(player, "m_knownStations");
		}

		public static void RefreshPlayerLists(Player player)
		{
			Call(player, "UpdateKnownRecipesList");
			Call(player, "UpdateAvailablePiecesList");
		}

		public static void SetSkillLevel(Skill skill, float level)
		{
			Traverse obj = Traverse.Create((object)skill);
			obj.Field("m_level").SetValue((object)level);
			obj.Field("m_accumulator").SetValue((object)0f);
		}

		public static IEnumerable<SkillType> AllSkillTypes()
		{
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			SkillType[] value = Traverse.Create(typeof(Skills)).Field("s_allSkills").GetValue<SkillType[]>();
			if (value != null && value.Length != 0)
			{
				return value;
			}
			List<SkillType> list = new List<SkillType>();
			foreach (SkillType value2 in Enum.GetValues(typeof(SkillType)))
			{
				list.Add(value2);
			}
			return list;
		}

		public static bool IsSkillValid(SkillType type)
		{
			//IL_0000: 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)
			try
			{
				return Skills.IsSkillValid(type);
			}
			catch (MethodAccessException)
			{
				return Traverse.Create(typeof(Skills)).Method("IsSkillValid", new object[1] { type }).GetValue<bool>();
			}
		}

		public static Skill GetSkill(Skills skills, SkillType type)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				return skills.GetSkill(type);
			}
			catch (MethodAccessException)
			{
				return Traverse.Create((object)skills).Method("GetSkill", new object[1] { type }).GetValue<Skill>();
			}
		}

		public static T? Field<T>(object obj, string name) where T : class
		{
			try
			{
				return Traverse.Create(obj).Field(name).GetValue<T>();
			}
			catch (InvalidCastException)
			{
				return null;
			}
		}

		public static T RequireField<T>(object obj, string name) where T : class
		{
			return Field<T>(obj, name) ?? throw new InvalidOperationException(obj.GetType().Name + "." + name + " unavailable.");
		}

		public static void Call(object obj, string method, params object[] args)
		{
			((args.Length == 0) ? Traverse.Create(obj).Method(method, Array.Empty<object>()) : Traverse.Create(obj).Method(method, args)).GetValue();
		}

		private static IEnumerable<GameObject> EnumerateGameObjects(object obj, string fieldName)
		{
			object value;
			try
			{
				value = Traverse.Create(obj).Field(fieldName).GetValue();
			}
			catch (Exception)
			{
				yield break;
			}
			if (value == null)
			{
				yield break;
			}
			if (value is IEnumerable enumerable)
			{
				foreach (object item in enumerable)
				{
					GameObject val = AsGameObject(item);
					if (Object.op_Implicit((Object)(object)val))
					{
						yield return val;
					}
				}
			}
			else
			{
				GameObject val2 = AsGameObject(value);
				if (Object.op_Implicit((Object)(object)val2))
				{
					yield return val2;
				}
			}
		}

		private static GameObject? AsGameObject(object? item)
		{
			GameObject val = (GameObject)((item is GameObject) ? item : null);
			if (val == null)
			{
				Component val2 = (Component)((item is Component) ? item : null);
				if (val2 != null)
				{
					if (!Object.op_Implicit((Object)(object)val2))
					{
						return null;
					}
					return val2.gameObject;
				}
				return null;
			}
			if (!Object.op_Implicit((Object)(object)val))
			{
				return null;
			}
			return val;
		}
	}
	[BepInPlugin("matthb1.sandboxtools", "SandboxTools", "1.0.0")]
	public class SandboxToolsPlugin : BaseUnityPlugin
	{
		public const string GUID = "matthb1.sandboxtools";

		public const string NAME = "SandboxTools";

		public const string VERSION = "1.0.0";

		private bool _exportedThisSession;

		private void Awake()
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			((BaseUnityPlugin)this).Logger.LogInfo((object)"SandboxTools 1.0.0 loaded - type 'sandbox' for commands.");
			new Harmony("matthb1.sandboxtools").PatchAll();
			SandboxCommand.Register(((BaseUnityPlugin)this).Logger);
			((MonoBehaviour)this).StartCoroutine(WaitUntilReadyThenExport());
		}

		private IEnumerator WaitUntilReadyThenExport()
		{
			while ((Object)(object)ObjectDB.instance == (Object)null || (Object)(object)ZNetScene.instance == (Object)null)
			{
				yield return (object)new WaitForSeconds(1f);
			}
			int lastRecipes = -1;
			int lastItems = -1;
			int stableTicks = 0;
			while (stableTicks < 4)
			{
				yield return (object)new WaitForSeconds(0.5f);
				ObjectDB instance = ObjectDB.instance;
				ZNetScene instance2 = ZNetScene.instance;
				if ((Object)(object)instance == (Object)null || (Object)(object)instance2 == (Object)null)
				{
					stableTicks = 0;
					continue;
				}
				int count = GameAccess.Recipes(instance).Count;
				int count2 = GameAccess.Items(instance).Count;
				if (count > 0 && count2 > 0 && count == lastRecipes && count2 == lastItems)
				{
					stableTicks++;
					continue;
				}
				stableTicks = 0;
				lastRecipes = count;
				lastItems = count2;
			}
			if (_exportedThisSession)
			{
				yield break;
			}
			_exportedThisSession = true;
			try
			{
				ExportService.Run(((BaseUnityPlugin)this).Logger);
			}
			catch (Exception arg)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)$"Automatic export failed: {arg}");
				_exportedThisSession = false;
			}
		}
	}
}