Decompiled source of ExpansionKit v0.7.2

BepInEx/plugins/ExpansionKit/HowToFish.ExpansionKit.dll

Decompiled 15 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Numerics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using Microsoft.CodeAnalysis;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("HowToFish.ExpansionKit")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Unofficial, game-asset-free content and layout contracts for How to Fish expansions.")]
[assembly: AssemblyFileVersion("0.7.2.0")]
[assembly: AssemblyInformationalVersion("0.7.2")]
[assembly: AssemblyProduct("HowToFish.ExpansionKit")]
[assembly: AssemblyTitle("HowToFish.ExpansionKit")]
[assembly: AssemblyVersion("0.7.2.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 HowToFish.ExpansionKit
{
	public enum ItemKind
	{
		Fish,
		Rod,
		Weapon,
		Boss,
		Trophy
	}
	public enum NpcRole
	{
		Merchant,
		Quest,
		Dealer
	}
	public enum ShopKind
	{
		Item,
		Lure,
		Attachment,
		Ammunition,
		BoatRadar,
		NativeItem,
		NativeLure,
		Sharpening,
		Pocket,
		Motor
	}
	public enum PlacementKind
	{
		Player,
		Boat,
		Npc,
		Shop,
		Service,
		Fishing,
		Landmark,
		Table,
		TablePlayer
	}
	public enum RewardKind
	{
		Lure,
		Coordinates
	}
	public enum SupportKind
	{
		Inferred,
		None,
		Ground,
		Surface,
		Water
	}
	public sealed class ExpansionDefinition
	{
		public const int CurrentSchema = 2;

		public int SchemaVersion { get; set; } = 2;

		public string Key { get; set; } = "";

		public string Title { get; set; } = "";

		public string GameBuild { get; set; } = "";

		public string SceneBundle { get; set; } = "";

		public string SceneName { get; set; } = "";

		public int IslandId { get; set; }

		public int NativeUnlockCount { get; set; }

		public int NextIslandId { get; set; }

		public int ContentCollectionId { get; set; }

		public int EncounterCollectionId { get; set; }

		public List<ItemDefinition> Items { get; set; } = new List<ItemDefinition>();

		public List<LureDefinition> Lures { get; set; } = new List<LureDefinition>();

		public List<NpcDefinition> Npcs { get; set; } = new List<NpcDefinition>();

		public List<ShopDefinition> Shops { get; set; } = new List<ShopDefinition>();

		public List<PlacementDefinition> Placements { get; set; } = new List<PlacementDefinition>();

		public List<EncounterDefinition> Encounters { get; set; } = new List<EncounterDefinition>();

		public List<QuestStageDefinition> Quests { get; set; } = new List<QuestStageDefinition>();

		public IEnumerable<string> AssetKeys => Items.Select((ItemDefinition item) => item.AssetKey).Concat(Lures.Select((LureDefinition lure) => lure.AssetKey)).Distinct<string>(StringComparer.Ordinal);

		public void Validate()
		{
			IReadOnlyList<string> readOnlyList = Inspect();
			if (readOnlyList.Count != 0)
			{
				throw new InvalidDataException(string.Join(Environment.NewLine, readOnlyList));
			}
		}

		public IReadOnlyList<string> Inspect()
		{
			List<string> errors = new List<string>();
			Require(SchemaVersion == 2, "Unsupported expansion schema.");
			Require(ValidKey(Key) && !string.IsNullOrWhiteSpace(Title), "The expansion needs a key and title.");
			Require(Regex.IsMatch(GameBuild ?? "", "^[0-9]+$"), "GameBuild must pin a native build number.");
			Require(ValidFile(SceneBundle) && ValidSceneName(SceneName), "Scene bundle and scene names must be safe local names.");
			Require(IslandId >= 5 && IslandId <= 254, "IslandId must be 5..254; 255 is the native unload sentinel.");
			Require(NativeUnlockCount >= 1 && NativeUnlockCount <= 5, "NativeUnlockCount must be between one and five.");
			Require(NextIslandId >= 0 && NextIslandId <= 255 && NextIslandId != IslandId, "The next-island identifier is invalid.");
			Require(ContentCollectionId >= 1 && ContentCollectionId <= 65535 && EncounterCollectionId >= 1 && EncounterCollectionId <= 65535 && ContentCollectionId != EncounterCollectionId, "Content and encounter collections must be distinct nonzero ushort identifiers.");
			if (Items == null || Lures == null || Npcs == null || Shops == null || Placements == null || Encounters == null || Quests == null)
			{
				errors.Add("All definition collections are required, even when empty.");
				return errors;
			}
			Require(Items.Count > 0 && Items.Count <= 128 && Lures.Count <= 64 && Npcs.Count <= 32 && Shops.Count <= 128 && Placements.Count <= 256 && Encounters.Count <= 32 && Quests.Count <= 32, "The definition exceeds its bounded content budget or has no items.");
			CheckKeys(Items, (ItemDefinition item) => item.Key, "item", errors);
			CheckKeys(Lures, (LureDefinition lure) => lure.Key, "lure", errors);
			CheckKeys(Npcs, (NpcDefinition npcDefinition) => npcDefinition.Key, "NPC", errors);
			CheckKeys(Shops, (ShopDefinition shopDefinition) => shopDefinition.Key, "shop", errors);
			CheckKeys(Encounters, (EncounterDefinition encounterDefinition) => encounterDefinition.Key, "encounter", errors);
			CheckKeys(Quests, (QuestStageDefinition questStageDefinition) => questStageDefinition.Key, "quest", errors);
			if (Items.Any((ItemDefinition item) => item == null) || Lures.Any((LureDefinition lure) => lure == null) || Npcs.Any((NpcDefinition npcDefinition) => npcDefinition == null) || Shops.Any((ShopDefinition shopDefinition) => shopDefinition == null) || Placements.Any((PlacementDefinition placementDefinition) => placementDefinition == null) || Encounters.Any((EncounterDefinition encounterDefinition) => encounterDefinition == null) || Quests.Any((QuestStageDefinition questStageDefinition) => questStageDefinition == null))
			{
				errors.Add("Content collections cannot contain null entries.");
				return errors;
			}
			Require(Items.Select((ItemDefinition item) => item.Id).Distinct().Count() == Items.Count, "Item identifiers must be unique.");
			Require(Npcs.Select((NpcDefinition npcDefinition) => npcDefinition.Id).Distinct().Count() == Npcs.Count, "NPC identifiers must be unique.");
			Require(Placements.Select((PlacementDefinition placementDefinition) => placementDefinition.Marker).Distinct<string>(StringComparer.Ordinal).Count() == Placements.Count, "Placement marker names must be unique.");
			foreach (ItemDefinition item in Items)
			{
				Require(item.Id >= 0 && item.Id <= 255 && Enum.IsDefined(typeof(ItemKind), item.Kind) && ValidKey(item.AssetKey) && !string.IsNullOrWhiteSpace(item.Title) && item.Worth >= 0 && item.Cost >= 0 && Finite(item.CatchWeight) && item.CatchWeight >= 0f, "Invalid item fields: " + item.Key);
				Require(item.NativeDonorId == -1 || (item.NativeDonorId >= 0 && item.NativeDonorId <= 85), "Invalid native item donor: " + item.Key);
				Require(item.Kind != ItemKind.Fish || item.Health > 0, "A fish needs positive health: " + item.Key);
			}
			foreach (LureDefinition lure in Lures)
			{
				Require(ValidKey(lure.AssetKey) && !string.IsNullOrWhiteSpace(lure.Title) && !string.IsNullOrWhiteSpace(lure.Description) && lure.Cost >= 0 && Finite(lure.LossPercent) && lure.LossPercent >= 0f && lure.LossPercent <= 100f && Finite(lure.CatchTimeMin) && Finite(lure.CatchTimeMax) && lure.CatchTimeMin > 0f && lure.CatchTimeMax >= lure.CatchTimeMin, "Invalid lure fields: " + lure.Key);
				if (lure.Catches == null || lure.Catches.Count == 0 || lure.Catches.Count > 128 || lure.Catches.Any((CatchDefinition catchEntry) => catchEntry == null))
				{
					errors.Add("The lure needs a nonempty catch table: " + lure.Key);
					continue;
				}
				Require(lure.Catches.Select((CatchDefinition catchDefinition) => catchDefinition.Item).Distinct<string>(StringComparer.Ordinal).Count() == lure.Catches.Count, "A lure repeats a catch-table entry: " + lure.Key);
				Require(((IEnumerable<CatchDefinition>)lure.Catches).Sum((Func<CatchDefinition, double>)((CatchDefinition catchDefinition) => catchDefinition.Weight)) <= 3.4028234663852886E+38, "A lure's combined weight exceeds native floating-point capacity: " + lure.Key);
				foreach (CatchDefinition entry in lure.Catches)
				{
					Require(Finite(entry.Weight) && entry.Weight > 0f && Items.Any((ItemDefinition item) => item.Key == entry.Item && (item.Kind == ItemKind.Fish || item.Kind == ItemKind.Boss)), "Invalid lure catch reference: " + lure.Key + " -> " + entry.Item);
				}
			}
			foreach (NpcDefinition npc2 in Npcs)
			{
				Require(npc2.Id >= 0 && npc2.Id <= 255 && !string.IsNullOrWhiteSpace(npc2.Title) && Enum.IsDefined(typeof(NpcRole), npc2.Role), "Invalid NPC fields: " + npc2.Key);
			}
			foreach (ShopDefinition shop in Shops)
			{
				Require(Enum.IsDefined(typeof(ShopKind), shop.Kind), "Unknown shop kind: " + shop.Key);
				Require((shop.Kind != ShopKind.NativeItem && shop.Kind != ShopKind.NativeLure) ? (shop.NativeId == -1 && shop.NativeName == "") : (shop.NativeId >= ((shop.Kind == ShopKind.NativeLure) ? 1 : 0) && shop.NativeId <= 255 && !string.IsNullOrWhiteSpace(shop.NativeName) && shop.NativeName.Length <= 120 && shop.NativeName == shop.NativeName.Trim() && !shop.NativeName.Any(char.IsControl)), "Native shop identity fields are missing or attached to a non-native shop: " + shop.Key);
				bool condition;
				switch (shop.Kind)
				{
				case ShopKind.Item:
					condition = Items.Any((ItemDefinition item) => item.Key == shop.Reference && item.Kind != ItemKind.Boss && item.Kind != ItemKind.Trophy);
					break;
				case ShopKind.Lure:
					condition = Lures.Any((LureDefinition lure) => lure.Key == shop.Reference && lure.Cost > 0);
					break;
				case ShopKind.Attachment:
				case ShopKind.Ammunition:
					condition = ValidKey(shop.Reference);
					break;
				case ShopKind.BoatRadar:
					condition = shop.Reference == "boat_radar";
					break;
				case ShopKind.NativeItem:
					condition = ValidKey(shop.Reference) && !Items.Any((ItemDefinition item) => item.Id == shop.NativeId);
					break;
				case ShopKind.NativeLure:
					condition = ValidKey(shop.Reference);
					break;
				case ShopKind.Sharpening:
					condition = shop.Reference == "native_sharpening";
					break;
				case ShopKind.Pocket:
					condition = shop.Reference == "native_pocket_slot";
					break;
				case ShopKind.Motor:
					condition = shop.Reference == "native_big_motor" || shop.Reference == "native_dual_motors";
					break;
				default:
					condition = false;
					break;
				}
				Require(condition, "Invalid shop content reference: " + shop.Key + " -> " + shop.Reference);
			}
			foreach (PlacementDefinition placement in Placements)
			{
				Require(ValidSceneName(placement.Marker) && Enum.IsDefined(typeof(PlacementKind), placement.Kind), "Invalid placement marker: " + placement.Marker);
				Require(Enum.IsDefined(typeof(SupportKind), placement.Support) && Finite(placement.FootprintWidth) && Finite(placement.FootprintDepth) && Finite(placement.ClearanceHeight) && placement.FootprintWidth >= 0f && placement.FootprintDepth >= 0f && placement.ClearanceHeight >= 0f && placement.FootprintWidth <= 50f && placement.FootprintDepth <= 50f && placement.ClearanceHeight <= 20f && placement.FootprintWidth == 0f == (placement.FootprintDepth == 0f) && (placement.ClearanceHeight == 0f || placement.FootprintWidth > 0f), "Invalid placement footprint or support rule: " + placement.Marker);
				Require(placement.Kind switch
				{
					PlacementKind.Npc => Npcs.Any((NpcDefinition npcDefinition) => npcDefinition.Key == placement.Reference), 
					PlacementKind.Shop => Shops.Any((ShopDefinition shopDefinition) => shopDefinition.Key == placement.Reference), 
					PlacementKind.Service => placement.Reference == "grill" || placement.Reference == "slot_machine", 
					_ => ValidKey(placement.Reference), 
				}, "Invalid marker reference: " + placement.Marker + " -> " + placement.Reference);
			}
			Require(Placements.Count((PlacementDefinition marker) => marker.Kind == PlacementKind.Player) == 1 && Placements.Count((PlacementDefinition marker) => marker.Kind == PlacementKind.Boat) == 1, "An island needs exactly one player spawn and one boat spawn.");
			foreach (NpcDefinition npc in Npcs)
			{
				Require(Placements.Count((PlacementDefinition marker) => marker.Kind == PlacementKind.Npc && marker.Reference == npc.Key) == 1, "An NPC needs exactly one placement: " + npc.Key);
			}
			foreach (ShopDefinition shop2 in Shops)
			{
				Require(Placements.Count((PlacementDefinition marker) => marker.Kind == PlacementKind.Shop && marker.Reference == shop2.Key) == 1, "A shop needs exactly one placement: " + shop2.Key);
			}
			foreach (EncounterDefinition encounter in Encounters)
			{
				Require(Items.Any((ItemDefinition item) => item.Key == encounter.Item && item.Kind == ItemKind.Boss) && Items.Any((ItemDefinition item) => item.Key == encounter.Trophy && item.Kind == ItemKind.Trophy) && encounter.NativeDonorId >= 0 && encounter.NativeDonorId <= 255 && encounter.Health > 0 && encounter.Damage > 0 && Finite(encounter.Force) && encounter.Force > 0f, "Invalid encounter: " + encounter.Key);
			}
			Require(Encounters.Select((EncounterDefinition encounterDefinition) => encounterDefinition.Item).Distinct<string>(StringComparer.Ordinal).Count() == Encounters.Count, "A boss item cannot have multiple encounter configurations.");
			foreach (ItemDefinition boss in Items.Where((ItemDefinition item) => item.Kind == ItemKind.Boss))
			{
				Require(Encounters.Count((EncounterDefinition encounterDefinition) => encounterDefinition.Item == boss.Key) == 1, "Every boss item needs one encounter configuration: " + boss.Key);
			}
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			foreach (QuestStageDefinition quest in Quests)
			{
				Require(Npcs.Any((NpcDefinition npcDefinition) => npcDefinition.Key == quest.Npc && npcDefinition.Role == NpcRole.Quest) && Items.Any((ItemDefinition item) => item.Key == quest.Trophy && item.Kind == ItemKind.Trophy) && quest.Requires != null && (quest.Requires.Length == 0 || hashSet.Contains(quest.Requires)) && Enum.IsDefined(typeof(RewardKind), quest.RewardKind), "Invalid or out-of-order quest stage: " + quest.Key);
				Require((quest.RewardKind == RewardKind.Lure) ? Lures.Any((LureDefinition lure) => lure.Key == quest.Reward && lure.Cost == 0) : (quest.Reward == "next_island"), "Invalid quest reward: " + quest.Key);
				hashSet.Add(quest.Key);
			}
			Require(Quests.Count == 0 || Quests.Count((QuestStageDefinition questStageDefinition) => questStageDefinition.RewardKind == RewardKind.Coordinates) == 1, "A progression chain must award next-island coordinates exactly once.");
			Require(Quests.Count == 0 || Quests[Quests.Count - 1].RewardKind == RewardKind.Coordinates, "Next-island coordinates must be the final progression stage.");
			return errors;
			void Require(bool flag, string message)
			{
				if (!flag)
				{
					errors.Add(message);
				}
			}
		}

		public void ValidateMarkers(IEnumerable<string> names)
		{
			if (names == null)
			{
				throw new ArgumentNullException("names");
			}
			Validate();
			string[] array = names.ToArray();
			if (array.Any((string name) => !ValidSceneName(name)))
			{
				throw new InvalidDataException("The scene contains an invalid placement marker name.");
			}
			if (array.Distinct<string>(StringComparer.Ordinal).Count() != array.Length)
			{
				throw new InvalidDataException("The scene contains duplicate placement marker names.");
			}
			string[] array2 = Placements.Select((PlacementDefinition placement) => placement.Marker).Except<string>(array, StringComparer.Ordinal).ToArray();
			if (array2.Length != 0)
			{
				throw new InvalidDataException("Missing scene markers: " + string.Join(", ", array2));
			}
		}

		public static void ValidatePackSet(IEnumerable<ExpansionDefinition> definitions)
		{
			if (definitions == null)
			{
				throw new ArgumentNullException("definitions");
			}
			ExpansionDefinition[] array = definitions.ToArray();
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			HashSet<int> hashSet2 = new HashSet<int>();
			HashSet<int> hashSet3 = new HashSet<int>();
			HashSet<int> items = new HashSet<int>();
			HashSet<int> npcs = new HashSet<int>();
			HashSet<string> hashSet4 = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			HashSet<string> hashSet5 = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			Dictionary<(ShopKind, int), string> dictionary = new Dictionary<(ShopKind, int), string>();
			string text = null;
			ExpansionDefinition[] array2 = array;
			foreach (ExpansionDefinition expansionDefinition in array2)
			{
				if (expansionDefinition == null)
				{
					throw new InvalidDataException("An expansion pack entry is null.");
				}
				expansionDefinition.Validate();
				if (text != null && text != expansionDefinition.GameBuild)
				{
					throw new InvalidDataException("Expansion packs target different native game builds.");
				}
				text = expansionDefinition.GameBuild;
				if (!hashSet.Add(expansionDefinition.Key) || !hashSet2.Add(expansionDefinition.IslandId) || !hashSet3.Add(expansionDefinition.ContentCollectionId) || !hashSet3.Add(expansionDefinition.EncounterCollectionId) || !hashSet4.Add(expansionDefinition.SceneBundle) || !hashSet5.Add(expansionDefinition.SceneName) || expansionDefinition.Items.Any((ItemDefinition item) => !items.Add(item.Id)) || expansionDefinition.Npcs.Any((NpcDefinition npc) => !npcs.Add(npc.Id)))
				{
					throw new InvalidDataException("Expansion pack identifiers collide: " + expansionDefinition.Key);
				}
				foreach (ShopDefinition item in expansionDefinition.Shops.Where((ShopDefinition shop) => shop.Kind == ShopKind.NativeItem || shop.Kind == ShopKind.NativeLure))
				{
					(ShopKind, int) key = (item.Kind, item.NativeId);
					if (dictionary.TryGetValue(key, out var value) && value != item.NativeName)
					{
						throw new InvalidDataException("Expansion packs disagree about a native stock identity: " + item.Reference);
					}
					dictionary[key] = item.NativeName;
				}
			}
			array2 = array;
			foreach (ExpansionDefinition expansionDefinition2 in array2)
			{
				foreach (ShopDefinition item2 in expansionDefinition2.Shops.Where((ShopDefinition shop) => shop.Kind == ShopKind.NativeItem))
				{
					if (items.Contains(item2.NativeId))
					{
						throw new InvalidDataException("A native stock reference points at an expansion item: " + expansionDefinition2.Key + "/" + item2.Key);
					}
				}
			}
		}

		private static void CheckKeys<T>(IEnumerable<T> values, Func<T, string> key, string kind, List<string> errors) where T : class
		{
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			foreach (T value in values)
			{
				if (value == null || !ValidKey(key(value)) || !hashSet.Add(key(value)))
				{
					errors.Add("Missing, invalid or duplicate " + kind + " key.");
				}
			}
		}

		private static bool ValidKey(string value)
		{
			if (value != null)
			{
				return Regex.IsMatch(value, "^[a-z][a-z0-9_]{0,63}$");
			}
			return false;
		}

		private static bool ValidSceneName(string value)
		{
			if (value != null)
			{
				return Regex.IsMatch(value, "^[A-Za-z][A-Za-z0-9_]{0,95}$");
			}
			return false;
		}

		private static bool ValidFile(string value)
		{
			if (value != null && Regex.IsMatch(value, "^[a-z][a-z0-9_.-]{0,95}$"))
			{
				return !value.Contains("..");
			}
			return false;
		}

		private static bool Finite(float value)
		{
			if (!float.IsNaN(value))
			{
				return !float.IsInfinity(value);
			}
			return false;
		}
	}
	public sealed class ItemDefinition
	{
		public string Key { get; set; } = "";

		public string Title { get; set; } = "";

		public int Id { get; set; }

		public int NativeDonorId { get; set; } = -1;

		public ItemKind Kind { get; set; }

		public string AssetKey { get; set; } = "";

		public int Worth { get; set; }

		public int Cost { get; set; }

		public float CatchWeight { get; set; }

		public int Health { get; set; }
	}
	public sealed class LureDefinition
	{
		public string Key { get; set; } = "";

		public string Title { get; set; } = "";

		public string Description { get; set; } = "";

		public string AssetKey { get; set; } = "";

		public int Cost { get; set; }

		public float LossPercent { get; set; }

		public float CatchTimeMin { get; set; }

		public float CatchTimeMax { get; set; }

		public List<CatchDefinition> Catches { get; set; } = new List<CatchDefinition>();
	}
	public sealed class CatchDefinition
	{
		public string Item { get; set; } = "";

		public float Weight { get; set; }
	}
	public sealed class NpcDefinition
	{
		public string Key { get; set; } = "";

		public string Title { get; set; } = "";

		public int Id { get; set; }

		public NpcRole Role { get; set; }
	}
	public sealed class ShopDefinition
	{
		public string Key { get; set; } = "";

		public ShopKind Kind { get; set; }

		public string Reference { get; set; } = "";

		public int NativeId { get; set; } = -1;

		public string NativeName { get; set; } = "";
	}
	public sealed class PlacementDefinition
	{
		public string Marker { get; set; } = "";

		public PlacementKind Kind { get; set; }

		public string Reference { get; set; } = "";

		public SupportKind Support { get; set; }

		public float FootprintWidth { get; set; }

		public float FootprintDepth { get; set; }

		public float ClearanceHeight { get; set; }

		public SupportKind GetSupportKind()
		{
			if (Support != SupportKind.Inferred)
			{
				return Support;
			}
			SupportKind result;
			switch (Kind)
			{
			case PlacementKind.Boat:
				result = SupportKind.Water;
				break;
			case PlacementKind.Shop:
			case PlacementKind.Table:
				result = SupportKind.Surface;
				break;
			case PlacementKind.Player:
			case PlacementKind.Npc:
			case PlacementKind.Service:
			case PlacementKind.Fishing:
			case PlacementKind.TablePlayer:
				result = SupportKind.Ground;
				break;
			default:
				result = SupportKind.None;
				break;
			}
			return result;
		}
	}
	public sealed class EncounterDefinition
	{
		public string Key { get; set; } = "";

		public string Item { get; set; } = "";

		public string Trophy { get; set; } = "";

		public int NativeDonorId { get; set; }

		public int Health { get; set; }

		public int Damage { get; set; }

		public float Force { get; set; }
	}
	public sealed class QuestStageDefinition
	{
		public string Key { get; set; } = "";

		public string Npc { get; set; } = "";

		public string Trophy { get; set; } = "";

		public string Requires { get; set; } = "";

		public RewardKind RewardKind { get; set; }

		public string Reward { get; set; } = "";
	}
}
namespace HowToFish.ExpansionKit.World
{
	public readonly struct SurfaceSample
	{
		public Vector3 Point { get; }

		public Vector3 Normal { get; }

		public SurfaceSample(Vector3 point, Vector3 normal)
		{
			Point = point;
			Normal = normal;
		}
	}
	public sealed class TriangleSurface
	{
		public const int MaximumIndexedCellVisits = 1000000;

		private readonly Vector3[] vertices;

		private readonly int[] triangles;

		private readonly Dictionary<long, List<int>> cells = new Dictionary<long, List<int>>();

		private readonly float cellSize;

		public Vector3 Minimum { get; }

		public Vector3 Maximum { get; }

		public float Area { get; }

		public TriangleSurface(IReadOnlyList<Vector3> vertices, IReadOnlyList<int> triangles, float cellSize = 1f)
		{
			if (vertices == null)
			{
				throw new ArgumentNullException("vertices");
			}
			if (triangles == null)
			{
				throw new ArgumentNullException("triangles");
			}
			if (!IsFinite(cellSize) || cellSize <= 0f)
			{
				throw new ArgumentOutOfRangeException("cellSize");
			}
			if (triangles.Count == 0 || triangles.Count % 3 != 0)
			{
				throw new ArgumentException("Triangle indices must contain one or more complete triangles.", "triangles");
			}
			this.vertices = new Vector3[vertices.Count];
			for (int i = 0; i < vertices.Count; i++)
			{
				Vector3 vector = vertices[i];
				if (!IsFinite(vector.X) || !IsFinite(vector.Y) || !IsFinite(vector.Z))
				{
					throw new ArgumentException("Surface vertices must be finite.", "vertices");
				}
				this.vertices[i] = vector;
			}
			this.triangles = new int[triangles.Count];
			this.cellSize = cellSize;
			bool flag = false;
			float minX = 0f;
			float minY = 0f;
			float minZ = 0f;
			float maxX = 0f;
			float maxY = 0f;
			float maxZ = 0f;
			float num = 0f;
			long num2 = 0L;
			for (int j = 0; j < triangles.Count; j += 3)
			{
				int num3 = triangles[j];
				int num4 = triangles[j + 1];
				int num5 = triangles[j + 2];
				ValidateVertexIndex(num3, vertices.Count, triangles);
				ValidateVertexIndex(num4, vertices.Count, triangles);
				ValidateVertexIndex(num5, vertices.Count, triangles);
				this.triangles[j] = num3;
				this.triangles[j + 1] = num4;
				this.triangles[j + 2] = num5;
				Vector3 vector2 = this.vertices[num3];
				Vector3 vector3 = this.vertices[num4];
				Vector3 vector4 = this.vertices[num5];
				Vector3 vector5 = Cross(Subtract(vector3, vector2), Subtract(vector4, vector2));
				float num6 = MathF.Sqrt(vector5.X * vector5.X + vector5.Y * vector5.Y + vector5.Z * vector5.Z);
				if (!IsFinite(num6))
				{
					throw new ArgumentException("Surface triangle area must be finite.", "triangles");
				}
				num += num6 * 0.5f;
				if (!flag)
				{
					minX = (maxX = vector2.X);
					minY = (maxY = vector2.Y);
					minZ = (maxZ = vector2.Z);
					flag = true;
				}
				Include(vector2, ref minX, ref minY, ref minZ, ref maxX, ref maxY, ref maxZ);
				Include(vector3, ref minX, ref minY, ref minZ, ref maxX, ref maxY, ref maxZ);
				Include(vector4, ref minX, ref minY, ref minZ, ref maxX, ref maxY, ref maxZ);
				float num7 = MathF.Min(vector2.X, MathF.Min(vector3.X, vector4.X));
				float num8 = MathF.Min(vector2.Z, MathF.Min(vector3.Z, vector4.Z));
				float num9 = MathF.Max(vector2.X, MathF.Max(vector3.X, vector4.X));
				float num10 = MathF.Max(vector2.Z, MathF.Max(vector3.Z, vector4.Z));
				int num11 = FloorToInt(num7 / cellSize);
				int num12 = FloorToInt(num9 / cellSize);
				int num13 = FloorToInt(num8 / cellSize);
				int num14 = FloorToInt(num10 / cellSize);
				long num15 = (long)num12 - (long)num11 + 1;
				long num16 = (long)num14 - (long)num13 + 1;
				if (num15 > 1000000 || num16 > 1000000 || num15 * num16 > 1000000 - num2)
				{
					throw new ArgumentOutOfRangeException("cellSize", "The surface exceeds its spatial-index budget.");
				}
				num2 += num15 * num16;
				for (int k = num11; k <= num12; k++)
				{
					for (int l = num13; l <= num14; l++)
					{
						long key = Key(k, l);
						if (!cells.TryGetValue(key, out List<int> value))
						{
							value = new List<int>();
							cells.Add(key, value);
						}
						value.Add(j);
					}
				}
			}
			if (!IsFinite(num) || num <= 0f)
			{
				throw new ArgumentException("Surface must have positive finite area.", "triangles");
			}
			Minimum = new Vector3(minX, minY, minZ);
			Maximum = new Vector3(maxX, maxY, maxZ);
			Area = num;
		}

		public bool TrySample(Vector3 world, out SurfaceSample sample)
		{
			if (!IsFinite(world.X) || !IsFinite(world.Z))
			{
				throw new ArgumentException("Sample coordinates must be finite.", "world");
			}
			sample = default(SurfaceSample);
			if (!cells.TryGetValue(Key(FloorToInt(world.X / cellSize), FloorToInt(world.Z / cellSize)), out List<int> value))
			{
				return false;
			}
			bool flag = false;
			Vector3 point = default(Vector3);
			Vector3 normal = default(Vector3);
			foreach (int item in value)
			{
				Vector3 vector = vertices[triangles[item]];
				Vector3 vector2 = vertices[triangles[item + 1]];
				Vector3 vector3 = vertices[triangles[item + 2]];
				if (Barycentric(world, vector, vector2, vector3, out var u, out var v, out var w))
				{
					Vector3 vector4 = new Vector3(vector.X * u + vector2.X * v + vector3.X * w, vector.Y * u + vector2.Y * v + vector3.Y * w, vector.Z * u + vector2.Z * v + vector3.Z * w);
					if (!flag || !(vector4.Y <= point.Y))
					{
						Vector3 vector5 = Normalize(Cross(Subtract(vector2, vector), Subtract(vector3, vector)));
						point = vector4;
						normal = ((vector5.Y < 0f) ? new Vector3(0f - vector5.X, 0f - vector5.Y, 0f - vector5.Z) : vector5);
						flag = true;
					}
				}
			}
			if (flag)
			{
				sample = new SurfaceSample(point, normal);
			}
			return flag;
		}

		private static void ValidateVertexIndex(int index, int count, IReadOnlyList<int> argument)
		{
			if (index < 0 || index >= count)
			{
				throw new ArgumentException("A triangle index is outside the vertex array.", "argument");
			}
		}

		private static void Include(Vector3 value, ref float minX, ref float minY, ref float minZ, ref float maxX, ref float maxY, ref float maxZ)
		{
			minX = MathF.Min(minX, value.X);
			minY = MathF.Min(minY, value.Y);
			minZ = MathF.Min(minZ, value.Z);
			maxX = MathF.Max(maxX, value.X);
			maxY = MathF.Max(maxY, value.Y);
			maxZ = MathF.Max(maxZ, value.Z);
		}

		private static bool Barycentric(Vector3 p, Vector3 a, Vector3 b, Vector3 c, out float u, out float v, out float w)
		{
			float num = b.X - a.X;
			float num2 = b.Z - a.Z;
			float num3 = c.X - a.X;
			float num4 = c.Z - a.Z;
			float num5 = p.X - a.X;
			float num6 = p.Z - a.Z;
			float num7 = num * num4 - num3 * num2;
			u = (v = (w = 0f));
			if (MathF.Abs(num7) < 1E-09f)
			{
				return false;
			}
			v = (num5 * num4 - num3 * num6) / num7;
			w = (num * num6 - num5 * num2) / num7;
			u = 1f - v - w;
			if (u >= -0.0001f && v >= -0.0001f)
			{
				return w >= -0.0001f;
			}
			return false;
		}

		private static Vector3 Subtract(Vector3 left, Vector3 right)
		{
			return new Vector3(left.X - right.X, left.Y - right.Y, left.Z - right.Z);
		}

		private static Vector3 Cross(Vector3 left, Vector3 right)
		{
			return new Vector3(left.Y * right.Z - left.Z * right.Y, left.Z * right.X - left.X * right.Z, left.X * right.Y - left.Y * right.X);
		}

		private static Vector3 Normalize(Vector3 value)
		{
			float num = MathF.Sqrt(value.X * value.X + value.Y * value.Y + value.Z * value.Z);
			return new Vector3(value.X / num, value.Y / num, value.Z / num);
		}

		private static int FloorToInt(float value)
		{
			if (!IsFinite(value) || (double)value < -2147483648.0 || (double)value > 2147483647.0)
			{
				throw new ArgumentOutOfRangeException("value");
			}
			return (int)MathF.Floor(value);
		}

		private static bool IsFinite(float value)
		{
			if (!float.IsNaN(value))
			{
				return !float.IsInfinity(value);
			}
			return false;
		}

		private static long Key(int x, int z)
		{
			return ((long)x << 32) ^ (uint)z;
		}
	}
	public sealed class GroundCoverSettings
	{
		public float MeshSize { get; }

		public float Density { get; }

		public float PositionRandomness { get; }

		public float RotationRandomness { get; }

		public float MinimumScale { get; }

		public float MaximumScale { get; }

		public float MinimumNormalY { get; }

		public float SeaLevel { get; }

		public float MinimumHeightAboveSea { get; }

		public float EdgeMargin { get; }

		public int Seed { get; }

		public float Step => MeshSize / Density;

		public GroundCoverSettings(float meshSize, float density, float positionRandomness, float rotationRandomness, float minimumScale, float maximumScale, float minimumNormalY, float seaLevel, float minimumHeightAboveSea, float edgeMargin, int seed)
		{
			if (!FinitePositive(meshSize))
			{
				throw new ArgumentOutOfRangeException("meshSize");
			}
			if (!FinitePositive(density))
			{
				throw new ArgumentOutOfRangeException("density");
			}
			if (!Finite(positionRandomness) || positionRandomness < 0f || positionRandomness > 1f)
			{
				throw new ArgumentOutOfRangeException("positionRandomness");
			}
			if (!Finite(rotationRandomness) || rotationRandomness < 0f || rotationRandomness > 360f)
			{
				throw new ArgumentOutOfRangeException("rotationRandomness");
			}
			if (!FinitePositive(minimumScale))
			{
				throw new ArgumentOutOfRangeException("minimumScale");
			}
			if (!Finite(maximumScale) || maximumScale < minimumScale)
			{
				throw new ArgumentOutOfRangeException("maximumScale");
			}
			if (!Finite(minimumNormalY) || minimumNormalY < -1f || minimumNormalY > 1f)
			{
				throw new ArgumentOutOfRangeException("minimumNormalY");
			}
			if (!Finite(seaLevel))
			{
				throw new ArgumentOutOfRangeException("seaLevel");
			}
			if (!Finite(minimumHeightAboveSea) || minimumHeightAboveSea < 0f)
			{
				throw new ArgumentOutOfRangeException("minimumHeightAboveSea");
			}
			if (!Finite(edgeMargin) || edgeMargin < 0f)
			{
				throw new ArgumentOutOfRangeException("edgeMargin");
			}
			MeshSize = meshSize;
			Density = density;
			PositionRandomness = positionRandomness;
			RotationRandomness = rotationRandomness;
			MinimumScale = minimumScale;
			MaximumScale = maximumScale;
			MinimumNormalY = minimumNormalY;
			SeaLevel = seaLevel;
			MinimumHeightAboveSea = minimumHeightAboveSea;
			EdgeMargin = edgeMargin;
			Seed = seed;
		}

		private static bool Finite(float value)
		{
			if (!float.IsNaN(value))
			{
				return !float.IsInfinity(value);
			}
			return false;
		}

		private static bool FinitePositive(float value)
		{
			if (Finite(value))
			{
				return value > 0f;
			}
			return false;
		}
	}
	public readonly struct GroundCoverPlacement
	{
		public Vector3 Point { get; }

		public Vector3 Normal { get; }

		public float RollDegrees { get; }

		public float Scale { get; }

		public GroundCoverPlacement(Vector3 point, Vector3 normal, float rollDegrees, float scale)
		{
			Point = point;
			Normal = normal;
			RollDegrees = rollDegrees;
			Scale = scale;
		}
	}
	public enum GroundCoverExclusion
	{
		None,
		Covered,
		Blocked
	}
	public sealed class GroundCoverPlanStatistics
	{
		public int Candidates { get; internal set; }

		public int OffSurface { get; internal set; }

		public int Steep { get; internal set; }

		public int Low { get; internal set; }

		public int Edge { get; internal set; }

		public int Covered { get; internal set; }

		public int Blocked { get; internal set; }

		public int Thinned { get; internal set; }

		public int Accepted { get; internal set; }
	}
	public sealed class GroundCoverPlan
	{
		public IReadOnlyList<GroundCoverPlacement> Placements { get; }

		public GroundCoverPlanStatistics Statistics { get; }

		internal GroundCoverPlan(IReadOnlyList<GroundCoverPlacement> placements, GroundCoverPlanStatistics statistics)
		{
			Placements = placements;
			Statistics = statistics;
		}
	}
	public sealed class GroundCoverPlanner
	{
		public const int MaximumCandidates = 1000000;

		private readonly TriangleSurface surface;

		private readonly GroundCoverSettings settings;

		public GroundCoverPlanner(TriangleSurface surface, GroundCoverSettings settings)
		{
			this.surface = surface ?? throw new ArgumentNullException("surface");
			this.settings = settings ?? throw new ArgumentNullException("settings");
		}

		public GroundCoverPlan Plan(int maximumPlacements, Func<GroundCoverPlacement, GroundCoverExclusion> exclude)
		{
			if (maximumPlacements <= 0)
			{
				throw new ArgumentOutOfRangeException("maximumPlacements");
			}
			if (exclude == null)
			{
				throw new ArgumentNullException("exclude");
			}
			Random random = new Random(settings.Seed);
			List<GroundCoverPlacement> list = new List<GroundCoverPlacement>();
			GroundCoverPlanStatistics groundCoverPlanStatistics = new GroundCoverPlanStatistics();
			float step = settings.Step;
			int num = CeilingToInt((surface.Maximum.X - surface.Minimum.X) / step);
			int num2 = CeilingToInt((surface.Maximum.Z - surface.Minimum.Z) / step);
			if ((long)num * (long)num2 > 1000000)
			{
				throw new ArgumentOutOfRangeException("settings", "The ground-cover grid exceeds its candidate budget.");
			}
			for (int i = 0; i < num; i++)
			{
				for (int j = 0; j < num2; j++)
				{
					float num3 = Range(random, 0f - settings.PositionRandomness, settings.PositionRandomness) * settings.MeshSize * 0.5f;
					float num4 = Range(random, 0f - settings.PositionRandomness, settings.PositionRandomness) * settings.MeshSize * 0.5f;
					float rollDegrees = Range(random, 0f - settings.RotationRandomness, settings.RotationRandomness);
					float scale = Range(random, settings.MinimumScale, settings.MaximumScale);
					groundCoverPlanStatistics.Candidates++;
					Vector3 world = new Vector3(surface.Minimum.X + ((float)i + 0.5f) * step + num3, 0f, surface.Minimum.Z + ((float)j + 0.5f) * step + num4);
					if (!surface.TrySample(world, out var sample))
					{
						groundCoverPlanStatistics.OffSurface++;
						continue;
					}
					if (sample.Normal.Y < settings.MinimumNormalY)
					{
						groundCoverPlanStatistics.Steep++;
						continue;
					}
					if (sample.Point.Y < settings.SeaLevel + settings.MinimumHeightAboveSea)
					{
						groundCoverPlanStatistics.Low++;
						continue;
					}
					if (!HasEdgeClearance(sample.Point))
					{
						groundCoverPlanStatistics.Edge++;
						continue;
					}
					GroundCoverPlacement groundCoverPlacement = new GroundCoverPlacement(sample.Point, sample.Normal, rollDegrees, scale);
					switch (exclude(groundCoverPlacement))
					{
					case GroundCoverExclusion.Covered:
						groundCoverPlanStatistics.Covered++;
						break;
					case GroundCoverExclusion.Blocked:
						groundCoverPlanStatistics.Blocked++;
						break;
					default:
						throw new ArgumentOutOfRangeException("exclude", "The exclusion callback returned an undefined reason.");
					case GroundCoverExclusion.None:
						list.Add(groundCoverPlacement);
						break;
					}
				}
			}
			if (list.Count > maximumPlacements)
			{
				float num5 = (float)maximumPlacements / (float)list.Count;
				List<GroundCoverPlacement> list2 = new List<GroundCoverPlacement>(maximumPlacements);
				for (int k = 0; k < list.Count; k++)
				{
					if (list2.Count >= maximumPlacements)
					{
						break;
					}
					if ((float)k * 0.618034f % 1f < num5)
					{
						list2.Add(list[k]);
					}
				}
				groundCoverPlanStatistics.Thinned = list.Count - list2.Count;
				list = list2;
			}
			groundCoverPlanStatistics.Accepted = list.Count;
			return new GroundCoverPlan(list.AsReadOnly(), groundCoverPlanStatistics);
		}

		private bool HasEdgeClearance(Vector3 point)
		{
			float edgeMargin = settings.EdgeMargin;
			if (SampleOffset(point, edgeMargin, edgeMargin) && SampleOffset(point, 0f - edgeMargin, edgeMargin) && SampleOffset(point, edgeMargin, 0f - edgeMargin))
			{
				return SampleOffset(point, 0f - edgeMargin, 0f - edgeMargin);
			}
			return false;
		}

		private bool SampleOffset(Vector3 point, float x, float z)
		{
			SurfaceSample sample;
			return surface.TrySample(new Vector3(point.X + x, point.Y, point.Z + z), out sample);
		}

		private static float Range(Random random, float minimum, float maximum)
		{
			return minimum + (float)random.NextDouble() * (maximum - minimum);
		}

		private static int CeilingToInt(float value)
		{
			if (float.IsNaN(value) || float.IsInfinity(value) || value < 0f || (double)value > 2147483647.0)
			{
				throw new ArgumentOutOfRangeException("value");
			}
			return (int)MathF.Ceiling(value);
		}
	}
}
namespace HowToFish.ExpansionKit.Progression
{
	public sealed class InterludeRouteSelection
	{
		internal int RequiredNativeUnlockCount { get; }

		internal int ContinuationIslandId { get; }

		public int NativeUnlockCount { get; }

		public bool IsEligible { get; }

		public bool HadContinuationAccess { get; }

		internal InterludeRouteSelection(int requiredNativeUnlockCount, int continuationIslandId, int nativeUnlockCount, bool isEligible, bool hadContinuationAccess)
		{
			RequiredNativeUnlockCount = requiredNativeUnlockCount;
			ContinuationIslandId = continuationIslandId;
			NativeUnlockCount = nativeUnlockCount;
			IsEligible = isEligible;
			HadContinuationAccess = hadContinuationAccess;
		}
	}
	public sealed class InterludeRoutePolicy
	{
		public int RequiredNativeUnlockCount { get; }

		public int ContinuationIslandId { get; }

		public InterludeRoutePolicy(int requiredNativeUnlockCount, int continuationIslandId)
		{
			if (requiredNativeUnlockCount < 1 || requiredNativeUnlockCount > 255)
			{
				throw new ArgumentOutOfRangeException("requiredNativeUnlockCount");
			}
			if (continuationIslandId < 0 || continuationIslandId > 255)
			{
				throw new ArgumentOutOfRangeException("continuationIslandId");
			}
			RequiredNativeUnlockCount = requiredNativeUnlockCount;
			ContinuationIslandId = continuationIslandId;
		}

		public static bool IsUnlocked(int zeroBasedIslandId, int exclusiveNativeUnlockCount)
		{
			if (zeroBasedIslandId < 0 || zeroBasedIslandId > 255)
			{
				throw new ArgumentOutOfRangeException("zeroBasedIslandId");
			}
			ValidateUnlockCount(exclusiveNativeUnlockCount);
			return zeroBasedIslandId < exclusiveNativeUnlockCount;
		}

		public bool IsEligible(int exclusiveNativeUnlockCount)
		{
			ValidateUnlockCount(exclusiveNativeUnlockCount);
			return exclusiveNativeUnlockCount >= RequiredNativeUnlockCount;
		}

		public InterludeRouteSelection CaptureAtSaveSelection(int exclusiveNativeUnlockCount)
		{
			ValidateUnlockCount(exclusiveNativeUnlockCount);
			return new InterludeRouteSelection(RequiredNativeUnlockCount, ContinuationIslandId, exclusiveNativeUnlockCount, IsEligible(exclusiveNativeUnlockCount), IsUnlocked(ContinuationIslandId, exclusiveNativeUnlockCount));
		}

		public bool CanAccessContinuation(InterludeRouteSelection selection, bool interludeCompleted, int currentExclusiveNativeUnlockCount)
		{
			if (selection == null)
			{
				throw new ArgumentNullException("selection");
			}
			if (selection.RequiredNativeUnlockCount != RequiredNativeUnlockCount || selection.ContinuationIslandId != ContinuationIslandId)
			{
				throw new ArgumentException("The selection snapshot belongs to a different route policy.", "selection");
			}
			ValidateUnlockCount(currentExclusiveNativeUnlockCount);
			return selection.HadContinuationAccess || interludeCompleted;
		}

		private static void ValidateUnlockCount(int count)
		{
			if (count < 0 || count > 255)
			{
				throw new ArgumentOutOfRangeException("count");
			}
		}
	}
}
namespace HowToFish.ExpansionKit.Packs
{
	public static class NativePackCatalog
	{
		private static readonly HashSet<int> Weapons = new HashSet<int> { 54, 57, 58, 60, 63, 64, 66, 68, 69, 70 };

		private static readonly HashSet<int> RangedWeapons = new HashSet<int> { 54, 66, 68, 69, 70 };

		private static readonly int[][] Npcs = new int[6][]
		{
			new int[0],
			new int[1] { 1 },
			new int[2] { 2, 3 },
			new int[3] { 4, 5, 6 },
			new int[6] { 4, 7, 8, 9, 10, 11 },
			new int[3] { 12, 13, 14 }
		};

		public static bool Contains(ContentDomain domain, int id)
		{
			switch (domain)
			{
			case ContentDomain.Item:
				if (id >= 0 && id <= 85)
				{
					return id != 30;
				}
				return false;
			case ContentDomain.Lure:
				if (id >= 0)
				{
					return id <= 16;
				}
				return false;
			case ContentDomain.Npc:
				if (id >= 1)
				{
					return id <= 14;
				}
				return false;
			case ContentDomain.Island:
				if (id >= 1)
				{
					return id <= 5;
				}
				return false;
			case ContentDomain.Attachment:
				if (id >= 0)
				{
					return id <= 6;
				}
				return false;
			case ContentDomain.Ammunition:
				if (id >= 1)
				{
					return id <= 12;
				}
				return false;
			case ContentDomain.BoatRadar:
				return id == 0;
			case ContentDomain.Sharpening:
				if (id >= 1)
				{
					return id <= 15;
				}
				return false;
			case ContentDomain.Pocket:
				if (id >= 1)
				{
					return id <= 5;
				}
				return false;
			case ContentDomain.Motor:
				if (id != 1)
				{
					return id == 2;
				}
				return true;
			default:
				return false;
			}
		}

		public static bool IsCreature(int id)
		{
			if ((id < 0 || id > 52 || id == 30) && id != 56)
			{
				if (id >= 79)
				{
					return id <= 85;
				}
				return false;
			}
			return true;
		}

		public static bool IsRangedWeapon(int id)
		{
			return RangedWeapons.Contains(id);
		}

		public static bool SupportsItemKind(int id, PackItemKind kind)
		{
			if (!Contains(ContentDomain.Item, id))
			{
				return false;
			}
			switch (kind)
			{
			case PackItemKind.Fish:
				if (id >= 0 && id <= 52)
				{
					return id != 30;
				}
				return false;
			case PackItemKind.Creature:
				return IsCreature(id);
			case PackItemKind.Food:
				if (id >= 81)
				{
					return id <= 85;
				}
				return false;
			case PackItemKind.Weapon:
				return Weapons.Contains(id);
			case PackItemKind.Rod:
				if (id != 59)
				{
					return id == 61;
				}
				return true;
			case PackItemKind.Item:
				return true;
			default:
				return false;
			}
		}

		public static bool HasNpcDonor(int island, int npc)
		{
			if (island >= 1 && island <= 5)
			{
				return Array.IndexOf(Npcs[island], npc) >= 0;
			}
			return false;
		}
	}
	internal static class PackCanonical
	{
		internal const int FingerprintVersion = 2;

		internal static T Copy<T>(T value) where T : class
		{
			return (T)CopyValue(value);
		}

		private static object? CopyValue(object? value)
		{
			if (value == null || value is string || value.GetType().IsValueType)
			{
				return value;
			}
			if (value is IList list)
			{
				IList list2 = (IList)Activator.CreateInstance(value.GetType());
				{
					foreach (object item in list)
					{
						list2.Add(CopyValue(item));
					}
					return list2;
				}
			}
			object obj = Activator.CreateInstance(value.GetType());
			PropertyInfo[] array = Properties(value.GetType());
			foreach (PropertyInfo propertyInfo in array)
			{
				propertyInfo.SetValue(obj, CopyValue(propertyInfo.GetValue(value)));
			}
			return obj;
		}

		internal static string Hash(object value)
		{
			return Hash(value, LogicalType, versioned: true);
		}

		internal static IEnumerable<string> LegacyHashes(object value)
		{
			string[] array = new string[2] { "0.4.0.0", "0.5.0.0" };
			foreach (string version in array)
			{
				string[] array2 = new string[2] { "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", "System.Private.CoreLib, Version=9.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e" };
				foreach (string core in array2)
				{
					yield return Hash(value, (Type type) => LegacyType(type, version, core), versioned: false);
				}
			}
		}

		private static string Hash(object value, Func<Type, string> typeName, bool versioned)
		{
			using MemoryStream memoryStream = new MemoryStream();
			using (BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8, leaveOpen: true))
			{
				if (versioned)
				{
					binaryWriter.Write("ExpansionKit canonical");
					binaryWriter.Write(2);
				}
				Write(binaryWriter, value, typeName);
			}
			using SHA256 sHA = SHA256.Create();
			return string.Concat(from x in sHA.ComputeHash(memoryStream.ToArray())
				select x.ToString("x2"));
		}

		private static string LogicalType(Type type)
		{
			if (type.IsArray)
			{
				return LogicalType(type.GetElementType()) + "[]";
			}
			if (!type.IsGenericType)
			{
				return type.FullName;
			}
			return type.GetGenericTypeDefinition().FullName + "<" + string.Join(",", type.GetGenericArguments().Select(LogicalType)) + ">";
		}

		private static string LegacyType(Type type, string sdkVersion, string core)
		{
			if (type.IsArray)
			{
				return LegacyType(type.GetElementType(), sdkVersion, core) + "[]";
			}
			if (!type.IsGenericType)
			{
				return type.FullName;
			}
			return type.GetGenericTypeDefinition().FullName + "[" + string.Join(",", type.GetGenericArguments().Select(delegate(Type argument)
			{
				string text = ((argument.Assembly == typeof(PackDefinition).Assembly) ? ("HowToFish.ExpansionKit, Version=" + sdkVersion + ", Culture=neutral, PublicKeyToken=null") : ((argument.Assembly == typeof(string).Assembly) ? core : argument.Assembly.FullName));
				return "[" + LegacyType(argument, sdkVersion, core) + ", " + text + "]";
			})) + "]";
		}

		private static PropertyInfo[] Properties(Type type)
		{
			return (from x in type.GetProperties(BindingFlags.Instance | BindingFlags.Public)
				where x.CanRead && x.CanWrite
				select x).OrderBy<PropertyInfo, string>((PropertyInfo x) => x.Name, StringComparer.Ordinal).ToArray();
		}

		private static void Write(BinaryWriter writer, object? value, Func<Type, string> typeName)
		{
			if (value == null)
			{
				writer.Write((byte)0);
				return;
			}
			writer.Write((byte)1);
			writer.Write(typeName(value.GetType()));
			if (value is string value2)
			{
				writer.Write(value2);
			}
			else if (value is float value3)
			{
				writer.Write(value3);
			}
			else if (value is bool value4)
			{
				writer.Write(value4);
			}
			else if (value.GetType().IsValueType)
			{
				writer.Write(Convert.ToInt64(value));
			}
			else if (value is IList source)
			{
				object[] array = source.Cast<object>().ToArray();
				if (array.Length != 0 && array[0].GetType().GetProperty("Key") != null)
				{
					array = array.OrderBy<object, string>((object x) => (string)x.GetType().GetProperty("Key").GetValue(x), StringComparer.Ordinal).ToArray();
				}
				writer.Write(array.Length);
				object[] array2 = array;
				foreach (object value5 in array2)
				{
					Write(writer, value5, typeName);
				}
			}
			else
			{
				PropertyInfo[] array3 = Properties(value.GetType());
				writer.Write(array3.Length);
				PropertyInfo[] array4 = array3;
				foreach (PropertyInfo propertyInfo in array4)
				{
					writer.Write(propertyInfo.Name);
					Write(writer, propertyInfo.GetValue(value), typeName);
				}
			}
		}
	}
	public enum ContentDomain
	{
		Item,
		Lure,
		Npc,
		Island,
		Quest,
		Shop,
		Loot,
		Attachment,
		Ammunition,
		BoatRadar,
		Sharpening,
		Pocket,
		Motor
	}
	public enum RecipeImplementation
	{
		NativeClone,
		External
	}
	public enum PackItemKind
	{
		Item,
		Food,
		Fish,
		Creature,
		Weapon,
		Rod
	}
	public enum PackNpcRole
	{
		Merchant,
		DeliveryQuest,
		External
	}
	public enum PackShopKind
	{
		Item,
		Lure,
		Attachment,
		Ammunition,
		BoatRadar,
		Sharpening,
		Pocket,
		Motor
	}
	public enum ShopCapPolicy
	{
		Unlimited,
		PerPlayer,
		Shared
	}
	public enum QuestObjectiveKind
	{
		DeliverItem,
		CatchItem
	}
	public enum QuestRewardKind
	{
		Item,
		Lure,
		Coordinates,
		Money
	}
	public enum CatchPatchMode
	{
		Additive,
		ExclusiveReplace
	}
	public enum ReloadPolicy
	{
		Donor,
		Magazine,
		SingleRound
	}
	public enum MagazinePolicy
	{
		Donor,
		Fixed,
		PerUpgrade
	}
	public sealed class PackDefinition
	{
		public const int CurrentSchema = 1;

		public const string SupportedGameBuild = "25127368";

		public int SchemaVersion { get; set; } = 1;

		public string Key { get; set; } = "";

		public string Title { get; set; } = "";

		public string Version { get; set; } = "1.0.0";

		public string GameBuild { get; set; } = "25127368";

		public List<PackDependency> Dependencies { get; set; } = new List<PackDependency>();

		public List<string> ExtensionHooks { get; set; } = new List<string>();

		public List<NetworkCollectionRecipe> Collections { get; set; } = new List<NetworkCollectionRecipe>();

		public List<ItemRecipe> Items { get; set; } = new List<ItemRecipe>();

		public List<LureRecipe> Lures { get; set; } = new List<LureRecipe>();

		public List<CatchTablePatch> CatchPatches { get; set; } = new List<CatchTablePatch>();

		public List<NpcRecipe> Npcs { get; set; } = new List<NpcRecipe>();

		public List<ShopRecipe> Shops { get; set; } = new List<ShopRecipe>();

		public List<IslandRecipe> Islands { get; set; } = new List<IslandRecipe>();

		public List<QuestRecipe> Quests { get; set; } = new List<QuestRecipe>();

		public List<LootRecipe> Loot { get; set; } = new List<LootRecipe>();

		public IReadOnlyList<string> Inspect()
		{
			return PackValidation.Inspect(this);
		}

		public void Validate()
		{
			PackValidation.ThrowIfInvalid(Inspect());
		}
	}
	public sealed class PackDependency
	{
		public string Key { get; set; } = "";

		public string? MinimumVersion { get; set; }

		public string? ExactVersion { get; set; }
	}
	public sealed class NetworkCollectionRecipe
	{
		public string Key { get; set; } = "";

		public ushort Id { get; set; }

		public int Capacity { get; set; } = 256;
	}
	public sealed class NetworkBinding
	{
		public ushort CollectionId { get; set; }

		public ushort Slot { get; set; }
	}
	public abstract class PackRecipe
	{
		public string Key { get; set; } = "";

		public RecipeImplementation Implementation { get; set; }

		public string? ExtensionHook { get; set; }
	}
	public sealed class VectorRecipe
	{
		public float X { get; set; }

		public float Y { get; set; }

		public float Z { get; set; }
	}
	public sealed class PoseRecipe
	{
		public VectorRecipe Position { get; set; } = new VectorRecipe();

		public float Yaw { get; set; }
	}
	public sealed class PlacementRecipe
	{
		public string? Marker { get; set; }

		public PoseRecipe? Pose { get; set; }
	}
	public sealed class ArtRecipe
	{
		public string Bundle { get; set; } = "";

		public string Sha256 { get; set; } = "";

		public string Prefab { get; set; } = "";

		public VectorRecipe Position { get; set; } = new VectorRecipe();

		public VectorRecipe Rotation { get; set; } = new VectorRecipe();

		public VectorRecipe Scale { get; set; } = new VectorRecipe
		{
			X = 1f,
			Y = 1f,
			Z = 1f
		};

		public PoseRecipe? Grip { get; set; }
	}
	public sealed class ItemRecipe : PackRecipe
	{
		public byte Id { get; set; }

		public string Title { get; set; } = "";

		public PackItemKind Kind { get; set; }

		public string? NativeDonor { get; set; }

		public NetworkBinding Network { get; set; } = new NetworkBinding();

		public ArtRecipe? Art { get; set; }

		public int? Worth { get; set; }

		public int? Cost { get; set; }

		public float? Health { get; set; }

		public float? HealthRestored { get; set; }

		public float? FoodValue { get; set; }

		public bool? Edible { get; set; }

		public bool? Cookable { get; set; }

		public bool? Cooked { get; set; }

		public string? CookedItem { get; set; }

		public float? BodyDamageFactor { get; set; }

		public float? CrewHealthFactor { get; set; }

		public float? CrewDamageFactor { get; set; }

		public WeaponRecipe? Weapon { get; set; }

		public RodRecipe? Rod { get; set; }
	}
	public sealed class WeaponRecipe
	{
		public float? DamageScale { get; set; }

		public List<float> UpgradeDamageScales { get; set; } = new List<float>();

		public MagazinePolicy MagazinePolicy { get; set; }

		public int? MagazineSize { get; set; }

		public List<int> UpgradeMagazineSizes { get; set; } = new List<int>();

		public ReloadPolicy ReloadPolicy { get; set; }

		public float? ReloadSeconds { get; set; }

		public float? ShotDelaySeconds { get; set; }

		public List<byte>? AllowedAttachmentIds { get; set; }
	}
	public sealed class RodRecipe
	{
		public float? MaximumLineLength { get; set; }

		public float? LineStrengthScale { get; set; }

		public float? ReelingSpeedScale { get; set; }

		public List<float> UpgradeLineStrengthScales { get; set; } = new List<float>();

		public List<float> UpgradeReelingSpeedScales { get; set; } = new List<float>();
	}
	public sealed class CatchRecipe
	{
		public string Item { get; set; } = "";

		public float Weight { get; set; }
	}
	public sealed class LureRecipe : PackRecipe
	{
		public byte Id { get; set; }

		public string Title { get; set; } = "";

		public string Description { get; set; } = "";

		public string? NativeVisualDonor { get; set; }

		public ArtRecipe? Art { get; set; }

		public int? Cost { get; set; }

		public float? LossPercent { get; set; }

		public float? CatchTimeMin { get; set; }

		public float? CatchTimeMax { get; set; }

		public List<CatchRecipe> Catches { get; set; } = new List<CatchRecipe>();
	}
	public sealed class CatchTablePatch : PackRecipe
	{
		public string TargetLure { get; set; } = "";

		public CatchPatchMode Mode { get; set; }

		public List<CatchRecipe> Catches { get; set; } = new List<CatchRecipe>();
	}
	public sealed class NpcRecipe : PackRecipe
	{
		public byte Id { get; set; }

		public string Title { get; set; } = "";

		public string? NativeDonorIsland { get; set; }

		public string? NativeDonor { get; set; }

		public NetworkBinding? Network { get; set; }

		public string Island { get; set; } = "";

		public PlacementRecipe Placement { get; set; } = new PlacementRecipe();

		public PackNpcRole Role { get; set; }

		public List<string> Dialogue { get; set; } = new List<string>();

		public string? Quest { get; set; }

		public ArtRecipe? Art { get; set; }
	}
	public sealed class ShopRecipe : PackRecipe
	{
		public string Island { get; set; } = "";

		public PlacementRecipe Placement { get; set; } = new PlacementRecipe();

		public PackShopKind Kind { get; set; }

		public string Content { get; set; } = "";

		public int? Cost { get; set; }

		public ShopCapPolicy CapPolicy { get; set; }

		public int? Cap { get; set; }
	}
	public sealed class RouteRecipe
	{
		public string AfterIsland { get; set; } = "";

		public string BeforeIsland { get; set; } = "";

		public int Priority { get; set; }
	}
	public sealed class IslandRecipe : PackRecipe
	{
		public byte Id { get; set; }

		public string Title { get; set; } = "";

		public string SceneBundle { get; set; } = "";

		public string SceneSha256 { get; set; } = "";

		public string SceneName { get; set; } = "";

		public string SceneRoot { get; set; } = "";

		public int UnlockThreshold { get; set; }

		public RouteRecipe? Route { get; set; }

		public PlacementRecipe PlayerSpawn { get; set; } = new PlacementRecipe();

		public PlacementRecipe BoatSpawn { get; set; } = new PlacementRecipe();

		public VectorRecipe WorldMapPosition { get; set; } = new VectorRecipe();
	}
	public sealed class QuestObjectiveRecipe
	{
		public QuestObjectiveKind Kind { get; set; }

		public string Item { get; set; } = "";

		public int Count { get; set; }
	}
	public sealed class QuestRewardRecipe
	{
		public QuestRewardKind Kind { get; set; }

		public string? Content { get; set; }

		public int Amount { get; set; } = 1;
	}
	public sealed class QuestRecipe : PackRecipe
	{
		public string Title { get; set; } = "";

		public List<string> Requires { get; set; } = new List<string>();

		public List<QuestObjectiveRecipe> Objectives { get; set; } = new List<QuestObjectiveRecipe>();

		public List<QuestRewardRecipe> Rewards { get; set; } = new List<QuestRewardRecipe>();
	}
	public sealed class LootDropRecipe
	{
		public string Item { get; set; } = "";

		public int FixedQuantity { get; set; }

		public int PerPlayerQuantity { get; set; }
	}
	public sealed class LootRecipe : PackRecipe
	{
		public string Creature { get; set; } = "";

		public List<LootDropRecipe> Drops { get; set; } = new List<LootDropRecipe>();
	}
	public sealed class PackQuestProgress
	{
		public string Quest { get; set; } = "";

		public List<int> Counts { get; set; } = new List<int>();

		public bool Claimed { get; set; }
	}
	public sealed class PackQuestSnapshot
	{
		public int SchemaVersion { get; set; } = 1;

		public List<PackQuestProgress> Quests { get; set; } = new List<PackQuestProgress>();
	}
	public sealed class PackQuestJournal
	{
		private readonly Dictionary<string, QuestRecipe> recipes;

		private readonly Dictionary<string, PackQuestProgress> progress;

		public PackQuestJournal(PackRegistry registry, PackQuestSnapshot? saved = null)
		{
			if (registry == null)
			{
				throw new ArgumentNullException("registry");
			}
			recipes = registry.Packs.SelectMany((PackDefinition p) => p.Quests.Select((QuestRecipe q) => (Key: p.Key + ":" + q.Key, Recipe: q))).ToDictionary<(string, QuestRecipe), string, QuestRecipe>(((string Key, QuestRecipe Recipe) x) => x.Key, ((string Key, QuestRecipe Recipe) x) => x.Recipe, StringComparer.Ordinal);
			progress = recipes.ToDictionary<KeyValuePair<string, QuestRecipe>, string, PackQuestProgress>((KeyValuePair<string, QuestRecipe> x) => x.Key, (KeyValuePair<string, QuestRecipe> x) => new PackQuestProgress
			{
				Quest = x.Key,
				Counts = Enumerable.Repeat(0, x.Value.Objectives.Count).ToList()
			}, StringComparer.Ordinal);
			if (saved == null)
			{
				return;
			}
			if (saved.SchemaVersion != 1 || saved.Quests == null || saved.Quests.Count > 8192 || saved.Quests.Any((PackQuestProgress x) => x == null))
			{
				throw new InvalidDataException("Invalid bounded quest snapshot.");
			}
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			foreach (PackQuestProgress quest in saved.Quests)
			{
				if (quest.Quest == null || !hashSet.Add(quest.Quest) || !recipes.TryGetValue(quest.Quest, out QuestRecipe recipe) || quest.Counts == null || quest.Counts.Count != recipe.Objectives.Count || quest.Counts.Where((int x, int i) => x < 0 || x > recipe.Objectives[i].Count).Any())
				{
					throw new InvalidDataException("Invalid, duplicate or missing quest in snapshot: " + quest.Quest);
				}
				progress[quest.Quest] = PackCanonical.Copy(quest);
			}
			foreach (PackQuestProgress value in progress.Values)
			{
				if (value.Claimed && (!Complete(value.Quest) || !Unlocked(value.Quest)))
				{
					throw new InvalidDataException("Claimed quest lacks objectives or claimed prerequisites: " + value.Quest);
				}
			}
		}

		public PackQuestSnapshot Snapshot()
		{
			return new PackQuestSnapshot
			{
				Quests = progress.Values.OrderBy<PackQuestProgress, string>((PackQuestProgress x) => x.Quest, StringComparer.Ordinal).Select(PackCanonical.Copy).ToList()
			};
		}

		public bool IsClaimed(string quest)
		{
			return Get(quest).Claimed;
		}

		public bool Unlocked(string quest)
		{
			Get(quest);
			return recipes[quest].Requires.All((string x) => progress[x].Claimed);
		}

		public bool Complete(string quest)
		{
			return Get(quest).Counts.Select((int count, int index) => count >= recipes[quest].Objectives[index].Count).All((bool x) => x);
		}

		public bool CanClaim(string quest)
		{
			if (!Get(quest).Claimed && Unlocked(quest))
			{
				return Complete(quest);
			}
			return false;
		}

		public void Record(string quest, int objectiveIndex, int amount)
		{
			PackQuestProgress packQuestProgress = Get(quest);
			if (objectiveIndex < 0 || objectiveIndex >= packQuestProgress.Counts.Count || amount < 1 || amount > 1000000)
			{
				throw new ArgumentOutOfRangeException("amount", "Invalid objective index or count.");
			}
			if (packQuestProgress.Claimed || !Unlocked(quest))
			{
				throw new InvalidOperationException("Quest is claimed or locked: " + quest);
			}
			packQuestProgress.Counts[objectiveIndex] = (int)Math.Min((long)packQuestProgress.Counts[objectiveIndex] + (long)amount, recipes[quest].Objectives[objectiveIndex].Count);
		}

		public bool TryMarkClaimed(string quest)
		{
			if (!CanClaim(quest))
			{
				return false;
			}
			Get(quest).Claimed = true;
			return true;
		}

		private PackQuestProgress Get(string quest)
		{
			if (quest == null || !progress.TryGetValue(quest, out PackQuestProgress value))
			{
				throw new InvalidDataException("Unknown quest: " + quest);
			}
			return value;
		}
	}
	public sealed class PackRegistryOptions
	{
		public bool RequireSupportedExtensionHooks { get; set; } = true;

		public List<string> SupportedExtensionHooks { get; set; } = new List<string>();
	}
	public sealed class ResolvedContent
	{
		public string PackKey { get; }

		public string Key { get; }

		public ContentDomain Domain { get; }

		public bool Native => PackKey == "native";

		public int? Id { get; }

		public string Reference => PackKey + ":" + Key;

		internal ResolvedContent(string pack, string key, ContentDomain domain, int? id)
		{
			PackKey = pack;
			Key = key;
			Domain = domain;
			Id = id;
		}
	}
	public sealed class PackRegistry
	{
		public const string HarborPackKey = "gamblers_reach";

		public const string HarborWildlifePackKey = "gamblers_reach_wildlife";

		private readonly PackDefinition[] packs;

		private readonly Dictionary<string, PackDefinition> byKey;

		private readonly Dictionary<string, ResolvedContent> references;

		public string Fingerprint { get; }

		public IReadOnlyList<PackDefinition> Packs => Array.AsReadOnly(packs.Select(PackCanonical.Copy).ToArray());

		public IReadOnlyList<ResolvedContent> Content { get; }

		private PackRegistry(PackDefinition[] ordered)
		{
			packs = ordered;
			byKey = packs.ToDictionary<PackDefinition, string>((PackDefinition x) => x.Key, StringComparer.Ordinal);
			references = new Dictionary<string, ResolvedContent>(StringComparer.Ordinal);
			PackDefinition[] array = packs;
			foreach (PackDefinition packDefinition in array)
			{
				foreach (var item in PackValidation.Recipes(packDefinition))
				{
					references.Add(packDefinition.Key + ":" + item.Recipe.Key, new ResolvedContent(packDefinition.Key, item.Recipe.Key, item.Domain, item.Id));
				}
			}
			Content = Array.AsReadOnly(references.Values.OrderBy<ResolvedContent, string>((ResolvedContent x) => x.Reference, StringComparer.Ordinal).ToArray());
			Fingerprint = PackCanonical.Hash(packs.Select((PackDefinition x) => new SavedPack
			{
				Key = x.Key,
				Version = x.Version,
				Fingerprint = PackCanonical.Hash(x)
			}).ToList());
		}

		public static PackRegistry Build(IEnumerable<PackDefinition> definitions, PackRegistryOptions? options = null)
		{
			if (definitions == null)
			{
				throw new ArgumentNullException("definitions");
			}
			PackDefinition[] array = definitions.Take(65).ToArray();
			if (array.Length > 64)
			{
				throw new InvalidDataException("At most 64 packs can be composed.");
			}
			if (options == null)
			{
				options = new PackRegistryOptions();
			}
			if (options.SupportedExtensionHooks == null || options.SupportedExtensionHooks.Count > 64 || options.SupportedExtensionHooks.Any((string x) => !PackValidation.Key(x)))
			{
				throw new InvalidDataException("Invalid supported extension hooks.");
			}
			List<string> list = new List<string>();
			PackDefinition[] array2 = array;
			foreach (PackDefinition packDefinition in array2)
			{
				if (packDefinition == null)
				{
					list.Add("Pack set contains null.");
				}
				else
				{
					list.AddRange(packDefinition.Inspect());
				}
			}
			PackValidation.ThrowIfInvalid(list);
			PackDefinition[] array3 = array.Select(PackCanonical.Copy).ToArray();
			if (array3.Select((PackDefinition x) => x.Key).Distinct<string>(StringComparer.Ordinal).Count() != array3.Length)
			{
				throw new InvalidDataException("Duplicate pack keys.");
			}
			Dictionary<string, PackDefinition> dictionary = array3.ToDictionary<PackDefinition, string>((PackDefinition x) => x.Key, StringComparer.Ordinal);
			array2 = array3;
			foreach (PackDefinition packDefinition2 in array2)
			{
				foreach (PackDependency dependency in packDefinition2.Dependencies)
				{
					if (!dictionary.TryGetValue(dependency.Key, out var value))
					{
						list.Add(packDefinition2.Key + ": missing required pack " + dependency.Key + ".");
					}
					else if ((dependency.ExactVersion != null) ? (dependency.ExactVersion != value.Version) : (PackValidation.CompareVersion(value.Version, dependency.MinimumVersion) < 0))
					{
						list.Add(packDefinition2.Key + ": dependency version mismatch for " + dependency.Key + ".");
					}
				}
				if (!options.RequireSupportedExtensionHooks)
				{
					continue;
				}
				foreach (string extensionHook in packDefinition2.ExtensionHooks)
				{
					if (!options.SupportedExtensionHooks.Contains(extensionHook))
					{
						list.Add(packDefinition2.Key + ": runtime does not support declared extension hook " + extensionHook + ".");
					}
				}
			}
			PackValidation.ThrowIfInvalid(list);
			PackRegistry packRegistry = new PackRegistry(Topological(array3, (PackDefinition p) => p.Key, (PackDefinition p) => p.Dependencies.Select((PackDependency x) => x.Key), "pack dependency").ToArray());
			packRegistry.ValidateSet(list);
			PackValidation.ThrowIfInvalid(list);
			return packRegistry;
		}

		public ResolvedContent Resolve(string ownerPackKey, string reference, ContentDomain domain)
		{
			if (!byKey.TryGetValue(ownerPackKey, out PackDefinition value))
			{
				throw new InvalidDataException("Unknown owner pack: " + ownerPackKey);
			}
			if (!PackValidation.TryReference(reference, out string targetPack, out string key))
			{
				throw new InvalidDataException(ownerPackKey + ": malformed reference " + reference + ".");
			}
			if (targetPack == "native")
			{
				int num = int.Parse(key, CultureInfo.InvariantCulture);
				if (!NativePackCatalog.Contains(domain, num))
				{
					throw new InvalidDataException("Unknown native " + domain.ToString() + " reference: " + reference);
				}
				return new ResolvedContent("native", key, domain, num);
			}
			if (targetPack != ownerPackKey && !value.Dependencies.Any((PackDependency x) => x.Key == targetPack))
			{
				throw new InvalidDataException(ownerPackKey + ": reference needs explicit dependency on " + targetPack + ".");
			}
			if (!references.TryGetValue(reference, out ResolvedContent value2))
			{
				throw new InvalidDataException(ownerPackKey + ": unresolved " + domain.ToString() + " reference " + reference + ".");
			}
			if (value2.Domain != domain)
			{
				throw new InvalidDataException(reference + ": expected " + domain.ToString() + ", found " + value2.Domain.ToString() + ".");
			}
			return value2;
		}

		public PackRegistryManifest CreateManifest()
		{
			PackRegistryManifest packRegistryManifest = new PackRegistryManifest
			{
				Fingerprint = Fingerprint,
				FingerprintVersion = 2
			};
			foreach (PackDefinition item in packs.OrderBy<PackDefinition, string>((PackDefinition x) => x.Key, StringComparer.Ordinal))
			{
				packRegistryManifest.Packs.Add(new SavedPack
				{
					Key = item.Key,
					Version = item.Version,
					Fingerprint = PackCanonical.Hash(item)
				});
			}
			foreach (ResolvedContent item2 in Content)
			{
				PackRecipe packRecipe = Recipe(item2);
				NetworkBinding networkBinding = ((packRecipe is ItemRecipe itemRecipe) ? itemRecipe.Network : ((packRecipe is NpcRecipe npcRecipe) ? npcRecipe.Network : null));
				packRegistryManifest.Reservations.Add(new SavedReservation
				{
					PackKey = item2.PackKey,
					Key = item2.Key,
					Domain = item2.Domain,
					Id = item2.Id,
					CollectionId = networkBinding?.CollectionId,
					Slot = networkBinding?.Slot
				});
			}
			foreach (PackDefinition item3 in packs.OrderBy<PackDefinition, string>((PackDefinition x) => x.Key, StringComparer.Ordinal))
			{
				foreach (NetworkCollectionRecipe item4 in item3.Collections.OrderBy<NetworkCollectionRecipe, string>((NetworkCollectionRecipe x) => x.Key, StringComparer.Ordinal))
				{
					packRegistryManifest.Collections.Add(new SavedCollection
					{
						PackKey = item3.Key,
						Key = item4.Key,
						Id = item4.Id,
						Capacity = item4.Capacity
					});
				}
			}
			return packRegistryManifest;
		}

		public IReadOnlyList<string> CompareManifest(PackRegistryManifest saved)
		{
			return PackManifestPolicy.Compare(saved, CreateManifest(), null, UnchangedLegacy);
		}

		public void RequireCompatible(PackRegistryManifest saved)
		{
			PackValidation.ThrowIfInvalid(CompareManifest(saved));
		}

		public void RequireCompatible(PackRegistryManifest saved, Func<SavedPack, SavedPack, bool> acceptRevision)
		{
			PackValidation.ThrowIfInvalid(PackManifestPolicy.Compare(saved, CreateManifest(), acceptRevision ?? throw new ArgumentNullException("acceptRevision"), UnchangedLegacy));
		}

		private bool UnchangedLegacy(SavedPack saved)
		{
			if (byKey.TryGetValue(saved.Key, out PackDefinition value) && value.Version == saved.Version)
			{
				return PackCanonical.LegacyHashes(value).Contains<string>(saved.Fingerprint, StringComparer.Ordinal);
			}
			return false;
		}

		private PackRecipe Recipe(ResolvedContent content)
		{
			return PackValidation.Recipes(byKey[content.PackKey]).First<(ContentDomain, PackRecipe, int?)>(((ContentDomain Domain, PackRecipe Recipe, int? Id) x) => x.Recipe.Key == content.Key).Item2;
		}

		private void ValidateSet(List<string> errors)
		{
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			HashSet<ushort> hashSet2 = new HashSet<ushort>();
			Dictionary<string, List<CatchPatchMode>> dictionary = new Dictionary<string, List<CatchPatchMode>>(StringComparer.Ordinal);
			HashSet<string> placements = new HashSet<string>(StringComparer.Ordinal);
			HashSet<string> hashSet3 = new HashSet<string>(StringComparer.Ordinal);
			Dictionary<string, (string Owner, string Hash)> assets = new Dictionary<string, (string, string)>(StringComparer.OrdinalIgnoreCase);
			List<(string Pack, IslandRecipe Island)> routes = new List<(string, IslandRecipe)>();
			PackDefinition[] array = packs;
			foreach (PackDefinition pack in array)
			{
				foreach (var item4 in PackValidation.Recipes(pack))
				{
					if (item4.Id.HasValue)
					{
						ContentDomain item = item4.Domain;
						string text = item.ToString();
						int? item2 = item4.Id;
						if (!hashSet.Add(text + ":" + item2))
						{
							List<string> list = errors;
							string[] obj = new string[5] { "Duplicate ", null, null, null, null };
							(item, _, _) = item4;
							obj[1] = item.ToString();
							obj[2] = " ID ";
							item2 = item4.Id;
							obj[3] = item2.ToString();
							obj[4] = ".";
							list.Add(string.Concat(obj));
						}
					}
					if (item4.Id.HasValue && IsHarborId(item4.Domain, item4.Id.Value) && pack.Key != "gamblers_reach")
					{
						List<string> list2 = errors;
						string[] obj2 = new string[6] { pack.Key, ": ", null, null, null, null };
						var (item, _, _) = item4;
						obj2[2] = item.ToString();
						obj2[3] = " ID ";
						int? item2 = item4.Id;
						obj2[4] = item2.ToString();
						obj2[5] = " is reserved for gamblers_reach.";
						list2.Add(string.Concat(obj2));
					}
					if (item4.Domain == ContentDomain.Item && item4.Id == 196 && pack.Key != "gamblers_reach_wildlife")
					{
						errors.Add(pack.Key + ": item ID 196 is reserved for gamblers_reach_wildlife.");
					}
					ArtRecipe artRecipe = ((item4.Recipe is ItemRecipe itemRecipe) ? itemRecipe.Art : ((item4.Recipe is LureRecipe lureRecipe) ? lureRecipe.Art : ((item4.Recipe is NpcRecipe npcRecipe) ? npcRecipe.Art : null)));
					if (artRecipe != null)
					{
						Asset(pack.Key, artRecipe.Bundle, artRecipe.Sha256);
					}
				}
				foreach (NetworkCollectionRecipe collection in pack.Collections)
				{
					if (!hashSet2.Add(collection.Id))
					{
						errors.Add("Duplicate network collection ID " + collection.Id + ".");
					}
					if ((collection.Id == 48187 || collection.Id == 48188 || collection.Id == 48189) && pack.Key != "gamblers_reach")
					{
						errors.Add(pack.Key + ": collection " + collection.Id + " is reserved for gamblers_reach.");
					}
					if (collection.Id == 48190 && pack.Key != "gamblers_reach_wildlife")
					{
						errors.Add(pack.Key + ": collection 48190 is reserved for gamblers_reach_wildlife.");
					}
				}
				foreach (ItemRecipe item3 in pack.Items)
				{
					if (item3.NativeDonor != null)
					{
						Guard(delegate
						{
							int value2 = Resolve(pack.Key, item3.NativeDonor, ContentDomain.Item).Id.Value;
							if (!NativePackCatalog.SupportsItemKind(value2, item3.Kind))
							{
								throw new InvalidDataException(pack.Key + ":" + item3.Key + ": native donor does not support " + item3.Kind.ToString() + ".");
							}
							if (item3.Implementation != RecipeImplementation.External && !NativePackCatalog.IsCreature(value2) && (item3.Health.HasValue || item3.HealthRestored.HasValue || item3.FoodValue.HasValue || item3.BodyDamageFactor.HasValue || item3.CrewHealthFactor.HasValue || item3.CrewDamageFactor.HasValue))
							{
								throw new InvalidDataException(item3.Key + ": health, food and creature damage overrides require a creature donor or an External hook.");
							}
							if (item3.Weapon != null && !NativePackCatalog.IsRangedWeapon(value2) && item3.Implementation != RecipeImplementation.External)
							{
								throw new InvalidDataException(item3.Key + ": ranged weapon policies require a ranged donor or an External hook.");
							}
						});
					}
					if (item3.CookedItem != null)
					{
						Guard(delegate
						{
							Resolve(pack.Key, item3.CookedItem, ContentDomain.Item);
						});
					}
					if (item3.Weapon?.AllowedAttachmentIds == null)
					{
						continue;
					}
					foreach (byte allowedAttachmentId in item3.Weapon.AllowedAttachmentIds)
					{
						if (!NativePackCatalog.Contains(ContentDomain.Attachment, allowedAttachmentId))
						{
							errors.Add(item3.Key + ": unknown native attachment " + allowedAttachmentId + ".");
						}
					}
				}
				foreach (LureRecipe lure in pack.Lures)
				{
					if (lure.NativeVisualDonor != null)
					{
						Guard(delegate
						{
							Resolve(pack.Key, lure.NativeVisualDonor, ContentDomain.Lure);
						});
					}
					foreach (CatchRecipe entry in lure.Catches)
					{
						Guard(delegate
						{
							Creature(pack.Key, entry.Item);
						});
					}
				}
				foreach (CatchTablePatch patch in pack.CatchPatches)
				{
					Guard(delegate
					{
						Resolve(pack.Key, patch.TargetLure, ContentDomain.Lure);
					});
					foreach (CatchRecipe entry2 in patch.Catches)
					{
						Guard(delegate
						{
							Creature(pack.Key, entry2.Item);
						});
					}
					if (!dictionary.TryGetValue(patch.TargetLure, out var value))
					{
						dictionary.Add(patch.TargetLure, value = new List<CatchPatchMode>());
					}
					value.Add(patch.Mode);
				}
				foreach (NpcRecipe npc in pack.Npcs)
				{
					Guard(delegate
					{
						Place(pack.Key, npc.Island, npc.Placement);
					});
					if (npc.NativeDonor != null && npc.NativeDonorIsland != null)
					{
						Guard(delegate
						{
							int value2 = Resolve(pack.Key, npc.NativeDonor, ContentDomain.Npc).Id.Value;
							if (!NativePackCatalog.HasNpcDonor(Resolve(pack.Key, npc.NativeDonorIsland, ContentDomain.Island).Id.Value, value2))
							{
								throw new InvalidDataException(npc.Key + ": NPC donor is absent from the specified native island.");
							}
						});
					}
					if (npc.Quest != null)
					{
						Guard(delegate
						{
							Resolve(pack.Key, npc.Quest, ContentDomain.Quest);
						});
					}
				}
				foreach (ShopRecipe shop in pack.Shops)
				{
					Guard(delegate
					{
						Place(pack.Key, shop.Island, shop.Placement);
					});
					Guard(delegate
					{
						Resolve(pack.Key, shop.Content, ShopDomain(shop.Kind));
					});
				}
				foreach (IslandRecipe island in pack.Islands)
				{
					if (!hashSet3.Add(island.SceneName))
					{
						errors.Add("Duplicate scene name " + island.SceneName + ".");
					}
					Asset(pack.Key, island.SceneBundle, island.SceneSha256);
					if (island.PlayerSpawn.Marker != null && island.PlayerSpawn.Marker == island.BoatSpawn.Marker)
					{
						errors.Add(island.Key + ": player and boat spawn markers must differ.");
					}
					if (island.Route == null)
					{
						continue;
					}
					Guard(delegate
					{
						ResolvedContent resolvedContent = Resolve(pack.Key, island.Route.AfterIsland, ContentDomain.Island);
						ResolvedContent resolvedContent2 = Resolve(pack.Key, island.Route.BeforeIsland, ContentDomain.Island);
						if (resolvedContent.Reference == pack.Key + ":" + island.Key || resolvedContent2.Reference == pack.Key + ":" + island.Key)
						{
							throw new InvalidDataException(island.Key + ": island cannot route to itself.");
						}
						if (!resolvedContent.Native || !resolvedContent2.Native || resolvedContent2.Id != resolvedContent.Id + 1)
						{
							throw new InvalidDataException(island.Key + ": routes must identify a consecutive native island gap.");
						}
						routes.Add((pack.Key, island));
					});
				}
				foreach (QuestRecipe quest in pack.Quests)
				{
					foreach (string requirement in quest.Requires)
					{
						Guard(delegate
						{
							Resolve(pack.Key, requirement, ContentDomain.Quest);
						});
					}
					foreach (QuestObjectiveRecipe objective in quest.Objectives)
					{
						Guard(delegate
						{
							Resolve(pack.Key, objective.Item, ContentDomain.Item);
							if (objective.Kind == QuestObjectiveKind.CatchItem)
							{
								Creature(pack.Key, objective.Item);
							}
						});
					}
					foreach (QuestRewardRecipe reward in quest.Rewards)
					{
						if (reward.Kind != QuestRewardKind.Money)
						{
							Guard(delegate
							{
								Resolve(pack.Key, reward.Content, (reward.Kind != QuestRewardKind.Item) ? ((reward.Kind == QuestRewardKind.Lure) ? ContentDomain.Lure : ContentDomain.Island) : ContentDomain.Item);
							});
						}
					}
				}
				foreach (LootRecipe loot in pack.Loot)
				{
					Guard(delegate
					{
						Creature(pack.Key, loot.Creature);
					});
					foreach (LootDropRecipe drop in loot.Drops)
					{
						Guard(delegate
						{
							Resolve(pack.Key, drop.Item, ContentDomain.Item);
						});
					}
				}
			}
			foreach (KeyValuePair<string, List<CatchPatchMode>> item5 in dictionary)
			{
				if (item5.Value.Contains(CatchPatchMode.ExclusiveReplace) && item5.Value.Count != 1)
				{
					errors.Add("Exclusive catch-table patch collision at " + item5.Key + ".");
				}
			}
			foreach (IGrouping<string, (string, IslandRecipe)> item6 in from x in routes
				group x by x.Island.Route.AfterIsland + ">" + x.Island.Route.BeforeIsland)
			{
				(string, IslandRecipe)[] array2 = item6.OrderBy<(string, IslandRecipe), int>(((string Pack, IslandRecipe Island) x) => x.Island.Route.Priority).ThenBy<(string, IslandRecipe), string>(((string Pack, IslandRecipe Island) x) => x.Pack, StringComparer.Ordinal).ToArray();
				for (int num = 1; num < array2.Length; num++)
				{
					if (array2[num - 1].Item2.Route.Priority == array2[num].Item2.Route.Priority || (array2[num - 1].Item1 != array2[num].Item1 && !DependsOn(array2[num].Item1, array2[num - 1].Item1)))
					{
						errors.Add("Shared route gap " + item6.Key + " needs distinct priorities and a dependency on the preceding pack.");
					}
				}
			}
			if (errors.Count != 0)
			{
				return;
			}
			Guard(delegate
			{
				Topological(packs.SelectMany((PackDefinition p) => p.Quests.Select((QuestRecipe q) => (Key: p.Key + ":" + q.Key, Quest: q))).ToArray(), ((string Key, QuestRecipe Quest) x) => x.Key, ((string Key, QuestRecipe Quest) x) => x.Quest.Requires, "quest dependency").ToArray();
			});
			void Asset(string owner, string bundle, string hash)
			{
				if (assets.TryGetValue(bundle, out (string, string) value2) && (value2.Item1 != owner || value2.Item2 != hash))
				{
					errors.Add(bundle + ": bundle names must have one pack owner and a consistent hash.");
				}
				else
				{
					assets[bundle] = (owner, hash);
				}
			}
			void Guard(Action action)
			{
				try
				{
					action();
				}
				catch (InvalidDataException ex)
				{
					if (errors.Count < 512)
					{
						errors.Add(ex.Message);
					}
				}
			}
			void Place(string owner, string islandRef, PlacementRecipe placement)
			{
				ResolvedContent resolvedContent = Resolve(owner, islandRef, ContentDomain.Island);
				if (placement.Marker != null)
				{
					if (resolvedContent.Native)
					{
						throw new InvalidDataException("Native-island additions must use explicit local poses, not unverified authored markers.");
					}
					if (!placements.Add(resolvedContent.Reference + ":" + placement.Marker))
					{
						throw new InvalidDataException("Duplicate authored placement marker: " + placement.Marker);
					}
				}
			}
		}

		private void Creature(string owner, string reference)
		{
			ResolvedContent resolvedContent = Resolve(owner, reference, ContentDomain.Item);
			bool num;
			if (!resolvedContent.Native)
			{
				if (!(Recipe(resolvedContent) is ItemRecipe itemRecipe))
				{
					goto IL_0051;
				}
				if (itemRecipe.Kind == PackItemKind.Fish)
				{
					return;
				}
				num = itemRecipe.Kind == PackItemKind.Creature;
			}
			else
			{
				num = NativePackCatalog.IsCreature(resolvedContent.Id.Value);
			}
			if (num)
			{
				return;
			}
			goto IL_0051;
			IL_0051:
			throw new InvalidDataException(reference + ": a fish/creature reference is required.");
		}

		private bool DependsOn(string owner, string target)
		{
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			Stack<string> stack = new Stack<string>();
			stack.Push(owner);
			while (stack.Count != 0)
			{
				string text = stack.Pop();
				if (!hashSet.Add(text))
				{
					continue;
				}
				foreach (PackDependency dependency in byKey[text].Dependencies)
				{
					if (dependency.Key == target)
					{
						return true;
					}
					stack.Push(dependency.Key);
				}
			}
			return false;
		}

		public static ContentDomain ShopDomain(PackShopKind kind)
		{
			return kind switch
			{
				PackShopKind.Item => ContentDomain.Item, 
				PackShopKind.Lure => ContentDomain.Lure, 
				PackShopKind.Attachment => ContentDomain.Attachment, 
				PackShopKind.Ammunition => ContentDomain.Ammunition, 
				PackShopKind.BoatRadar => ContentDomain.BoatRadar, 
				PackShopKind.Sharpening => ContentDomain.Sharpening, 
				PackShopKind.Pocket => ContentDomain.Pocket, 
				PackShopKind.Motor => ContentDomain.Motor, 
				_ => throw new InvalidDataException("Unknown shop kind."), 
			};
		}

		public static bool IsHarborId(ContentDomain domain, int id)
		{
			return domain switch
			{
				ContentDomain.Item => (id >= 180 && id <= 185) || id == 187 || id == 188 || (id >= 190 && id <= 194), 
				ContentDomain.Lure => id >= 17 && id <= 19, 
				ContentDomain.Npc => id >= 230 && id <= 232, 
				ContentDomain.Island => id == 6, 
				_ => false, 
			};
		}

		private static IEnumerable<T> Topological<T>(IEnumerable<T> values, Func<T, string> key, Func<T, IEnumerable<string>> dependencies, string label)
		{
			Dictionary<string, T> remaining = values.ToDictionary<T, string>(key, StringComparer.Ordinal);
			HashSet<string> done = new HashSet<string>(StringComparer.Ordinal);
			while (remaining.Count > 0)
			{
				string[] array = remaining.Keys.Where((string x) => dependencies(remaining[x]).All(done.Contains)).OrderBy<string, string>((string x) => x, StringComparer.Ordinal).ToArray();
				if (array.Length == 0)
				{
					throw new InvalidDataException("Cycle or missing " + label + ": " + string.Join(", ", remaining.Keys.OrderBy<string, string>((string x) => x, StringComparer.Ordinal)));
				}
				string[] array2 = array;
				foreach (string text in array2)
				{
					T val = remaining[text];
					remaining.Remove(text);
					done.Add(text);
					yield return val;
				}
			}
		}
	}
	public sealed class PackRegistryManifest
	{
		public int SchemaVersion { get; set; } = 1;

		public int FingerprintVersion { get; set; } = 1;

		public string GameBuild { get; set; } = "25127368";

		public string Fingerprint { get; set; } = "";

		public List<SavedPack> Packs { get; set; } = new List<SavedPack>();

		public List<SavedReservation> Reservations { get; set; } = new List<SavedReservation>();

		public List<SavedCollection> Collections { get; set; } = new List<SavedCollection>();
	}
	public sealed class SavedPack
	{
		public string Key { get; set; } = "";

		public string Version { get; set; } = "";

		public string Fingerprint { get; set; } = "";
	}
	public sealed class SavedReservation
	{
		public string PackKey { get; set; } = "";

		public string Key { get; set; } = "";

		public ContentDomain Domain { get; set; }

		public int? Id { get; set; }

		public ushort? CollectionId { get; set; }

		public ushort? Slot { get; set; }
	}
	public sealed class SavedCollection
	{
		public string PackKey { get; set; } = "";

		public string Key { get; set; } = "";

		public ushort Id { get; set; }

		public int Capacity { get; set; }
	}
	internal static class PackManifestPolicy
	{
		internal static IReadOnlyList<string> Compare(PackRegistryManifest saved, PackRegistryManifest current, Func<SavedPack, SavedPack, bool>? acceptRevision = null, Func<SavedPack, bool>? unchangedLegacy = null)
		{
			List<string> list = new List<string>();
			bool flag = saved == null || saved.SchemaVersion != 1;
			if (!flag)
			{
				int fingerprintVersion = saved.FingerprintVersion;
				bool flag2 = ((fingerprintVersion < 1 || fingerprintVersion > 2) ? true : false);
				flag = flag2;
			}
			if (flag || saved.GameBuild != "25127368" || saved.Packs == null || saved.Packs.Count > 64 || saved.Packs.Any((SavedPack x) => x == null) || saved.Reservations == null || saved.Reservations.Count > 65536 || saved.Reservations.Any((SavedReservation x) => x == null) || saved.Collections == null || saved.Collections.Count > 2048 || saved.Collections.Any((SavedCollection x) => x == null))
			{
				list.Add("Saved pack registry schema, game build or bounded collections are invalid.");
				return list.AsReadOnly();
			}
			if (saved.Packs.Any((SavedPack x) => !PackValidation.Key(x.Key) || !PackValidation.Version(x.Version) || !PackValidation.Hash(x.Fingerprint)) || saved.Packs.Select((SavedPack x) => x.Key).Distinct<string>(StringComparer.Ordinal).Count() != saved.Packs.Count)
			{
				list.Add("Saved pack identities are invalid or duplicated.");
				return list.AsReadOnly();
			}
			bool flag3 = ((saved.FingerprintVersion == 2) ? (saved.Fingerprint == PackCanonical.Hash(saved.Packs)) : PackCanonical.LegacyHashes(saved.Packs).Contains<string>(saved.Fingerprint, StringComparer.Ordinal));
			if (!PackValidation.Hash(saved.Fingerprint) || !flag3)
			{
				list.Add("Saved registry fingerprint does not match its pack metadata.");
			}
			HashSet<string> owners = new HashSet<string>(saved.Packs.Select((SavedPack x) => x.Key), StringComparer.Ordinal);
			bool num = saved.Reservations.Any((SavedReservation x) => !owners.Contains(x.PackKey) || !PackValidation.Key(x.Key) || !Enum.IsDefined(typeof(ContentDomain), x.Domain) || x.Id < 0 || x.Id > 255 || x.CollectionId.HasValue != x.Slot.HasValue);
			bool flag4 = saved.Collections.Any((SavedCollection x) => !owners.Contains(x.PackKey) || !PackValidation.Key(x.Key) || x.Id < 48000 || x.Capacity < 1 || x.Capacity > 65536);
			if (num || flag4 || saved.Reservations.Select(Identity).Distinct<string>(StringComparer.Ordinal).Count() != saved.Reservations.Count || saved.Collections.Select((SavedCollection x) => x.PackKey + ":" + x.Key).Distinct<string>(StringComparer.Ordinal).Count() != saved.Collections.Count || saved.Collections.Select((SavedCollection x) => x.Id).Distinct().Count() != saved.Collections.Count)
			{
				list.Add("Saved reservations/collections are invalid or duplicated.");
				return list.AsReadOnly();
			}
			if (list.Count != 0)
			{
				return list.AsReadOnly();
			}
			foreach (SavedPack pack in saved.Packs)
			{
				if (current.Packs.FirstOrDefault((SavedPack x) => x.Key == pack.Key) == null)
				{
					list.Add("Missing required saved pack " + pack.Key + " " + pack.Version + ".");
				}
			}
			Dictionary<string, SavedReservation> dictionary = current.Reservations.ToDictionary<SavedReservation, string>(Identity, StringComparer.Ordinal);
			foreach (SavedReservation reservation in saved.Reservations)
			{
				if (!dictionary.TryGetValue(Identity(reservation), out var value))
				{
					list.Add("Missing saved content " + Identity(reservation) + ".");
				}
				else if (reservation.Domain != value.Domain || reservation.Id != value.Id || reservation.CollectionId != value.CollectionId || reservation.Slot != value.Slot)
				{
					list.Add("Saved ID/collection slot remapped: " + Identity(reservation) + ".");
				}
			}
			Dictionary<string, SavedCollection> dictionary2 = current.Collections.ToDictionary<SavedCollection, string>((SavedCollection x) => x.PackKey + ":" + x.Key, StringComparer.Ordinal);
			foreach (SavedCollection collection in saved.Collections)
			{
				if (!dictionary2.TryGetValue(collection.PackKey + ":" + collection.Key, out var value2) || collection.Id != value2.Id || collection.Capacity != value2.Capacity)
				{
					list.Add("Saved collection missing or remapped: " + collection.PackKey + ":" + collection.Key + ".");
				}
			}
			if (current.Reservations.Count((SavedReservation x) => owners.Contains(x.PackKey)) != saved.Reservations.Count || current.Collections.Count((SavedCollection x) => owners.Contains(x.PackKey)) != saved.Collections.Count)
			{
				list.Add("Saved registry content/reservation inventory differs from its required packs.");
			}
			if (list.Count != 0)
			{
				return list.AsReadOnly();
			}
			foreach (SavedPack pack2 in saved.Packs)
			{
				SavedPack savedPack = current.Packs.Single((SavedPack x) => x.Key == pack2.Key);
				if ((!(savedPack.Version == pack2.Version) || (!(savedPack.Fingerprint == pack2.Fingerprint) && (saved.FingerprintVersion != 1 || unchangedLegacy == null || !unchangedLegacy(pack2)))) && (acceptRevision == null || !acceptRevision(new SavedPack
				{
					Key = pack2.Key,
					Version = pack2.Version,
					Fingerprint = pack2.Fingerprint
				}, new SavedPack
				{
					Key = savedPack.Key,
					Version = savedPack.Version,
					Fingerprint = savedPack.Fingerprint
				})))
				{
					list.Add("Changed required saved pack " + pack2.Key + ": version or gameplay/assets fingerprint differs.");
				}
			}
			return list.AsReadOnly();
		}

		private static string Identity(SavedReservation entry)
		{
			return entry.PackKey + ":" + entry.Key;
		}
	}
	internal static class PackValidation
	{
		private sealed class Inspector
		{
			private readonly string pack;

			internal readonly List<string> Errors = new List<string>();

			internal Inspector(string pack)
			{
				this.pack = pack;
			}

			internal void Require(bool condition, string error)
			{
				if (!condition && Errors.Count < 256)
				{
					Errors.Add(pack + ": " + error);
				}
			}

			internal void Text(string? text, int max, string label)
			{
				Require(!string.IsNullOrWhiteSpace(text) && text.Length <= max && !text.Contains("\0"), "Invalid " + label + ".");
			}

			internal bool List<T>(List<T>? list, int max, string label)
			{
				bool flag = list != null && list.Count <= max && list.All((T x) => x != null);
				Require(flag, label + " must be a non-null bounded list without null entries (maximum " + max + ").");
				return flag;
			}

			internal void Unique<T>(IEnumerable<T> values, string label)
			{
				Require(values.Distinct().Count() == values.Count(), "Duplicate " + label + ".");
			}

			internal void Enum<T>(T value, string label) where T : struct
			{
				Require(System.Enum.IsDefined(typeof(T), value), "Invalid " + label + ".");
			}

			internal void Integer(int? value, int min, int max, string label)
			{
				Require(!value.HasValue || (value >= min && value <= max), label + " out of bounds.");
			}

			internal void Number(float? value, float min, float max, string label)
			{
				Require(!value.HasValue || (!float.IsNaN(value.Value) && !float.IsInfinity(value.Value) && value >= min && value <= max), label + " is not finite or out of bounds.");
			}

			internal void Reference(string? reference, string label)
			{
				Require(TryReference(reference, out string _, out string _), "Invalid " + label + " reference.");
			}

			internal void Donor(PackRecipe recipe, string? donor)
			{
				if (recipe.Implementation == RecipeImplementation.NativeClone || donor != null)
				{
					Require(NativeRef(donor), recipe.Key + ": a native donor reference is required.");
				}
			}

			internal void Binding(NetworkBinding? binding, List<NetworkCollectionRecipe> collections, List<string> used, string label)
			{
				if (binding == null)
				{
					Require(condition: false, label + ": network binding required.");
					return;
				}
				Require(collections.Any((NetworkCollectionRecipe x) => x.Id == binding.CollectionId && binding.Slot < x.Capacity), label + ": network binding is outside declared collection capacity.");
				used.Add(binding.CollectionId + ":" + binding.Slot);
			}

			internal void File(string? value, string label)
			{
				Require(value != null && value.Length <= 128 && Regex.IsMatch(value, "^[a-zA-Z0-9][a-zA-Z0-9_.-]*$") && !value.Contains(".."), "Invalid local " + label + ".");
			}

			internal void Name(string? value, string label)
			{
				Require(value != null && value.Length <= 128 && Regex.IsMatch(value, "^[a-zA-Z0-9_][a-zA-Z0-9_ .-]*$") && !value.Contains(".."), "Invalid " + label + ".");
			}

			internal void Vector(VectorRecipe? vector, bool scale)
			{
				if (vector == null)
				{
					Require(condition: false, "Vector cannot be null.");
					return;
				}
				float min = (scale ? 0.0001f : (-100000f));
				float max = (scale ? 1000 : 100000);
				Number(vector.X, min, max, "vector X");
				Number(vector.Y, min, max, "vector Y");
				Number(vector.Z, min, max, "vector Z");
			}

			private void Pose(PoseRecipe pose)
			{
				Vector(pose.Position, scale: false);
				Number(pose.Yaw, -360f, 360f, "yaw");
			}

			internal void Placement(PlacementRecipe? placement)
			{
				if (placement == null)
				{
					Require(condition: false, "Placement required.");
					return;
				}
				Require(placement.Marker == null != (placement.Pose == null), "Placement must specify exactly one marker or local pose.");
				if (placement.Marker != null)
				{
					Name(placement.Marker, "marker");
				}
				if (placement.Pose != null)
				{
					Pose(placement.Pose);
				}
			}

			internal void Art(ArtRecipe? art)
			{
				if (art != null)
				{
					File(art.Bundle, "art bundle");
					Name(art.Prefab, "prefab");
					Require(Hash(art.Sha256), "Art bundle SHA256 must be lowercase hexadecimal.");
					Vector(art.Position, scale: false);
					Vector(art.Rotation, scale: false);
					Vector(art.Scale, scale: true);
					if (art.Grip != null)
					{
						Pose(art.Grip);
					}
				}
			}

			internal void Scales(List<float>? values, string label)
			{
				if (!List(values, 32, label))
				{
					return;
				}
				foreach (float value in values)
				{
					Number(value, 0.001f, 1000f, label);
				}
			}

			internal void Weapon(WeaponRecipe weapon)
			{
				Number(weapon.DamageScale, 0.001f, 1000f, "weapon damage");
				Scales(weapon.UpgradeDamageScales, "damage upgrades");
				Enum(weapon.MagazinePolicy, "magazine policy");
				Enum(weapon.ReloadPolicy, "reload policy");
				Integer(weapon.MagazineSize, 1, 10000, "magazine size");
				if (List(weapon.UpgradeMagazineSizes, 32, "magazine upgrades"))
				{
					foreach (int upgradeMagazineSize in weapon.UpgradeMagazineSizes)
					{
						Integer(upgradeMagazineSize, 1, 10000, "upgrade magazine");
					}
				}
				Require((weapon.MagazinePolicy == MagazinePolicy.Fixed) ? weapon.MagazineSize.HasValue : (!weapon.MagazineSize.HasValue), "Fixed magazine policy requires size; other policies must omit size.");
				Require(weapon.UpgradeMagazineSizes != null && ((weapon.MagazinePolicy == MagazinePolicy.PerUpgrade) ? (weapon.UpgradeMagazineSizes.Count > 0) : (weapon.UpgradeMagazineSizes.Count == 0)), "PerUpgrade magazine policy requires upgrade sizes exclusively.");
				Number(weapon.ReloadSeconds, 0.001f, 3600f, "reload time");
				Number(weapon.ShotDelaySeconds, 0.001f, 3600f, "shot delay");
				if (weapon.AllowedAttachmentIds != null && List(weapon.AllowedAttachmentIds, 32, "attachments"))
				{
					Unique(weapon.AllowedAttachmentIds, "attachments");
				}
			}

			internal void Catches(List<CatchRecipe>? catches, string key)
			{
				if (!List(catches, 128, "catches"))
				{
					return;
				}
				Require(catches.Count > 0, key + ": catch table cannot be empty.");
				Unique(catches.Select((CatchRecipe x) => x.Item), "catch items");
				foreach (CatchRecipe @catch in catches)
				{
					Reference(@catch.Item, "catch item");
					Number(@catch.Weight, 1E-06f, 1000000f, "catch weight");
				}
			}
		}

		internal const int MaxPacks = 64;

		internal static bool Key(string? value)
		{
			if (value != null && value.Length <= 64)
			{
				return Regex.IsMatch(value, "^[a-z][a-z0-9_-]*$", RegexOptions.CultureInvariant);
			}
			return false;
		}

		internal static bool Version(string? value)
		{
			if (value != null && value.Length <= 32)
			{
				return Regex.IsMatch(value, "^(0|[1-9][0-9]{0,5})\\.(0|[1-9][0-9]{0,5})\\.(0|[1-9][0-9]{0,5})$", RegexOptions.CultureInvariant);
			}
			return false;
		}

		internal static int CompareVersion(string a, string b)
		{
			int[] array = a.Split('.').Select(int.Parse).ToArray();
			int[] array2 = b.Split('.').Select(int.Parse).ToArray();
			for (int i = 0; i < 3; i++)
			{
				if (array[i] != array2[i])
				{
					return array[i].CompareTo(array2[i]);
				}
			}
			return 0;
		}

		internal static bool Hash(string? value)
		{
			if (valu

BepInEx/plugins/ExpansionKit/HowToFish.ExpansionKit.Runtime.dll

Decompiled 15 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
using BepInEx;
using BepInEx.Logging;
using FishNet;
using FishNet.Authenticating;
using FishNet.Broadcast;
using FishNet.Connection;
using FishNet.Managing;
using FishNet.Managing.Client;
using FishNet.Managing.Logging;
using FishNet.Managing.Object;
using FishNet.Managing.Server;
using FishNet.Object;
using FishNet.Serializing;
using FishNet.Transporting;
using HarmonyLib;
using HowToFish.ExpansionKit.Packs;
using HowToFish.ExpansionKit.Runtime.Characters;
using HowToFish.ExpansionKit.Runtime.Commerce;
using HowToFish.ExpansionKit.Runtime.Content;
using HowToFish.ExpansionKit.Runtime.Networking;
using HowToFish.ExpansionKit.Runtime.Persistence;
using HowToFish.ExpansionKit.Runtime.Rendering;
using HowToFish.ExpansionKit.Runtime.Shops;
using HowToFish.ExpansionKit.Runtime.World;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Steamworks;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.SceneManagement;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("HowToFish.ExpansionKit.Runtime")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Version-pinned native runtime for independent How to Fish content packs.")]
[assembly: AssemblyFileVersion("0.7.2.0")]
[assembly: AssemblyInformationalVersion("0.7.2")]
[assembly: AssemblyProduct("HowToFish.ExpansionKit.Runtime")]
[assembly: AssemblyTitle("HowToFish.ExpansionKit.Runtime")]
[assembly: AssemblyVersion("0.7.2.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 HowToFish.ExpansionKit.Runtime
{
	[BepInPlugin("howtofish.expansionkit.runtime", "How to Fish ExpansionKit", "0.7.2")]
	public sealed class ExpansionKitPlugin : BaseUnityPlugin
	{
		public const string Id = "howtofish.expansionkit.runtime";

		public const string Name = "How to Fish ExpansionKit";

		public const string Version = "0.7.2";

		public const string GameBuild = "25127368";

		public static readonly Guid SupportedGame = new Guid("91a1729d-8ab8-4d3b-afcf-c6edfa435491");

		private Harmony? patches;

		internal static ManualLogSource Log { get; private set; } = null;

		private void Awake()
		{
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			ExpansionPlatform expansionPlatform = ((Component)this).gameObject.AddComponent<ExpansionPlatform>();
			try
			{
				Guid moduleVersionId = typeof(Item).Assembly.ManifestModule.ModuleVersionId;
				if (moduleVersionId != SupportedGame)
				{
					Guid guid = moduleVersionId;
					throw new NotSupportedException("ExpansionKit requires How to Fish 1.0.12, build 25127368. The current game assembly is " + guid.ToString() + ".");
				}
				patches = new Harmony("howtofish.expansionkit.runtime");
				patches.PatchAll(typeof(ExpansionKitPlugin).Assembly);
				NativeContentSelfCheck.RequireClean(NativeContentSelfCheck.InspectSharedHooks());
			}
			catch (Exception error)
			{
				expansionPlatform.Fail(error);
				throw;
			}
			Log.LogInfo((object)"ExpansionKit 0.7.2: native build 25127368 verified.");
		}
	}
	public sealed class ExpansionPlatform : MonoBehaviour
	{
		private sealed class AssetFactory : IPackAssetFactory
		{
			private readonly PackAssets assets;

			internal AssetFactory(PackAssets assets)
			{
				this.assets = assets;
			}

			public GameObject Instantiate(string packKey, ArtRecipe art)
			{
				return assets.CreateVisual(packKey, art);
			}
		}

		private sealed class RegistryState
		{
			public string RuntimeVersion { get; set; } = "";

			public PackRegistryManifest Registry { get; set; } = new PackRegistryManifest();

			public Dictionary<string, Dictionary<string, string>> ExtensionCode { get; set; } = new Dictionary<string, Dictionary<string, string>>(StringComparer.Ordinal);
		}

		private readonly Dictionary<string, PackExtension> extensions = new Dictionary<string, PackExtension>(StringComparer.Ordinal);

		private readonly Dictionary<string, PackSource> sources = new Dictionary<string, PackSource>(StringComparer.Ordinal);

		private readonly Dictionary<string, PackContentInstaller> content = new Dictionary<string, PackContentInstaller>(StringComparer.Ordinal);

		private readonly List<IDisposable> policies = new List<IDisposable>();

		private readonly NativeSceneMaterials sceneMaterials = new NativeSceneMaterials();

		private PackAssets? assets;

		private NativeTemplateCache? templates;

		private PackWorldRuntime? world;

		private QuestProgressStore? progress;

		private NativeSaveBridge? saves;

		private bool registrationClosed;

		private bool disposed;

		private long publishedRevision = -1L;

		private long remoteRevision = -1L;

		private readonly HashSet<string> policiesByShop = new HashSet<string>(StringComparer.Ordinal);

		public static ExpansionPlatform Current { get; private set; }

		public PackRegistry Registry { get; private set; }

		public PackHandshake? Handshake { get; private set; }

		public PackChannels Channels { get; private set; }

		public BoatEquipment Boats { get; private set; }

		public PackWorldRuntime World => world ?? throw new InvalidOperationException("This pack set has no world modules.");

		public bool HasWorld => world != null;

		public NativeSaveBridge Saves => saves ?? throw new InvalidOperationException("Pack saves are still preparing.");

		public bool CoreReady { get; private set; }

		public bool Ready
		{
			get
			{
				if (CoreReady && Failure == null)
				{
					return extensions.Values.All((PackExtension extension) => extension.GameplayReady?.Invoke() ?? true);
				}
				return false;
			}
		}

		public Exception? Failure { get; private set; }

		public string Status { get; private set; } = "Preparing expansion packs";

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

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

		public IReadOnlyCollection<PackContentInstaller> InstalledPacks => (IReadOnlyCollection<PackContentInstaller>)(object)content.Values.ToArray();

		private void Awake()
		{
			if (Object.op_Implicit((Object)(object)Current))
			{
				throw new InvalidOperationException("Only one expansion platform may be active.");
			}
			Current = this;
		}

		public void RegisterExtension(PackExtension extension)
		{
			if (extension == null)
			{
				throw new ArgumentNullException("extension");
			}
			if (registrationClosed)
			{
				throw new InvalidOperationException("Register extensions during plugin Awake, before platform preparation.");
			}
			if (extensions.ContainsKey(extension.Key))
			{
				throw new InvalidOperationException("Duplicate pack extension: " + extension.Key);
			}
			extensions.Add(extension.Key, extension);
		}

		public PackContentInstaller GetContent(string packKey)
		{
			if (!content.TryGetValue(packKey, out PackContentInstaller value))
			{
				throw new KeyNotFoundException("No installed content pack: " + packKey);
			}
			return value;
		}

		private IEnumerator Start()
		{
			if (Failure == null)
			{
				yield return Guarded(Prepare());
			}
		}

		private IEnumerator Prepare()
		{
			while (true)
			{
				if (Object.op_Implicit((Object)(object)InstanceFinder.NetworkManager) && NativeAccess.Field(typeof(GameInfo), "_allItems").GetValue(null) is IDictionary { Count: not 0 })
				{
					break;
				}
				yield return null;
			}
			registrationClosed = true;
			foreach (string item in FindManifests(Paths.PluginPath))
			{
				AddSource(PackSource.Load(item));
			}
			foreach (PackExtension item2 in extensions.Values.OrderBy<PackExtension, string>((PackExtension packExtension) => packExtension.Key, StringComparer.Ordinal))
			{
				PackSource packSource = item2.PrepareSource();
				if (packSource.Key != item2.Key || packSource.CodeFingerprints.Count == 0)
				{
					throw new InvalidOperationException("An extension must supply its matching pack and code fingerprints: " + item2.Key);
				}
				AddSource(packSource);
			}
			string[] source = extensions.Keys.SelectMany((string key) => sources[key].Definition.ExtensionHooks).Distinct<string>(StringComparer.Ordinal).ToArray();
			Registry = PackRegistry.Build(sources.Values.Select((PackSource packSource2) => packSource2.Definition), new PackRegistryOptions
			{
				SupportedExtensionHooks = source.ToList()
			});
			foreach (PackDefinition pack in Registry.Packs)
			{
				if (pack.ExtensionHooks.Count != 0 && !extensions.ContainsKey(pack.Key))
				{
					throw new InvalidOperationException("The required code extension was not registered for " + pack.Key + ".");
				}
			}
			assets = new PackAssets(sources);
			assets.Verify(Registry);
			Fingerprint = ComputeFingerprint();
			foreach (PackDefinition pack2 in Registry.Packs)
			{
				Status = "Registering " + pack2.Title;
				PackContentOptions packContentOptions = new PackContentOptions
				{
					Assets = new AssetFactory(assets)
				};
				extensions.TryGetValue(pack2.Key, out PackExtension value);
				value?.ConfigureContent(packContentOptions);
				PackContentInstaller packContentInstaller = new PackContentInstaller(Registry, pack2, packContentOptions);
				content.Add(pack2.Key, packContentInstaller);
				packContentInstaller.Install();
				value?.ContentInstalled?.Invoke(packContentInstaller);
			}
			progress = new QuestProgressStore(CommitQuest);
			if (Registry.Packs.Any((PackDefinition pack) => pack.Npcs.Count + pack.Shops.Count + pack.Islands.Count + pack.Quests.Count != 0))
			{
				Status = "Preparing native world services";
				templates = new NativeTemplateCache(IsConnected, CloneTemplate);
				world = new PackWorldRuntime(Registry, templates, progress, ResolveItem, ResolveLure, assets.ResolveScene, IsConnected, AuthorizeActor, BindAmmunition);
				world.Faulted += Fail;
				world.SceneMounted += VerifyScene;
				foreach (PackExtension value2 in extensions.Values)
				{
					value2.BindWorld?.Invoke(this);
				}
				world.Configure(Object.FindAnyObjectByType<IslandManager>() ?? throw new InvalidOperationException("The native island manager is unavailable."));
				yield return templates.Prepare();
			}
			saves = new NativeSaveBridge(CaptureRegistry, ValidateRegistry, ValidateNativeSave, Fail);
			saves.RegisterParticipant("expansionkit_world", () => (JToken)(object)JObject.FromObject((object)progress.CaptureState()), delegate(JToken? token)
			{
				MountedPackState mountedPackState = ((token == null) ? new MountedPackState() : (token.ToObject<MountedPackState>() ?? throw new InvalidDataException("The saved world module is empty.")));
				if (world != null)
				{
					world.RestoreState(mountedPackState);
				}
				else
				{
					progress.RestoreState(mountedPackState);
				}
			});
			saves.SaveSelected += delegate(int count)
			{
				byte nativeUnlockedCount = checked((byte)Math.Min(5, count));
				if (world != null)
				{
					world.SelectSave(nativeUnlockedCount);
				}
				else
				{
					progress.SelectSave(nativeUnlockedCount);
				}
			};
			policies.Add(AmmunitionPurchasePolicies.Register("expansionkit_progression", MaximumProgressionTier));
			if (Registry.Packs.Count > 0)
			{
				Handshake = new PackHandshake(InstanceFinder.NetworkManager, Fingerprint, delegate(string message)
				{
					ConnectionProblem = message;
					ExpansionKitPlugin.Log.LogWarning((object)message);
				});
				Handshake.StateRequested += delegate(NetworkConnection connection)
				{
					Handshake.SendState(connection, JsonConvert.SerializeObject((object)progress.CaptureState()));
				};
				Handshake.StateReceived += ReceiveState;
				Channels = new PackChannels(InstanceFinder.NetworkManager, Handshake, Registry.Packs.Select((PackDefinition pack) => pack.Key), () => Ready, Fail);
			}
			InstanceFinder.NetworkManager.ClientManager.OnClientConnectionState += ClientStateChanged;
			Boats = new BoatEquipment();
			foreach (PackExtension value3 in extensions.Values)
			{
				if (value3.PrepareGameplay != null)
				{
					yield return value3.PrepareGameplay();
				}
			}
			foreach (PackContentInstaller value4 in content.Values)
			{
				value4.Validate();
			}
			saves.Ready = true;
			CoreReady = true;
			Status = ((Registry.Packs.Count == 0) ? "ExpansionKit ready" : (Registry.Packs.Count + " expansion pack(s) ready"));
			ExpansionKitPlugin.Log.LogInfo((object)(Status + "; registry " + Fingerprint + "."));
		}

		private void AddSource(PackSource source)
		{
			if (sources.ContainsKey(source.Key))
			{
				throw new InvalidDataException("Two installed sources declare pack " + source.Key + ".");
			}
			sources.Add(source.Key, source);
		}

		private static GameObject CloneTemplate(GameObject source, Transform parent)
		{
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				return NativeSceneMeshes.Clone(source, parent);
			}
			catch (InvalidOperationException innerException)
			{
				string text = string.Join("; ", from filter in source.GetComponentsInChildren<MeshFilter>(true)
					where !Object.op_Implicit((Object)(object)filter.sharedMesh) || !Object.op_Implicit((Object)(object)((Component)filter).GetComponent<MeshRenderer>())
					select ((Object)filter).name + " parent=" + ((Object)((Component)filter).transform.parent).name + " active=" + ((Component)filter).gameObject.activeInHierarchy + " reward=" + Object.op_Implicit((Object)(object)((Component)filter).GetComponentInParent<QuestInteractable>()) + " shop=" + Object.op_Implicit((Object)(object)((Component)filter).GetComponentInParent<Purchasable>()));
				string[] obj = new string[6]
				{
					"Native template '",
					((Object)source).name,
					"' in ",
					null,
					null,
					null
				};
				Scene scene = source.scene;
				obj[3] = ((Scene)(ref scene)).name;
				obj[4] = " could not be cloned. Empty geometry: ";
				obj[5] = text;
				throw new InvalidOperationException(string.Concat(obj), innerException);
			}
		}

		private static IEnumerable<string> FindManifests(string root)
		{
			Stack<string> directories = new Stack<string>();
			directories.Push(root);
			int count = 0;
			while (directories.Count != 0)
			{
				string directory = directories.Pop();
				foreach (string item in Directory.EnumerateFiles(directory).OrderBy<string, string>((string value) => value, StringComparer.Ordinal))
				{
					string fileName = Path.GetFileName(item);
					if (fileName.EndsWith(".pack.json", StringComparison.OrdinalIgnoreCase) || fileName.Equals("pack.json", StringComparison.OrdinalIgnoreCase))
					{
						int num = count + 1;
						count = num;
						if (num > 64)
						{
							throw new InvalidDataException("More than 64 pack manifests are installed.");
						}
						yield return item;
					}
				}
				foreach (string item2 in Directory.EnumerateDirectories(directory).OrderByDescending<string, string>((string value) => value, StringComparer.Ordinal))
				{
					if ((File.GetAttributes(item2) & FileAttributes.ReparsePoint) == 0)
					{
						directories.Push(item2);
					}
				}
			}
		}

		private string ComputeFingerprint()
		{
			StringBuilder stringBuilder = new StringBuilder("25127368").Append('|').Append(Registry.Fingerprint).Append('|')
				.Append(PackSource.FileHash(typeof(ExpansionKitPlugin).Assembly.Location))
				.Append('|')
				.Append(PackSource.FileHash(typeof(PackDefinition).Assembly.Location));
			foreach (PackSource item in sources.Values.OrderBy<PackSource, string>((PackSource value) => value.Key, StringComparer.Ordinal))
			{
				foreach (KeyValuePair<string, string> item2 in item.CodeFingerprints.OrderBy<KeyValuePair<string, string>, string>((KeyValuePair<string, string> pair) => pair.Key, StringComparer.Ordinal))
				{
					stringBuilder.Append('\n').Append(item.Key).Append(':')
						.Append(item2.Key)
						.Append('=')
						.Append(item2.Value);
				}
			}
			using SHA256 sHA = SHA256.Create();
			return BitConverter.ToString(sHA.ComputeHash(Encoding.UTF8.GetBytes(stringBuilder.ToString()))).Replace("-", "");
		}

		public Item ResolveItem(string reference)
		{
			ContentReference reference2 = ContentReference.Parse(reference);
			if (reference2.IsNative)
			{
				return NativeItemRegistrar.NativeDonor(reference2.NativeId);
			}
			return GetContent(reference2.PackKey).ResolveItem(reference2);
		}

		public BaitInfo ResolveLure(string reference)
		{
			ContentReference reference2 = ContentReference.Parse(reference);
			if (reference2.IsNative)
			{
				return NativeLureCatalog.NativeLure(reference2.NativeId);
			}
			return GetContent(reference2.PackKey).ResolveLure(reference2);
		}

		private JObject CaptureRegistry()
		{
			return JObject.FromObject((object)new RegistryState
			{
				RuntimeVersion = "0.7.2",
				Registry = Registry.CreateManifest(),
				ExtensionCode = sources.Values.ToDictionary<PackSource, string, Dictionary<string, string>>((PackSource source) => source.Key, (PackSource source) => source.CodeFingerprints.ToDictionary<KeyValuePair<string, string>, string, string>((KeyValuePair<string, string> pair) => pair.Key, (KeyValuePair<string, string> pair) => pair.Value, StringComparer.Ordinal), StringComparer.Ordinal)
			});
		}

		private void ValidateRegistry(JObject json)
		{
			//IL_01cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0201: Expected O, but got Unknown
			RegistryState saved = ((JToken)json).ToObject<RegistryState>() ?? throw new InvalidDataException("Missing saved registry.");
			if ((saved.RuntimeVersion != "0.7.2" && saved.RuntimeVersion != "0.4.0" && saved.RuntimeVersion != "0.5.0" && saved.RuntimeVersion != "0.5.1" && saved.RuntimeVersion != "0.6.0" && saved.RuntimeVersion != "0.7.0" && saved.RuntimeVersion != "0.7.1") || saved.Registry == null || saved.ExtensionCode == null)
			{
				throw new InvalidDataException("The save requires a different ExpansionKit runtime or has incomplete registry metadata.");
			}
			Registry.RequireCompatible(saved.Registry, (Func<SavedPack, SavedPack, bool>)((SavedPack _, SavedPack _) => true));
			HashSet<string> acceptedRevisions = new HashSet<string>(StringComparer.Ordinal);
			foreach (SavedPack pack in saved.Registry.Packs)
			{
				if (!saved.ExtensionCode.TryGetValue(pack.Key, out Dictionary<string, string> value) || value == null)
				{
					throw new InvalidDataException("The save is missing extension-code identity for " + pack.Key + ".");
				}
				if (!sources.TryGetValue(pack.Key, out PackSource value2))
				{
					continue;
				}
				if (extensions.TryGetValue(pack.Key, out PackExtension value3))
				{
					Func<SavedPack, IReadOnlyDictionary<string, string>, bool>? acceptSavedRevision = value3.AcceptSavedRevision;
					if (acceptSavedRevision != null && acceptSavedRevision(new SavedPack
					{
						Key = pack.Key,
						Version = pack.Version,
						Fingerprint = pack.Fingerprint
					}, new ReadOnlyDictionary<string, string>(value)))
					{
						acceptedRevisions.Add(pack.Key);
					}
				}
				IReadOnlyDictionary<string, string> current2 = value2.CodeFingerprints;
				if (!acceptedRevisions.Contains(pack.Key) && (value.Count != current2.Count || value.Any((KeyValuePair<string, string> pair) => !current2.TryGetValue(pair.Key, out string value4) || pair.Value != value4)))
				{
					throw new InvalidDataException("The save requires different extension code for " + pack.Key + ".");
				}
			}
			Registry.RequireCompatible(saved.Registry, (Func<SavedPack, SavedPack, bool>)((SavedPack old, SavedPack _) => acceptedRevisions.Contains(old.Key)));
			if (saved.ExtensionCode.Keys.Any((string key) => !saved.Registry.Packs.Any((SavedPack pack) => pack.Key == key)))
			{
				throw new InvalidDataException("The saved extension identities contain an undeclared pack.");
			}
		}

		private void ValidateNativeSave(JObject root)
		{
			foreach (JObject item in from value in ((JContainer)root).Descendants().OfType<JObject>()
				where (bool?)value["Exists"] == true && value["ItemID"] != null
				select value)
			{
				int num = (int)item["ItemID"];
				if (num < 0 || num > 255 || !Object.op_Implicit((Object)(object)GameInfo.GetSpawnable((byte)num)))
				{
					throw new InvalidDataException("The crew save requires unavailable item " + num + ". Restore its content pack before loading.");
				}
			}
			foreach (JArray item2 in ((JToken)root).SelectTokens("$.Players[*].OwnedBaits").OfType<JArray>())
			{
				if (((JContainer)item2).Count > GameInfo.AllBaits.Count - 1)
				{
					throw new InvalidDataException("The crew save requires a larger lure registry; no inventory indices were discarded.");
				}
				int index;
				for (index = 0; index < ((JContainer)item2).Count; index++)
				{
					int num2 = (int)item2[index];
					if (num2 < 0)
					{
						throw new InvalidDataException("The saved lure quantity is negative.");
					}
					if (num2 > 0 && index + 1 >= 17 && !Registry.Content.Any((ResolvedContent entry) => (int)entry.Domain == 1 && entry.Id == index + 1))
					{
						throw new InvalidDataException("The crew owns an unavailable lure at slot " + (index + 1) + ".");
					}
				}
			}
			int island = ((int?)root["SpawnedIsland"]).GetValueOrDefault();
			if (island >= 5 && !Registry.Content.Any((ResolvedContent entry) => (int)entry.Domain == 3 && entry.Id == island))
			{
				throw new InvalidDataException("The crew save requires unavailable island " + island + ".");
			}
		}

		private void CommitQuest(QuestCheckpoint checkpoint, Action apply)
		{
			if (checkpoint.Kind == "select-save")
			{
				apply();
				return;
			}
			try
			{
				if (!Object.op_Implicit((Object)(object)Server.Instance) || !((NetworkBehaviour)Server.Instance).IsServerInitialized)
				{
					throw new InvalidOperationException("Only the host can commit a quest mutation.");
				}
				Saves.Checkpoint();
				apply();
				Saves.Checkpoint();
			}
			catch (Exception error)
			{
				saves?.Fail(error);
				throw;
			}
		}

		private static bool IsConnected()
		{
			if (Object.op_Implicit((Object)(object)InstanceFinder.NetworkManager))
			{
				if (!InstanceFinder.NetworkManager.IsClientStarted)
				{
					return InstanceFinder.NetworkManager.IsServerStarted;
				}
				return true;
			}
			return false;
		}

		private bool AuthorizeActor(Player player)
		{
			if (Ready && Object.op_Implicit((Object)(object)player) && ((NetworkBehaviour)player).IsServerInitialized)
			{
				return Handshake?.IsConfirmed(((NetworkBehaviour)player).Owner) ?? (Registry.Packs.Count == 0);
			}
			return false;
		}

		private void BindAmmunition(BulletPurchasable stand, string key)
		{
			string packKey = key.Substring(0, key.IndexOf(':'));
			string shopKey = key.Substring(key.IndexOf(':') + 1);
			ShopRecipe val = Registry.Packs.Single((PackDefinition pack) => pack.Key == packKey).Shops.Single((ShopRecipe shop) => ((PackRecipe)shop).Key == shopKey);
			byte island = EngineIsland(val.Island);
			byte maximum = ContentReference.Parse(val.Content).NativeId;
			if (!policiesByShop.Contains(key))
			{
				policies.Add(AmmunitionPurchasePolicies.Register(key, (Weapon _) => (!Object.op_Implicit((Object)(object)OnlineIslandManager.Instance) || OnlineIslandManager.CurIsland != island) ? ((byte?)null) : new byte?(maximum)));
				policiesByShop.Add(key);
			}
			AmmunitionPurchasePolicies.Bind(stand, key);
		}

		private byte EngineIsland(string reference)
		{
			ContentReference contentReference = ContentReference.Parse(reference);
			checked
			{
				if (contentReference.IsNative)
				{
					return (byte)(contentReference.NativeId - 1);
				}
				return (byte)(Registry.Content.Single((ResolvedContent entry) => entry.Reference == reference && unchecked((int)entry.Domain) == 3).Id ?? throw new InvalidDataException("An island has no engine ID."));
			}
		}

		private byte? MaximumProgressionTier(Weapon weapon)
		{
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Invalid comparison between Unknown and I4
			if (!Object.op_Implicit((Object)(object)OnlineIslandManager.Instance))
			{
				return null;
			}
			if (OwnedContentIdentity.TryGet((Item?)(object)weapon, out OwnedContentIdentity identity) && (int)((PackRecipe)Registry.Packs.Single((PackDefinition pack) => pack.Key == identity.PackKey).Items.Single((ItemRecipe item) => ((PackRecipe)item).Key == identity.Key)).Implementation == 1)
			{
				return null;
			}
			byte island = OnlineIslandManager.CurIsland;
			checked
			{
				if (island < 5)
				{
					return (byte)(island * 3);
				}
				IslandRecipe val = Registry.Packs.SelectMany((PackDefinition pack) => pack.Islands).FirstOrDefault((Func<IslandRecipe, bool>)((IslandRecipe value) => value.Id == island));
				if (val == null)
				{
					return null;
				}
				if (val.Route != null)
				{
					PackWorldRuntime? packWorldRuntime = world;
					if (packWorldRuntime != null && packWorldRuntime.CanVisit((byte)(ContentReference.Parse(val.Route.BeforeIsland).NativeId - 1)))
					{
						return (byte)((ContentReference.Parse(val.Route.BeforeIsland).NativeId - 1) * 3);
					}
				}
				return (byte)Math.Max(0, Math.Min(12, (val.UnlockThreshold - 2) * 3));
			}
		}

		private void VerifyScene(MountedIsland scene)
		{
			if (!scene.Reference.StartsWith("native:", StringComparison.Ordinal) && Registry.Packs.SelectMany((PackDefinition pack) => pack.Islands).Any((IslandRecipe island) => island.Id == scene.NativeEngineId && (int)((PackRecipe)island).Implementation == 0))
			{
				sceneMaterials.Apply(((Component)scene.Root).gameObject);
			}
			foreach (PackExtension value in extensions.Values)
			{
				value.VerifyScene?.Invoke(scene);
			}
		}

		private void ReceiveState(string json)
		{
			if (Object.op_Implicit((Object)(object)Server.Instance) && ((NetworkBehaviour)Server.Instance).IsServerInitialized)
			{
				return;
			}
			MountedPackState mountedPackState = JsonConvert.DeserializeObject<MountedPackState>(json) ?? throw new InvalidDataException("The host sent an empty world snapshot.");
			if (mountedPackState.Revision > remoteRevision)
			{
				if (world != null)
				{
					world.RestoreState(mountedPackState);
				}
				else
				{
					progress.RestoreState(mountedPackState);
				}
				remoteRevision = mountedPackState.Revision;
			}
		}

		private void ClientStateChanged(ClientConnectionStateArgs state)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Invalid comparison between Unknown and I4
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Invalid comparison between Unknown and I4
			if ((int)state.ConnectionState == 1)
			{
				remoteRevision = -1L;
			}
			if ((int)state.ConnectionState == 8)
			{
				ConnectionProblem = "";
			}
		}

		private void Update()
		{
			if (disposed || !CoreReady || Failure != null)
			{
				return;
			}
			try
			{
				Handshake?.Tick();
				Boats?.Tick();
				world?.Tick();
				if (Object.op_Implicit((Object)(object)Server.Instance) && ((NetworkBehaviour)Server.Instance).IsServerInitialized && Handshake != null && progress != null)
				{
					MountedPackState mountedPackState = progress.CaptureState();
					if (mountedPackState.Revision != publishedRevision)
					{
						Handshake.PublishState(JsonConvert.SerializeObject((object)mountedPackState));
						publishedRevision = mountedPackState.Revision;
					}
				}
			}
			catch (Exception error)
			{
				Fail(error);
			}
		}

		public void Fail(Exception error)
		{
			if (Failure == null)
			{
				Failure = error;
				Status = "ExpansionKit stopped: " + error.Message;
				ExpansionKitPlugin.Log.LogError((object)(Status + Environment.NewLine + error));
				if (saves != null && saves.Failure == null)
				{
					saves.Fail(error);
				}
			}
		}

		private IEnumerator Guarded(IEnumerator routine)
		{
			Stack<IEnumerator> stack = new Stack<IEnumerator>();
			stack.Push(routine);
			while (stack.Count != 0)
			{
				IEnumerator enumerator = stack.Peek();
				bool flag = false;
				object obj = null;
				Exception ex = null;
				try
				{
					flag = enumerator.MoveNext();
					if (flag)
					{
						obj = enumerator.Current;
					}
				}
				catch (Exception ex2)
				{
					ex = ex2;
				}
				if (ex != null)
				{
					Fail(ex);
					while (stack.Count != 0)
					{
						if (stack.Pop() is IDisposable disposable)
						{
							disposable.Dispose();
						}
					}
					break;
				}
				if (!flag)
				{
					stack.Pop();
					if (enumerator is IDisposable disposable2)
					{
						disposable2.Dispose();
					}
				}
				else if (obj is IEnumerator item)
				{
					stack.Push(item);
				}
				else
				{
					yield return obj;
				}
			}
		}

		private void OnGUI()
		{
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			if (Failure != null)
			{
				GUI.Box(new Rect(16f, 16f, (float)Math.Max(300, Screen.width - 32), 100f), Status + "\nFurther SDK saves and new connections are blocked. Check the BepInEx log, correct the pack set, and restart.");
			}
			else if (ConnectionProblem.Length != 0)
			{
				GUI.Box(new Rect(16f, 16f, (float)Math.Max(300, Screen.width - 32), 80f), ConnectionProblem);
			}
		}

		private void OnDestroy()
		{
			disposed = true;
			if (Object.op_Implicit((Object)(object)InstanceFinder.NetworkManager))
			{
				InstanceFinder.NetworkManager.ClientManager.OnClientConnectionState -= ClientStateChanged;
			}
			Channels?.Dispose();
			Boats?.Dispose();
			Handshake?.Dispose();
			saves?.Dispose();
			world?.Dispose();
			templates?.Dispose();
			foreach (IDisposable policy in policies)
			{
				policy.Dispose();
			}
			foreach (PackContentInstaller item in content.Values.Reverse())
			{
				item.Dispose();
			}
			assets?.Dispose();
			sceneMaterials.Dispose();
			if (Current == this)
			{
				Current = null;
			}
		}
	}
	[HarmonyPatch]
	internal static class WaitForPackPreparation
	{
		[HarmonyPatch]
		internal static class WaitForPackLobbyPreparation
		{
			private static IEnumerable<MethodBase> TargetMethods()
			{
				return new string[4] { "CreateOfflineLobby", "JoinOfflineLobby", "CreateOnlineLobby", "JoinOnlineLobby" }.Select((string name) => AccessTools.Method(typeof(ConnectionManager), name, (Type[])null, (Type[])null));
			}

			private static bool Prefix()
			{
				ExpansionPlatform current = ExpansionPlatform.Current;
				if (!Object.op_Implicit((Object)(object)current) || current.Ready)
				{
					return true;
				}
				ExpansionKitPlugin.Log.LogWarning((object)("Lobby not opened: " + current.Status));
				return false;
			}
		}

		private static IEnumerable<MethodBase> TargetMethods()
		{
			return from method in new Type[2]
				{
					typeof(ServerManager),
					typeof(ClientManager)
				}.SelectMany((Type type) => type.GetMethods(BindingFlags.Instance | BindingFlags.Public))
				where method.Name == "StartConnection" && method.ReturnType == typeof(bool)
				select method;
		}

		private static bool Prefix(MethodBase __originalMethod, ref bool __result)
		{
			ExpansionPlatform current = ExpansionPlatform.Current;
			if (!Object.op_Implicit((Object)(object)current) || (current.Ready && (__originalMethod.DeclaringType != typeof(ServerManager) || SaveManager.CurServerSave != null)))
			{
				return true;
			}
			ExpansionKitPlugin.Log.LogWarning((object)("Connection not started: " + current.Status));
			__result = false;
			return false;
		}
	}
	public static class NativeAccess
	{
		private static readonly Dictionary<(Type Type, string Name), FieldInfo> Fields = new Dictionary<(Type, string), FieldInfo>();

		public static FieldInfo Field(Type type, string name)
		{
			if (type == null)
			{
				throw new ArgumentNullException("type");
			}
			(Type, string) key = (type, name);
			lock (Fields)
			{
				if (!Fields.TryGetValue(key, out FieldInfo value))
				{
					value = AccessTools.Field(type, name) ?? throw new MissingFieldException(type.FullName, name);
					Fields.Add(key, value);
				}
				return value;
			}
		}

		public static bool HasField(Type type, string name)
		{
			return AccessTools.Field(type, name) != null;
		}

		public static T Get<T>(object target, string name)
		{
			return Read<T>(Field((target ?? throw new ArgumentNullException("target")).GetType(), name), target);
		}

		public static T Static<T>(Type type, string name)
		{
			return Read<T>(Field(type, name), null);
		}

		public static T? Optional<T>(object target, string name) where T : class
		{
			return Field((target ?? throw new ArgumentNullException("target")).GetType(), name).GetValue(target) as T;
		}

		public static void Set(object target, string name, object? value)
		{
			Field((target ?? throw new ArgumentNullException("target")).GetType(), name).SetValue(target, value);
		}

		public static void SetStatic(Type type, string name, object? value)
		{
			Field(type, name).SetValue(null, value);
		}

		private static T Read<T>(FieldInfo field, object? target)
		{
			object value = field.GetValue(target);
			if (value is T)
			{
				return (T)value;
			}
			throw new InvalidOperationException("Native " + field.DeclaringType?.Name + "." + field.Name + " is not an initialized " + typeof(T).Name + ".");
		}

		public static bool Finite(float value)
		{
			if (!float.IsNaN(value))
			{
				return !float.IsInfinity(value);
			}
			return false;
		}

		public static bool Positive(float value)
		{
			if (Finite(value))
			{
				return value > 0f;
			}
			return false;
		}

		public static void PrepareNetworkClone(GameObject clone, GameObject source)
		{
			if (!Object.op_Implicit((Object)(object)clone))
			{
				throw new ArgumentNullException("clone");
			}
			if (!Object.op_Implicit((Object)(object)source))
			{
				throw new ArgumentNullException("source");
			}
			NetworkBehaviour[] componentsInChildren = source.GetComponentsInChildren<NetworkBehaviour>(true);
			NetworkBehaviour[] componentsInChildren2 = clone.GetComponentsInChildren<NetworkBehaviour>(true);
			if (componentsInChildren2.Length != componentsInChildren.Length)
			{
				throw new InvalidOperationException("Cloning changed the native network behaviour layout.");
			}
			for (int i = 0; i < componentsInChildren2.Length; i++)
			{
				if (((object)componentsInChildren2[i]).GetType() != ((object)componentsInChildren[i]).GetType())
				{
					throw new InvalidOperationException("Cloning changed the native network behaviour order.");
				}
				string[] array = new string[4] { "_syncTypes", "_serverRpcDelegates", "_observersRpcDelegates", "_targetRpcDelegates" };
				foreach (string text in array)
				{
					IDictionary dictionary = Get<IDictionary>(componentsInChildren2[i], text);
					if (dictionary == Get<IDictionary>(componentsInChildren[i], text))
					{
						throw new InvalidOperationException("The clone shares its " + text + " with the native prefab.");
					}
					dictionary.Clear();
				}
				Type type = ((object)componentsInChildren2[i]).GetType();
				while (type != null && type != typeof(MonoBehaviour))
				{
					FieldInfo[] fields = type.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
					foreach (FieldInfo fieldInfo in fields)
					{
						if (fieldInfo.FieldType == typeof(bool) && fieldInfo.Name.StartsWith("NetworkInitialize___", StringComparison.Ordinal) && fieldInfo.Name.EndsWith("_Excuted", StringComparison.Ordinal))
						{
							fieldInfo.SetValue(componentsInChildren2[i], false);
						}
					}
					type = type.BaseType;
				}
			}
			NetworkObject target = clone.GetComponent<NetworkObject>() ?? throw new InvalidOperationException("The clone has no root NetworkObject.");
			Set(target, "_initializedValusSet", false);
			Set(target, "_disabledNetworkBehavioursInitialized", false);
		}
	}
	public static class NativeSceneMeshes
	{
		public static GameObject Clone(GameObject source, Transform parent)
		{
			if (!Object.op_Implicit((Object)(object)source) || !Object.op_Implicit((Object)(object)parent) || ((Component)parent).gameObject.activeInHierarchy)
			{
				throw new ArgumentException("Capture a native object into an inactive template parent.");
			}
			MeshFilter[] componentsInChildren = source.GetComponentsInChildren<MeshFilter>(true);
			HashSet<MeshFilter> deferred = DeferredSlots(source);
			Mesh[] array = componentsInChildren.Select((MeshFilter filter) => OwnedMesh(filter, deferred)).ToArray();
			GameObject val = Object.Instantiate<GameObject>(source, parent, false);
			val.SetActive(false);
			bool flag = false;
			try
			{
				MeshFilter[] componentsInChildren2 = val.GetComponentsInChildren<MeshFilter>(true);
				if (componentsInChildren2.Length != componentsInChildren.Length)
				{
					throw new InvalidOperationException("Native clone mesh layout changed.");
				}
				for (int num = 0; num < componentsInChildren2.Length; num++)
				{
					componentsInChildren2[num].sharedMesh = array[num];
					MeshRenderer component = ((Component)componentsInChildren2[num]).GetComponent<MeshRenderer>();
					if (Object.op_Implicit((Object)(object)component) && ((Renderer)component).isPartOfStaticBatch)
					{
						(AccessTools.Method(typeof(Renderer), "SetStaticBatchInfo", (Type[])null, (Type[])null) ?? throw new MissingMethodException("Renderer", "SetStaticBatchInfo")).Invoke(component, new object[2] { 0, 0 });
						(AccessTools.Property(typeof(Renderer), "staticBatchRootTransform") ?? throw new MissingMemberException("Renderer", "staticBatchRootTransform")).SetValue(component, null);
						if (((Renderer)component).isPartOfStaticBatch)
						{
							throw new InvalidOperationException("The native clone retained another scene's static batch.");
						}
					}
				}
				Transform[] componentsInChildren3 = val.GetComponentsInChildren<Transform>(true);
				for (int num2 = 0; num2 < componentsInChildren3.Length; num2++)
				{
					((Component)componentsInChildren3[num2]).gameObject.isStatic = false;
				}
				flag = true;
				return val;
			}
			finally
			{
				if (!flag)
				{
					Object.Destroy((Object)(object)val);
				}
			}
		}

		private static Mesh? OwnedMesh(MeshFilter filter, HashSet<MeshFilter> deferred)
		{
			Mesh mesh = filter.sharedMesh;
			MeshRenderer renderer = ((Component)filter).GetComponent<MeshRenderer>();
			if (!Object.op_Implicit((Object)(object)mesh))
			{
				if (Object.op_Implicit((Object)(object)renderer) && ((Renderer)renderer).isPartOfStaticBatch)
				{
					throw new InvalidOperationException("Missing native geometry on a statically batched renderer: " + Path(((Component)filter).transform));
				}
				if (Object.op_Implicit((Object)(object)renderer) && ((Renderer)renderer).enabled && ((Component)filter).gameObject.activeInHierarchy && !deferred.Contains(filter))
				{
					throw new InvalidOperationException("Missing native geometry on a visible renderer that is not a native deferred slot: " + Path(((Component)filter).transform));
				}
				return null;
			}
			if (((Object)mesh).name.StartsWith("Combined Mesh", StringComparison.OrdinalIgnoreCase) && (!Object.op_Implicit((Object)(object)renderer) || !((Renderer)renderer).isPartOfStaticBatch))
			{
				throw new InvalidOperationException("An unowned combined-scene mesh was supplied for " + Path(((Component)filter).transform));
			}
			if (!Object.op_Implicit((Object)(object)renderer) || !((Renderer)renderer).isPartOfStaticBatch)
			{
				return mesh;
			}
			MeshCollider component = ((Component)filter).GetComponent<MeshCollider>();
			if (Object.op_Implicit((Object)(object)component) && Object.op_Implicit((Object)(object)component.sharedMesh) && (Object)(object)component.sharedMesh != (Object)(object)mesh && Fits(component.sharedMesh, renderer))
			{
				return component.sharedMesh;
			}
			Mesh[] array = (from candidate in Resources.FindObjectsOfTypeAll<Mesh>()
				where Object.op_Implicit((Object)(object)candidate) && (Object)(object)candidate != (Object)(object)mesh && !((Object)candidate).name.StartsWith("Combined Mesh", StringComparison.OrdinalIgnoreCase) && candidate.subMeshCount == ((Renderer)renderer).sharedMaterials.Length && Fits(candidate, renderer)
				select candidate).Distinct().ToArray();
			if (array.Length != 1)
			{
				throw new InvalidOperationException("Cannot safely recover native geometry '" + Path(((Component)filter).transform) + "': " + $"{array.Length} originals match; refusing to clone combined scene geometry.");
			}
			return array[0];
		}

		public static HashSet<MeshFilter> DeferredSlots(GameObject source)
		{
			HashSet<MeshFilter> hashSet = new HashSet<MeshFilter>();
			QuestInteractable[] componentsInChildren = source.GetComponentsInChildren<QuestInteractable>(true);
			for (int i = 0; i < componentsInChildren.Length; i++)
			{
				MeshFilter val = NativeAccess.Optional<MeshFilter>(componentsInChildren[i], "_meshToUpdate");
				if (Object.op_Implicit((Object)(object)val))
				{
					hashSet.Add(val);
				}
			}
			return hashSet;
		}

		private static string Path(Transform node)
		{
			List<string> list = new List<string>();
			Transform val = node;
			while (Object.op_Implicit((Object)(object)val))
			{
				list.Add(((Object)val).name);
				val = val.parent;
			}
			list.Reverse();
			return string.Join("/", list);
		}

		private static bool Fits(Mesh mesh, MeshRenderer renderer)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: 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_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			Bounds val = TransformBounds(mesh.bounds, ((Component)renderer).transform.localToWorldMatrix);
			Bounds bounds = ((Renderer)renderer).bounds;
			Vector3 size = ((Bounds)(ref bounds)).size;
			float num = Mathf.Max(0.0005f, ((Vector3)(ref size)).magnitude * 0.002f);
			Vector3 center = ((Bounds)(ref val)).center;
			bounds = ((Renderer)renderer).bounds;
			if (Vector3.Distance(center, ((Bounds)(ref bounds)).center) <= num)
			{
				Vector3 size2 = ((Bounds)(ref val)).size;
				bounds = ((Renderer)renderer).bounds;
				return Vector3.Distance(size2, ((Bounds)(ref bounds)).size) <= num;
			}
			return false;
		}

		public static Bounds TransformBounds(Bounds bounds, Matrix4x4 matrix)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			Bounds result = default(Bounds);
			((Bounds)(ref result))..ctor(((Matrix4x4)(ref matrix)).MultiplyPoint3x4(((Bounds)(ref bounds)).center), Vector3.zero);
			for (int i = 0; i < 8; i++)
			{
				((Bounds)(ref result)).Encapsulate(((Matrix4x4)(ref matrix)).MultiplyPoint3x4(((Bounds)(ref bounds)).center + Vector3.Scale(((Bounds)(ref bounds)).extents, new Vector3((float)(((i & 1) != 0) ? 1 : (-1)), (float)(((i & 2) != 0) ? 1 : (-1)), (float)(((i & 4) != 0) ? 1 : (-1))))));
			}
			return result;
		}
	}
	public sealed class PackAssets : IDisposable
	{
		private readonly IReadOnlyDictionary<string, PackSource> sources;

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

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

		private bool disposed;

		public PackAssets(IReadOnlyDictionary<string, PackSource> sources)
		{
			this.sources = sources ?? throw new ArgumentNullException("sources");
		}

		public void Verify(PackRegistry registry)
		{
			if (disposed)
			{
				throw new ObjectDisposedException("PackAssets");
			}
			foreach (PackDefinition pack in registry.Packs)
			{
				foreach (ArtRecipe item in from art in pack.Items.Select((ItemRecipe item) => item.Art).Concat(pack.Lures.Select((LureRecipe lure) => lure.Art)).Concat(pack.Npcs.Select((NpcRecipe npc) => npc.Art))
					where art != null
					select (art))
				{
					VerifyFile(pack.Key, item.Bundle, item.Sha256);
				}
				foreach (IslandRecipe island in pack.Islands)
				{
					VerifyFile(pack.Key, island.SceneBundle, island.SceneSha256);
				}
			}
		}

		public GameObject CreateVisual(string packKey, ArtRecipe art)
		{
			if (art == null)
			{
				throw new ArgumentNullException("art");
			}
			GameObject val = Load(packKey, art.Bundle, art.Sha256).LoadAsset<GameObject>(art.Prefab);
			if (!Object.op_Implicit((Object)(object)val) || val.GetComponentsInChildren<MonoBehaviour>(true).Length != 0 || val.GetComponentsInChildren<Collider>(true).Length != 0 || val.GetComponentsInChildren<Renderer>(true).Length == 0)
			{
				throw new InvalidDataException("An authored item visual must contain renderable art, without runtime scripts or collision components: " + art.Prefab);
			}
			GameObject obj = Object.Instantiate<GameObject>(val);
			obj.SetActive(false);
			return obj;
		}

		public string ResolveScene(string packKey, IslandRecipe island)
		{
			string[] array = (from path in Load(packKey, island.SceneBundle, island.SceneSha256).GetAllScenePaths()
				where string.Equals(Path.GetFileNameWithoutExtension(path), island.SceneName, StringComparison.Ordinal)
				select path).ToArray();
			if (array.Length != 1)
			{
				throw new InvalidDataException("The pack bundle must contain exactly one scene matching " + island.SceneName + ".");
			}
			return array[0];
		}

		private AssetBundle Load(string packKey, string bundleName, string expectedHash)
		{
			string text = VerifyFile(packKey, bundleName, expectedHash);
			if (bundles.TryGetValue(text, out AssetBundle value))
			{
				return value;
			}
			AssetBundle val = AssetBundle.LoadFromFile(text);
			if (!Object.op_Implicit((Object)(object)val))
			{
				throw new InvalidDataException("Unity could not load the verified pack asset bundle: " + bundleName);
			}
			bundles.Add(text, val);
			return val;
		}

		private string VerifyFile(string packKey, string name, string expectedHash)
		{
			if (disposed)
			{
				throw new ObjectDisposedException("PackAssets");
			}
			if (!sources.TryGetValue(packKey, out PackSource value))
			{
				throw new InvalidDataException("No asset source exists for pack " + packKey + ".");
			}
			string text = value.ResolveFile(name);
			if (verified.TryGetValue(text, out string value2))
			{
				if (!string.Equals(value2, expectedHash, StringComparison.OrdinalIgnoreCase))
				{
					throw new InvalidDataException("Two recipes disagree about the same asset hash: " + name);
				}
				return text;
			}
			FileInfo fileInfo = new FileInfo(text);
			if (!fileInfo.Exists || fileInfo.Length == 0L || fileInfo.Length > 536870912)
			{
				throw new InvalidDataException("The required pack bundle is missing, empty or larger than 512 MiB: " + text);
			}
			string text2 = PackSource.FileHash(text);
			if (!string.Equals(text2, expectedHash, StringComparison.OrdinalIgnoreCase))
			{
				throw new InvalidDataException("The installed pack bundle differs from its manifest: " + name);
			}
			verified.Add(text, text2);
			return text;
		}

		public void Dispose()
		{
			if (disposed)
			{
				return;
			}
			disposed = true;
			foreach (AssetBundle value in bundles.Values)
			{
				if (Object.op_Implicit((Object)(object)value))
				{
					value.Unload(false);
				}
			}
			bundles.Clear();
			verified.Clear();
		}
	}
	public sealed class PackExtension
	{
		public string Key { get; }

		public Func<PackSource> PrepareSource { get; }

		public Action<PackContentOptions> ConfigureContent { get; }

		public Action<PackContentInstaller>? ContentInstalled { get; set; }

		public Action<ExpansionPlatform>? BindWorld { get; set; }

		public Func<IEnumerator>? PrepareGameplay { get; set; }

		public Func<bool>? GameplayReady { get; set; }

		public Action<MountedIsland>? VerifyScene { get; set; }

		public Func<SavedPack, IReadOnlyDictionary<string, string>, bool>? AcceptSavedRevision { get; set; }

		public PackExtension(string key, Func<PackSource> prepareSource, Action<PackContentOptions> configureContent)
		{
			if (!ContentReference.IsKey(key))
			{
				throw new ArgumentException("A pack extension requires a valid key.", "key");
			}
			Key = key;
			PrepareSource = prepareSource ?? throw new ArgumentNullException("prepareSource");
			ConfigureContent = configureContent ?? throw new ArgumentNullException("configureContent");
		}
	}
	public sealed class PackSource
	{
		private static readonly JsonSerializerSettings Json = new JsonSerializerSettings
		{
			MissingMemberHandling = (MissingMemberHandling)1,
			MaxDepth = 64
		};

		private readonly PackDefinition definition;

		public string Key => definition.Key;

		public string Root { get; }

		public IReadOnlyDictionary<string, string> CodeFingerprints { get; }

		internal PackDefinition Definition => JsonConvert.DeserializeObject<PackDefinition>(JsonConvert.SerializeObject((object)definition), Json) ?? throw new InvalidDataException("The pack definition could not be copied.");

		public PackSource(PackDefinition definition, string root, params Assembly[] extensionAssemblies)
		{
			if (definition == null)
			{
				throw new ArgumentNullException("definition");
			}
			definition.Validate();
			if (string.IsNullOrWhiteSpace(root) || !Path.IsPathRooted(root) || !Directory.Exists(root))
			{
				throw new ArgumentException("The pack needs an existing, absolute asset directory.", "root");
			}
			this.definition = JsonConvert.DeserializeObject<PackDefinition>(JsonConvert.SerializeObject((object)definition), Json) ?? throw new InvalidDataException("The pack definition could not be copied.");
			Root = Path.GetFullPath(root);
			if (extensionAssemblies == null || extensionAssemblies.Any((Assembly assembly) => assembly == null))
			{
				throw new ArgumentNullException("extensionAssemblies");
			}
			Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.Ordinal);
			foreach (Assembly item in extensionAssemblies.OrderBy<Assembly, string>((Assembly assembly) => assembly.GetName().Name, StringComparer.Ordinal))
			{
				string text = item.GetName().Name ?? throw new InvalidDataException("An extension assembly has no identity.");
				if (dictionary.ContainsKey(text))
				{
					throw new InvalidDataException("Duplicate extension assembly: " + text);
				}
				dictionary.Add(text, FileHash(item.Location));
			}
			CodeFingerprints = new ReadOnlyDictionary<string, string>(dictionary);
		}

		public static PackSource Load(string path)
		{
			string fullPath = Path.GetFullPath(path);
			FileInfo fileInfo = new FileInfo(fullPath);
			if (!fileInfo.Exists || fileInfo.Length > 2097152)
			{
				throw new InvalidDataException("A pack manifest must exist and be no larger than 2 MiB: " + fullPath);
			}
			return new PackSource(JsonConvert.DeserializeObject<PackDefinition>(File.ReadAllText(fullPath), Json) ?? throw new InvalidDataException("The pack manifest is empty: " + fullPath), fileInfo.DirectoryName);
		}

		internal string ResolveFile(string name)
		{
			if (string.IsNullOrWhiteSpace(name) || Path.IsPathRooted(name))
			{
				throw new InvalidDataException("A pack asset must use a relative local filename.");
			}
			string fullPath = Path.GetFullPath(Path.Combine(Root, name));
			string value = Root.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar;
			if (!fullPath.StartsWith(value, StringComparison.OrdinalIgnoreCase))
			{
				throw new InvalidDataException("A pack asset leaves its owning directory.");
			}
			return fullPath;
		}

		public static string FileHash(string path)
		{
			using FileStream inputStream = File.OpenRead(path);
			using SHA256 sHA = SHA256.Create();
			return BitConverter.ToString(sHA.ComputeHash(inputStream)).Replace("-", "");
		}
	}
}
namespace HowToFish.ExpansionKit.Runtime.World
{
	public sealed class BoatMount
	{
		public sealed class BoatSupport : IDisposable
		{
			private readonly GameObject root;

			private readonly Collider collider;

			private readonly Boat boat;

			internal BoatSupport(GameObject root, Collider collider, Boat boat)
			{
				this.root = root;
				this.collider = collider;
				this.boat = boat;
			}

			public void SetActive(bool active)
			{
				if (Object.op_Implicit((Object)(object)root))
				{
					root.SetActive(active);
				}
			}

			public void Dispose()
			{
				if (BoatManager.ColToBoat.TryGetValue(collider, out var value) && (Object)(object)value == (Object)(object)boat)
				{
					BoatManager.ColToBoat.Remove(collider);
				}
				if (Object.op_Implicit((Object)(object)root))
				{
					Object.Destroy((Object)(object)root);
				}
			}
		}

		public Boat Boat { get; }

		public Transform Root { get; }

		public Transform VisualRoot => Boat.VisualBoat;

		public Rigidbody Physics
		{
			get
			{
				if (!IsServer)
				{
					return Boat.VisualPhysicsRig;
				}
				return Boat.HiddenPhysicsRig;
			}
		}

		public bool IsServer => ((NetworkBehaviour)Boat).IsServerInitialized;

		private Transform CollisionRoot => NativeAccess.Get<Transform>(Boat, "_dynamicObjectColsHolder");

		private Transform PhysicsFrame
		{
			get
			{
				if (!IsServer)
				{
					return CollisionRoot;
				}
				return ((Component)Physics).transform;
			}
		}

		private Vector3 Velocity
		{
			get
			{
				//IL_001a: Unknown result type (might be due to invalid IL or missing references)
				//IL_000e: Unknown result type (might be due to invalid IL or missing references)
				if (!IsServer)
				{
					return Boat.Velocity;
				}
				return Physics.linearVelocity;
			}
		}

		public float Speed
		{
			get
			{
				//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)
				Vector3 velocity = Velocity;
				return ((Vector3)(ref velocity)).magnitude;
			}
		}

		public float HorizontalSpeed
		{
			get
			{
				//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_000b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0010: Unknown result type (might be due to invalid IL or missing references)
				Vector3 val = Vector3.ProjectOnPlane(Velocity, Vector3.up);
				return ((Vector3)(ref val)).magnitude;
			}
		}

		public Quaternion PhysicsRotation => PhysicsFrame.rotation * Quaternion.Inverse(((Component)Boat.VisualPhysicsRig).transform.rotation) * Root.rotation;

		internal BoatMount(Boat boat, Transform root)
		{
			Boat = boat;
			Root = root;
		}

		public Vector3 PhysicsPoint(Vector3 local)
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			return PhysicsFrame.TransformPoint(((Component)Boat.VisualPhysicsRig).transform.InverseTransformPoint(Root.TransformPoint(local)));
		}

		public Vector3 CollisionPoint(Vector3 local)
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			return CollisionRoot.TransformPoint(((Component)Boat.VisualPhysicsRig).transform.InverseTransformPoint(Root.TransformPoint(local)));
		}

		public bool TryDeck(Vector3 local, out Vector3 surface)
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: 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_0032: 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_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: 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_004c: 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_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_0145: Unknown result type (might be due to invalid IL or missing references)
			//IL_0111: Unknown result type (might be due to invalid IL or missing references)
			//IL_0113: Unknown result type (might be due to invalid IL or missing references)
			//IL_0118: Unknown result type (might be due to invalid IL or missing references)
			//IL_0131: Unknown result type (might be due to invalid IL or missing references)
			//IL_0133: Unknown result type (might be due to invalid IL or missing references)
			//IL_0138: Unknown result type (might be due to invalid IL or missing references)
			//IL_013d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
			Physics.SyncTransforms();
			Transform collisionRoot = CollisionRoot;
			Vector3 val = ((Component)Boat.VisualPhysicsRig).transform.InverseTransformPoint(Root.TransformPoint(local + Vector3.up * 3f));
			Vector3 up = collisionRoot.up;
			Ray val2 = default(Ray);
			((Ray)(ref val2))..ctor(collisionRoot.TransformPoint(val), -up);
			float num = float.PositiveInfinity;
			Vector3 val3 = default(Vector3);
			Collider[] componentsInChildren = ((Component)collisionRoot).GetComponentsInChildren<Collider>();
			RaycastHit val5 = default(RaycastHit);
			foreach (Collider val4 in componentsInChildren)
			{
				if (val4.enabled && !val4.isTrigger && BoatManager.ColToBoat.TryGetValue(val4, out var value) && !((Object)(object)value != (Object)(object)Boat) && val4.Raycast(val2, ref val5, 6f) && !(Vector3.Dot(((RaycastHit)(ref val5)).normal, up) < 0.6f) && !(((RaycastHit)(ref val5)).distance >= num))
				{
					num = ((RaycastHit)(ref val5)).distance;
					val3 = ((RaycastHit)(ref val5)).point;
				}
			}
			if (num < float.PositiveInfinity)
			{
				Vector3 val6 = collisionRoot.InverseTransformPoint(val3);
				surface = Root.InverseTransformPoint(((Component)Boat.VisualPhysicsRig).transform.TransformPoint(val6));
				return true;
			}
			surface = default(Vector3);
			return false;
		}

		public bool Near(Player player, Vector3 local, float range)
		{
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			if (!Object.op_Implicit((Object)(object)player) || player.Dying.IsDead || !Object.op_Implicit((Object)(object)Boat) || !((NetworkBehaviour)Boat).IsSpawned || !NativeAccess.Positive(range))
			{
				return false;
			}
			return Vector3.Distance(player.Transform.position, PhysicsPoint(local)) <= range;
		}

		public BoatSupport SupportBox(string name, Vector3 localCenter, Vector3 size)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: 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_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: 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_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ae: 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_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_0105: Unknown result type (might be due to invalid IL or missing references)
			//IL_010d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0130: Expected O, but got Unknown
			if (!NativeAccess.Positive(size.x) || !NativeAccess.Positive(size.y) || !NativeAccess.Positive(size.z) || !NativeAccess.Finite(localCenter.x) || !NativeAccess.Finite(localCenter.y) || !NativeAccess.Finite(localCenter.z))
			{
				throw new ArgumentException("Boat support dimensions must be positive and its center finite.");
			}
			int num = LayerMask.NameToLayer("BoatDynamic");
			if (num < 0)
			{
				throw new InvalidOperationException("The native dynamic-boat collision layer is unavailable.");
			}
			GameObject val = new GameObject(name);
			val.SetActive(false);
			val.transform.SetParent(CollisionRoot, false);
			val.transform.localPosition = ((Component)Boat.VisualPhysicsRig).transform.InverseTransformPoint(Root.TransformPoint(localCenter));
			val.transform.localRotation = Quaternion.Inverse(((Component)Boat.VisualPhysicsRig).transform.rotation) * Root.rotation;
			val.layer = num;
			val.tag = "Boat";
			BoxCollider val2 = val.AddComponent<BoxCollider>();
			val2.size = size;
			BoatManager.ColToBoat.Add((Collider)(object)val2, Boat);
			return new BoatSupport(val, (Collider)(object)val2, Boat);
		}
	}
	public sealed class BoatEquipment : IDisposable
	{
		private sealed class Registration : IDisposable
		{
			internal BoatEquipment Owner;

			internal string Key = "";

			internal Func<BoatMount, IDisposable> Attach;

			internal Func<string?> BlockTravel;

			internal Func<string?>? BlockDriving;

			internal Action<string> Notify;

			internal GameObject? Root;

			internal IDisposable? Lifetime;

			public void Dispose()
			{
				Owner.Remove(this);
			}
		}

		private readonly Dictionary<string, Registration> registrations = new Dictionary<string, Registration>(StringComparer.Ordinal);

		private Boat? currentBoat;

		private bool disposed;

		internal static BoatEquipment? Current { get; private set; }

		public BoatEquipment()
		{
			if (Current != null)
			{
				throw new InvalidOperationException("Only one shared boat-equipment registry is supported.");
			}
			Current = this;
		}

		public IDisposable Register(string packKey, string key, Func<BoatMount, IDisposable> attach, Func<string?> blockTravel, Action<string> notify)
		{
			return Register(packKey, key, attach, blockTravel, notify, null);
		}

		public IDisposable Register(string packKey, string key, Func<BoatMount, IDisposable> attach, Func<string?> blockTravel, Action<string> notify, Func<string?>? blockDriving)
		{
			if (disposed)
			{
				throw new ObjectDisposedException("BoatEquipment");
			}
			if (!ContentReference.IsKey(packKey) || !ContentReference.IsKey(key))
			{
				throw new ArgumentException("Boat equipment needs a namespaced pack identity.");
			}
			string text = packKey + ":" + key;
			if (registrations.ContainsKey(text))
			{
				throw new InvalidOperationException("Duplicate boat equipment: " + text);
			}
			Registration registration = new Registration
			{
				Owner = this,
				Key = text,
				Attach = (attach ?? throw new ArgumentNullException("attach")),
				BlockTravel = (blockTravel ?? throw new ArgumentNullException("blockTravel")),
				BlockDriving = blockDriving,
				Notify = (notify ?? throw new ArgumentNullException("notify"))
			};
			registrations.Add(text, registration);
			return registration;
		}

		public void Tick()
		{
			//IL_014c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0153: Expected O, but got Unknown
			if (disposed)
			{
				return;
			}
			Boat boat = BoatManager.Boat;
			if (!Object.op_Implicit((Object)(object)boat) || !((NetworkBehaviour)boat).IsSpawned || !((NetworkBehaviour)boat).IsClientInitialized || !Object.op_Implicit((Object)(object)boat.VisualBoat) || !Object.op_Implicit((Object)(object)boat.VisualPhysicsRig) || !Object.op_Implicit((Object)(object)boat.HiddenPhysicsRig) || !BoatManager.ColToBoat.Values.Any((Boat owner) => (Object)(object)owner == (Object)(object)boat))
			{
				boat = null;
			}
			if (currentBoat != boat)
			{
				foreach (Registration value in registrations.Values)
				{
					Detach(value);
				}
				currentBoat = boat;
			}
			if (!Object.op_Implicit((Object)(object)currentBoat))
			{
				return;
			}
			foreach (Registration item in registrations.Values.Where((Registration value) => !Object.op_Implicit((Object)(object)value.Root)))
			{
				GameObject val = new GameObject("ExpansionKit boat equipment " + item.Key);
				val.SetActive(false);
				val.transform.SetParent(currentBoat.VisualBoat, false);
				item.Root = val;
				try
				{
					item.Lifetime = item.Attach(new BoatMount(currentBoat, val.transform)) ?? throw new InvalidOperationException("The boat equipment did not return an owned lifetime: " + item.Key);
					val.SetActive(true);
				}
				catch
				{
					Detach(item);
					throw;
				}
			}
			if (DrivingBlockReason(currentBoat) != null)
			{
				Boat.ToggleWantToDrive(false);
				if (((NetworkBehaviour)currentBoat).IsServerInitialized && Object.op_Implicit((Object)(object)currentBoat.Driver))
				{
					AllowDriving(currentBoat);
					currentBoat.TrySetDriver((Player)null);
				}
			}
		}

		public string? DrivingBlockReason(Boat boat)
		{
			FindDrivingBlock(boat, out string reason);
			return reason;
		}

		private Registration? FindDrivingBlock(Boat boat, out string? reason)
		{
			reason = null;
			if (!Object.op_Implicit((Object)(object)boat) || (Object)(object)boat != (Object)(object)currentBoat || !((NetworkBehaviour)boat).IsSpawned)
			{
				return null;
			}
			foreach (Registration value in registrations.Values)
			{
				if (value.Lifetime != null)
				{
					string text = value.BlockDriving?.Invoke();
					if (!string.IsNullOrEmpty(text))
					{
						reason = text;
						return value;
					}
				}
			}
			return null;
		}

		internal bool AllowDriving(Boat boat)
		{
			string reason;
			Registration registration = FindDrivingBlock(boat, out reason);
			if (registration == null)
			{
				return true;
			}
			registration.Notify(reason);
			ExpansionKitPlugin.Log.LogWarning((object)("Boat driving paused: " + reason));
			return false;
		}

		internal bool AllowTravel(byte island)
		{
			if (island == byte.MaxValue || !Object.op_Implicit((Object)(object)currentBoat) || !((NetworkBehaviour)currentBoat).IsSpawned)
			{
				return true;
			}
			foreach (Registration value in registrations.Values)
			{
				string text = value.BlockTravel();
				if (!string.IsNullOrEmpty(text))
				{
					value.Notify(text);
					ExpansionKitPlugin.Log.LogWarning((object)("Boat travel paused: " + text));
					return false;
				}
			}
			return true;
		}

		private static void Detach(Registration registration)
		{
			registration.Lifetime?.Dispose();
			registration.Lifetime = null;
			if (Object.op_Implicit((Object)(object)registration.Root))
			{
				Object.Destroy((Object)(object)registration.Root);
			}
			registration.Root = null;
		}

		private void Remove(Registration registration)
		{
			if (registrations.TryGetValue(registration.Key, out Registration value) && value == registration)
			{
				Detach(registration);
				registrations.Remove(registration.Key);
			}
		}

		public void Dispose()
		{
			if (!disposed)
			{
				Registration[] array = registrations.Values.ToArray();
				for (int i = 0; i < array.Length; i++)
				{
					array[i].Dispose();
				}
				disposed = true;
				currentBoat = null;
				if (Current == this)
				{
					Current = null;
				}
			}
		}
	}
	[HarmonyPatch(typeof(OnlineIslandManager), "SpawnIsland")]
	internal static class BoatEquipmentTravelGuard
	{
		private static bool Prefix(byte islandIndex)
		{
			return BoatEquipment.Current?.AllowTravel(islandIndex) ?? true;
		}
	}
	[HarmonyPatch(typeof(BoatInteractable), "Hover")]
	internal static class BoatEquipmentHelmLabel
	{
		private static void Postfix(BoatInteractable __instance)
		{
			//IL_0040: 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)
			Boat boat = BoatManager.Boat;
			string text = BoatEquipment.Current?.DrivingBlockReason(boat);
			if (text != null)
			{
				PlayerUI.UpdateLookAtText(text);
				NativeAccess.Set(__instance, "_hoverText", text);
			}
			PlayerUI.SetLookAtColor((text != null || !boat.BoatUnlocked) ? GameInfo.RedColor : Color.white);
		}
	}
	[HarmonyPatch(typeof(BoatInteractable), "Interact")]
	internal static class BoatEquipmentHelmGuard
	{
		private static bool Prefix()
		{
			BoatEquipment? current = BoatEquipment.Current;
			int num;
			if (current == null)
			{
				num = 1;
			}
			else
			{
				num = (current.AllowDriving(BoatManager.Boat) ? 1 : 0);
				if (num == 0)
				{
					Boat.ToggleWantToDrive(false);
				}
			}
			return (byte)num != 0;
		}
	}
	[HarmonyPatch(typeof(Boat), "TrySetDriver")]
	internal static class BoatEquipmentDriverGuard
	{
		private static bool Prefix(Boat __instance, Player newDriver)
		{
			if (Object.op_Implicit((Object)(object)newDriver))
			{
				return BoatEquipment.Current?.AllowDriving(__instance) ?? true;
			}
			return true;
		}
	}
	[HarmonyPatch(typeof(Boat), "ServerSetInput")]
	internal static class BoatEquipmentThrottleGuard
	{
		private static void Prefix(Boat __instance, ref half x, ref half y)
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			if (BoatEquipment.Current?.DrivingBlockReason(__instance) != null)
			{
				x = (y = (half)0f);
			}
		}
	}
	public readonly struct IslandRouteAccess
	{
		public bool Completed { get; }

		public bool? PreservedContinuationAccess { get; }

		public bool? IntroductionPreservedAccess { get; }

		public IslandRouteAccess(bool completed, bool? preservedContinuationAccess = null, bool? introductionPreservedAccess = null)
		{
			Completed = completed;
			PreservedContinuationAccess = preservedContinuationAccess;
			IntroductionPreservedAccess = introductionPreservedAccess;
		}
	}
	public static class IslandRoutePolicy
	{
		public static bool PreservesNativeAccess(int nativeOrdinal, MountedPackState selectedSave)
		{
			if (nativeOrdinal < 1 || nativeOrdinal > 5)
			{
				throw new ArgumentOutOfRangeException("nativeOrdinal");
			}
			if (selectedSave == null)
			{
				throw new ArgumentNullException("selectedSave");
			}
			if (selectedSave.SaveSelectionInitialized)
			{
				return selectedSave.NativeUnlockedAtSelection >= nativeOrdinal;
			}
			return false;
		}

		public static bool CanVisitNative(int nativeOrdinal, int nativeUnlockedCount, MountedPackState selectedSave, IEnumerable<bool> insertedIslandsCompleted)
		{
			if (insertedIslandsCompleted == null)
			{
				throw new ArgumentNullException("insertedIslandsCompleted");
			}
			return CanVisitNative(nativeOrdinal, nativeUnlockedCount, selectedSave, insertedIslandsCompleted.Select((bool completed) => new IslandRouteAccess(completed)));
		}

		public static bool RouteSatisfied(IslandRouteAccess interlude, bool globalPreserved)
		{
			if (!interlude.Completed)
			{
				if (interlude.IntroductionPreservedAccess ?? globalPreserved)
				{
					return interlude.PreservedContinuationAccess != false;
				}
				return false;
			}
			return true;
		}

		public static bool CanVisitNative(int nativeOrdinal, int nativeUnlockedCount, MountedPackState selectedSave, IEnumerable<IslandRouteAccess> insertedIslands)
		{
			bool preserved = PreservesNativeAccess(nativeOrdinal, selectedSave);
			if (insertedIslands == null)
			{
				throw new ArgumentNullException("insertedIslands");
			}
			IslandRouteAccess[] source = insertedIslands.ToArray();
			if (preserved || source.Any((IslandRouteAccess interlude) => interlude.IntroductionPreservedAccess == true) || nativeUnlockedCount >= nativeOrdinal)
			{
				return source.All((IslandRouteAccess interlude) => RouteSatisfied(interlude, preserved));
			}
			return false;
		}

		public static bool CanVisitCustom(int minimumNativeUnlockCount, int nativeUnlockedCount, int? beforeNativeOrdinal, MountedPackState selectedSave, IEnumerable<bool> precedingIslandsCompleted)
		{
			if (precedingIslandsCompleted == null)
			{
				throw new ArgumentNullException("precedingIslandsCompleted");
			}
			return CanVisitCustom(minimumNativeUnlockCount, nativeUnlockedCount, beforeNativeOrdinal, selectedSave, precedingIslandsCompleted.Select((bool completed) => new IslandRouteAccess(completed)));
		}

		public static bool CanVisitCustom(int minimumNativeUnlockCount, int nativeUnlockedCount, int? beforeNativeOrdinal, MountedPackState selectedSave, IEnumerable<IslandRouteAccess> precedingIslands)
		{
			if (minimumNativeUnlockCount < 0 || minimumNativeUnlockCount > 5)
			{
				throw new ArgumentOutOfRangeException("minimumNativeUnlockCount");
			}
			if (selectedSave == null || precedingIslands == null)
			{
				throw new ArgumentNullException("selectedSave");
			}
			if (nativeUnlockedCount < minimumNativeUnlockCount)
			{
				return false;
			}
			if (!beforeNativeOrdinal.HasValue)
			{
				return true;
			}
			int value = beforeNativeOrdinal.Value;
			bool preserved = PreservesNativeAccess(value, selectedSave);
			if (value < 2)
			{
				throw new ArgumentException("An interlude must follow an existing mandatory native stage.");
			}
			if (nativeUnlockedCount >= value - 1)
			{
				return precedingIslands.All((IslandRouteAccess interlude) => RouteSatisfied(interlude, preserved));
			}
			return false;
		}
	}
	public readonly struct MountPose
	{
		public Vector3 Position { get; }

		public Quaternion Rotation { get; }

		public MountPose(Vector3 position, Quaternion rotation)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			Position = position;
			Rotation = rotation;
		}
	}
	public static class MountPlacement
	{
		public static MountPose Resolve(Transform root, PlacementRecipe placement)
		{
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d2: 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_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: Unknown result type (might be due to invalid IL or missing references)
			//IL_010b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0110: Unknown result type (might be due to invalid IL or missing references)
			//IL_0116: Unknown result type (might be due to invalid IL or missing references)
			//IL_011b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0130: Unknown result type (might be due to invalid IL or missing references)
			//IL_0135: Unknown result type (might be due to invalid IL or missing references)
			if (!Object.op_Implicit((Object)(object)root) || placement == null)
			{
				throw new ArgumentException("A live root and placement are required.");
			}
			Transform val = root;
			if (!string.IsNullOrWhiteSpace(placement.Marker))
			{
				string marker = placement.Marker;
				Transform[] array = (from candidate in ((Component)root).GetComponentsInChildren<Transform>(true)
					where (marker.IndexOf('/') < 0) ? (((Object)candidate).name == marker) : (RelativePath(root, candidate) == marker)
					select candidate).ToArray();
				if (array.Length != 1)
				{
					throw new InvalidOperationException($"Mount marker '{marker}' under '{((Object)root).name}' must resolve exactly once; found {array.Length}.");
				}
				val = array[0];
			}
			else if (placement.Pose == null)
			{
				throw new InvalidOperationException("A placement needs an explicit local pose or an unambiguous marker.");
			}
			PoseRecipe val2 = (PoseRecipe)(((object)placement.Pose) ?? ((object)new PoseRecipe()));
			Vector3 val3 = Vector(val2.Position);
			if (!WorldFields.Finite(val3) || !WorldFields.Finite(val2.Yaw))
			{
				throw new InvalidOperationException("Mount coordinates and yaw must be finite.");
			}
			return new MountPose(root.InverseTransformPoint(val.TransformPoint(val3)), Quaternion.Inverse(root.rotation) * val.rotation * Quaternion.Euler(0f, val2.Yaw, 0f));
		}

		public static void Apply(Transform target, Transform root, PlacementRecipe placement)
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			MountPose mountPose = Resolve(root, placement);
			target.SetParent(root, false);
			target.localPosition = mountPose.Position;
			target.localRotation = mountPose.Rotation;
		}

		internal static Vector3 Vector(VectorRecipe value)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			if (value != null)
			{
				return new Vector3(value.X, value.Y, value.Z);
			}
			throw new ArgumentException("Missing position.");
		}

		private static string RelativePath(Transform root, Transform target)
		{
			if ((Object)(object)target == (Object)(object)root)
			{
				return "";
			}
			if (!((Object)(object)target.parent == (Object)(object)root))
			{
				return RelativePath(root, target.parent) + "/" + ((Object)target).name;
			}
			return ((Object)target).name;
		}
	}
	internal static class MountReferences
	{
		internal static string Key(string pack, string key)
		{
			return pack + ":" + key;
		}

		internal static string Qualify(string pack, string reference)
		{
			if (reference.IndexOf(':') >= 0)
			{
				return reference;
			}
			return Key(pack, reference);
		}

		internal static byte NativeId(string reference)
		{
			if (!reference.StartsWith("native:", StringComparison.Ordinal) || !byte.TryParse(reference.Substring(7), NumberStyles.None, CultureInfo.InvariantCulture, out var result))
			{
				throw new ArgumentException("Expected native:<numeric-id>, got " + reference);
			}
			return result;
		}
	}
	public static class NativePlayerGeometry
	{
		public static float StandingClearance()
		{
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: 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_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			Player playerPrefab = GameInfo.PlayerPrefab;
			CapsuleCollider val = NativeAccess.Get<CapsuleCollider>(playerPrefab.Movement, "_col");
			SphereCollider val2 = NativeAccess.Get<SphereCollider>(playerPrefab.Movement, "_footCol");
			if (!Object.op_Implicit((Object)(object)val) || !Object.op_Implicit((Object)(object)val2) || val.direction != 1)
			{
				throw new InvalidOperationException("The native player's vertical collision geometry is unavailable.");
			}
			float y = ((Component)playerPrefab).transform.InverseTransformPoint(((Component)val).transform.TransformPoint(val.center - Vector3.up * val.height * 0.5f)).y;
			float y2 = ((Component)playerPrefab).transform.InverseTransformPoint(((Component)val2).transform.TransformPoint(val2.center - Vector3.up * val2.radius)).y;
			float num = 0f - Math.Min(y, y2) + 0.15f;
			if (!NativeAccess.Finite(num) || num < 0.2f || num > 3f)
			{
				throw new InvalidOperationException("The native player clearance is outside supported bounds.");
			}
			return num;
		}
	}
	public sealed class NativeTemplateCache : IDisposable
	{
		private readonly GameObject nursery;

		private readonly Dictionary<int, List<Purchasable>> shops = new Dictionary<int, List<Purchasable>>();

		private readonly Dictionary<string, NPC> npcs = new Dictionary<string, NPC>(StringComparer.Ordinal);

		private readonly List<NPCQuest> quests = new List<NPCQuest>();

		private readonly Func<bool> connected;

		private readonly Func<GameObject, Transform, GameObject> cloneOwned;

		private bool disposed;

		private bool preparing;

		private int capturingBuildIndex;

		public bool Ready { get; private set; }

		public Exception? Failure { get; private set; }

		public Boat BoatPrefab { get; private set; }

		public QuestInteractable RewardHand { get; private set; }

		public IReadOnlyList<NPCQuest> QuestTemplates => quests.AsReadOnly();

		public NativeTemplateCache(Func<bool> isConnected, Func<GameObject, Transform, GameObject> cloneSceneObject)
		{
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Expected O, but got Unknown
			connected = isConnected ?? throw new ArgumentNullException("isConnected");
			cloneOwned = cloneSceneObject ?? throw new ArgumentNullException("cloneSceneObject");
			nursery = new GameObject("ExpansionKit inactive native templates");
			nursery.SetActive(false);
			nursery.AddComponent<NativeTemplateRoot>();
			((Object)nursery).hideFlags = (HideFlags)52;
			Object.DontDestroyOnLoad((Object)(object)nursery);
		}

		public IEnumerator Prepare()
		{
			ThrowIfDisposed();
			if (Ready)
			{
				yield break;
			}
			if (preparing)
			{
				throw new InvalidOperationException("Template preparation is already running; wait for Ready.");
			}
			if (Failure != null)
			{
				throw new InvalidOperationException("Template preparation previously failed.", Failure);
			}
			if (connected())
			{
				throw new InvalidOperationException("Capture native templates before starting any network connection.");
			}
			Patches patchInfo = Harmony.GetPatchInfo((MethodBase)AccessTools.Method(typeof(NPC), "OnDestroy", (Type[])null, (Type[])null));
			if (patchInfo == null || !patchInfo.Prefixes.Any((Patch patch) => patch.PatchMethod.DeclaringType == typeof(MountedNpcDestroy)))
			{
				throw new InvalidOperationException("Install the runtime assembly's Harmony patches before native template preparation.");
			}
			preparing = true;
			try
			{
				while (IslandManager.IsLoading)
				{
					yield return null;
				}
				for (int build = 1; build <= 5; build++)
				{
					if (connected())
					{
						throw new InvalidOperationException("A network connection started during template capture.");
					}
					Scene sceneByBuildIndex = SceneManager.GetSceneByBuildIndex(build);
					if (((Scene)(ref sceneByBuildIndex)).IsValid() && ((Scene)(ref sceneByBuildIndex)).isLoaded)
					{
						throw new InvalidOperationException("Native template scene is already loaded: " + build);
					}
					capturingBuildIndex = build;
					yield return SceneManager.LoadSceneAsync(build, (LoadSceneMode)1);
					sceneByBuildIndex = SceneManager.GetSceneByBuildIndex(build);
					try
					{
						if (connected())
						{
							throw new InvalidOperationException("A network connection started during template capture.");
						}
						Capture(sceneByBuildIndex, build);
					}
					catch (Exception failure)
					{
						Failure = failure;
					}
					if (((Scene)(ref sceneByBuildIndex)).IsValid() && ((Scene)(ref sceneByBuildIndex)).isLoaded)
					{
						yield return SceneManager.UnloadSceneAsync(sceneByBuildIndex);
					}
					capturingBuildIndex = 0;
					if (Failure != null)
					{
						throw new InvalidOperationException("Native template capture failed.", Failure);
					}
				}
				if (!Object.op_Implicit((Object)(object)BoatPrefab) || !Object.op_Implicit((Object)(object)RewardHand))
				{
					throw new InvalidOperationException("Native boat and held-reward templates were not found.");
				}
				Ready = true;
			}
			finally
			{
				NativeTemplateCache nativeTemplateCache = this;
				nativeTemplateCache.preparing = false;
				if (!nativeTemplateCache.Ready && nativeTemplateCache.Failure == null)
				{
					nativeTemplateCache.Failure = new InvalidOperationException("Native template preparation was interrupted.");
				}
				if (nativeTemplateCache.capturingBuildIndex != 0)
				{
					Scene sceneByBuildIndex2 = SceneManager.GetSceneByBuildIndex(nativeTemplateCache.capturingBuildIndex);
					if (((Scene)(ref sceneByBuildIndex2)).IsValid() && ((Scene)(ref sceneByBuildIndex2)).isLoaded)
					{
						SceneManager.UnloadSceneAsync(sceneByBuildIndex2);
					}
					nativeTemplateCache.capturingBuildIndex = 0;
				}
			}
		}

		private void Capture(Scene scene, int island)
		{
			//IL_0239: Unknown result type (might be due to invalid IL or missing references)
			//IL_0249: Unknown result type (might be due to invalid IL or missing references)
			//IL_0260: Unknown result type (might be due to invalid IL or missing references)
			if (!((Scene)(ref scene)).IsValid() || !((Scene)(ref scene)).isLoaded)
			{
				throw new InvalidOperationException("Missing native template scene.");
			}
			GameObject[] rootGameObjects = ((Scene)(ref scene)).GetRootGameObjects();
			if (!Object.op_Implicit((Object)(object)BoatPrefab))
			{
				SpawnManager instance = rootGameObjects.SelectMany((GameObject root) => root.GetComponentsInChildren<SpawnManager>(true)).Single();
				BoatPrefab = WorldFields.Get<Boat>(instance, "_boatPrefab");
			}
			List<Purchasable> list = new List<Purchasable>();
			shops.Add(island, list);
			foreach (Purchasable item in rootGameObjects.SelectMany((GameObject root) => root.GetComponentsInChildren<Purchasable>(true)).OrderBy<Purchasable, string>((Purchasable source) => HierarchyPath(((Component)source).transform), StringComparer.Ordinal))
			{
				if (((Component)item).GetComponentsInChildren<Purchasable>(true).Length == 1 && ((Component)item).GetComponentsInChildren<NetworkBehaviour>(true).Length == 0)
				{
					Purchasable component = Clone(((Component)item).gameObject, nursery.transform).GetComponent<Purchasable>();
					((Object)component).name = ((Object)item).name;
					((Component)component).gameObject.SetActive(false);
					list.Add(component);
				}
			}
			foreach (NPC item2 in from source in rootGameObjects.SelectMany((GameObject root) => root.GetComponentsInChildren<NPC>(true))
				where !WorldFields.Get<bool>(source, "_forScreenshot")
				orderby source.ID
				select source)
			{
				string text = NpcKey(island, item2.ID);
				if (npcs.ContainsKey(text))
				{
					throw new InvalidOperationException("Ambiguous native NPC donor " + text);
				}
				GameObject obj = Clone(((Component)item2).gameObject, nursery.transform);
				NPC component2 = obj.GetComponent<NPC>();
				obj.SetActive(false);
				((Object)obj).name = ((Object)item2).name;
				obj.transform.localPosition = Vector3.zero;
				obj.transform.localRotation = Quaternion.identity;
				obj.transform.localScale = ((Component)item2).transform.lossyScale;
				obj.AddComponent<MountedNpc>().BindTemplate(component2);
				WorldFields.Set(component2, "_forScreenshot", true);
				((Behaviour)component2).enabled = false;
				npcs.Add(text, component2);
				List<NPCQuest> list2 = new List<NPCQuest>();
				foreach (NPCQuest quest in item2.Quests)
				{
					if (!Object.op_Implicit((Object)(object)quest))
					{
						throw new InvalidOperationException("Native donor has a missing quest: " + text);
					}
					NPCQuest val = Object.Instantiate<NPCQuest>(quest);
					((Object)val).hideFlags = (HideFlags)52;
					list2.Add(val);
					quests.Add(val);
				}
				WorldFields.Set(component2, "_quests", list2);
				if (!Object.op_Implicit((Object)(object)RewardHand))
				{
					QuestInteractable val2 = WorldFields.Optional<QuestInteractable>(item2, "questInteractable");
					if (val2 != null && (Object)(object)((Component)val2).gameObject != (Object)(object)((Component)item2).gameObject && !Object.op_Implicit((Object)(object)((Component)val2).GetComponentInChildren<NPC>(true)))
					{
						RewardHand = Clone(((Component)val2).gameObject, nursery.transform).GetComponent<QuestInteractable>();
					}
				}
			}
		}

		public NPC GetNpc(byte donorIslandId, byte donorNpcId)
		{
			RequireReady();
			if (!npcs.TryGetValue(NpcKey(donorIslandId, donorNpcId), out NPC value) || !Object.op_Implicit((Object)(object)value))
			{
				throw new InvalidOperationException($"Missing native NPC donor island {donorIslandId}, NPC {donorNpcId}.");
			}
			return value;
		}

		public IReadOnlyList<Purchasable> GetShops(byte donorIslandId)
		{
			RequireReady();
			if (!shops.TryGetValue(donorIslandId, out List<Purchasable> value))
			{
				throw new InvalidOperationException("Missing native shop donor island " + donorIslandId);
			}
			return value.AsReadOnly();
		}

		public void RequireReady()
		{
			ThrowIfDisposed();
			if (!Ready)
			{
				throw new InvalidOperationException("Wait for NativeTemplateCache.Ready before mounting content.", Failure);
			}
		}

		internal GameObject Clone(GameObject source, Transform inactiveParent)
		{
			ThrowIfDisposed();
			if (!Object.op_Implicit((Object)(object)source) || !Object.op_Implicit((Object)(object)inactiveParent) || ((Component)inactiveParent).gameObject.activeInHierarchy)
			{
				throw new ArgumentException("Native clones require a source and an inactive owned parent.");
			}
			GameObject val = cloneOwned(source, inactiveParent);
			if (!Object.op_Implicit((Object)(object)val) || (Object)(object)val == (Object)(object)source || val.activeInHierarchy || (Object)(object)val.transform.parent != (Object)(object)inactiveParent)
			{
				throw new InvalidOperationException("The native clone adapter returned an active, unowned or aliased source.");
			}
			if (val.GetComponentsInChildren<NetworkObject>(true).Any((NetworkObject network) => network.IsSceneObject))
			{
				Object.Destroy((Object)(object)val);
				throw new InvalidOperationException("The native clone adapter retained donor scene/network IDs.");
			}
			val.SetActive(false);
			return val;
		}

		private static string NpcKey(int island, byte npc)
		{
			return island + ":" + npc;
		}

		internal static string HierarchyPath(Transform transform)
		{
			if (!Object.op_Implicit((Object)(object)transform.parent))
			{
				return ((Object)transform).name;
			}
			return HierarchyPath(transform.parent) + "/" + ((Object)transform).name;
		}

		private void ThrowIfDisposed()
		{
			if (disposed)
			{
				throw new ObjectDisposedException("NativeTemplateCache");
			}
		}

		public void Dispose()
		{
			if (disposed)
			{
				return;
			}
			if (preparing)
			{
				throw new InvalidOperationException("Stop and dispose the capture coroutine before disposing its templates.");
			}
			disposed = true;
			Ready = false;
			foreach (NPCQuest quest in quests)
			{
				if (Object.op_Implicit((Object)(object)quest))
				{
					Object.Destroy((Object)(object)quest);
				}
			}
			quests.Clear();
			npcs.Clear();
			shops.Clear();
			if (Object.op_Implicit((Object)(object)nursery))
			{
				Object.Destroy((Object)(object)nursery);
			}
		}
	}
	internal sealed class NativeTemplateRoot : MonoBehaviour
	{
	}
	internal static class WorldFields
	{
		internal static FieldInfo Field(Type type, string name)
		{
			return AccessTools.Field(type, name) ?? throw new MissingFieldException(type.FullName, name);
		}

		internal static T Get<T>(object instance, string name)
		{
			object value = Field(instance.GetType(), name).GetValue(instance);
			if (value is T)
			{
				return (T)value;
			}
			throw new InvalidOperationException("Missing native field value " + instance.GetType().Name + "." + name);
		}

		internal static T? Optional<T>(object instance, string name) where T : class
		{
			return Field(instance.GetType(), name).GetValue(instance) as T;
		}

		internal static void Set(object instance, string name, object value)
		{
			Field(instance.GetType(), name).SetValue(instance, value);
		}

		internal static bool Finite(float value)
		{
			if (!float.IsNaN(value))
			{
				return !float.IsInfinity(value);
			}
			return false;
		}

		internal static bool Finite(Vector3 value)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			if (Finite(value.x) && Finite(value.y))
			{
				return Finite(value.z);
			}
			return false;
		}
	}
	public sealed class PackControl : Interactable
	{
		private Func<Player, string>? label;

		private Action<Player>? pressed;

		private Func<Player, bool>? available;

		private string shown = "";

		private bool registered;

		public static PackControl Create(Transform parent, string name, Vector3 position, Vector3 size, GameObject[] outlines, Func<Player, string> label, Action<Player> pressed, Func<Player, bool>? available = null)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: 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_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Expected O, but got Unknown
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d0: Expected O, but got Unknown
			//IL_00e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ff: Unknown result type (might be due to invalid IL or missing references)
			if (!Object.op_Implicit((Object)(object)parent) || ((Component)parent).gameObject.activeInHierarchy || !NativeAccess.Positive(size.x) || !NativeAccess.Positive(size.y) || !NativeAccess.Positive(size.z) || !NativeAccess.Finite(position.x) || !NativeAccess.Finite(position.y) || !NativeAccess.Finite(position.z))
			{
				throw new ArgumentException("Create controls under an inactive owned root with positive bounds.");
			}
			GameObject val = new GameObject(name);
			val.SetActive(false);
			val.transform.SetParent(parent, false);
			val.transform.localPosition = position;
			val.layer = LayerMask.NameToLayer("Interactable");
			val.tag = "Interactable";
			BoxCollider val2 = val.AddComponent<BoxCollider>();
			val2.size = size;
			((Collider)val2).isTrigger = true;
			GameObject val3 = new GameObject("Label");
			val3.transform.SetParent(val.transform, false);
			val3.transform.localPosition = Vector3.up * (size.y * 0.5f + 0.15f);
			PackControl packControl = val.AddComponent<PackControl>();
			packControl.label = label ?? throw new ArgumentNullException("labe