using System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Threading; using BepInEx; using BepInEx.Logging; using FishNet; using FishNet.Managing; using FishNet.Managing.Object; using FishNet.Object; using HarmonyLib; using HowToFish.ItemSDK; using Microsoft.CodeAnalysis; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.UI; [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("HTFMoreItemAPI")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Runtime custom item registration API for How to Fish")] [assembly: AssemblyFileVersion("2.0.0.0")] [assembly: AssemblyInformationalVersion("2.0.0+4cf7ec4204765620bc4bfcfabac8a7b944cfe3f3")] [assembly: AssemblyProduct("HTFMoreItemAPI")] [assembly: AssemblyTitle("HTFMoreItemAPI")] [assembly: AssemblyVersion("2.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace HowToFish.ItemAPI { public sealed class GeneratedItemRegistration { public string ModGuid { get; set; } public string InternalName { get; set; } public GameObject ContentPrefab { get; set; } public InventoryIcon InventoryIcon { get; set; } public HTFItemHoldPoseAsset HoldPose { get; set; } public string DisplayName { get; set; } public float PickupRadius { get; set; } = 0.6f; public float Buoyancy { get; set; } public ushort NetworkCollectionId { get; set; } public static GeneratedItemRegistration FromDefinition(HTFItemDefinitionAsset definition, string modGuid) { if (!Object.op_Implicit((Object)(object)definition)) { throw new ArgumentNullException("definition"); } string message = default(string); if (!definition.Validate(ref message)) { throw new ArgumentException(message, "definition"); } return new GeneratedItemRegistration { ModGuid = modGuid, InternalName = definition.InternalName, ContentPrefab = definition.ContentPrefab, InventoryIcon = InventoryIcon.FromTexture((Texture)(object)definition.InventoryIcon), HoldPose = definition.HoldPose, DisplayName = definition.DisplayName, PickupRadius = definition.PickupRadius, Buoyancy = definition.Buoyancy }; } } internal static class GeneratedPrefabFactory { private const byte GenericTemplateItemId = 55; private static readonly Guid SupportedGameMvid = new Guid("48815c0b-02c6-4024-a20a-1cb6cbfee2ec"); private static readonly FieldInfo ItemIdField = AccessTools.Field(typeof(Item), "_id"); private static readonly FieldInfo WorldCollidersField = AccessTools.Field(typeof(Item), "_worldColliders"); private static readonly FieldInfo PickupColliderField = AccessTools.Field(typeof(Item), "_pickUpCollider"); private static readonly FieldInfo RenderersField = AccessTools.Field(typeof(Item), "_renderers"); private static readonly FieldInfo SkinRenderersField = AccessTools.Field(typeof(Item), "_skinRenderers"); private static readonly FieldInfo MeshField = AccessTools.Field(typeof(Item), "_mesh"); private static readonly FieldInfo WeightField = AccessTools.Field(typeof(Item), "_weight"); private static readonly FieldInfo BuoyancyField = AccessTools.Field(typeof(Item), "_buoyancy"); private static readonly FieldInfo HeldPositionField = AccessTools.Field(typeof(Item), "_heldPos"); private static readonly FieldInfo HeldRotationField = AccessTools.Field(typeof(Item), "_heldRot"); private static readonly FieldInfo DrawStartPositionField = AccessTools.Field(typeof(Item), "_drawAnimStartPos"); private static readonly FieldInfo DrawStartRotationField = AccessTools.Field(typeof(Item), "_drawAnimStartRot"); private static readonly FieldInfo RightHandTransformsField = AccessTools.Field(typeof(Item), "_handTransformsRight"); private static readonly FieldInfo LeftHandTransformsField = AccessTools.Field(typeof(Item), "_handTransformsLeft"); private static readonly FieldInfo HoldBobMultiplierField = AccessTools.Field(typeof(Item), "_holdBobMulti"); private static readonly FieldInfo OverridePlayerVelocityField = AccessTools.Field(typeof(Item), "_overridePlayerVelAmount"); private static readonly FieldInfo PlayerVelocityAmountField = AccessTools.Field(typeof(Item), "_playerVelAmount"); private static GameObject _runtimeRoot; internal static bool ValidateSource(GeneratedItemRegistration registration, out string error) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) error = null; if (!Object.op_Implicit((Object)(object)registration.ContentPrefab)) { error = "ContentPrefab cannot be null."; return false; } Scene scene = registration.ContentPrefab.scene; if (((Scene)(ref scene)).IsValid()) { error = "ContentPrefab must be a prefab asset returned by AssetBundle.LoadAsset()."; return false; } if (registration.InventoryIcon == null || !Object.op_Implicit((Object)(object)registration.InventoryIcon.Texture)) { error = "A generated item must provide a valid icon from its AssetBundle."; return false; } if (registration.PickupRadius <= 0f || float.IsNaN(registration.PickupRadius) || float.IsInfinity(registration.PickupRadius)) { error = "PickupRadius must be a finite value greater than zero."; return false; } if (float.IsNaN(registration.Buoyancy) || float.IsInfinity(registration.Buoyancy)) { error = "Buoyancy must be a finite value."; return false; } if (Object.op_Implicit((Object)(object)registration.HoldPose) && !registration.HoldPose.Validate(ref error)) { error = "HoldPose is invalid: " + error; return false; } if (Object.op_Implicit((Object)(object)registration.ContentPrefab.GetComponentInChildren(true))) { error = "ContentPrefab must not contain Item; the API provides it through the game skeleton."; return false; } if (Object.op_Implicit((Object)(object)registration.ContentPrefab.GetComponentInChildren(true))) { error = "ContentPrefab must not contain RigidbodySync; the API provides it."; return false; } if (Object.op_Implicit((Object)(object)registration.ContentPrefab.GetComponentInChildren(true))) { error = "ContentPrefab must not contain NetworkObject; the API provides it."; return false; } Rigidbody[] componentsInChildren = registration.ContentPrefab.GetComponentsInChildren(true); Rigidbody component = registration.ContentPrefab.GetComponent(); if (componentsInChildren.Length != 1 || !Object.op_Implicit((Object)(object)component) || (Object)(object)componentsInChildren[0] != (Object)(object)component) { error = $"The ContentPrefab root must contain exactly one Rigidbody; the hierarchy currently contains {componentsInChildren.Length}."; return false; } if (registration.ContentPrefab.GetComponentsInChildren(true).Length == 0) { error = "ContentPrefab must contain at least one Collider."; return false; } return true; } internal static bool TryCreate(string key, byte itemId, GeneratedItemRegistration registration, out Item item, out NetworkObject networkObject, out string error) { //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Unknown result type (might be due to invalid IL or missing references) //IL_01de: Unknown result type (might be due to invalid IL or missing references) item = null; networkObject = null; error = null; Guid moduleVersionId = typeof(Item).Assembly.ManifestModule.ModuleVersionId; if (moduleVersionId != SupportedGameMvid) { error = "The current Assembly-CSharp is not supported by the generation pipeline; " + $"MVID={moduleVersionId}, Supported={SupportedGameMvid}."; return false; } if (!ValidateSource(registration, out error)) { return false; } if (!ValidateReflection(out error)) { return false; } Item spawnable = GameInfo.GetSpawnable((byte)55); if (!ValidateTemplate(spawnable, out error)) { return false; } EnsureRuntimeRoot(); GameObject val = null; try { val = Object.Instantiate(((Component)spawnable).gameObject, _runtimeRoot.transform, false); ((Object)val).name = "HTFMoreItemAPI_" + SanitizeObjectName(key); if (val.activeInHierarchy) { throw new InvalidOperationException("The generation container is unexpectedly active."); } Item component = val.GetComponent(); NetworkObject component2 = val.GetComponent(); Rigidbody component3 = val.GetComponent(); SphereCollider val2 = FindRootPickupCollider(val); if (!Object.op_Implicit((Object)(object)component) || !Object.op_Implicit((Object)(object)component2) || !Object.op_Implicit((Object)(object)component3) || !Object.op_Implicit((Object)(object)val2)) { throw new InvalidOperationException("The cloned base-item skeleton is missing required components."); } RemoveTemplateChildren(val.transform); GameObject val3 = Object.Instantiate(registration.ContentPrefab, val.transform, false); ((Object)val3).name = "Content"; val3.transform.localPosition = Vector3.zero; val3.transform.localRotation = Quaternion.identity; Rigidbody[] componentsInChildren = val3.GetComponentsInChildren(true); if (componentsInChildren.Length != 1) { throw new InvalidOperationException("The number of Rigidbodies changed after cloning the content prefab."); } CopyRigidbodySettings(componentsInChildren[0], component3); Object.DestroyImmediate((Object)(object)componentsInChildren[0]); Collider[] componentsInChildren2 = val3.GetComponentsInChildren(true); if (componentsInChildren2.Length == 0) { throw new InvalidOperationException("The cloned content prefab has no Collider."); } Collider[] array = componentsInChildren2; foreach (Collider obj in array) { obj.isTrigger = false; obj.enabled = true; } ((Collider)val2).isTrigger = true; val2.center = Vector3.zero; val2.radius = registration.PickupRadius; ((Collider)val2).enabled = true; List list = new List(val3.GetComponentsInChildren(true)); List list2 = new List(); foreach (Renderer item2 in list) { if (item2 is SkinnedMeshRenderer) { list2.Add(item2); } } Mesh value = FindFirstMesh(val3); ItemIdField.SetValue(component, itemId); WorldCollidersField.SetValue(component, componentsInChildren2); PickupColliderField.SetValue(component, val2); RenderersField.SetValue(component, list); SkinRenderersField.SetValue(component, list2); MeshField.SetValue(component, value); WeightField.SetValue(component, component3.mass); BuoyancyField.SetValue(component, registration.Buoyancy); if (Object.op_Implicit((Object)(object)registration.HoldPose)) { ApplyHoldPose(component, val.transform, registration.HoldPose); } component2.SetIsSpawnable(true); item = component; networkObject = component2; return true; } catch (Exception ex) { if (Object.op_Implicit((Object)(object)val)) { Object.DestroyImmediate((Object)(object)val); } error = "Failed to generate the item skeleton: " + ex.GetType().Name + ": " + ex.Message; return false; } } internal static void Shutdown() { if (Object.op_Implicit((Object)(object)_runtimeRoot)) { Object.Destroy((Object)(object)_runtimeRoot); } _runtimeRoot = null; } private static bool ValidateReflection(out string error) { error = null; if (ItemIdField != null && WorldCollidersField != null && PickupColliderField != null && RenderersField != null && SkinRenderersField != null && MeshField != null && WeightField != null && BuoyancyField != null && HeldPositionField != null && HeldRotationField != null && DrawStartPositionField != null && DrawStartRotationField != null && RightHandTransformsField != null && LeftHandTransformsField != null && HoldBobMultiplierField != null && OverridePlayerVelocityField != null && PlayerVelocityAmountField != null) { return true; } error = "The current game version is missing Item serialization fields required by the generation pipeline."; return false; } private static bool ValidateTemplate(Item template, out string error) { error = null; if (!Object.op_Implicit((Object)(object)template)) { error = $"The original generic item template ItemId={(byte)55} was not found."; return false; } if (((object)template).GetType() != typeof(Item)) { error = "The generic template must be exactly Item, but was " + ((object)template).GetType().FullName + "."; return false; } if (!Object.op_Implicit((Object)(object)((Component)template).GetComponent()) || !Object.op_Implicit((Object)(object)((Component)template).GetComponent()) || !Object.op_Implicit((Object)(object)((Component)template).GetComponent())) { error = "The original generic item template is missing NetworkObject, RigidbodySync, or Rigidbody."; return false; } NetworkBehaviour[] components = ((Component)template).GetComponents(); if (components.Length != 2) { error = $"The original generic template must contain 2 NetworkBehaviours, but contains {components.Length}."; return false; } if ((Object)(object)FindRootPickupCollider(((Component)template).gameObject) == (Object)null) { error = "The original generic item template is missing its root pickup SphereCollider."; return false; } return true; } private static void EnsureRuntimeRoot() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Expected O, but got Unknown if (!Object.op_Implicit((Object)(object)_runtimeRoot)) { _runtimeRoot = new GameObject("HTFMoreItemAPI.RuntimePrefabs"); ((Object)_runtimeRoot).hideFlags = (HideFlags)61; Object.DontDestroyOnLoad((Object)(object)_runtimeRoot); _runtimeRoot.SetActive(false); } } private static SphereCollider FindRootPickupCollider(GameObject root) { SphereCollider[] components = root.GetComponents(); foreach (SphereCollider val in components) { if (((Collider)val).isTrigger) { return val; } } return null; } private static void RemoveTemplateChildren(Transform root) { for (int num = root.childCount - 1; num >= 0; num--) { Object.DestroyImmediate((Object)(object)((Component)root.GetChild(num)).gameObject); } } private static void CopyRigidbodySettings(Rigidbody source, Rigidbody target) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_004a: 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_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) target.mass = source.mass; target.linearDamping = source.linearDamping; target.angularDamping = source.angularDamping; target.useGravity = source.useGravity; target.isKinematic = source.isKinematic; target.interpolation = source.interpolation; target.collisionDetectionMode = source.collisionDetectionMode; target.constraints = source.constraints; target.detectCollisions = source.detectCollisions; target.maxAngularVelocity = source.maxAngularVelocity; target.maxLinearVelocity = source.maxLinearVelocity; target.automaticCenterOfMass = source.automaticCenterOfMass; if (!source.automaticCenterOfMass) { target.centerOfMass = source.centerOfMass; } target.automaticInertiaTensor = source.automaticInertiaTensor; if (!source.automaticInertiaTensor) { target.inertiaTensor = source.inertiaTensor; target.inertiaTensorRotation = source.inertiaTensorRotation; } } private static void ApplyHoldPose(Item item, Transform itemRoot, HTFItemHoldPoseAsset pose) { //IL_0007: 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_0033: 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) HeldPositionField.SetValue(item, pose.HeldPosition); HeldRotationField.SetValue(item, pose.HeldRotation); DrawStartPositionField.SetValue(item, pose.DrawStartPosition); DrawStartRotationField.SetValue(item, pose.DrawStartRotation); RightHandTransformsField.SetValue(item, CreateGameHandPose(pose.RightHand, itemRoot)); LeftHandTransformsField.SetValue(item, CreateGameHandPose(pose.LeftHand, itemRoot)); HoldBobMultiplierField.SetValue(item, pose.HoldBobMultiplier); OverridePlayerVelocityField.SetValue(item, pose.OverridePlayerVelocityAmount); PlayerVelocityAmountField.SetValue(item, pose.PlayerVelocityAmount); } private static HandTransforms CreateGameHandPose(HTFHandPoseData source, Transform itemRoot) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Expected O, but got Unknown //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Expected O, but got Unknown if (source != null && source.Enabled) { Quaternion[] fingerLocalRotations = source.FingerLocalRotations; if (fingerLocalRotations == null || fingerLocalRotations.Length != 15) { throw new InvalidOperationException($"An enabled hand pose must contain {15} finger-joint rotations."); } Quaternion[] array = (Quaternion[])(object)new Quaternion[fingerLocalRotations.Length]; Array.Copy(fingerLocalRotations, array, array.Length); return new HandTransforms(true, source.HandPosition, source.HandRotation, array, itemRoot); } return new HandTransforms(false); } private static Mesh FindFirstMesh(GameObject content) { MeshFilter[] componentsInChildren = content.GetComponentsInChildren(true); foreach (MeshFilter val in componentsInChildren) { if (Object.op_Implicit((Object)(object)val.sharedMesh)) { return val.sharedMesh; } } SkinnedMeshRenderer[] componentsInChildren2 = content.GetComponentsInChildren(true); foreach (SkinnedMeshRenderer val2 in componentsInChildren2) { if (Object.op_Implicit((Object)(object)val2.sharedMesh)) { return val2.sharedMesh; } } return null; } private static string SanitizeObjectName(string value) { return value.Replace(':', '_').Replace('/', '_').Replace('\\', '_'); } } public sealed class InventoryIcon { public Texture Texture { get; } public Rect UvRect { get; } public Color Tint { get; } public InventoryIcon(Texture texture) : this(texture, new Rect(0f, 0f, 1f, 1f), Color.white) { }//IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) public InventoryIcon(Texture texture, Rect uvRect, Color tint) { //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_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) if (!Object.op_Implicit((Object)(object)texture)) { throw new ArgumentNullException("texture"); } Texture = texture; UvRect = uvRect; Tint = tint; } public static InventoryIcon FromTexture(Texture texture) { return new InventoryIcon(texture); } public static InventoryIcon FromSprite(Sprite sprite) { //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_0065: 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) if (!Object.op_Implicit((Object)(object)sprite)) { throw new ArgumentNullException("sprite"); } Texture2D texture = sprite.texture; Rect textureRect = sprite.textureRect; Rect uvRect = default(Rect); ((Rect)(ref uvRect))..ctor(((Rect)(ref textureRect)).x / (float)((Texture)texture).width, ((Rect)(ref textureRect)).y / (float)((Texture)texture).height, ((Rect)(ref textureRect)).width / (float)((Texture)texture).width, ((Rect)(ref textureRect)).height / (float)((Texture)texture).height); return new InventoryIcon((Texture)(object)texture, uvRect, Color.white); } } public static class ItemApiMetadata { public const string PluginGuid = "xiaohai.HTF.MoreItemAPI"; public const string PluginName = "HTF MoreItemAPI"; public const string PluginVersion = "2.0.0"; } [HarmonyPatch(typeof(GameInfo), "Awake")] internal static class GameInfoAwakePatch { [HarmonyPostfix] private static void Postfix() { RegistryRuntime.OnGameInfoAwakeCompleted(); } } [HarmonyPatch(typeof(Item), "GetName")] internal static class CustomItemNamePatch { [HarmonyPostfix] private static void Postfix(Item __instance, ref string __result) { if (Object.op_Implicit((Object)(object)__instance) && RegistryRuntime.TryGetDisplayName(__instance.ID, out var displayName)) { __result = displayName; } } } [HarmonyPatch(typeof(InventorySlot), "SetItem")] internal static class InventorySlotIconPatch { private sealed class NativeImageState { internal Texture Texture; internal Rect UvRect; internal Color Color; internal bool Enabled; } private static readonly FieldInfo FilterField = AccessTools.Field(typeof(InventorySlot), "_filter"); private static readonly FieldInfo ImageField = AccessTools.Field(typeof(InventorySlot), "_itemImage"); private static readonly Dictionary NativeStates = new Dictionary(); [HarmonyPostfix] private static void Postfix(InventorySlot __instance, Item item) { //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_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: 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_0113: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance == (Object)null || ImageField == null) { return; } object? value = ImageField.GetValue(__instance); RawImage val = (RawImage)((value is RawImage) ? value : null); if (!Object.op_Implicit((Object)(object)val)) { return; } if (!NativeStates.TryGetValue(val, out var value2)) { value2 = new NativeImageState { Texture = val.texture, UvRect = val.uvRect, Color = ((Graphic)val).color, Enabled = ((Behaviour)val).enabled }; NativeStates.Add(val, value2); } if (!Object.op_Implicit((Object)(object)item) || !RegistryRuntime.TryGetIcon(item.ID, out var icon)) { val.texture = (Texture)(Object.op_Implicit((Object)(object)value2.Texture) ? ((object)value2.Texture) : ((object)GameInfo.InventoryTexture)); val.uvRect = value2.UvRect; ((Graphic)val).color = value2.Color; ((Behaviour)val).enabled = value2.Enabled; return; } object? obj = FilterField?.GetValue(__instance); MeshFilter val2 = (MeshFilter)((obj is MeshFilter) ? obj : null); if (Object.op_Implicit((Object)(object)val2)) { val2.mesh = null; } val.texture = icon.Texture; val.uvRect = icon.UvRect; ((Graphic)val).color = icon.Tint; ((Behaviour)val).enabled = true; ((Component)val).transform.localScale = Vector3.one; } } [BepInPlugin("xiaohai.HTF.MoreItemAPI", "HTF MoreItemAPI", "2.0.0")] public sealed class ItemApiPlugin : BaseUnityPlugin { private Harmony _harmony; private void Awake() { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Expected O, but got Unknown RegistryRuntime.Initialize(((BaseUnityPlugin)this).Logger); _harmony = new Harmony("xiaohai.HTF.MoreItemAPI"); _harmony.PatchAll(typeof(ItemApiPlugin).Assembly); ((BaseUnityPlugin)this).Logger.LogInfo((object)"HTF MoreItemAPI v2.0.0 loaded."); } private void Update() { RegistryRuntime.Tick(); } private void OnDestroy() { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } GeneratedPrefabFactory.Shutdown(); RegistryRuntime.Shutdown(); } } internal static class ItemIdAllocator { internal const int SlotCount = 256; internal static Dictionary Allocate(IEnumerable keys, IEnumerable occupiedIds) { if (keys == null) { throw new ArgumentNullException("keys"); } HashSet hashSet = ((occupiedIds == null) ? new HashSet() : new HashSet(occupiedIds)); hashSet.Add(byte.MaxValue); List list = new List(); foreach (string key in keys) { if (string.IsNullOrWhiteSpace(key)) { throw new ArgumentException("Allocation keys cannot be null or empty.", "keys"); } list.Add(key); } list.Sort(StringComparer.Ordinal); if (list.Count > 256 - hashSet.Count) { throw new InvalidOperationException($"No free Item ID slots remain: requested={list.Count}, free={256 - hashSet.Count}."); } Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (string item in list) { if (dictionary.ContainsKey(item)) { throw new InvalidOperationException("Duplicate item registration key '" + item + "'."); } byte b = DerivePreferredId(item); bool flag = false; for (int i = 0; i < 256; i++) { byte b2 = (byte)(b + i); if (hashSet.Add(b2)) { dictionary.Add(item, b2); flag = true; break; } } if (!flag) { throw new InvalidOperationException("No free Item ID slots remain."); } } return dictionary; } internal static byte DerivePreferredId(string key) { uint num = 2166136261u; byte[] bytes = Encoding.UTF8.GetBytes(key); foreach (byte b in bytes) { num ^= b; num *= 16777619; } return (byte)num; } } public sealed class ItemRegistration { public string ModGuid { get; set; } public string InternalName { get; set; } public GameObject Prefab { get; set; } public string DisplayName { get; set; } public InventoryIcon InventoryIcon { get; set; } public ushort NetworkCollectionId { get; set; } } public enum ItemRegistrationState { Rejected, PendingGameRegistry, PendingNetworkManager, Registered, Failed } public sealed class ItemRegistrationHandle { public string Key { get; } public byte? ItemId { get; internal set; } public bool HasAssignedItemId => ItemId.HasValue; public ushort NetworkCollectionId { get; } public Item ItemPrefab { get; internal set; } public ItemRegistrationState State { get; internal set; } public string Message { get; internal set; } public bool IsRegistered => State == ItemRegistrationState.Registered; internal ItemRegistrationHandle(string key, ushort networkCollectionId, Item itemPrefab, ItemRegistrationState state, string message) { Key = key; NetworkCollectionId = networkCollectionId; ItemPrefab = itemPrefab; State = state; Message = message; } public override string ToString() { string text = (ItemId.HasValue ? ItemId.Value.ToString() : ""); return $"{Key} (ItemId={text}, CollectionId={NetworkCollectionId}, State={State})"; } } public static class ItemRegistry { public static event Action RegistrationChanged; public static ItemRegistrationHandle Register(ItemRegistration registration) { return RegistryRuntime.Register(registration); } public static ItemRegistrationHandle RegisterGenerated(GeneratedItemRegistration registration) { return RegistryRuntime.RegisterGenerated(registration); } public static bool TryGet(byte itemId, out ItemRegistrationHandle handle) { return RegistryRuntime.TryGet(itemId, out handle); } public static bool TryGet(string modGuid, string internalName, out ItemRegistrationHandle handle) { return RegistryRuntime.TryGet(RegistryRuntime.BuildKey(modGuid, internalName), out handle); } public static IReadOnlyList GetAll() { return RegistryRuntime.GetAllHandles(); } internal static void RaiseRegistrationChanged(ItemRegistrationHandle handle) { Action registrationChanged = ItemRegistry.RegistrationChanged; if (registrationChanged == null) { return; } try { registrationChanged(handle); } catch (Exception exception) { RegistryRuntime.LogSubscriberException(exception); } } } internal static class RegistryRuntime { private sealed class RegistrationRecord { internal string Key; internal string NormalizedName; internal string DisplayName; internal Item ItemPrefab; internal NetworkObject NetworkPrefab; internal InventoryIcon Icon; internal GeneratedItemRegistration GeneratedRegistration; internal ItemRegistrationHandle Handle; internal bool GameRegistryApplied; internal readonly HashSet RegisteredNetworkManagers = new HashSet(); } private static readonly object Sync = new object(); private static readonly Dictionary ByKey = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary ById = new Dictionary(); private static readonly Dictionary ByCollectionId = new Dictionary(); private static readonly FieldInfo ItemIdField = AccessTools.Field(typeof(Item), "_id"); private static readonly FieldInfo GameInfoInstanceField = AccessTools.Field(typeof(GameInfo), "_instance"); private static readonly FieldInfo AllItemsField = AccessTools.Field(typeof(GameInfo), "_allItems"); private static readonly FieldInfo NameToSpawnableField = AccessTools.Field(typeof(GameInfo), "_nameToSpawnable"); private static readonly FieldInfo IdToSpawnableField = AccessTools.Field(typeof(GameInfo), "_idToSpawnable"); private static ManualLogSource _logger; private static int _mainThreadId; private static bool _initialized; private static bool _gameInfoAwakeCompleted; private static float _nextRetryAt; internal static void Initialize(ManualLogSource logger) { _logger = logger; _mainThreadId = Thread.CurrentThread.ManagedThreadId; _initialized = true; _gameInfoAwakeCompleted = GameInfoInstanceField?.GetValue(null) != null; if (ItemIdField == null || GameInfoInstanceField == null || AllItemsField == null || NameToSpawnableField == null || IdToSpawnableField == null) { _logger.LogError((object)"[ItemAPI] The current game version is missing expected Item/GameInfo fields; registration will fail safely."); } } internal static void Shutdown() { _initialized = false; _logger = null; } internal static ItemRegistrationHandle Register(ItemRegistration registration) { //IL_00a3: 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) if (!_initialized) { return Rejected("", 0, null, "The Item API plugin is not initialized. Declare a hard BepInEx dependency."); } if (Thread.CurrentThread.ManagedThreadId != _mainThreadId) { return Rejected("", 0, null, "Register must be called from Unity's main thread."); } if (registration == null) { return Rejected("", 0, null, "registration cannot be null."); } string key; try { key = BuildKey(registration.ModGuid, registration.InternalName); } catch (ArgumentException ex) { return Rejected("", 0, null, ex.Message); } if (!Object.op_Implicit((Object)(object)registration.Prefab)) { return Rejected(key, 0, null, "Prefab cannot be null."); } Scene scene = registration.Prefab.scene; if (((Scene)(ref scene)).IsValid()) { return Rejected(key, 0, null, "Prefab is a scene instance. Pass the prefab asset returned by AssetBundle.LoadAsset() directly."); } Item component = registration.Prefab.GetComponent(); if (!Object.op_Implicit((Object)(object)component)) { return Rejected(key, 0, null, "The prefab root is missing an Item component."); } NetworkObject component2 = registration.Prefab.GetComponent(); if (!Object.op_Implicit((Object)(object)component2)) { return Rejected(key, 0, component, "The prefab root is missing a FishNet NetworkObject component."); } if (ItemIdField == null) { return Rejected(key, 0, component, "Item._id could not be located in the current game version."); } ushort num = ((registration.NetworkCollectionId != 0) ? registration.NetworkCollectionId : DeriveCollectionId(key)); string text = NormalizeName(registration.InternalName); if (text.Length == 0) { return Rejected(key, num, component, "InternalName cannot be empty after normalization."); } lock (Sync) { if (ByKey.TryGetValue(key, out var value)) { if ((Object)(object)value.ItemPrefab == (Object)(object)component) { return value.Handle; } return Rejected(key, num, component, "The registration key is already used by another prefab."); } if (ByCollectionId.TryGetValue(num, out var value2)) { return Rejected(key, num, component, "FishNet collection ID is already used by " + value2.Key + "; explicitly specify another NetworkCollectionId."); } component2.SetIsSpawnable(true); ItemRegistrationHandle itemRegistrationHandle = new ItemRegistrationHandle(key, num, component, ItemRegistrationState.PendingGameRegistry, "Waiting for GameInfo to complete original item registration."); RegistrationRecord value3 = new RegistrationRecord { Key = key, NormalizedName = text, DisplayName = registration.DisplayName, ItemPrefab = component, NetworkPrefab = component2, Icon = registration.InventoryIcon, Handle = itemRegistrationHandle }; ByKey.Add(key, value3); ByCollectionId.Add(num, value3); _logger.LogInfo((object)$"[ItemAPI] Accepted {itemRegistrationHandle}."); } ProcessRegistrations(); return GetHandleByKey(key); } internal static ItemRegistrationHandle RegisterGenerated(GeneratedItemRegistration registration) { if (!_initialized) { return Rejected("", 0, null, "The Item API plugin is not initialized. Declare a hard BepInEx dependency."); } if (Thread.CurrentThread.ManagedThreadId != _mainThreadId) { return Rejected("", 0, null, "RegisterGenerated must be called from Unity's main thread."); } if (registration == null) { return Rejected("", 0, null, "registration cannot be null."); } string key; try { key = BuildKey(registration.ModGuid, registration.InternalName); } catch (ArgumentException ex) { return Rejected("", 0, null, ex.Message); } ushort num = ((registration.NetworkCollectionId != 0) ? registration.NetworkCollectionId : DeriveCollectionId(key)); string text = NormalizeName(registration.InternalName); if (text.Length == 0) { return Rejected(key, num, null, "InternalName cannot be empty after normalization."); } if (!GeneratedPrefabFactory.ValidateSource(registration, out var error)) { return Rejected(key, num, null, error); } lock (Sync) { if (ByKey.TryGetValue(key, out var value)) { if (value.GeneratedRegistration != null && (Object)(object)value.GeneratedRegistration.ContentPrefab == (Object)(object)registration.ContentPrefab) { return value.Handle; } return Rejected(key, num, null, "The registration key is already used by another generated-item definition."); } if (ByCollectionId.TryGetValue(num, out var value2)) { return Rejected(key, num, null, "FishNet collection ID is already used by " + value2.Key + "; explicitly specify another NetworkCollectionId."); } GeneratedItemRegistration generatedItemRegistration = new GeneratedItemRegistration { ModGuid = registration.ModGuid, InternalName = registration.InternalName, ContentPrefab = registration.ContentPrefab, InventoryIcon = registration.InventoryIcon, HoldPose = registration.HoldPose, DisplayName = registration.DisplayName, PickupRadius = registration.PickupRadius, Buoyancy = registration.Buoyancy, NetworkCollectionId = num }; ItemRegistrationHandle itemRegistrationHandle = new ItemRegistrationHandle(key, num, null, ItemRegistrationState.PendingGameRegistry, "Waiting for GameInfo before generating the original item skeleton."); RegistrationRecord value3 = new RegistrationRecord { Key = key, NormalizedName = text, DisplayName = generatedItemRegistration.DisplayName, GeneratedRegistration = generatedItemRegistration, Icon = generatedItemRegistration.InventoryIcon, Handle = itemRegistrationHandle }; ByKey.Add(key, value3); ByCollectionId.Add(num, value3); _logger.LogInfo((object)$"[ItemAPI] Accepted generated item {itemRegistrationHandle}."); } ProcessRegistrations(); return GetHandleByKey(key); } internal static void Tick() { if (_initialized && !(Time.unscaledTime < _nextRetryAt)) { _nextRetryAt = Time.unscaledTime + 0.25f; ProcessRegistrations(); } } internal static void OnGameInfoAwakeCompleted() { _gameInfoAwakeCompleted = true; ProcessRegistrations(); } private static void ProcessRegistrations() { if (!_initialized || Thread.CurrentThread.ManagedThreadId != _mainThreadId) { return; } RegistrationRecord[] array; lock (Sync) { array = new RegistrationRecord[ByKey.Count]; ByKey.Values.CopyTo(array, 0); } if (_gameInfoAwakeCompleted) { AssignPendingItemIds(array); } RegistrationRecord[] array2 = array; foreach (RegistrationRecord registrationRecord in array2) { if (registrationRecord.Handle.State == ItemRegistrationState.Failed || registrationRecord.Handle.State == ItemRegistrationState.Rejected) { continue; } if (!_gameInfoAwakeCompleted) { SetState(registrationRecord, ItemRegistrationState.PendingGameRegistry, "Waiting for GameInfo before HTF MoreItemAPI assigns a game-local Item ID."); continue; } if (!registrationRecord.Handle.ItemId.HasValue) { SetState(registrationRecord, ItemRegistrationState.PendingGameRegistry, "Waiting for HTF MoreItemAPI to assign a free game-local Item ID."); continue; } if (!Object.op_Implicit((Object)(object)registrationRecord.ItemPrefab)) { if (registrationRecord.GeneratedRegistration == null) { Fail(registrationRecord, "The registration record does not contain a valid Item prefab."); continue; } if (!GeneratedPrefabFactory.TryCreate(registrationRecord.Key, registrationRecord.Handle.ItemId.Value, registrationRecord.GeneratedRegistration, out var item, out var networkObject, out var error)) { Fail(registrationRecord, error); continue; } registrationRecord.ItemPrefab = item; registrationRecord.NetworkPrefab = networkObject; registrationRecord.Handle.ItemPrefab = item; _logger.LogInfo((object)("[ItemAPI] Generated the item skeleton for " + registrationRecord.Key + " through the fixed pipeline; " + $"WorldColliders={((Component)item).GetComponentsInChildren(true).Length - 1}.")); } if (!registrationRecord.GameRegistryApplied && !TryApplyGameRegistry(registrationRecord, out var error2)) { Fail(registrationRecord, error2); continue; } NetworkManager networkManager = InstanceFinder.NetworkManager; if (!Object.op_Implicit((Object)(object)networkManager)) { SetState(registrationRecord, ItemRegistrationState.PendingNetworkManager, "The game registry is ready; waiting for FishNet NetworkManager."); continue; } if (!registrationRecord.RegisteredNetworkManagers.Contains(networkManager)) { if (!TryApplyNetworkRegistry(registrationRecord, networkManager, out var error3)) { Fail(registrationRecord, error3); continue; } registrationRecord.RegisteredNetworkManagers.Add(networkManager); } SetState(registrationRecord, ItemRegistrationState.Registered, "Registered with GameInfo and the FishNet runtime spawnable collection."); } } private static void AssignPendingItemIds(RegistrationRecord[] records) { if (AllItemsField == null || IdToSpawnableField == null || ItemIdField == null) { return; } Dictionary dictionary = AllItemsField.GetValue(null) as Dictionary; Dictionary dictionary2 = IdToSpawnableField.GetValue(null) as Dictionary; if (dictionary == null || dictionary2 == null) { return; } List list = new List(); HashSet hashSet = new HashSet(dictionary.Keys); hashSet.UnionWith(dictionary2.Keys); lock (Sync) { hashSet.UnionWith(ById.Keys); foreach (RegistrationRecord registrationRecord in records) { if (!registrationRecord.Handle.ItemId.HasValue && registrationRecord.Handle.State != ItemRegistrationState.Failed && registrationRecord.Handle.State != ItemRegistrationState.Rejected) { list.Add(registrationRecord); } } } if (list.Count == 0) { return; } List list2 = new List(list.Count); foreach (RegistrationRecord item in list) { list2.Add(item.Key); } Dictionary dictionary3; try { dictionary3 = ItemIdAllocator.Allocate(list2, hashSet); } catch (InvalidOperationException ex) { foreach (RegistrationRecord item2 in list) { Fail(item2, "Automatic Item ID allocation failed: " + ex.Message); } return; } lock (Sync) { foreach (RegistrationRecord item3 in list) { if (!item3.Handle.ItemId.HasValue) { byte b = dictionary3[item3.Key]; item3.Handle.ItemId = b; ById.Add(b, item3); if (Object.op_Implicit((Object)(object)item3.ItemPrefab)) { ItemIdField.SetValue(item3.ItemPrefab, b); } _logger.LogInfo((object)($"[ItemAPI] Automatically assigned ItemId={b} to {item3.Key}; " + $"Preferred={ItemIdAllocator.DerivePreferredId(item3.Key)}.")); } } } } private static bool TryApplyGameRegistry(RegistrationRecord record, out string error) { error = null; if (AllItemsField == null || NameToSpawnableField == null || IdToSpawnableField == null) { error = "GameInfo registry fields could not be located in the current game version."; return false; } Dictionary dictionary = AllItemsField.GetValue(null) as Dictionary; Dictionary dictionary2 = NameToSpawnableField.GetValue(null) as Dictionary; Dictionary dictionary3 = IdToSpawnableField.GetValue(null) as Dictionary; if (dictionary == null || dictionary2 == null || dictionary3 == null) { error = "The GameInfo registry is not available yet."; return false; } byte value = record.Handle.ItemId.Value; if (dictionary.TryGetValue(value, out var value2) && (Object)(object)value2 != (Object)(object)record.ItemPrefab) { error = $"Automatically assigned Item ID {value} is already used by {Describe(value2)} from the game or another mod."; return false; } if (dictionary3.TryGetValue(value, out var value3) && (Object)(object)value3 != (Object)(object)record.ItemPrefab) { error = $"Automatically assigned Spawnable ID {value} is already used by {Describe(value3)}."; return false; } if (dictionary2.TryGetValue(record.NormalizedName, out var value4) && (Object)(object)value4 != (Object)(object)record.ItemPrefab) { error = "Normalized name '" + record.NormalizedName + "' is already used by " + Describe(value4) + "."; return false; } dictionary[value] = record.ItemPrefab; dictionary3[value] = record.ItemPrefab; dictionary2[record.NormalizedName] = record.ItemPrefab; record.GameRegistryApplied = true; _logger.LogInfo((object)$"[ItemAPI] Added {record.Key} to GameInfo, ItemId={value}."); return true; } private static bool TryApplyNetworkRegistry(RegistrationRecord record, NetworkManager manager, out string error) { error = null; ushort networkCollectionId = record.Handle.NetworkCollectionId; PrefabObjects prefabObjects = manager.GetPrefabObjects(networkCollectionId, false); if ((Object)(object)prefabObjects != (Object)null && prefabObjects.GetObjectCount() > 0) { if ((Object)(object)prefabObjects.GetObject(true, 0) == (Object)(object)record.NetworkPrefab) { return true; } error = $"FishNet runtime collection {networkCollectionId} is already used by an external mod."; return false; } if (InstanceFinder.IsClientStarted || InstanceFinder.IsServerStarted) { error = "The network session has already started, so network items cannot be appended safely. Call Register before connecting or hosting."; return false; } PrefabObjects val = prefabObjects ?? manager.GetPrefabObjects(networkCollectionId, true); if ((Object)(object)val == (Object)null) { error = $"FishNet could not create runtime collection {networkCollectionId}."; return false; } val.AddObject(record.NetworkPrefab, true, true); if (val.GetObjectCount() != 1 || (Object)(object)val.GetObject(true, 0) != (Object)(object)record.NetworkPrefab) { error = $"FishNet runtime collection {networkCollectionId} is inconsistent after initialization."; return false; } _logger.LogInfo((object)$"[ItemAPI] Added {record.Key} to FishNet collection={networkCollectionId}, PrefabId=0."); return true; } internal static bool TryGet(byte itemId, out ItemRegistrationHandle handle) { lock (Sync) { if (ById.TryGetValue(itemId, out var value)) { handle = value.Handle; return true; } } handle = null; return false; } internal static bool TryGet(string key, out ItemRegistrationHandle handle) { lock (Sync) { if (key != null && ByKey.TryGetValue(key, out var value)) { handle = value.Handle; return true; } } handle = null; return false; } internal static bool TryGetIcon(byte itemId, out InventoryIcon icon) { lock (Sync) { if (ById.TryGetValue(itemId, out var value) && value.Icon != null && Object.op_Implicit((Object)(object)value.Icon.Texture)) { icon = value.Icon; return true; } } icon = null; return false; } internal static bool TryGetDisplayName(byte itemId, out string displayName) { lock (Sync) { if (ById.TryGetValue(itemId, out var value) && !string.IsNullOrWhiteSpace(value.DisplayName)) { displayName = value.DisplayName; return true; } } displayName = null; return false; } internal static IReadOnlyList GetAllHandles() { lock (Sync) { List list = new List(ByKey.Count); foreach (RegistrationRecord value in ByKey.Values) { list.Add(value.Handle); } list.Sort(delegate(ItemRegistrationHandle left, ItemRegistrationHandle right) { if (left.ItemId.HasValue && right.ItemId.HasValue) { return left.ItemId.Value.CompareTo(right.ItemId.Value); } if (left.ItemId.HasValue) { return -1; } return right.ItemId.HasValue ? 1 : string.Compare(left.Key, right.Key, StringComparison.Ordinal); }); return list; } } internal static string BuildKey(string modGuid, string internalName) { if (string.IsNullOrWhiteSpace(modGuid)) { throw new ArgumentException("ModGuid cannot be empty.", "modGuid"); } if (string.IsNullOrWhiteSpace(internalName)) { throw new ArgumentException("InternalName cannot be empty.", "internalName"); } return modGuid.Trim().ToLowerInvariant() + ":" + internalName.Trim().ToLowerInvariant(); } private static string NormalizeName(string value) { return value.Replace(" ", string.Empty).Trim().ToLowerInvariant(); } private static ushort DeriveCollectionId(string key) { uint num = 2166136261u; byte[] bytes = Encoding.UTF8.GetBytes(key); foreach (byte b in bytes) { num ^= b; num *= 16777619; } return (ushort)(32768 + num % 32767); } private static ItemRegistrationHandle Rejected(string key, ushort collectionId, Item item, string message) { ManualLogSource logger = _logger; if (logger != null) { logger.LogError((object)("[ItemAPI] Rejected registration " + key + ": " + message)); } return new ItemRegistrationHandle(key, collectionId, item, ItemRegistrationState.Rejected, message); } private static ItemRegistrationHandle GetHandleByKey(string key) { lock (Sync) { return ByKey[key].Handle; } } private static void Fail(RegistrationRecord record, string message) { SetState(record, ItemRegistrationState.Failed, message); _logger.LogError((object)("[ItemAPI] Registration failed for " + record.Key + ": " + message)); } private static void SetState(RegistrationRecord record, ItemRegistrationState state, string message) { if (record.Handle.State != state || !string.Equals(record.Handle.Message, message, StringComparison.Ordinal)) { record.Handle.State = state; record.Handle.Message = message; ItemRegistry.RaiseRegistrationChanged(record.Handle); } } private static string Describe(Item item) { if (!Object.op_Implicit((Object)(object)item)) { return ""; } return "'" + ((Object)item).name + "'"; } internal static void LogSubscriberException(Exception exception) { ManualLogSource logger = _logger; if (logger != null) { logger.LogError((object)$"[ItemAPI] A RegistrationChanged subscriber threw an exception: {exception}"); } } } }