Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of DrakeModsLibs v0.9.1
DrakeModsLibs.dll
Decompiled a day 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.IO.Compression; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Runtime.Versioning; using System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using DrakeModsLibs.API; using DrakeModsLibs.Data; using DrakeModsLibs.Display; using DrakeModsLibs.Patches; using DrakeModsLibs.Runtime; using DrakeModsLibs.Stack; using DrakeModsLibs.Sync; using DrakeModsLibs.Tags; using HarmonyLib; using JetBrains.Annotations; using Jotunn.Managers; using Microsoft.CodeAnalysis; using ServerSync; using TMPro; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: AssemblyTitle("DrakeModsLibs")] [assembly: AssemblyDescription("Shared customization library for Drake mods.")] [assembly: AssemblyCompany("DrakesWorkshop")] [assembly: AssemblyProduct("DrakeModsLibs")] [assembly: ComVisible(false)] [assembly: Guid("a1111111-1111-4111-8111-111111111101")] [assembly: AssemblyFileVersion("0.3.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyVersion("0.3.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace DrakeModsLibs { [BepInPlugin("com.drakemods.libs", "DrakeModsLibs", "0.9.1")] public class CustomizeLibsPlugin : BaseUnityPlugin { private readonly Harmony _harmony = new Harmony("drakemods.DrakeModsLibs"); public const string CompanyName = "DrakeMods"; public const string ModName = "DrakeModsLibs"; public const string Version = "0.9.1"; public const string GUID = "com.drakemods.libs"; private void Awake() { HarmonyPatchHub.ApplyAll(_harmony, ((BaseUnityPlugin)this).Logger); ((BaseUnityPlugin)this).Logger.LogInfo((object)"DrakeModsLibs 0.9.1 loaded (display patches, DrakeConfigSync API, shared API)."); } } internal static class HarmonyPatchHub { internal static void ApplyAll(Harmony harmony, ManualLogSource log) { InventoryStackPatches.Apply(harmony, log); ItemTooltipPatches.Apply(harmony, log); DropHudMessagePatches.ApplyDropItemPendingCapture(harmony, log); harmony.PatchAll(typeof(HarmonyPatchHub).Assembly); DropHudMessagePatches.ApplyMessageHudShowMessage(harmony, log); log.LogInfo((object)"[DrakeModsLibs] Display Harmony patches applied."); } } } namespace DrakeModsLibs.Tags { public delegate bool CustomizeEditValidator(ItemData? item, Player? player); public static class CustomizationGatekeeper { private static readonly Dictionary<CustomizeOperation, List<CustomizeEditValidator>> Validators = new Dictionary<CustomizeOperation, List<CustomizeEditValidator>>(); private static readonly List<TagBlockRule> TagBlockRules = new List<TagBlockRule>(); private static bool _defaultTagRulesRegistered; public static Func<Player?, bool>? TagBypass { get; set; } private static void EnsureDefaultTagRules() { if (!_defaultTagRulesRegistered) { _defaultTagRulesRegistered = true; RegisterTagBlockRule("Drake_NoRename", CustomizeOperation.RenameName); RegisterTagBlockRule("Drake_NoDesc", CustomizeOperation.RenameDescription); RegisterTagBlockRule("Drake_NoCraftedByEdit", CustomizeOperation.EditCraftedBy); RegisterTagBlockRule("Drake_QuestItem", CustomizeOperation.AllEdits); } } public static void RegisterTagBlockRule(string tagKey, CustomizeOperation blockedOperations) { if (!string.IsNullOrEmpty(tagKey)) { TagBlockRules.Add(new TagBlockRule(tagKey, blockedOperations)); } } public static void RegisterValidator(CustomizeOperation operation, CustomizeEditValidator validator) { if (!Validators.TryGetValue(operation, out List<CustomizeEditValidator> value)) { value = new List<CustomizeEditValidator>(); Validators[operation] = value; } value.Add(validator); } public static bool IsBlockedByTag(CustomizeOperation operation, ItemData? item) { if (item == null) { return true; } EnsureDefaultTagRules(); foreach (TagBlockRule tagBlockRule in TagBlockRules) { if ((tagBlockRule.BlockedOperations & operation) == 0 || !DrakeTagManager.HasTag(item, tagBlockRule.TagKey)) { continue; } return true; } return false; } public static bool CanPerform(CustomizeOperation operation, ItemData? item, Player? player) { if (item == null) { return false; } if (IsBlockedByTag(operation, item)) { Func<Player?, bool>? tagBypass = TagBypass; if (tagBypass == null || !tagBypass(player)) { return false; } } if (!Validators.TryGetValue(operation, out List<CustomizeEditValidator> value) || value.Count == 0) { return true; } foreach (CustomizeEditValidator item2 in value) { if (!item2(item, player)) { return false; } } return true; } } public static class DrakeTagManager { public static bool HasTag(ItemData? item, string tagKey) { if (item?.m_customData == null || string.IsNullOrEmpty(tagKey)) { return false; } string value; return item.m_customData.TryGetValue(tagKey, out value) && (value == "1" || string.Equals(value, "true", StringComparison.OrdinalIgnoreCase)); } public static void SetTag(ItemData item, string tagKey) { if (!string.IsNullOrEmpty(tagKey)) { if (item.m_customData == null) { item.m_customData = new Dictionary<string, string>(); } item.m_customData[tagKey] = "1"; } } public static void ClearTag(ItemData item, string tagKey) { if (item.m_customData != null && !string.IsNullOrEmpty(tagKey)) { item.m_customData.Remove(tagKey); } } } } namespace DrakeModsLibs.Stack { internal static class StackIdentity { internal static string GetFingerprint(ItemData? item) { if (item?.m_customData == null) { return ""; } StringBuilder stringBuilder = new StringBuilder(); Append(stringBuilder, item, "Drake_Rename"); Append(stringBuilder, item, "Drake_Rename_Desc"); Append(stringBuilder, item, "Drake_CraftedByDisplay"); Append(stringBuilder, item, "Drake_CraftedByLineLabel"); return stringBuilder.ToString(); } private static void Append(StringBuilder sb, ItemData item, string key) { if (item.m_customData.TryGetValue(key, out var value) && !string.IsNullOrEmpty(value)) { sb.Append('|').Append(value); } else { sb.Append('|'); } } internal static bool SameDrakeStackIdentity(ItemData? a, ItemData? b) { if (a == null || b == null) { return false; } return string.Equals(GetFingerprint(a), GetFingerprint(b), StringComparison.Ordinal); } } } namespace DrakeModsLibs.Sync { public sealed class DrakeConfigSync { private readonly ConfigSync _inner; private int _syncedEntryCount; private bool _lockingConfigRegistered; public bool IsSourceOfTruth => _inner.IsSourceOfTruth; public event Action<bool> SourceOfTruthChanged { add { _inner.SourceOfTruthChanged += value; } remove { _inner.SourceOfTruthChanged -= value; } } private DrakeConfigSync(ConfigSync inner) { _inner = inner; } public static DrakeConfigSync Create(string modId, string displayName, string currentVersion, string minimumRequiredVersion = null) { ConfigSync inner = new ConfigSync(modId) { DisplayName = displayName, CurrentVersion = currentVersion, MinimumRequiredVersion = (minimumRequiredVersion ?? currentVersion) }; return new DrakeConfigSync(inner); } public ConfigEntry<T> BindSynced<T>(ConfigFile config, string section, string configurationManagerCategory, string key, T defaultValue, string description) { return BindSynced(config, section, configurationManagerCategory, key, defaultValue, description, null); } public ConfigEntry<T> BindSynced<T>(ConfigFile config, string section, string configurationManagerCategory, string key, T defaultValue, string description, AcceptableValueBase? acceptableValues) { //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_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Expected O, but got Unknown ConfigEntry<T> val = config.Bind<T>(section, key, defaultValue, new ConfigDescription(description, acceptableValues, new object[1] { (object)(string.IsNullOrEmpty(configurationManagerCategory) ? ((ConfigurationManagerAttributes)null) : new ConfigurationManagerAttributes { Category = configurationManagerCategory }) })); SyncedConfigEntry<T> syncedConfigEntry = _inner.AddConfigEntry<T>(val); syncedConfigEntry.SynchronizedConfig = true; _syncedEntryCount++; return val; } public ConfigEntry<T> BindClientOnly<T>(ConfigFile config, string section, string configurationManagerCategory, string key, T defaultValue, string description) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Expected O, but got Unknown return config.Bind<T>(section, key, defaultValue, new ConfigDescription(description, (AcceptableValueBase)null, new object[1] { (object)(string.IsNullOrEmpty(configurationManagerCategory) ? ((ConfigurationManagerAttributes)null) : new ConfigurationManagerAttributes { Category = configurationManagerCategory }) })); } public void AddLockingConfigEntry(ConfigEntry<bool> lockEntry) { _inner.AddLockingConfigEntry<bool>(lockEntry); _lockingConfigRegistered = true; } public void FinalizeBinding(ManualLogSource log, int expectedSyncedEntryCount, Func<bool> getLockSyncedConfig) { if (!_lockingConfigRegistered) { ManualLogSource obj = log; if (obj != null) { obj.LogError((object)"[DrakeConfigSync] LockSyncedConfig was not registered via AddLockingConfigEntry."); } } if (_syncedEntryCount != expectedSyncedEntryCount) { ManualLogSource obj2 = log; if (obj2 != null) { obj2.LogError((object)$"[DrakeConfigSync] Expected {expectedSyncedEntryCount} synced entries, registered {_syncedEntryCount}."); } } SourceOfTruthChanged += delegate(bool authoritative) { OnSourceOfTruthChanged(log, authoritative, getLockSyncedConfig); }; OnSourceOfTruthChanged(log, IsSourceOfTruth, getLockSyncedConfig); } private static void OnSourceOfTruthChanged(ManualLogSource log, bool localIsAuthoritative, Func<bool> getLockSyncedConfig) { if (localIsAuthoritative) { if (!getLockSyncedConfig() && log != null) { log.LogWarning((object)"[DrakeConfigSync] LockSyncedConfig is false on the host. Non-admin clients can change synced gameplay settings."); } } else if (!getLockSyncedConfig() && log != null) { log.LogInfo((object)"[DrakeConfigSync] Connected to a host with config lock disabled; server values still apply until changed."); } } } } namespace DrakeModsLibs.Runtime { internal static class CustomizeLibsRuntime { internal static readonly List<IDisplayNameModifier> DisplayNameModifiers = new List<IDisplayNameModifier>(); internal static readonly List<IItemDisplayNameLayer> DisplayNameLayers = new List<IItemDisplayNameLayer>(); internal static bool DefaultDisplayLayersRegistered; internal static bool ShowItemStandItemNameWhenNoAccess { get; set; } = true; internal static IStackMergePolicy? StackMergePolicy { get; set; } } } namespace DrakeModsLibs.Patches { [HarmonyPatch] internal static class ItemDataDecoratedNameLikePatch { private static bool _loggedMissing; private static MethodBase? TargetMethod() { Type typeFromHandle = typeof(ItemData); BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; string[] array = new string[3] { "GetDecoratedName", "GetDecoratedNameWithQuality", "GetName" }; foreach (string name in array) { MethodInfo methodInfo = TryBindInstanceStringMethod(typeFromHandle, name, bindingFlags); if (methodInfo != null) { return methodInfo; } } MethodInfo[] methods = typeFromHandle.GetMethods(bindingFlags); foreach (MethodInfo methodInfo2 in methods) { if (!methodInfo2.IsStatic && !(methodInfo2.ReturnType != typeof(string))) { string text = methodInfo2.Name ?? ""; if (text.IndexOf("Decor", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("Name", StringComparison.OrdinalIgnoreCase) >= 0) { return methodInfo2; } } } if (!_loggedMissing) { _loggedMissing = true; Debug.LogWarning((object)"[DrakeModsLibs] Upgrade list rename: no suitable ItemData decorated-name method found; upgrade tab names may remain vanilla."); } return null; } private static MethodInfo? TryBindInstanceStringMethod(Type t, string name, BindingFlags flags) { MethodInfo methodInfo = null; MethodInfo[] methods = t.GetMethods(flags); foreach (MethodInfo methodInfo2 in methods) { if (!(methodInfo2.Name != name) && !methodInfo2.IsStatic && !(methodInfo2.ReturnType != typeof(string))) { if (methodInfo2.GetParameters().Length == 0) { return methodInfo2; } if ((object)methodInfo == null) { methodInfo = methodInfo2; } } } return methodInfo; } [HarmonyPostfix] private static void Postfix(ItemData __instance, ref string __result) { if (__instance?.m_shared == null || string.IsNullOrEmpty(__result) || (!ItemDisplayService.HasCustomName(__instance) && !DisplayNameModifierHub.AffectsDisplay(__instance))) { return; } string text = ((Localization.instance != null) ? Localization.instance.Localize(__instance.m_shared.m_name) : __instance.m_shared.m_name); if (string.IsNullOrEmpty(text)) { return; } string displayNameForUi = ItemDisplayService.GetDisplayNameForUi(__instance, localize: true); if (!string.IsNullOrEmpty(displayNameForUi)) { if (__result.Contains(text)) { __result = __result.Replace(text, displayNameForUi); } else if (!string.IsNullOrEmpty(__instance.m_shared.m_name) && __result.Contains(__instance.m_shared.m_name)) { __result = __result.Replace(__instance.m_shared.m_name, displayNameForUi); } } } } [HarmonyPatch(typeof(InventoryGrid), "CreateItemTooltip")] [HarmonyPriority(500)] public static class InventoryGridDisplayTooltipPatch { [HarmonyPostfix] private static void ApplyDisplayTooltip(InventoryGrid __instance, ItemData? item, UITooltip tooltip) { //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) if (item?.m_shared != null && !((Object)(object)tooltip == (Object)null)) { string displayNameForUi = ItemDisplayService.GetDisplayNameForUi(item, localize: false); string tooltip2 = item.GetTooltip(-1); tooltip2 = ItemTooltipPatches.ApplyCraftedByDisplayToTooltipText(tooltip2, item); tooltip2 = UpdateDescription(item, tooltip2); tooltip.Set(displayNameForUi, tooltip2, __instance.m_tooltipAnchor, default(Vector2)); } } private static string UpdateDescription(ItemData? item, string currentText) { if (item?.m_shared == null) { return currentText; } if (!ItemDisplayService.HasCustomDescription(item)) { return currentText; } string newValue = TooltipRichText.EnsureRichTextTagsClosedForTooltip(ItemDisplayService.GetProperDescription(item, item.m_shared.m_description)); string description = item.m_shared.m_description; if (!string.IsNullOrEmpty(description) && currentText.Contains(description)) { currentText = currentText.Replace(description, newValue); } return currentText; } } internal static class InventoryStackPatches { internal static ItemData? IncomingStackItem; internal static void Apply(Harmony harmony, ManualLogSource log) { //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Expected O, but got Unknown //IL_0094: Expected O, but got Unknown //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Expected O, but got Unknown //IL_0287: Unknown result type (might be due to invalid IL or missing references) //IL_0295: Expected O, but got Unknown Type typeFromHandle = typeof(Inventory); BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; MethodInfo methodInfo = AccessTools.DeclaredMethod(typeFromHandle, "AddItem", new Type[1] { typeof(ItemData) }, (Type[])null) ?? AccessTools.Method(typeFromHandle, "AddItem", new Type[1] { typeof(ItemData) }, (Type[])null); if (methodInfo != null) { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(InventoryStackPatches), "AddItem_IncomingPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(InventoryStackPatches), "AddItem_IncomingCleanup", (Type[])null), (HarmonyMethod)null); } else { log.LogWarning((object)"[DrakeModsLibs] SeparateStacks: AddItem(ItemData) not found — incoming stack tracking disabled."); } MethodInfo methodInfo2 = null; MethodInfo[] methods = typeFromHandle.GetMethods(bindingAttr); foreach (MethodInfo methodInfo3 in methods) { if (!(methodInfo3.Name != "FindFreeStackItem")) { ParameterInfo[] parameters = methodInfo3.GetParameters(); if (parameters.Length >= 2 && !(parameters[0].ParameterType != typeof(string)) && !(parameters[1].ParameterType != typeof(int))) { methodInfo2 = methodInfo3; break; } } } if (methodInfo2 != null) { harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(typeof(InventoryStackPatches), "FindFreeStackItem_Prefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else { log.LogWarning((object)"[DrakeModsLibs] SeparateStacks: FindFreeStackItem not found — merge-from-pickup may ignore identity."); } MethodInfo methodInfo4 = null; MethodInfo[] methods2 = typeFromHandle.GetMethods(bindingAttr); foreach (MethodInfo methodInfo5 in methods2) { if (!(methodInfo5.Name != "AddItem")) { ParameterInfo[] parameters2 = methodInfo5.GetParameters(); if (parameters2.Length >= 4 && !(parameters2[0].ParameterType != typeof(ItemData)) && !(parameters2[1].ParameterType != typeof(int)) && !(parameters2[2].ParameterType != typeof(int)) && !(parameters2[3].ParameterType != typeof(int))) { methodInfo4 = methodInfo5; break; } } } if (methodInfo4 != null) { harmony.Patch((MethodBase)methodInfo4, new HarmonyMethod(typeof(InventoryStackPatches), "AddItemAtCell_Prefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else { log.LogWarning((object)"[DrakeModsLibs] SeparateStacks: AddItem(ItemData,int,int,int...) not found — cell merge guard disabled."); } } internal static void AddItem_IncomingPrefix(ItemData item) { IncomingStackItem = item; } internal static void AddItem_IncomingCleanup(Exception? __exception) { IncomingStackItem = null; } internal static bool FindFreeStackItem_Prefix(Inventory __instance, string name, int quality, ref ItemData? __result) { IStackMergePolicy stackMergePolicy = CustomizeLibsRuntime.StackMergePolicy; if (stackMergePolicy == null || !stackMergePolicy.SeparateStacksEnabled || IncomingStackItem == null) { return true; } __result = null; foreach (ItemData allItem in __instance.GetAllItems()) { if (TryPickStackSlot(IncomingStackItem, name, quality, allItem, ref __result)) { continue; } break; } return false; } private static bool TryPickStackSlot(ItemData incoming, string name, int quality, ItemData? itemData, ref ItemData? __result) { if (itemData?.m_shared == null) { return true; } if (itemData.m_shared.m_name != name || itemData.m_quality != quality) { return true; } if (itemData.m_stack >= itemData.m_shared.m_maxStackSize) { return true; } if (!StackIdentity.SameDrakeStackIdentity(incoming, itemData)) { return true; } __result = itemData; return false; } internal static bool AddItemAtCell_Prefix(ItemData item, int amount, int x, int y, Inventory __instance, ref bool __result) { IStackMergePolicy stackMergePolicy = CustomizeLibsRuntime.StackMergePolicy; if (stackMergePolicy == null || !stackMergePolicy.SeparateStacksEnabled) { return true; } ItemData itemAt = __instance.GetItemAt(x, y); if (itemAt == null) { return true; } if (itemAt.m_shared.m_name != item.m_shared.m_name) { return true; } if (itemAt.m_shared.m_maxQuality > 1 && itemAt.m_quality != item.m_quality) { return true; } if (!StackIdentity.SameDrakeStackIdentity(item, itemAt)) { if (!stackMergePolicy.SeparateStacksHardLock) { return true; } __result = false; return false; } return true; } } internal static class HoverRenameHelper { internal static void ApplyRenameToHoverResult(ref string __result, ItemData item) { if (item?.m_shared == null || string.IsNullOrEmpty(__result)) { return; } if (ItemDisplayService.HasCustomName(item)) { string displayNameForUi = ItemDisplayService.GetDisplayNameForUi(item, localize: false); if (displayNameForUi == null || item.m_shared.m_name == null) { return; } if (Localization.instance == null) { __result = __result.Replace(item.m_shared.m_name, displayNameForUi); return; } string text = Localization.instance.Localize(item.m_shared.m_name); string newValue = Localization.instance.Localize(displayNameForUi); if (__result.Contains(text)) { __result = __result.Replace(text, newValue); } } else { if (!DisplayNameModifierHub.AffectsDisplay(item)) { return; } string text2 = ((Localization.instance != null) ? Localization.instance.Localize(item.m_shared.m_name) : item.m_shared.m_name); if (!string.IsNullOrEmpty(text2)) { string prefixRaw = DisplayNameModifierHub.GetPrefixRaw(item); string text3 = ((Localization.instance != null) ? Localization.instance.Localize(prefixRaw) : prefixRaw); string newValue2 = TooltipRichText.EnsureRichTextTagsClosedForTooltip(text3 + " " + text2); if (__result.Contains(text2)) { __result = __result.Replace(text2, newValue2); } else if (!string.IsNullOrEmpty(item.m_shared.m_name) && __result.Contains(item.m_shared.m_name)) { string newValue3 = TooltipRichText.EnsureRichTextTagsClosedForTooltip(text3 + " " + item.m_shared.m_name); __result = __result.Replace(item.m_shared.m_name, newValue3); } } } } } internal static class PickupHudMessageHelper { internal static bool TryGetLocalizedCustomNameForHud(ItemData? item, out string localizedName) { localizedName = ""; if (item?.m_shared == null) { return false; } if (!ItemDisplayService.HasCustomName(item) && !DisplayNameModifierHub.AffectsDisplay(item)) { return false; } localizedName = ItemDisplayService.GetDisplayNameForUi(item, localize: true); if (string.IsNullOrEmpty(localizedName)) { return false; } return true; } } [HarmonyPatch(typeof(Character), "ShowPickupMessage")] internal static class CharacterShowPickupMessagePatch { [HarmonyPrefix] private static bool Prefix(Character __instance, ItemData item, int amount) { if (!PickupHudMessageHelper.TryGetLocalizedCustomNameForHud(item, out string localizedName)) { return true; } __instance.Message((MessageType)1, "$msg_added " + localizedName, amount, item.GetIcon()); return false; } } [HarmonyPatch(typeof(Character), "ShowRemovedMessage")] internal static class CharacterShowRemovedMessagePatch { [HarmonyPrefix] private static bool Prefix(Character __instance, ItemData item, int amount) { if (!PickupHudMessageHelper.TryGetLocalizedCustomNameForHud(item, out string localizedName)) { return true; } __instance.Message((MessageType)1, "$msg_removed " + localizedName, amount, item.GetIcon()); return false; } } public static class Patches { [HarmonyPatch(typeof(ItemDrop))] public static class HoverTextPatch { [HarmonyPatch("GetHoverText")] [HarmonyPostfix] private static void FixHoverText(ItemDrop __instance, ref string __result) { ItemData itemData = __instance.m_itemData; if (itemData != null && __instance?.m_itemData?.m_shared != null && !string.IsNullOrEmpty(__result)) { HoverRenameHelper.ApplyRenameToHoverResult(ref __result, itemData); } } [HarmonyPatch(typeof(ItemDrop), "GetHoverName")] [HarmonyPostfix] private static void FixHoverName(ItemDrop __instance, ref string __result) { ItemData val = __instance?.m_itemData; if (val == null) { return; } if (ItemDisplayService.HasCustomName(val)) { string displayNameForUi = ItemDisplayService.GetDisplayNameForUi(val, localize: false); if (!string.IsNullOrEmpty(displayNameForUi)) { __result = displayNameForUi; } } else if (DisplayNameModifierHub.AffectsDisplay(val)) { string prefixRaw = DisplayNameModifierHub.GetPrefixRaw(val); string text = ((Localization.instance != null) ? Localization.instance.Localize(prefixRaw) : prefixRaw); __result = TooltipRichText.EnsureRichTextTagsClosedForTooltip(text + " " + __result); } } } } [HarmonyPatch(typeof(ItemStand))] public static class ItemStandPatch { private const int StandItemDataZdoIndex = -1; internal static void RefreshAllItemStandDisplayNames() { ItemStand[] array; try { array = Object.FindObjectsByType<ItemStand>((FindObjectsSortMode)0); } catch { try { array = Object.FindObjectsOfType<ItemStand>(); } catch { return; } } if (array == null || array.Length == 0) { return; } ItemStand[] array2 = array; foreach (ItemStand val in array2) { if (!((Object)(object)val == (Object)null)) { try { ApplyLiveDisplayNameToStand(val); } catch { } } } } private static ZDO? TryGetStandZdo(ItemStand stand) { object? obj = AccessTools.Field(typeof(ItemStand), "m_nview")?.GetValue(stand); ZNetView val = (ZNetView)((obj is ZNetView) ? obj : null); return (val != null) ? val.GetZDO() : null; } private static int TryGetAttachedPrefabHash(ItemStand stand) { if ((Object)(object)stand == (Object)null) { return 0; } MethodInfo methodInfo = AccessTools.Method(typeof(ItemStand), "GetAttachedItem", (Type[])null, (Type[])null); if (methodInfo == null) { return 0; } object obj; try { obj = methodInfo.Invoke(stand, null); } catch { return 0; } if (obj is int result) { return result; } if (obj is string s && int.TryParse(s, out var result2)) { return result2; } return 0; } private static byte[]? TryGetStandItemBytes(ZDO zdo) { FieldInfo fieldInfo = AccessTools.Field(typeof(ZDOVars), "s_itemData"); if (fieldInfo != null) { object value = fieldInfo.GetValue(null); if (value is int num) { return zdo.GetByteArray(num, (byte[])null); } if (value is string text && !string.IsNullOrEmpty(text)) { return zdo.GetByteArray(text, (byte[])null); } } return zdo.GetByteArray("-1_itemData", (byte[])null); } private static bool TryLoadItemFromZdo(ItemData item, ZDO zdo, int index) { MethodInfo[] methods = typeof(ItemDrop).GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); MethodInfo methodInfo = methods.FirstOrDefault(delegate(MethodInfo m) { if (m.Name != "LoadFromZDO") { return false; } ParameterInfo[] parameters = m.GetParameters(); return parameters.Length == 3 && parameters[0].ParameterType == typeof(ItemData) && parameters[1].ParameterType == typeof(ZDO) && parameters[2].ParameterType == typeof(int); }); if (methodInfo != null) { methodInfo.Invoke(null, new object[3] { item, zdo, index }); return true; } MethodInfo methodInfo2 = methods.FirstOrDefault(delegate(MethodInfo m) { if (m.Name != "LoadFromZDO") { return false; } ParameterInfo[] parameters = m.GetParameters(); return parameters.Length == 3 && parameters[0].ParameterType == typeof(int) && parameters[1].ParameterType == typeof(ItemData) && parameters[2].ParameterType == typeof(ZDO); }); if (methodInfo2 != null) { methodInfo2.Invoke(null, new object[3] { index, item, zdo }); return true; } MethodInfo methodInfo3 = methods.FirstOrDefault(delegate(MethodInfo m) { if (m.Name != "LoadFromZDO") { return false; } ParameterInfo[] parameters = m.GetParameters(); return parameters.Length == 2 && parameters[0].ParameterType == typeof(ItemData) && parameters[1].ParameterType == typeof(ZDO); }); if (methodInfo3 == null) { return false; } methodInfo3.Invoke(null, new object[2] { item, zdo }); return true; } private static ItemData? TryGetStandItemForDisplay(ItemStand stand, out bool loadedInstance) { loadedInstance = false; if ((Object)(object)stand == (Object)null) { return null; } int num = 0; try { num = TryGetAttachedPrefabHash(stand); } catch { } if (num != 0 && (Object)(object)ObjectDB.instance != (Object)null) { GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(num); ItemData val = ((!((Object)(object)itemPrefab != (Object)null)) ? null : itemPrefab.GetComponent<ItemDrop>()?.m_itemData); if (val == null) { return null; } ItemData val2 = val.Clone(); ZDO val3 = TryGetStandZdo(stand); if (val3 == null) { return val2; } try { byte[] array = TryGetStandItemBytes(val3); if (array != null && array.Length > 2 && TryLoadItemFromZdo(val2, val3, -1)) { loadedInstance = true; } } catch { } return val2; } ItemData val4 = TryGetFirstContainerItem(stand); if (val4?.m_shared != null) { loadedInstance = true; return val4; } return null; } private static void ApplyDisplayNameFromItemInstance(ItemStand stand, ItemData item) { if ((Object)(object)stand == (Object)null || item?.m_shared == null) { return; } FieldInfo fieldInfo = AccessTools.Field(typeof(ItemStand), "m_currentItemName"); if (!(fieldInfo == null)) { if (ItemDisplayService.HasCustomName(item) || DisplayNameModifierHub.AffectsDisplay(item)) { string displayNameForUi = ItemDisplayService.GetDisplayNameForUi(item, localize: false); fieldInfo.SetValue(stand, TooltipRichText.EnsureRichTextTagsClosedForTooltip(displayNameForUi)); } else { fieldInfo.SetValue(stand, item.m_shared.m_name); } } } private static string ResolveStandBaseLabel(ItemStand stand, ZDO? zdo, ItemData? item) { if (item != null && ItemDisplayService.HasCustomName(item)) { return ItemDisplayService.GetProperName(item); } string text = ((zdo != null) ? zdo.GetString("DrakeRenameIt_CustomName", "") : ""); if (!string.IsNullOrWhiteSpace(text)) { return StripLegacyDurabilityPrefix(item, text.Trim()); } if (item?.m_shared != null && !string.IsNullOrEmpty(item.m_shared.m_name)) { return item.m_shared.m_name; } string text2 = AccessTools.Field(typeof(ItemStand), "m_currentItemName")?.GetValue(stand) as string; return string.IsNullOrWhiteSpace(text2) ? "" : text2.Trim(); } private static void ApplyLiveDisplayNameToStand(ItemStand stand) { if ((Object)(object)stand == (Object)null) { return; } bool loadedInstance; ItemData val = TryGetStandItemForDisplay(stand, out loadedInstance); if (val?.m_shared == null) { return; } ZDO val2 = TryGetStandZdo(stand); string text = ResolveStandBaseLabel(stand, val2, val); if (string.IsNullOrEmpty(text)) { return; } if (val2 != null) { if (ItemDisplayService.HasCustomName(val)) { string properName = ItemDisplayService.GetProperName(val); val2.Set("DrakeRenameIt_CustomName", TooltipRichText.EnsureRichTextTagsClosedForTooltip(properName)); } else { string text2 = val2.GetString("DrakeRenameIt_CustomName", ""); if (!string.IsNullOrWhiteSpace(text2)) { string text3 = StripLegacyDurabilityPrefix(val, text2.Trim()); if (!string.Equals(text3, text2.Trim(), StringComparison.Ordinal)) { val2.Set("DrakeRenameIt_CustomName", TooltipRichText.EnsureRichTextTagsClosedForTooltip(text3)); } } else if (loadedInstance) { val2.Set("DrakeRenameIt_CustomName", string.Empty); } } } string text4; if (loadedInstance && (ItemDisplayService.HasCustomName(val) || DisplayNameModifierHub.AffectsDisplay(val))) { text4 = ItemDisplayService.GetDisplayNameForUi(val, localize: false); } else { text4 = text; if (DisplayNameModifierHub.AffectsDisplay(val)) { string prefixRaw = DisplayNameModifierHub.GetPrefixRaw(val); if (!string.IsNullOrEmpty(prefixRaw)) { string text5 = StripLegacyDurabilityPrefix(val, text); text4 = prefixRaw.TrimEnd(Array.Empty<char>()) + " " + text5; } } } AccessTools.Field(typeof(ItemStand), "m_currentItemName")?.SetValue(stand, TooltipRichText.EnsureRichTextTagsClosedForTooltip(text4)); } private static string StripLegacyDurabilityPrefix(ItemData? item, string cached) { if (string.IsNullOrEmpty(cached)) { return cached; } if (item != null) { string prefixRaw = DisplayNameModifierHub.GetPrefixRaw(item); cached = StripOnePrefix(cached, prefixRaw); } cached = StripOnePrefix(cached, "Pristine"); cached = StripOnePrefix(cached, "Worn"); cached = StripOnePrefix(cached, "Rusty"); cached = StripOnePrefix(cached, "Tarnished"); cached = StripOnePrefix(cached, "Broken"); return cached; } private static string StripOnePrefix(string cached, string? prefix) { if (string.IsNullOrWhiteSpace(prefix) || string.IsNullOrEmpty(cached)) { return cached; } foreach (string item in UniquePrefixCandidates(prefix)) { string text = item.TrimEnd(Array.Empty<char>()) + " "; if (cached.StartsWith(text, StringComparison.OrdinalIgnoreCase)) { return cached.Substring(text.Length).TrimStart(Array.Empty<char>()); } } return cached; } private static List<string> UniquePrefixCandidates(string prefix) { List<string> list = new List<string>(); consider(prefix); if (Localization.instance != null) { consider(Localization.instance.Localize(prefix)); } return list; void consider(string? s) { if (!string.IsNullOrWhiteSpace(s)) { s = s.Trim(); if (!list.Exists((string x) => string.Equals(x, s, StringComparison.Ordinal))) { list.Add(s); if (s.IndexOf('<') >= 0) { string plain = Regex.Replace(s, "<[^>]+>", "").Trim(); if (plain.Length > 0 && !list.Exists((string x) => string.Equals(x, plain, StringComparison.Ordinal))) { list.Add(plain); } } } } } } private static bool ItemStandHadVisualBeforeUse(ItemStand stand) { if ((Object)(object)stand == (Object)null) { return false; } try { if (stand.HaveAttachment()) { return true; } } catch { } string value = AccessTools.Field(typeof(ItemStand), "m_visualName")?.GetValue(stand) as string; return !string.IsNullOrEmpty(value); } private static string TryGetStandCustomNameFromZdo(ItemStand stand) { if ((Object)(object)stand == (Object)null) { return ""; } ZDO val = TryGetStandZdo(stand); if (val == null) { return ""; } bool loadedInstance; ItemData val2 = TryGetStandItemForDisplay(stand, out loadedInstance); if (val2 != null && loadedInstance && (ItemDisplayService.HasCustomName(val2) || DisplayNameModifierHub.AffectsDisplay(val2))) { string displayNameForUi = ItemDisplayService.GetDisplayNameForUi(val2, localize: true); if (!string.IsNullOrEmpty(displayNameForUi)) { return displayNameForUi; } } string text = val.GetString("DrakeRenameIt_CustomName", ""); if (string.IsNullOrWhiteSpace(text)) { return ""; } string text2 = StripLegacyDurabilityPrefix(val2, text.Trim()); string text3 = text2; if (val2 != null && DisplayNameModifierHub.AffectsDisplay(val2)) { string prefixRaw = DisplayNameModifierHub.GetPrefixRaw(val2); if (!string.IsNullOrEmpty(prefixRaw)) { text3 = prefixRaw.TrimEnd(Array.Empty<char>()) + " " + text2; } } string text4 = TooltipRichText.EnsureRichTextTagsClosedForTooltip(text3); return (Localization.instance != null) ? Localization.instance.Localize(text4) : text4; } private static string TryGetStandCurrentItemName(ItemStand stand) { if ((Object)(object)stand == (Object)null) { return ""; } FieldInfo fieldInfo = AccessTools.Field(typeof(ItemStand), "m_currentItemName"); if (fieldInfo == null) { return ""; } string text = fieldInfo.GetValue(stand) as string; if (string.IsNullOrWhiteSpace(text)) { return ""; } string text2 = TooltipRichText.EnsureRichTextTagsClosedForTooltip(text); return (Localization.instance != null) ? Localization.instance.Localize(text2) : text2; } private static bool HoverTextContainsNoAccess(string hoverText) { if (string.IsNullOrEmpty(hoverText)) { return false; } if (hoverText.Contains("$piece_noaccess")) { return true; } if (Localization.instance == null) { return false; } string value = Localization.instance.Localize("$piece_noaccess"); if (string.IsNullOrEmpty(value)) { return false; } return hoverText.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0; } private static ItemData? TryGetFirstContainerItem(ItemStand stand) { Container component = ((Component)stand).GetComponent<Container>(); if ((Object)(object)component == (Object)null) { return null; } Inventory inventory = component.GetInventory(); if (inventory == null) { return null; } List<ItemData> allItems = inventory.GetAllItems(); if (allItems == null || allItems.Count == 0) { return null; } return allItems[0]; } private static string TryGetBestStandLabel(ItemStand stand) { string text = TryGetStandCurrentItemName(stand); if (!string.IsNullOrEmpty(text)) { return text; } text = TryGetStandCustomNameFromZdo(stand); if (!string.IsNullOrEmpty(text)) { return text; } ItemData val = TryGetFirstContainerItem(stand); if (val?.m_shared == null) { return ""; } return ItemDisplayService.GetDisplayNameForUi(val, localize: true); } [HarmonyPatch("GetHoverText")] [HarmonyPostfix] [HarmonyPriority(0)] private static void FixItemStandHoverText(ItemStand __instance, ref string __result) { if ((Object)(object)__instance == (Object)null || string.IsNullOrEmpty(__result)) { return; } if (HoverTextContainsNoAccess(__result)) { if (CustomizeLibsRuntime.ShowItemStandItemNameWhenNoAccess) { string text = TryGetBestStandLabel(__instance); if (!string.IsNullOrEmpty(text) && __result.IndexOf(text, StringComparison.Ordinal) < 0) { __result = text + "\n" + __result; } } return; } try { if (TryGetAttachedPrefabHash(__instance) != 0) { return; } } catch { } ItemData val = TryGetFirstContainerItem(__instance); if (val?.m_shared != null && (ItemDisplayService.HasCustomName(val) || DisplayNameModifierHub.AffectsDisplay(val))) { HoverRenameHelper.ApplyRenameToHoverResult(ref __result, val); } } [HarmonyPatch("UseItem")] [HarmonyPrefix] private static void UseItem_Prefix(ItemStand __instance, out bool __state) { __state = ItemStandHadVisualBeforeUse(__instance); } [HarmonyPatch("UseItem")] [HarmonyPostfix] [HarmonyPriority(0)] private static void GrabItem(ItemStand __instance, Humanoid user, ItemData? item, bool __state) { if (item?.m_shared == null) { return; } ZDO val = TryGetStandZdo(__instance); if (val == null) { return; } Inventory val2 = ((user != null) ? user.GetInventory() : null); if (__state && val2 != null && val2.ContainsItem(item)) { val.Set("DrakeRenameIt_CustomName", string.Empty); return; } if (ItemDisplayService.HasCustomName(item)) { string properName = ItemDisplayService.GetProperName(item); val.Set("DrakeRenameIt_CustomName", TooltipRichText.EnsureRichTextTagsClosedForTooltip(properName)); } else { val.Set("DrakeRenameIt_CustomName", string.Empty); } ApplyDisplayNameFromItemInstance(__instance, item); } [HarmonyPatch("SetVisualItem")] [HarmonyPostfix] [HarmonyPriority(0)] private static void FixStandText(ItemStand __instance, int itemHash, int variant, int quality, int orientation) { if (!((Object)(object)__instance == (Object)null)) { ApplyLiveDisplayNameToStand(__instance); } } } internal static class DropHudMessagePatches { private static ItemData? PendingDroppedItem; private static float PendingDroppedItemSetAt; private const float PendingDroppedItemTtlSeconds = 2.5f; internal static void ApplyDropItemPendingCapture(Harmony harmony, ManualLogSource log) { //IL_0156: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Expected O, but got Unknown try { Type[] array = new Type[3] { typeof(Inventory), typeof(ItemData), typeof(int) }; MethodInfo methodInfo = AccessTools.Method(typeof(Humanoid), "DropItem", array, (Type[])null) ?? AccessTools.DeclaredMethod(typeof(Humanoid), "DropItem", array, (Type[])null); if (methodInfo == null) { MethodInfo[] methods = typeof(Humanoid).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo2 in methods) { if (!(methodInfo2.Name != "DropItem")) { ParameterInfo[] parameters = methodInfo2.GetParameters(); if (parameters.Length == 3 && parameters[0].ParameterType == typeof(Inventory) && parameters[1].ParameterType == typeof(ItemData) && parameters[2].ParameterType == typeof(int)) { methodInfo = methodInfo2; break; } } } } if (methodInfo == null) { log.LogWarning((object)"[DrakeModsLibs] Drop HUD: Humanoid.DropItem(Inventory,ItemData,int) not found."); return; } harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(DropHudMessagePatches), "HumanoidDropItemTypedPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); log.LogInfo((object)("[DrakeModsLibs] Drop HUD: patched typed " + methodInfo.DeclaringType?.Name + "." + methodInfo.Name)); } catch (Exception ex) { log.LogError((object)("[DrakeModsLibs] Drop HUD: typed DropItem patch failed: " + ex)); } } private static void HumanoidDropItemTypedPrefix(Inventory inventory, ItemData item, int amount) { PendingDroppedItem = item; PendingDroppedItemSetAt = Time.time; } internal static void TryRewriteDroppedMessage(ref string msg) { ItemData pendingDroppedItem = PendingDroppedItem; if (pendingDroppedItem?.m_shared == null || string.IsNullOrEmpty(msg)) { return; } float num = Time.time - PendingDroppedItemSetAt; if (num > 2.5f) { PendingDroppedItem = null; return; } string text = "$msg_dropped"; string text2 = ((Localization.instance != null) ? Localization.instance.Localize(text) : text); bool flag = msg.IndexOf(text, StringComparison.Ordinal) >= 0; bool flag2 = msg.IndexOf(text2, StringComparison.OrdinalIgnoreCase) >= 0; bool flag3 = ItemDisplayService.HasCustomName(pendingDroppedItem) || DisplayNameModifierHub.AffectsDisplay(pendingDroppedItem); bool flag4 = flag3 && msg.IndexOf("drop", StringComparison.OrdinalIgnoreCase) >= 0 && msg.Length < 280; if (!flag && !flag2 && !flag4) { return; } string text3 = ((Localization.instance != null) ? Localization.instance.Localize(pendingDroppedItem.m_shared.m_name) : pendingDroppedItem.m_shared.m_name); string displayNameLocalized = ItemDisplayService.GetDisplayNameForUi(pendingDroppedItem, localize: true); if (string.IsNullOrEmpty(displayNameLocalized)) { return; } if (flag3 && msg.StartsWith("$msg_dropped ", StringComparison.Ordinal) && !string.IsNullOrEmpty(pendingDroppedItem.m_shared.m_name)) { string a = msg.Substring("$msg_dropped ".Length).TrimStart(Array.Empty<char>()); if (string.Equals(a, pendingDroppedItem.m_shared.m_name, StringComparison.Ordinal)) { msg = "$msg_dropped " + displayNameLocalized; PendingDroppedItem = null; return; } } if (!string.IsNullOrEmpty(text3)) { try { Regex regex = new Regex(Regex.Escape(text3), RegexOptions.IgnoreCase | RegexOptions.CultureInvariant, TimeSpan.FromMilliseconds(250.0)); if (regex.IsMatch(msg)) { msg = regex.Replace(msg, (Match _) => displayNameLocalized, 1); PendingDroppedItem = null; return; } } catch (RegexMatchTimeoutException) { } } if (!string.IsNullOrEmpty(pendingDroppedItem.m_shared.m_name)) { try { Regex regex2 = new Regex(Regex.Escape(pendingDroppedItem.m_shared.m_name), RegexOptions.IgnoreCase | RegexOptions.CultureInvariant, TimeSpan.FromMilliseconds(250.0)); if (regex2.IsMatch(msg)) { msg = regex2.Replace(msg, (Match _) => displayNameLocalized, 1); PendingDroppedItem = null; return; } } catch (RegexMatchTimeoutException) { } } if (string.Equals(msg, text, StringComparison.Ordinal) || string.Equals(msg, text2, StringComparison.OrdinalIgnoreCase)) { msg = (string.Equals(msg, text, StringComparison.Ordinal) ? (text + " " + displayNameLocalized) : (text2 + " " + displayNameLocalized)); PendingDroppedItem = null; } } internal static void ApplyMessageHudShowMessage(Harmony harmony, ManualLogSource log) { //IL_01f2: Unknown result type (might be due to invalid IL or missing references) //IL_01db: Unknown result type (might be due to invalid IL or missing references) //IL_01f9: Expected O, but got Unknown try { Type typeFromHandle = typeof(MessageHud); Type type = AccessTools.Inner(typeFromHandle, "MessageType") ?? typeFromHandle.GetNestedType("MessageType", BindingFlags.Public | BindingFlags.NonPublic); if (type == null) { log.LogWarning((object)"[DrakeModsLibs] Drop HUD: MessageHud.MessageType nested type not found."); return; } MethodBase methodBase = null; MethodInfo[] methods = typeFromHandle.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (methodInfo.Name != "ShowMessage") { continue; } ParameterInfo[] parameters = methodInfo.GetParameters(); if (parameters.Length >= 2 && !(parameters[0].ParameterType != type) && !(parameters[1].ParameterType != typeof(string))) { if (parameters.Length == 4 && parameters[2].ParameterType == typeof(int) && parameters[3].ParameterType == typeof(Sprite)) { methodBase = methodInfo; break; } if ((object)methodBase == null) { methodBase = methodInfo; } } } if (methodBase == null) { log.LogWarning((object)"[DrakeModsLibs] Drop HUD: no suitable MessageHud.ShowMessage overload found."); return; } int num = -1; ParameterInfo[] parameters2 = methodBase.GetParameters(); for (int j = 0; j < parameters2.Length; j++) { if (parameters2[j].ParameterType == typeof(string)) { num = j; break; } } if (num < 0) { log.LogWarning((object)("[DrakeModsLibs] Drop HUD: ShowMessage overload has no string parameter: " + methodBase)); return; } HarmonyMethod val = ((num == 0) ? new HarmonyMethod(typeof(DropHudMessagePatches), "MessageHudShowMessageStringArg0", (Type[])null) : new HarmonyMethod(typeof(DropHudMessagePatches), "MessageHudShowMessageStringArg1", (Type[])null)); harmony.Patch(methodBase, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); log.LogInfo((object)("[DrakeModsLibs] Drop HUD: patched MessageHud." + methodBase.Name + " stringArg=" + num + " :: " + methodBase)); } catch (Exception ex) { log.LogError((object)("[DrakeModsLibs] Drop HUD: MessageHud patch failed: " + ex)); } } private static void MessageHudShowMessageStringArg0(ref string __0) { TryRewriteDroppedMessage(ref __0); } private static void MessageHudShowMessageStringArg1(ref string __1) { TryRewriteDroppedMessage(ref __1); } [HarmonyPatch(typeof(Character), "Message", new Type[] { typeof(MessageType), typeof(string), typeof(int), typeof(Sprite) })] [HarmonyPrefix] private static void CharacterMessagePrefix(MessageType type, ref string msg) { TryRewriteDroppedMessage(ref msg); } [HarmonyPatch(typeof(Player), "Message", new Type[] { typeof(MessageType), typeof(string), typeof(int), typeof(Sprite) })] [HarmonyPrefix] private static void PlayerMessagePrefix(MessageType type, ref string msg) { TryRewriteDroppedMessage(ref msg); } } internal static class ItemTooltipPatches { private const BindingFlags DeclaredTooltipFlags = BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; internal static void Apply(Harmony harmony, ManualLogSource log) { MethodInfo methodInfo = ResolveGetTooltipPatchTarget(typeof(ItemData)); if (methodInfo == null) { log.LogDebug((object)"[DrakeModsLibs] Crafted-by display: no patchable GetTooltip overload on ItemData hierarchy; grid tooltips still apply crafted-by via CreateItemTooltip."); } else { harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, SelectPostfix(methodInfo), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } private static MethodInfo? ResolveGetTooltipPatchTarget(Type leafType) { MethodInfo methodInfo = null; MethodInfo methodInfo2 = null; MethodInfo methodInfo3 = null; Type type = leafType; while (type != null && type != typeof(object)) { MethodInfo[] methods = type.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo4 in methods) { if (methodInfo4.Name != "GetTooltip" || methodInfo4.ReturnType != typeof(string)) { continue; } if (methodInfo4.IsStatic) { ParameterInfo[] parameters = methodInfo4.GetParameters(); if (parameters.Length == 3 && parameters[0].ParameterType == typeof(ItemData) && parameters[1].ParameterType == typeof(int) && parameters[2].ParameterType == typeof(bool) && (object)methodInfo == null) { methodInfo = methodInfo4; } continue; } ParameterInfo[] parameters2 = methodInfo4.GetParameters(); if (parameters2.Length == 0) { if ((object)methodInfo2 == null) { methodInfo2 = methodInfo4; } } else if (parameters2.Length == 2 && parameters2[0].ParameterType == typeof(int) && parameters2[1].ParameterType == typeof(bool) && (object)methodInfo3 == null) { methodInfo3 = methodInfo4; } } type = type.BaseType; } return methodInfo ?? methodInfo2 ?? methodInfo3; } private static HarmonyMethod SelectPostfix(MethodInfo target) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Expected O, but got Unknown //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Expected O, but got Unknown //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Expected O, but got Unknown if (target.IsStatic) { return new HarmonyMethod(typeof(ItemTooltipPatches), "CraftedByDisplayStaticPostfix", (Type[])null); } ParameterInfo[] parameters = target.GetParameters(); if (parameters.Length == 0) { return new HarmonyMethod(typeof(ItemTooltipPatches), "CraftedByDisplayInstancePostfix", (Type[])null); } if (parameters.Length == 2 && parameters[0].ParameterType == typeof(int) && parameters[1].ParameterType == typeof(bool)) { return new HarmonyMethod(typeof(ItemTooltipPatches), "CraftedByDisplayInstanceQualityCraftingPostfix", (Type[])null); } throw new InvalidOperationException("ResolveGetTooltipPatchTarget and SelectPostfix are out of sync."); } internal static void CraftedByDisplayStaticPostfix(ItemData item, int qualityLevel, bool crafting, ref string __result) { __result = ApplyCraftedByDisplayToTooltipText(__result, item); } internal static void CraftedByDisplayInstancePostfix(ItemData __instance, ref string __result) { __result = ApplyCraftedByDisplayToTooltipText(__result, __instance); } internal static void CraftedByDisplayInstanceQualityCraftingPostfix(ItemData __instance, int qualityLevel, bool crafting, ref string __result) { __result = ApplyCraftedByDisplayToTooltipText(__result, __instance); } internal static string ApplyCraftedByDisplayToTooltipText(string text, ItemData item) { if (string.IsNullOrEmpty(text) || item?.m_customData == null) { return text; } if (item.m_crafterID == 0) { return text; } string text2 = item.m_crafterName ?? ""; if (string.IsNullOrEmpty(text2)) { return text; } string value; bool flag = item.m_customData.TryGetValue("Drake_CraftedByDisplay", out value) && !string.IsNullOrEmpty(value); string value2; bool flag2 = item.m_customData.TryGetValue("Drake_CraftedByLineLabel", out value2) && !string.IsNullOrEmpty(value2); if (!flag && !flag2) { return text; } string text3 = (flag ? value : text2); if (flag) { text3 = TooltipRichText.EnsureRichTextTagsClosedForTooltip(text3); text3 = TooltipRichText.WrapCraftedByDisplayWithDefaultStatColorIfNeeded(text3); } string lineLabelOverride = (flag2 ? value2 : null); return ReplaceCraftedBySegment(text, text2, text3, lineLabelOverride); } internal static string ReplaceCraftedBySegment(string text, string oldName, string newDisplay, string? lineLabelOverride = null) { if (string.IsNullOrEmpty(text) || string.IsNullOrEmpty(oldName)) { return text; } if (!string.IsNullOrEmpty(lineLabelOverride)) { string text2 = TryReplaceCraftedByWithCustomLineLabel(text, oldName, newDisplay, lineLabelOverride); if (text2 != null) { return text2; } } string text3 = "\n$item_crafter: " + oldName + " "; if (text.Contains(text3)) { return text.Replace(text3, "\n$item_crafter: " + newDisplay + " "); } string text4 = "\n$item_crafter: " + oldName; if (text.Contains(text4)) { return text.Replace(text4, "\n$item_crafter: " + newDisplay); } string text5 = "Crafted by"; if (Localization.instance != null) { string text6 = Localization.instance.Localize("$item_crafter"); if (!string.IsNullOrEmpty(text6)) { text5 = text6; } } string[] array = new string[2] { " ", "" }; foreach (string text7 in array) { string text8 = "\n" + text5 + ": " + oldName + text7; if (text.Contains(text8)) { return text.Replace(text8, "\n" + text5 + ": " + newDisplay + ((text7 == " ") ? " " : "")); } } try { string text9 = Regex.Escape(text5); string text10 = Regex.Escape(oldName); Regex regex = new Regex("(?<prefix>\\n[^\\n]*?" + text9 + "\\s*:\\s*)(?:<color[^>]*>)?" + text10 + "(?:</color>)?", RegexOptions.None, TimeSpan.FromMilliseconds(250.0)); if (regex.IsMatch(text)) { return regex.Replace(text, (Match m) => m.Groups["prefix"].Value + newDisplay); } } catch (RegexMatchTimeoutException) { } try { Regex regex2 = new Regex("(?<prefix>\\n[^\\n]*?\\$item_crafter\\s*:\\s*)(?:<color[^>]*>)?" + Regex.Escape(oldName) + "(?:</color>)?", RegexOptions.None, TimeSpan.FromMilliseconds(250.0)); if (regex2.IsMatch(text)) { return regex2.Replace(text, (Match m) => m.Groups["prefix"].Value + newDisplay); } } catch (RegexMatchTimeoutException) { } string[] array2 = text.Split(new string[2] { "\r\n", "\n" }, StringSplitOptions.None); for (int num = 0; num < array2.Length; num++) { string text11 = array2[num]; if ((text11.IndexOf("$item_crafter", StringComparison.OrdinalIgnoreCase) >= 0 || text11.IndexOf(text5, StringComparison.OrdinalIgnoreCase) >= 0) && text11.IndexOf(oldName, StringComparison.Ordinal) >= 0) { int startIndex = text11.IndexOf(oldName, StringComparison.Ordinal); array2[num] = text11.Remove(startIndex, oldName.Length).Insert(startIndex, newDisplay); return string.Join("\n", array2); } } return text; } private static string? TryReplaceCraftedByWithCustomLineLabel(string text, string oldName, string newDisplay, string lineLabelOverride) { string text2 = "\n$item_crafter: " + oldName + " "; if (text.Contains(text2)) { return text.Replace(text2, "\n" + lineLabelOverride + ": " + newDisplay + " "); } string text3 = "\n$item_crafter: " + oldName; if (text.Contains(text3)) { return text.Replace(text3, "\n" + lineLabelOverride + ": " + newDisplay); } string text4 = "Crafted by"; if (Localization.instance != null) { string text5 = Localization.instance.Localize("$item_crafter"); if (!string.IsNullOrEmpty(text5)) { text4 = text5; } } string[] array = new string[2] { " ", "" }; foreach (string text6 in array) { string text7 = "\n" + text4 + ": " + oldName + text6; if (text.Contains(text7)) { return text.Replace(text7, "\n" + lineLabelOverride + ": " + newDisplay + ((text6 == " ") ? " " : "")); } } try { string text8 = Regex.Escape(text4); string text9 = Regex.Escape(oldName); Regex regex = new Regex("(?<whole>\\n[^\\n]*?" + text8 + "\\s*:\\s*)(?:<color[^>]*>)?" + text9 + "(?:</color>)?", RegexOptions.None, TimeSpan.FromMilliseconds(250.0)); if (regex.IsMatch(text)) { return regex.Replace(text, (Match _) => "\n" + lineLabelOverride + ": " + newDisplay); } } catch (RegexMatchTimeoutException) { } try { Regex regex2 = new Regex("(?<whole>\\n[^\\n]*?\\$item_crafter\\s*:\\s*)(?:<color[^>]*>)?" + Regex.Escape(oldName) + "(?:</color>)?", RegexOptions.None, TimeSpan.FromMilliseconds(250.0)); if (regex2.IsMatch(text)) { return regex2.Replace(text, (Match _) => "\n" + lineLabelOverride + ": " + newDisplay); } } catch (RegexMatchTimeoutException) { } string[] array2 = text.Split(new string[2] { "\r\n", "\n" }, StringSplitOptions.None); for (int num = 0; num < array2.Length; num++) { string text10 = array2[num]; if ((text10.IndexOf("$item_crafter", StringComparison.OrdinalIgnoreCase) >= 0 || text10.IndexOf(text4, StringComparison.OrdinalIgnoreCase) >= 0) && text10.IndexOf(oldName, StringComparison.Ordinal) >= 0) { array2[num] = lineLabelOverride + ": " + newDisplay; return string.Join("\n", array2); } } return null; } } } namespace DrakeModsLibs.Input { public sealed class MenuBindingRegistration { public string Id { get; } public string Scope { get; } public int Priority { get; } public Func<string?> GetBindingString { get; } public string ModLabel { get; } internal MenuBindingRegistration(string id, string scope, int priority, Func<string?> getBindingString, string modLabel) { Id = id; Scope = scope; Priority = priority; GetBindingString = getBindingString; ModLabel = modLabel; } } public static class MenuBindingRegistry { public const string InventoryContextScope = "inventory.context"; private static readonly List<MenuBindingRegistration> Registrations = new List<MenuBindingRegistration>(); private static readonly HashSet<string> LoggedConflicts = new HashSet<string>(); private static ManualLogSource? Log; internal static void SetLogger(ManualLogSource log) { Log = log; } public static void Register(string id, string scope, int priority, Func<string?> getBindingString, string modLabel) { MenuBindingRegistration menuBindingRegistration = new MenuBindingRegistration(id, scope, priority, getBindingString, modLabel); Registrations.Add(menuBindingRegistration); WarnOnBindingConflict(menuBindingRegistration); } public static bool IsHeld(string scope, string id) { MenuBindingRegistration menuBindingRegistration = Registrations.FirstOrDefault((MenuBindingRegistration r) => r.Scope == scope && r.Id == id); return menuBindingRegistration != null && MenuKeyBinding.IsHeld(menuBindingRegistration.GetBindingString()); } public static string? GetActiveBindingId(string scope) { foreach (MenuBindingRegistration item in from r in Registrations where r.Scope == scope orderby r.Priority descending select r) { if (MenuKeyBinding.IsHeld(item.GetBindingString())) { return item.Id; } } return null; } private static void WarnOnBindingConflict(MenuBindingRegistration added) { string text = Normalize(added.GetBindingString()); if (string.IsNullOrEmpty(text)) { return; } foreach (MenuBindingRegistration registration in Registrations) { if (registration.Id == added.Id || registration.Scope != added.Scope || Normalize(registration.GetBindingString()) != text) { continue; } string item = registration.Id + "|" + added.Id + "|" + text; if (LoggedConflicts.Add(item)) { ManualLogSource? log = Log; if (log != null) { log.LogWarning((object)("[DrakeModsLibs] Menu binding conflict in scope '" + added.Scope + "': " + registration.ModLabel + " (" + registration.Id + ") and " + added.ModLabel + " (" + added.Id + ") both use '" + text + "'.")); } } } } private static string Normalize(string? binding) { if (string.IsNullOrWhiteSpace(binding)) { return ""; } return binding.Trim().ToLowerInvariant(); } } public static class MenuKeyBinding { public static bool IsHeld(string? binding) { if (string.IsNullOrWhiteSpace(binding)) { return false; } string text = binding.Trim(); if (string.Equals(text, "None", StringComparison.OrdinalIgnoreCase)) { return true; } foreach (string item in SplitTokens(text)) { if (!IsTokenHeld(item)) { return false; } } return true; } public static string FormatForDisplay(string? binding, string emptyFallback = "Key") { if (string.IsNullOrWhiteSpace(binding)) { return emptyFallback; } string text = binding.Trim(); if (string.Equals(text, "None", StringComparison.OrdinalIgnoreCase)) { return ""; } List<string> list = SplitTokens(text).ToList(); return (list.Count == 0) ? text : string.Join(" + ", list.Select(FormatToken)); } private static IEnumerable<string> SplitTokens(string binding) { return from t in binding.Split(new char[4] { '+', ',', '&', ';' }, StringSplitOptions.RemoveEmptyEntries) select t.Trim() into t where t.Length > 0 select t; } private static bool IsTokenHeld(string token) { //IL_009f: Unknown result type (might be due to invalid IL or missing references) switch (token.ToLowerInvariant()) { case "shift": return Input.GetKey((KeyCode)304) || Input.GetKey((KeyCode)303); case "ctrl": case "control": return Input.GetKey((KeyCode)306) || Input.GetKey((KeyCode)305); case "alt": return Input.GetKey((KeyCode)308) || Input.GetKey((KeyCode)307); default: { KeyCode result; return Enum.TryParse<KeyCode>(token, ignoreCase: true, out result) && Input.GetKey(result); } } } private unsafe static string FormatToken(string token) { switch (token.ToLowerInvariant()) { case "shift": return "Shift"; case "ctrl": case "control": return "Ctrl"; case "alt": return "Alt"; default: { KeyCode result; return Enum.TryParse<KeyCode>(token, ignoreCase: true, out result) ? ((object)(*(KeyCode*)(&result))/*cast due to .constrained prefix*/).ToString() : token; } } } } } namespace DrakeModsLibs.Display { internal sealed class BuiltInRenameItNameLayer : IItemDisplayNameLayer { public int Priority => 100; public bool TryApply(ItemData? item, ref string displayName) { if (item?.m_customData == null) { return false; } if (!item.m_customData.TryGetValue("Drake_Rename", out var value) || string.IsNullOrEmpty(value)) { return false; } displayName = value; return true; } } internal static class DisplayNameModifierHub { public static bool AffectsDisplay(ItemData? item) { foreach (IDisplayNameModifier displayNameModifier in CustomizeLibsRuntime.DisplayNameModifiers) { if (displayNameModifier.AffectsDisplay(item)) { return true; } } return false; } public static string GetPrefixRaw(ItemData? item) { foreach (IDisplayNameModifier displayNameModifier in CustomizeLibsRuntime.DisplayNameModifiers) { string prefixRaw = displayNameModifier.GetPrefixRaw(item); if (!string.IsNullOrEmpty(prefixRaw)) { return prefixRaw; } } return ""; } } internal static class DisplayNameResolutionPipeline { internal static void EnsureDefaultLayers() { if (!CustomizeLibsRuntime.DefaultDisplayLayersRegistered) { CustomizeLibsRuntime.DefaultDisplayLayersRegistered = true; RegisterLayer(new BuiltInRenameItNameLayer()); } } public static void RegisterLayer(IItemDisplayNameLayer layer) { if (layer != null && !CustomizeLibsRuntime.DisplayNameLayers.Contains(layer)) { CustomizeLibsRuntime.DisplayNameLayers.Add(layer); } } internal static string Resolve(ItemData? item, bool localize) { EnsureDefaultLayers(); if (item?.m_shared == null) { return ""; } string displayName = item.m_shared.m_name; foreach (IItemDisplayNameLayer item2 in CustomizeLibsRuntime.DisplayNameLayers.OrderBy((IItemDisplayNameLayer l) => l.Priority)) { item2.TryApply(item, ref displayName); } string prefixRaw = DisplayNameModifierHub.GetPrefixRaw(item); string text = (string.IsNullOrEmpty(prefixRaw) ? displayName : (prefixRaw + " " + displayName)); string text2 = TooltipRichText.EnsureRichTextTagsClosedForTooltip(text); if (!localize || Localization.instance == null) { return text2; } return Localization.instance.Localize(text2); } } public static class ItemDisplayService { public static string GetProperName(ItemData? item) { if (item?.m_shared == null) { return ""; } return GetProperName(item, item.m_shared.m_name); } public static string GetProperName(ItemData? item, string defaultName) { if (item == null) { return defaultName; } if (item.m_customData == null) { item.m_customData = new Dictionary<string, string>(); } string value; return item.m_customData.TryGetValue("Drake_Rename", out value) ? value : defaultName; } public static string GetProperDescription(ItemData? item) { if (item?.m_shared == null) { return ""; } return GetProperDescription(item, item.m_shared.m_description); } public static string GetProperDescription(ItemData? item, string defaultDesc) { if (item == null) { return defaultDesc; } if (item.m_customData == null) { item.m_customData = new Dictionary<string, string>(); } string value; return item.m_customData.TryGetValue("Drake_Rename_Desc", out value) ? value : defaultDesc; } public static string GetDisplayNameForUi(ItemData? item, bool localize) { return DisplayNameResolutionPipeline.Resolve(item, localize); } public static bool HasCustomName(ItemData? item) { return item?.m_customData != null && item.m_customData.ContainsKey("Drake_Rename"); } public static bool HasCustomDescription(ItemData? item) { return item?.m_customData != null && item.m_customData.ContainsKey("Drake_Rename_Desc"); } public static bool HasCraftedByDisplayOverride(ItemData? item) { string value; return item?.m_customData != null && item.m_customData.TryGetValue("Drake_CraftedByDisplay", out value) && !string.IsNullOrEmpty(value); } public static bool HasCraftedByLineLabelOverride(ItemData? item) { string value; return item?.m_customData != null && item.m_customData.TryGetValue("Drake_CraftedByLineLabel", out value) && !string.IsNullOrEmpty(value); } public static bool HasAnyCustomization(ItemData? item) { if (item?.m_customData == null) { return false; } if (HasCustomName(item) || HasCustomDescription(item)) { return true; } return HasCraftedByDisplayOverride(item) || HasCraftedByLineLabelOverride(item); } public static void SetCustomName(ItemData item, string? name) { if (item.m_customData == null) { item.m_customData = new Dictionary<string, string>(); } if (string.IsNullOrEmpty(name)) { item.m_customData.Remove("Drake_Rename"); } else { item.m_customData["Drake_Rename"] = name; } } public static void SetCustomDescription(ItemData item, string? desc) { if (item.m_customData == null) { item.m_customData = new Dictionary<string, string>(); } if (string.IsNullOrEmpty(desc)) { item.m_customData.Remove("Drake_Rename_Desc"); } else { item.m_customData["Drake_Rename_Desc"] = desc; } } public static string GetCraftedByDisplay(ItemData? item) { if (item?.m_customData == null) { return item?.m_crafterName ?? ""; } if (item.m_customData.TryGetValue("Drake_CraftedByDisplay", out var value) && !string.IsNullOrEmpty(value)) { return value; } return item.m_crafterName ?? ""; } public static void SetCraftedByDisplay(ItemData item, string? display) { if (item.m_customData == null) { item.m_customData = new Dictionary<string, string>(); } if (string.IsNullOrEmpty(display)) { item.m_customData.Remove("Drake_CraftedByDisplay"); } else { item.m_customData["Drake_CraftedByDisplay"] = display; } } public static void SetCraftedByLineLabel(ItemData item, string? lineLabel) { if (item.m_customData == null) { item.m_customData = new Dictionary<string, string>(); } if (string.IsNullOrEmpty(lineLabel)) { item.m_customData.Remove("Drake_CraftedByLineLabel"); } else { item.m_customData["Drake_CraftedByLineLabel"] = lineLabel; } } public static void ClearCraftedByOverrides(ItemData item) { if (item.m_customData != null) { item.m_customData.Remove("Drake_CraftedByDisplay"); item.m_customData.Remove("Drake_CraftedByLineLabel"); } } } internal static class TooltipRichText { private enum RtKind { Color, Size } private static readonly Regex HexColorRegex = new Regex("^[0-9a-fA-F]{3,8}$", RegexOptions.Compiled); internal static string EnsureRichTextTagsClosedForTooltip(string? text) { if (text == null || text.Length == 0) { return ""; } Stack<RtKind> stack = new Stack<RtKind>(); int num = 0; while (num < text.Length) { if (text[num] != '<') { num++; continue; } int num2 = text.IndexOf('>', num); if (num2 < 0) { break; } string text2 = text.Substring(num, num2 - num + 1); if (text2.Length >= 2 && text2[1] == '/') { if (text2.StartsWith("</color", StringComparison.OrdinalIgnoreCase)) { if (stack.Count > 0 && stack.Peek() == RtKind.Color) { stack.Pop(); } } else if (text2.StartsWith("</size", StringComparison.OrdinalIgnoreCase) && stack.Count > 0 && stack.Peek() == RtKind.Size) { stack.Pop(); } } else if (text2.StartsWith("<color", StringComparison.OrdinalIgnoreCase)) { stack.Push(RtKind.Color); } else if (IsHashColorOpenTag(text2)) { stack.Push(RtKind.Color); } else if (text2.StartsWith("<size", StringComparison.OrdinalIgnoreCase)) { stack.Push(RtKind.Size); } num = num2 + 1; } if (stack.Count == 0) { return text; } StringBuilder stringBuilder = new StringBuilder(text, text.Length + stack.Count * 10); while (stack.Count > 0) { RtKind rtKind = stack.Pop(); stringBuilder.Append((rtKind == RtKind.Color) ? "</color>" : "</size>"); } return stringBuilder.ToString(); } internal static string WrapCraftedByDisplayWithDefaultStatColorIfNeeded(string text) { if (string.IsNullOrEmpty(text)) { return text; } if (HasExplicitColorMarkup(text)) { return text; } return GetValheimTooltipStatColorOpenTag() + text + "</color>"; } internal static bool HasExplicitColorMarkup(string text) { if (string.IsNullOrEmpty(text)) { return false; } if (text.IndexOf("<color", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } for (int i = 0; i < text.Length - 3; i++) { if (text[i] == '<' && text[i + 1] == '#') { int j; for (j = i + 2; j < text.Length && IsHex(text[j]); j++) { } if (j != i + 2 && j - (i + 2) >= 3 && j - (i + 2) <= 8 && j < text.Length && text[j] == '>') { return true; } } } return false; } private static bool IsHex(char c) { switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': return true; default: return false; } } private static string GetValheimTooltipStatColorOpenTag() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: 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) try { GUIManager instance = GUIManager.Instance; if (instance != null) { Color valheimOrange = instance.ValheimOrange; return "<color=#" + ColorUtility.ToHtmlStringRGB(valheimOrange) + ">"; } } catch { } return "<color=#ff8800>"; } private static bool IsHashColorOpenTag(string tag) { if (tag.Length < 5 || tag[0] != '<' || tag[1] != '#' || tag[tag.Length - 1] != '>') { return false; } string input = tag.Substring(2, tag.Length - 3); return HexColorRegex.IsMatch(input); } } } namespace DrakeModsLibs.Data { public static class DrakeCustomDataCatalog { public const string ModWorkshopLibs = "DrakeModsLibs"; public const string ModRenameIt = "DrakesRenameIt"; public const string ModQuestItems = "DrakesQuestItems"; public const string ModItemShop = "DrakesItemShop"; private static readonly Dictionary<string, DrakeCustomDataField> ByKey; private static readonly Dictionary<string, List<DrakeCustomDataField>> ByMod; static DrakeCustomDataCatalog() { ByKey = new Dictionary<string, DrakeCustomDataField>(StringComparer.Ordinal); ByMod = new Dictionary<string, List<DrakeCustomDataField>>(StringComparer.Ordinal); RegisterBuiltInFields(); } public static void RegisterModFields(string modId, IEnumerable<DrakeCustomDataField> fields) { if (string.IsNullOrEmpty(modId) || fields == null) { return; } if (!ByMod.TryGetValue(modId, out List<DrakeCustomDataField> value)) { value = new List<DrakeCustomDataField>(); ByMod[modId] = value; } foreach (DrakeCustomDataField field in fields) { if (field != null && !string.IsNullOrEmpty(field.Key)) { ByKey[field.Key] = field; value.RemoveAll((DrakeCustomDataField f) => string.Equals(f.Key, field.Key, StringComparison.Ordinal)); value.Add(field); } } } public static bool TryGetField(string key, out DrakeCustomDataField field) { return ByKey.TryGetValue(key, out field); } public static IReadOnlyList<DrakeCustomDataField> GetFieldsForMod(string modId) { if (string.IsNullOrEmpty(modId) || !ByMod.TryGetValue(modId, out List<DrakeCustomDataField> value)) { return Array.Empty<DrakeCustomDataField>(); } return value.ToArray(); } public static IReadOnlyList<DrakeCustomDataField> GetAllFields() { return ByKey.Values.ToArray(); } public static bool IsDrakeKey(string key) { return !string.IsNullOrEmpty(key) && (key.StartsWith("Drake_", StringComparison.Ordinal) || ByKey.ContainsKey(key)); } private static void RegisterBuiltInFields() { RegisterModFields("DrakesRenameIt", new DrakeCustomDataField[7] { new DrakeCustomDataField("Drake_Rename", "DrakesRenameIt", DrakeCustomDataKind.Text, "Custom name"), new DrakeCustomDataField("Drake_Rename_Desc", "DrakesRenameIt", DrakeCustomDataKind.Text, "Custom description"), new DrakeCustomDataField("Drake_CraftedByDisplay", "DrakesRenameIt", DrakeCustomDataKind.Text, "Crafted-by display"), new DrakeCustomDataField("Drake_CraftedByLineLabel", "DrakesRenameIt", DrakeCustomDataKind.Text, "Crafted-by line label"), new DrakeCustomDataField("Drake_RenameUnlocked", "DrakesRenameIt", DrakeCustomDataKind.Tag, "Rename unlocked"), new DrakeCustomDataField("Drake_PublicRewrite", "DrakesRenameIt", DrakeCustomDataKind.Tag, "Anyone can rewrite name/desc"), new DrakeCustomDataField("DrakeRenameIt_CustomName", "DrakesRenameIt", DrakeCustomDataKind.Text, "Item stand hover name (ZDO)") }); RegisterModFields("DrakeModsLibs", new DrakeCustomDataField[3] { new DrakeCustomDataField("Drake_NoRename", "DrakeModsLibs", DrakeCustomDataKind.Tag, "Block rename"), new DrakeCustomDataField("Drake_NoDesc", "DrakeModsLibs", DrakeCustomDataKind.Tag, "Block description edit"), new DrakeCustomDataField("Drake_NoCraftedByEdit", "DrakeModsLibs", DrakeCustomDataKind.Tag, "Block crafted-by edit") }); RegisterModFields("DrakesQuestItems", new DrakeCustomDataField[1] { new DrakeCustomDataField("Drake_QuestItem", "DrakesQuestItems", DrakeCustomDataKind.Tag, "Quest item") }); RegisterModFields("DrakesItemShop", new DrakeCustomDataField[1] { new DrakeCustomDataField("Drake_MarketPrice", "DrakesItemShop", DrakeCustomDataKind.Text, "Market price") }); } } public static class DrakeCustomDataKeys { public const string Prefix = "Drake_"; public const string Rename = "Drake_Rename"; public const string RenameDescription = "Drake_Rename_Desc"; public const string CraftedByDisplay = "Drake_CraftedByDisplay"; public const string CraftedByLineLabel = "Drake_CraftedByLineLabel"; public const string RenameUnlocked = "Drake_RenameUnlocked"; public const string PublicRewrite = "Drake_PublicRewrite"; public const string ItemStandHoverName = "DrakeRenameIt_CustomName"; public const string NoRename = "Drake_NoRename"; public const string NoDescription = "Drake_NoDesc"; public const string NoCraftedByEdit = "Drake_NoCraftedByEdit"; public const string QuestItem = "Drake_QuestItem"; public const string MarketPrice = "Drake_MarketPrice"; } internal static class ItemCustomDataReader { private readonly struct DumpEntry { public string Key { get; } public string Display { get; } public string ModId { get; } public string? Label { get; } public DumpEntry(string key, string display, string modId, string? label) { Key = key; Display = display; ModId = modId; Label = label; } } public static bool TryGetRawValue(ItemData? item, string key, out string? value) { value = null; if (item?.m_customData == null || string.IsNullOrEmpty(key)) { return false; } if (!item.m_customData.TryGetValue(key, out var value2)) { return false; } value = value2; return true; } public static string? GetDisplayValue(ItemData? item, string key) { if (string.IsNullOrEmpty(key)) { return null; } if (DrakeCustomDataCatalog.TryGetField(key, out DrakeCustomDataField field)) { if (field.Kind == DrakeCustomDataKind.Tag) { return DrakeTagManager.HasTag(item, key) ? "true" : null; } string value; return TryGetRawValue(item, key, out value) ? value : null; } if (DrakeTagManager.HasTag(item, key)) { return "true"; } string value2; return TryGetRawValue(item, key, out value2) ? value2 : null; } public static string Dump(ItemData? item, ItemCustomDataDumpOptions? options) { if (options == null) { options = new ItemCustomDataDumpOptions(); } List<DumpEntry> entries = CollectEntries(item, options); return (options.Format == ItemCustomDataDumpFormat.Json) ? DumpJson(item, entries, options) : DumpNeat(item, entries, options); } private static List<DumpEntry> CollectEntries(ItemData? item, ItemCustomDataDumpOptions options) { List<DumpEntry> entries = new List<DumpEntry>(); HashSet<string> seen = new HashSet<string>(StringComparer.Ordinal); foreach (DrakeCustomDataField allField in DrakeCustomDataCatalog.GetAllFields()) { Add(allField.Key, GetDisplayValue(item, allField.Key), allField.ModId, allField.Label); } if (item?.m_customData != null && options.IncludeUnknownKeys) { foreach (KeyValuePair<string, string> customDatum in item.m_customData) { if (!seen.Contains(customDatum.Key) && PassesFilter(customDatum.Key, "(unregistered)", options)) { Add(customDatum.Key, GetDisplayValue(item, customDatum.Key), "(unregistered)", null); } } } entries.Sort(delegate(DumpEntry a, DumpEntry b) { int num = string.Compare(a.ModId, b.ModId, StringComparison.Ordinal); return (num != 0) ? num : string.Compare(a.Key, b.Key, StringComparison.Ordinal); }); return entries; void Add(string key, string? display, string modId, string? label) { if (PassesFilter(key, modId, options) && (display != null || options.IncludeEmpty) && seen.Add(key)) { entries.Add(new DumpEntry(key, display ?? "", modId, label)); } } } private static bool PassesFilter(string key, string modId, ItemCustomDataDumpOptions options) { if (!string.IsNullOrEmpty(options.Key) && !string.Equals(key, options.Key, StringComparison.Ordinal)) { return false; } if (!string.IsNullOrEmpty(options.ModId) && !string.Equals(modId, options.ModId, StringComparison.Ordinal)) { return false; } if (options.DrakeKeysOnly && !DrakeCustomDataCatalog.IsDrakeKey(key)) { return false; } return true; } private static string DumpNeat(ItemData? item, List<DumpEntry> entries, ItemCustomDataDumpOptions options) { StringBuilder stringBuilder = new StringBuilder(); if (options.IncludeItemSummary) { stringBuilder.AppendLine(SummarizeItem(item)); } string a = null; foreach (DumpEntry entry in entries) { if (!string.Equals(a, entry.ModId, StringComparison.Ordinal)) { if (stringBuilder.Length > 0 && stringBuilder[stringBuilder.Length - 1] != '\n') { stringBuilder.AppendLine(); } stringBuilder.Append('[').Append(entry.ModId).Append(']') .AppendLine(); a = entry.ModId; } stringBuilder.Append(" ").Append(entry.Key); if (!string.IsNullOrEmpty(entry.Label)) { stringBuilder.Append(" (").Append(entry.Label).Append(')'); } stringBuilder.Append(" = "); stringBuilder.AppendLine(FormatNeatValue(entry.Display)); } if (entries.Count == 0) { stringBuilder.AppendLine("(no matching custom data)"); } return stringBuilder.ToString().TrimEnd(Array.Empty<char>()); } private static string DumpJson(ItemData? item, List<DumpEntry> entries, ItemCustomDataDumpOptions options) { StringBuilder stringBuilder = new StringBuilder("{"); if (options.IncludeItemSummary) { AppendJsonProp(stringBuilder, "item", SummarizeItem(item), first: true); stringBuilder.Append(','); } bool flag = !options.IncludeItemSummary; foreach (DumpEntry entry in entries) { if (!flag) { stringBuilder.Append(','); } AppendJsonProp(stringBuilder, entry.Key, entry.Display, flag); flag = false; } stringBuilder.Append('}'); return stringBuilder.ToString(); } private static void AppendJsonProp(StringBuilder sb, string key, string value, bool first) { if (!first) { sb.Append(','); } sb.Append('"').Append(JsonEscape(key)).Append("\":\"") .Append(JsonEscape(value)) .Append('"'); } private static string FormatNeatValue(string display) { bool flag = ((display == "true" || display == "false") ? true : false); return flag ? display : ("\"" + display + "\""); } private static string SummarizeItem(ItemData? item) { if (item?.m_shared == null) { return "item: (null)"; } string arg = item.m_shared.m_name ?? "?"; return $"item: {arg} x{item.m_stack}"; } private static string JsonEscape(string s) { if (string.IsNullOrEmpty(s)) { return ""; } StringBuilder stringBuilder = new StringBuilder(s.Length); foreach (char c in s) { switch (c) { case '\\': stringBuilder.Append("\\\\"); continue; case '"': stringBuilder.Append("\\\""); continue; case '\n': stringBuilder.Append("\\n"); continue; case '\r': stringBuilder.Append("\\r"); continue; case '\t': stringBuilder.Append("\\t"); continue; } if (c < ' ') { stringBuilder.AppendFormat("\\u{0:X4}", (int)c); } else { stringBuilder.Append(c); } } return stringBuilder.ToString(); } } } namespace DrakeModsLibs.API { public static class CustomizationEvents { public static event Action<Player, ItemData, string, string>? OnItemNameChanged; public static event Action<Player, ItemData, string, string>? OnItemDescriptionChanged; public static event Action<Player, ItemData, string, string, string>? OnCraftedByDisplayChanged; public static void RaiseNameChanged(Player player, ItemData item, string oldName, string newName) { CustomizationEvents.OnItemNameChanged?.Invoke(player, item, oldName, newName); } public static void RaiseDescriptionChanged(Player player, ItemData item, string oldDesc, string newDesc) { CustomizationEvents.OnItemDescriptionChanged?.Invoke(player, item, oldDesc, newDesc); } public static void RaiseCraftedByDisplayChanged(Player player, ItemData item, string itemPrefabName, string oldDisplay, string newDisplay) { CustomizationEvents.OnCraftedByDisplayChanged?.Invoke(player, item, itemPrefabName, oldDisplay, newDisplay); } } public static class CustomizeLibsAPI { public static DrakeConfigSync CreateConfigSync(string modId, string displayName, string currentVersion, string minimumRequiredVersion = null) { return DrakeConfigSync.Create(modId, displayName, currentVersion, minimumRequiredVersion); } public static string GetDisplayNameForUi(ItemData? item, bool localize = false) { return ItemDisplayService.GetDisplayNameForUi(item, localize); } public static string GetProperName(ItemData? item) { return ItemDisplayService.GetProperName(item); } public static string GetProperDescription(ItemData? item) { return ItemDisplayService.GetProperDescription(item); } public static bool HasCustomName(ItemData? item) { return ItemDisplayService.HasCustomName(item); } public static bool HasCustomDescription(ItemData? item) { return ItemDisplayService.HasCustomDescription(item); } public static string GetProperName(ItemData? item, string defaultName) { return ItemDisplayService.GetProperName(item, defaultName); } public static string GetProperDescription(ItemData? item, string defaultDesc) { return ItemDisplayService.GetProperDescription(item, defaultDesc); } public static bool HasCraftedByDisplayOverride(ItemData? item) { return ItemDisplayService.HasCraftedByDisplayOverride(item); } public static bool HasCraftedByLineLabelOverride(ItemData? item) { return ItemDisplayService.HasCraftedByLineLabelOverride(item); } public static bool HasAnyCustomization(ItemData? item) { return ItemDisplayService.HasAnyCustomization(item); } public static string GetCraftedByDisplay(ItemData? item) { return ItemDisplayService.GetCraftedByDisplay(item); } public static void SetCustomName(ItemData item, string? name) { ItemDisplayService.SetCustomName(item, name); } public static void SetCustomDescription(ItemData item, string? desc) { ItemDisplayService.SetCustomDescription(item, desc); } public static void SetCraftedByDisplay(ItemData item, string? display) { ItemDisplayService.SetCraftedByDisplay(item, display); } public static void SetCraftedByLineLabel(ItemData item, string? lineLabel) { ItemDisplayService.SetCraftedByLineLabel(item, lineLabel); } public static void ClearCraftedByOverrides(ItemData item) { ItemDisplayService.ClearCraftedByOverrides(item); } public static bool IsBlockedByTag(CustomizeOperation operation, ItemData? item) { return CustomizationGatekeeper.IsBlockedByTag(operation, item); } public static void RegisterDisplayNameModifier(IDisplayNameModifier modifier) { if (modifier != null && !CustomizeLibsRuntime.DisplayNameModifiers.Contains(modifier)) { CustomizeLibsRuntime.DisplayNameModifiers.Add(modifier); } } public static void RegisterStackMergePolicy(IStackMergePolicy policy) { CustomizeLibsRuntime.StackMergePolicy = policy; } public static void SetShowItemStandItemNameWhenNoAccess(bool value) { CustomizeLibsRuntime.ShowItemStandItemNameWhenNoAccess = value; } public static void RefreshItemStandDisplayNames() { ItemStandPatch.RefreshAllItemStandDisplayNames(); } public static bool HasTag(ItemData? item, string tagKey) { return DrakeTagManager.HasTag(item, tagKey); } public static void SetTag(ItemData item, string tagKey) { DrakeTagManager.SetTag(item, tagKey); } public static void ClearTag(ItemData item, string tagKey) { DrakeTagManager.ClearTag(item, tagKey); } public static void RegisterEditValidator(CustomizeOperation operation, CustomizeEditValidator validator) { CustomizationGatekeeper.RegisterValidator(operation, validator); } public static bool CanPerform(CustomizeOperation operation, ItemData? item, Player? player) { return CustomizationGatekeeper.CanPerform(operation, item, player); } public static void RegisterTagBlockRule(string tagKey, CustomizeOperation blockedOperations) { CustomizationGatekeeper.RegisterTagBlockRule(tagKey, blockedOperations); } public static void RegisterDisplayNameLayer(IItemDisplayNameLayer layer) { DisplayNameResolutionPipeline.RegisterLayer(layer); } public static bool CanRenameItem(ItemData? item, Player? player) { return CanPerform(CustomizeOperation.RenameName, item, player); } public static bool CanEditDescription(ItemData? item, Player? player) { return CanPerform(CustomizeOperation.RenameDescription, item, player); } public static bool CanEditCraftedBy(ItemData? item, Player? player) { return CanPerform(CustomizeOperation.EditCraftedBy, item, player); } public static bool TryGetCustomDataRaw(ItemData? item, string key, out string? value) { return ItemCustomDataReader.TryGetRawValue(item, key, out value); } public static string? GetCustomDataRaw(ItemData? item, string key) { string value; return TryGetCustomDataRaw(item, key, out value) ? value : null; } public static string? GetCustomDataValue(ItemData? item, string key) { return ItemCustomDataReader.GetDisplayValue(item, key); } public static string DumpItemCustomData(ItemData? item, ItemCustomDataDumpOptions? options = null) { return ItemCustomDataReader.Dump(item, options); } public static string DumpItemCustomData(ItemData? item, string key) { return DumpItemCustomData(item, new ItemCustomDataDumpOptions { Key = key }); } public static string DumpItemCustomDataForMod(ItemData? item, string modId, ItemCustomDataDumpFormat format = ItemCustomDataDumpFormat.Neat) { return DumpItemCustomData(item, new ItemCustomDataDumpOptions { ModId = modId, Format = format }); } public static string DumpAllDrakeCustomData(ItemData? item, ItemCustomDataDumpFormat format = ItemCustomDataDumpFormat.Neat) { return DumpItemCustomData(item, new ItemCustomDataDumpOptions { DrakeKeysOnly = true, Format = format }); } public static void RegisterModCustomDataFields(string modId, IEnumerable<DrakeCustomDataField> fields) { DrakeCustomDataCatalog.RegisterModFields(modId, fields); } public static IReadOnlyList<DrakeCustomDataField> GetRegisteredCustomDataFields(string? modId = null) { return string.IsNullOrEmpty(modId) ? DrakeCustomDataCatalog.GetAllFields() : DrakeCustomDataCatalog.GetFieldsForMod(modId); } } [Flags] public enum CustomizeOperation { None = 0, RenameName = 1, RenameDescription = 2, EditCraftedBy = 4, SetTag = 8, AllEdits = 7 } public sealed class DrakeCustomDataField { public string Key { get; } public string ModId { get; } public DrakeCustomDataKind Kind { get; } public string? Label { get; } public DrakeCustomDataField(string key, string modId, DrakeCustomDataKind kind, string? label = null) { Key = key ?? throw new ArgumentNullException("key"); ModId = modId ?? throw new ArgumentNullException("modId"); Kind = kind; Label = label; } } public enum DrakeCustomDataKind { Tag, Text } public interface IDisplayNameModifier { bool AffectsDisplay(ItemData? item); string GetPrefixRaw(ItemData? item); } public interface IItemDisplayNameLayer { int Priority { get; } bool TryApply(ItemData? item, ref string displayName); } public static class DisplayNameLayerPriority { public const int RenameItBase = 100; public const int CosmeticPrefix = 150; public const int LootOverlay = 200; public const int ShopOverlay = 300; } public interface IStackMergePolicy { bool SeparateStacksEnabled { get; } bool SeparateStacksHardLock { get; } } public enum ItemCustomDataDumpFormat { Neat, Json } public sealed class ItemCustomDataDumpOptions { public ItemCustomDataDumpFormat Format { get; set; } = ItemCustomDataDumpFormat.Neat; public string? ModId { get; set; } public string? Key { get; set; } public bool DrakeKeysOnly { get; set; } public bool IncludeEmpty { get; set; } public bool IncludeUnknownKeys { get; set; } = true; public bool IncludeItemSummary { get; set; } = true; } public readonly struct TagBlockRule { public string TagKey { get; } public CustomizeOperation BlockedOperations { get; } public TagBlockRule(string tagKey, CustomizeOperation blockedOperations) { TagKey = tagKey; BlockedOperations = blockedOperations; } } } namespace ServerSync { [PublicAPI] internal abstract class OwnConfigEntryBase { public object? LocalBaseValue; public bool SynchronizedConfig = true; public abstract ConfigEntryBase BaseConfig { get; } } [PublicAPI] internal class SyncedConfigEntry<T>(ConfigEntry<T> sourceConfig) : OwnConfigEntryBase() { public readonly ConfigEntry<T> SourceConfig = sourceConfig; public override ConfigEntryBase BaseConfig => (ConfigEntryBase)(object)SourceConfig; public T Value { get { return SourceConfig.Value; } set { SourceConfig.Value = value; } } public void AssignLocalValue(T value) { if (LocalBaseValue == null) { Value = value; } else { LocalBaseValue = value; } } } internal abstract class CustomSyncedValueBase { public object? LocalBaseValue; public readonly string Identifier; public readonly Type Type; private object? boxedValue; protected bool localIsOwner; public readonly int Priority; public object? BoxedValue { get { return boxedValue; } set { boxedValue = value; this.ValueChanged?.Invoke(); } } public event Action? ValueChanged; protected CustomSyncedValueBase(ConfigSync configSync, string identifier, Type type, int priority) { Priority = priority; Identifier = identifier; Type = type; configSync.AddCustomValue(this); localIsOwner = configSync.IsSourceOfTruth; configSync.SourceOfTruthChanged += delegate(bool truth) { localIsOwner = truth; }; } } [PublicAPI] internal sealed class CustomSyncedValue<T> : CustomSyncedValueBase { public T Value { get { return (T)base.BoxedValue; } set { base.BoxedValue = value; } } public CustomSyncedValue(ConfigSync configSync, string identifier, T value = default(T), int priority = 0) : base(configSync, identifier, typeof(T), priority) { Value = value; } public void AssignLocalValue(T value) { if (localIsOwner) { Value = value; } else { LocalBaseValue = value; } } } internal class ConfigurationManagerAttributes { [UsedImplicitly] public bool? ReadOnly = false; } [PublicAPI] internal class ConfigSync { [HarmonyPatch(typeof(ZRpc), "HandlePackage")] private static class SnatchCurrentlyHandlingRPC { public static ZRpc? currentRpc; [HarmonyPrefix] private static void Prefix(ZRpc __instance) { currentRpc = __instance; } } [HarmonyPatch(typeof(ZNet), "Awake")] internal static class RegisterRPCPatch { [HarmonyPostfix] private static void Postfix(ZNet __instance) { isServer = __instance.IsServer(); foreach (ConfigSync configSync2 in configSyncs) { ZRoutedRpc.instance.Register<ZPackage>(configSync2.Name + " ConfigSync", (Action<long, ZPackage>)configSync2.RPC_FromOtherClientConfigSync); if (isServer) { configSync2.InitialSyncDone = true; Debug.Log((object)("Registered '" + configSync2.Name + " ConfigSync' RPC - waiting for incoming connections")); } } if (isServer) { ((MonoBehaviour)__instance).StartCoroutine(WatchAdminListChanges()); } static void SendAdmin(List<ZNetPeer> peers, bool isAdmin) { ZPackage package = ConfigsToPackage(null, null, new PackageEntry[1] { new PackageEntry { section = "Internal", key = "lockexempt", type = typeof(bool), value = isAdmin } }); ConfigSync configSync = configSyncs.First(); if (config