Decompiled source of ExpansionKit v0.7.2
BepInEx/plugins/ExpansionKit/HowToFish.ExpansionKit.dll
Decompiled 15 hours ago
The result has been truncated due to the large size, download it to view full contents!
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
The result has been truncated due to the large size, download it to view full contents!
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