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 BoostDrop v2.1.1
BoostDrop.dll
Decompiled a month ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using BoostDrop.Attribution; using BoostDrop.Console; using BoostDrop.Diagnostics; using BoostDrop.Gui; using BoostDrop.Models; using BoostDrop.Networking; using BoostDrop.Policies; using BoostDrop.Rewards; using BoostDrop.Security; using BoostDrop.Sync; using HarmonyLib; using Jotunn; using Jotunn.Entities; using Jotunn.Extensions; using Jotunn.Managers; using Jotunn.Utils; using Microsoft.CodeAnalysis; using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("BoostDrop")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("BoostDrop")] [assembly: AssemblyTitle("BoostDrop")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [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 BoostDrop { internal static class ModConfig { internal static ConfigEntry<bool> Enabled { get; private set; } internal static ConfigEntry<bool> EnableLogging { get; private set; } internal static ConfigEntry<double> DefaultMultiplier { get; private set; } internal static ConfigEntry<double> MaxMultiplier { get; private set; } internal static ConfigEntry<bool> EnableMining { get; private set; } internal static ConfigEntry<bool> EnableWood { get; private set; } internal static ConfigEntry<bool> EnableFishing { get; private set; } internal static ConfigEntry<bool> EnablePickable { get; private set; } internal static ConfigEntry<bool> EnableMobDrop { get; private set; } internal static ConfigEntry<bool> EnableBossDrop { get; private set; } internal static ConfigEntry<bool> EnableChestLoot { get; private set; } internal static ConfigEntry<bool> EnableCombatDamage { get; private set; } internal static ConfigEntry<bool> EnablePlantGrowth { get; private set; } internal static ConfigEntry<bool> EnableSkillXP { get; private set; } internal static ConfigEntry<bool> LogPlayerIdsOnJoin { get; private set; } internal static ConfigEntry<bool> EnableAdminGui { get; private set; } internal static ConfigEntry<string> PermissionsFile { get; private set; } internal static ConfigEntry<string> GrantsFile { get; private set; } internal static ConfigEntry<bool> WatchPermissionsFile { get; private set; } internal static ConfigEntry<KeyboardShortcut> AdminGuiHotkey { get; private set; } internal static void Bind(ConfigFile config) { //IL_01ee: Unknown result type (might be due to invalid IL or missing references) //IL_01f3: Unknown result type (might be due to invalid IL or missing references) //IL_0200: Expected O, but got Unknown //IL_0220: Unknown result type (might be due to invalid IL or missing references) //IL_022a: Expected O, but got Unknown //IL_024f: Unknown result type (might be due to invalid IL or missing references) //IL_0259: Expected O, but got Unknown //IL_027a: Unknown result type (might be due to invalid IL or missing references) //IL_0284: Expected O, but got Unknown //IL_029e: Unknown result type (might be due to invalid IL or missing references) //IL_02b1: Unknown result type (might be due to invalid IL or missing references) //IL_02b6: Unknown result type (might be due to invalid IL or missing references) //IL_02be: Expected O, but got Unknown //IL_02be: Unknown result type (might be due to invalid IL or missing references) //IL_02c8: Expected O, but got Unknown AcceptableValueRange<double> val = new AcceptableValueRange<double>(1.0, 20.0); Enabled = ConfigFileExtensions.BindConfig<bool>(config, "General", "Enabled", true, "Master switch. Server value is authoritative and synchronized by Jotunn.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); AcceptableValueBase val2 = (AcceptableValueBase)(object)val; DefaultMultiplier = ConfigFileExtensions.BindConfig<double>(config, "General", "DefaultMultiplier", 1.0, "Fallback multiplier when no YAML rule matches. Supports decimals such as 1.5.", true, (int?)null, val2, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); val2 = (AcceptableValueBase)(object)val; MaxMultiplier = ConfigFileExtensions.BindConfig<double>(config, "General", "MaxMultiplier", 10.0, "Safety cap for YAML and temporary grants.", true, (int?)null, val2, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); EnableLogging = ConfigFileExtensions.BindConfig<bool>(config, "General", "EnableLogging", false, "Log successful boosted actions. Server value is synchronized.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); LogPlayerIdsOnJoin = ConfigFileExtensions.BindConfig<bool>(config, "General", "LogPlayerIdsOnJoin", true, "Server logs character name and platform/Steam user ID on join.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); EnableAdminGui = ConfigFileExtensions.BindConfig<bool>(config, "General", "EnableAdminGui", true, "Allow server administrators to open the BoostDrop grant GUI.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); EnableMining = BindCategory(config, "EnableMining", "Apply boost to mining / mineable rocks."); EnableWood = BindCategory(config, "EnableWood", "Apply boost to trees and logs."); EnableFishing = BindCategory(config, "EnableFishing", "Apply boost to fishing."); EnablePickable = BindCategory(config, "EnablePickable", "Apply boost to world pickables/crops."); EnableMobDrop = BindCategory(config, "EnableMobDrop", "Apply boost to creature loot using the highest cumulative player damage contributor."); EnableBossDrop = BindCategory(config, "EnableBossDrop", "Apply boost to boss drops (Gdking, Moder, Yagluth, etc)."); EnableChestLoot = BindCategory(config, "EnableChestLoot", "Apply boost to chest/barrel/crypt loot."); EnableCombatDamage = BindCategory(config, "EnableCombatDamage", "Apply boost to player combat damage."); EnablePlantGrowth = BindCategory(config, "EnablePlantGrowth", "Apply boost to plant growth time. Animal breeding is not modified."); EnableSkillXP = BindCategory(config, "EnableSkillXP", "Apply boost to skill XP gain."); ConfigurationManagerAttributes val3 = new ConfigurationManagerAttributes { Browsable = false }; PermissionsFile = config.Bind<string>("ServerFiles", "PermissionsFile", "BoostDrop_permissions.yaml", new ConfigDescription("Server-only static permission file. Never synchronized.", (AcceptableValueBase)null, new object[1] { val3 })); GrantsFile = config.Bind<string>("ServerFiles", "GrantsFile", "BoostDrop_grants.yaml", new ConfigDescription("Server-only temporary grant file. Never synchronized.", (AcceptableValueBase)null, new object[1] { val3 })); WatchPermissionsFile = config.Bind<bool>("ServerFiles", "WatchPermissionsFile", true, new ConfigDescription("Watch static permissions and grants files for changes on the server.", (AcceptableValueBase)null, new object[1] { val3 })); AdminGuiHotkey = config.Bind<KeyboardShortcut>("Client", "AdminGuiHotkey", new KeyboardShortcut((KeyCode)289, Array.Empty<KeyCode>()), new ConfigDescription("Local hotkey for the admin grant GUI. This setting is not server synchronized.", (AcceptableValueBase)null, new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = false } })); } internal static bool IsCategoryEnabled(ResourceCategory category) { return category switch { ResourceCategory.Mining => EnableMining.Value, ResourceCategory.Wood => EnableWood.Value, ResourceCategory.Fishing => EnableFishing.Value, ResourceCategory.Pickable => EnablePickable.Value, ResourceCategory.MobDrop => EnableMobDrop.Value, ResourceCategory.BossDrop => EnableBossDrop.Value, ResourceCategory.ChestLoot => EnableChestLoot.Value, ResourceCategory.CombatDamage => EnableCombatDamage.Value, ResourceCategory.PlantGrowth => EnablePlantGrowth.Value, ResourceCategory.SkillXP => EnableSkillXP.Value, _ => false, }; } internal static bool IsServerFileSetting(ConfigEntryBase entry) { if ((object)entry != PermissionsFile) { return (object)entry == GrantsFile; } return true; } internal static bool IsWatcherSetting(ConfigEntryBase entry) { return (object)entry == WatchPermissionsFile; } internal static bool IsClientOnlySetting(ConfigEntryBase entry) { return (object)entry == AdminGuiHotkey; } internal static bool RequiresPolicyRefresh(ConfigEntryBase entry) { if ((object)entry != Enabled && (object)entry != DefaultMultiplier && (object)entry != MaxMultiplier && (object)entry != EnableMining && (object)entry != EnableWood && (object)entry != EnableFishing && (object)entry != EnablePickable && (object)entry != EnableMobDrop && (object)entry != EnableBossDrop && (object)entry != EnableChestLoot && (object)entry != EnableCombatDamage && (object)entry != EnablePlantGrowth) { return (object)entry == EnableSkillXP; } return true; } private static ConfigEntry<bool> BindCategory(ConfigFile config, string key, string description) { return ConfigFileExtensions.BindConfig<bool>(config, "Categories", key, true, description, true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); } } internal static class DurationParser { private static readonly Regex RelativePattern = new Regex("^(?<value>\\d+(?:[\\.,]\\d+)?)(?<unit>m|h|d|w)$", RegexOptions.IgnoreCase | RegexOptions.Compiled); internal static bool TryParseExpiry(string? text, DateTime nowUtc, out DateTime? expiryUtc, out string error) { expiryUtc = null; error = string.Empty; if (string.IsNullOrWhiteSpace(text) || text.Equals("permanent", StringComparison.OrdinalIgnoreCase) || text.Equals("perm", StringComparison.OrdinalIgnoreCase) || text.Equals("forever", StringComparison.OrdinalIgnoreCase)) { return true; } string text2 = text.Trim(); if (double.TryParse(text2.Replace(',', '.'), NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { if (result <= 0.0) { error = "duration must be greater than zero"; return false; } expiryUtc = nowUtc.AddDays(result); return true; } Match match = RelativePattern.Match(text2); if (match.Success) { if (!double.TryParse(match.Groups["value"].Value.Replace(',', '.'), NumberStyles.Float, CultureInfo.InvariantCulture, out var result2) || result2 <= 0.0) { error = "duration must be greater than zero"; return false; } expiryUtc = match.Groups["unit"].Value.ToLowerInvariant() switch { "m" => nowUtc.AddMinutes(result2), "h" => nowUtc.AddHours(result2), "d" => nowUtc.AddDays(result2), "w" => nowUtc.AddDays(result2 * 7.0), _ => null, }; return expiryUtc.HasValue; } if (DateTime.TryParse(text2, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, out var result3)) { if (result3 <= nowUtc) { error = "expiry is already in the past"; return false; } expiryUtc = result3.ToUniversalTime(); return true; } error = "invalid duration. Use 7 (days), 30m, 12h, 7d, 2w, permanent, or an ISO/UTC date"; return false; } internal static bool IsActive(string? until) { if (string.IsNullOrWhiteSpace(until)) { return true; } if (!DateTime.TryParse(until, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, out var result)) { return false; } return DateTime.UtcNow <= result.ToUniversalTime(); } internal static string FormatUtc(DateTime? value) { if (!value.HasValue) { return string.Empty; } return value.Value.ToUniversalTime().ToString("o", CultureInfo.InvariantCulture); } } internal static class PlayerDropPermissions { private static readonly object Sync = new object(); private static PermissionData _data = new PermissionData(); private static string _permissionsPath = string.Empty; private const string PermissionTemplate = "# BoostDrop permissions\n# Server-only file. Use only the sections you need.\n# Precedence: defaultMultiplier -> categories -> groups -> playerDetails.\n\n# Omit/default to use General.DefaultMultiplier from the synchronized config.\n# defaultMultiplier: 2.0\n\n# Optional global category defaults. Omit when all categories use the default.\n# categories:\n# mining: 1.0\n# wood: 1.0\n# fishing: 1.0\n# pickable: 1.0\n# mobDrop: 1.0\n# bossDrop: 1.0\n# chestLoot: 1.0\n# combatDamage: 1.0\n# farming: 1.0 # plant growth only; breeding is not modified\n# skillXP: 1.0\n\n# Optional group rule. Match players by name or platform user ID.\n# groups:\n# vip:\n# multiplier: 2.0\n# players:\n# - Odin\n# # userIds:\n# # - 76561198000000001\n# # categories:\n# # bossDrop: 2.0\n# # chestLoot: 2.0\n# # combatDamage: 1.5\n# # farming: 2.0 # plant growth\n# # skillXP: 2.0\n# # until: 2026-12-31T23:59:59Z\n\n# Optional per-player exception or override.\n# playerDetails:\n# FontaAccount:\n# userId: 76561198000000001\n# multiplier: 2.0\n# categories:\n# mining: 2.0\n# bossDrop: 2.0\n# chestLoot: 2.0\n# combatDamage: 1.5\n# farming: 2.0 # plant growth\n# skillXP: 2.0\n# items:\n# Coins: 1.0\n\n# Optional global item and mob overrides.\n# items:\n# Coins: 1.0\n# mobs:\n# Troll:\n# multiplier: 1.0\n# items:\n# TrollHide: 1.0\n\n# Legacy 1.x mappings. Use only while migrating an old file.\n# players:\n# Odin: 2.0\n# userIds:\n# 76561198000000001: 2.0\n"; internal static void Initialize(ConfigFile config) { lock (Sync) { _data = new PermissionData { DefaultMultiplier = ModConfig.DefaultMultiplier.Value }; } ClientPolicyCache.Clear(); } internal static bool Reload(bool reloadFromDisk, bool broadcast = true) { if (!reloadFromDisk || !DropBoostService.IsServerAuthoritative()) { return true; } _permissionsPath = Path.Combine(Paths.ConfigPath, ModConfig.PermissionsFile.Value); if (!PermissionFileRepository.TryLoadOrCreate(_permissionsPath, "# BoostDrop permissions\n# Server-only file. Use only the sections you need.\n# Precedence: defaultMultiplier -> categories -> groups -> playerDetails.\n\n# Omit/default to use General.DefaultMultiplier from the synchronized config.\n# defaultMultiplier: 2.0\n\n# Optional global category defaults. Omit when all categories use the default.\n# categories:\n# mining: 1.0\n# wood: 1.0\n# fishing: 1.0\n# pickable: 1.0\n# mobDrop: 1.0\n# bossDrop: 1.0\n# chestLoot: 1.0\n# combatDamage: 1.0\n# farming: 1.0 # plant growth only; breeding is not modified\n# skillXP: 1.0\n\n# Optional group rule. Match players by name or platform user ID.\n# groups:\n# vip:\n# multiplier: 2.0\n# players:\n# - Odin\n# # userIds:\n# # - 76561198000000001\n# # categories:\n# # bossDrop: 2.0\n# # chestLoot: 2.0\n# # combatDamage: 1.5\n# # farming: 2.0 # plant growth\n# # skillXP: 2.0\n# # until: 2026-12-31T23:59:59Z\n\n# Optional per-player exception or override.\n# playerDetails:\n# FontaAccount:\n# userId: 76561198000000001\n# multiplier: 2.0\n# categories:\n# mining: 2.0\n# bossDrop: 2.0\n# chestLoot: 2.0\n# combatDamage: 1.5\n# farming: 2.0 # plant growth\n# skillXP: 2.0\n# items:\n# Coins: 1.0\n\n# Optional global item and mob overrides.\n# items:\n# Coins: 1.0\n# mobs:\n# Troll:\n# multiplier: 1.0\n# items:\n# TrollHide: 1.0\n\n# Legacy 1.x mappings. Use only while migrating an old file.\n# players:\n# Odin: 2.0\n# userIds:\n# 76561198000000001: 2.0\n", ModConfig.DefaultMultiplier.Value, out PermissionData data) || data == null) { BoostDropPlugin.Log.LogWarning((object)("BoostDrop: keeping last-known-good permissions because '" + _permissionsPath + "' could not be parsed/read.")); return false; } data = Normalize(data); lock (Sync) { _data = data; } string text; lock (Sync) { text = string.Join(", ", _data.PlayerDetails.Keys); } BoostDropPlugin.Log.LogInfo((object)("BoostDrop permissions loaded from " + _permissionsPath + " - playerDetails: " + (string.IsNullOrWhiteSpace(text) ? "(none)" : text))); if (broadcast) { PolicySyncService.RefreshAllOnlinePlayers(); } return true; } internal static double GetMultiplier(Player player, ResourceCategory category) { if (!ModConfig.Enabled.Value || category == ResourceCategory.None || !ModConfig.IsCategoryEnabled(category)) { return 1.0; } if (DropBoostService.IsServerAuthoritative()) { return EffectivePolicyCache.ResolvePlayer(player).GetCategoryMultiplier(category); } if ((Object)(object)player != (Object)(object)Player.m_localPlayer) { return 1.0; } return ClientPolicyCache.GetMultiplier(category); } internal static double GetMultiplier(long userId, string playerName, ResourceCategory category) { if (!ModConfig.Enabled.Value || category == ResourceCategory.None || !ModConfig.IsCategoryEnabled(category)) { return 1.0; } if (!DropBoostService.IsServerAuthoritative()) { return ClientPolicyCache.GetMultiplier(category); } return ResolveEffectivePolicy(userId, playerName ?? string.Empty).GetCategoryMultiplier(category); } internal static double GetItemMultiplier(EffectivePlayerPolicy? policy, ResourceCategory category, string itemName, double baseMultiplier, string? mobName = null) { if (policy == null) { return ClampMultiplier(baseMultiplier); } double value = baseMultiplier; if ((category == ResourceCategory.MobDrop || category == ResourceCategory.BossDrop) && !string.IsNullOrWhiteSpace(mobName)) { string key = NormalizePrefabName(mobName); if (policy.Mobs.TryGetValue(key, out MobRule value2)) { if (value2.Multiplier > 0.0) { value = value2.Multiplier; } if (!string.IsNullOrWhiteSpace(itemName) && value2.Items.TryGetValue(NormalizePrefabName(itemName), out var value3)) { return ClampMultiplier(value3); } } } if (!string.IsNullOrWhiteSpace(itemName) && policy.Items.TryGetValue(NormalizePrefabName(itemName), out var value4)) { return ClampMultiplier(value4); } return ClampMultiplier(value); } internal static double GetItemMultiplier(Player? player, ResourceCategory category, string itemName, double baseMultiplier, string? mobName = null) { EffectivePlayerPolicy effectivePlayerPolicy = null; if (DropBoostService.IsServerAuthoritative()) { if ((Object)(object)player == (Object)null) { return ClampMultiplier(baseMultiplier); } effectivePlayerPolicy = EffectivePolicyCache.ResolvePlayer(player); } else { if ((Object)(object)player != (Object)(object)Player.m_localPlayer) { return 1.0; } effectivePlayerPolicy = ClientPolicyCache.Current; } return GetItemMultiplier(effectivePlayerPolicy, category, itemName, baseMultiplier, mobName); } internal static EffectivePlayerPolicy ResolveEffectivePolicy(Player player) { if (!DropBoostService.IsServerAuthoritative()) { return ClientPolicyCache.Current ?? CreateVanillaPolicy(player.GetPlayerID(), player.GetPlayerName()); } if (RpcPlayerResolver.TryGetIdentityForPlayer(player, out var identity)) { return ResolveEffectivePolicy(identity.PlatformUserId, identity.PlayerName); } return ResolveEffectivePolicy(player.GetPlayerID(), player.GetPlayerName() ?? string.Empty); } internal static EffectivePlayerPolicy ResolveEffectivePolicy(long userId, string name) { if (name == null) { name = string.Empty; } IReadOnlyList<TemporaryGrant> activeFor = TemporaryGrantStore.GetActiveFor(userId, name); lock (Sync) { return PolicyResolver.Resolve(_data, userId, name, activeFor); } } internal static string DescribePlayer(Player player) { return DescribePolicy(DropBoostService.IsServerAuthoritative() ? EffectivePolicyCache.ResolvePlayer(player) : (ClientPolicyCache.Current ?? CreateVanillaPolicy(player.GetPlayerID(), player.GetPlayerName()))); } internal static string DescribeIdentity(long userId, string playerName) { return DescribePolicy(DropBoostService.IsServerAuthoritative() ? ResolveEffectivePolicy(userId, playerName) : (ClientPolicyCache.Current ?? CreateVanillaPolicy(userId, playerName))); } internal static string DescribePolicy(EffectivePlayerPolicy policy) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine($"Player: {policy.PlayerName} (ID: {policy.UserId})"); stringBuilder.AppendLine($" Mining: x{policy.GetCategoryMultiplier(ResourceCategory.Mining):0.##}"); stringBuilder.AppendLine($" Wood: x{policy.GetCategoryMultiplier(ResourceCategory.Wood):0.##}"); stringBuilder.AppendLine($" Fishing: x{policy.GetCategoryMultiplier(ResourceCategory.Fishing):0.##}"); stringBuilder.AppendLine($" Pickable: x{policy.GetCategoryMultiplier(ResourceCategory.Pickable):0.##}"); stringBuilder.AppendLine($" MobDrop: x{policy.GetCategoryMultiplier(ResourceCategory.MobDrop):0.##}"); stringBuilder.AppendLine($" BossDrop: x{policy.GetCategoryMultiplier(ResourceCategory.BossDrop):0.##}"); stringBuilder.AppendLine($" ChestLoot: x{policy.GetCategoryMultiplier(ResourceCategory.ChestLoot):0.##}"); stringBuilder.AppendLine($" CombatDamage: x{policy.GetCategoryMultiplier(ResourceCategory.CombatDamage):0.##}"); stringBuilder.AppendLine($" PlantGrowth: x{policy.GetCategoryMultiplier(ResourceCategory.PlantGrowth):0.##}"); stringBuilder.AppendLine($" SkillXP: x{policy.GetCategoryMultiplier(ResourceCategory.SkillXP):0.##}"); if (!string.IsNullOrWhiteSpace(policy.TemporaryGrantUntil)) { stringBuilder.AppendLine(" Temporary grant until: " + policy.TemporaryGrantUntil); } if (policy.Items.Count > 0) { stringBuilder.AppendLine($" Item overrides: {policy.Items.Count}"); } if (policy.Mobs.Count > 0) { stringBuilder.AppendLine($" Mob rules: {policy.Mobs.Count}"); } return stringBuilder.ToString().TrimEnd(Array.Empty<char>()); } internal static string DescribeServerStaticPermissions() { if (!DropBoostService.IsServerAuthoritative()) { return "Static permissions are server-only."; } lock (Sync) { return PermissionFileRepository.Serialize(_data); } } internal static void LogPlayerId(Player player) { if (DropBoostService.IsServerAuthoritative()) { if (RpcPlayerResolver.TryGetIdentityForPlayer(player, out var identity)) { BoostDropPlugin.Log.LogInfo((object)$"BoostDrop: player joined - name='{identity.PlayerName}' platformId={identity.PlatformUserId} peer={identity.PeerUid}"); } else { BoostDropPlugin.Log.LogInfo((object)$"BoostDrop: player joined - name='{player.GetPlayerName()}' localPlayerId={player.GetPlayerID()} (peer identity unavailable)"); } } } internal static double ClampMultiplier(double value) { if (double.IsNaN(value) || double.IsInfinity(value)) { return 1.0; } double num = ModConfig.MaxMultiplier?.Value ?? 10.0; if (num < 1.0) { num = 1.0; } if (value < 1.0) { return 1.0; } return Math.Min(value, num); } internal static string NormalizePrefabName(string? value) { if (string.IsNullOrWhiteSpace(value)) { return string.Empty; } string text = value.Trim(); if (text.EndsWith("(Clone)", StringComparison.OrdinalIgnoreCase)) { text = text.Substring(0, text.Length - "(Clone)".Length).Trim(); } return text; } private static PermissionData Normalize(PermissionData data) { data.Items = ToInsensitive(data.Items); data.Mobs = ToInsensitiveMob(data.Mobs); data.Players = ToInsensitive(data.Players); if (data.UserIds == null) { Dictionary<long, double> dictionary = (data.UserIds = new Dictionary<long, double>()); } data.PlayerDetails = ToInsensitivePlayers(data.PlayerDetails); data.Groups = ToInsensitiveGroups(data.Groups); foreach (PlayerPermissionEntry value in data.PlayerDetails.Values) { value.Items = ToInsensitive(value.Items); value.Mobs = ToInsensitiveMob(value.Mobs); ValidateUntil(value.Until, "playerDetails"); } foreach (GroupPermissionEntry value2 in data.Groups.Values) { GroupPermissionEntry current2; GroupPermissionEntry groupPermissionEntry = (current2 = value2); if (current2.Players == null) { List<string> list = (current2.Players = new List<string>()); } current2 = groupPermissionEntry; if (current2.UserIds == null) { List<long> list3 = (current2.UserIds = new List<long>()); } groupPermissionEntry.Items = ToInsensitive(groupPermissionEntry.Items); groupPermissionEntry.Mobs = ToInsensitiveMob(groupPermissionEntry.Mobs); ValidateUntil(groupPermissionEntry.Until, "group"); } if (data.DefaultMultiplier <= 0.0) { data.DefaultMultiplier = ModConfig.DefaultMultiplier.Value; } return data; } internal static EffectivePlayerPolicy CreateVanillaPolicyForContext(long userId, string? playerName) { return CreateVanillaPolicy(userId, playerName); } private static EffectivePlayerPolicy CreateVanillaPolicy(long userId, string? playerName) { return new EffectivePlayerPolicy { UserId = userId, PlayerName = (playerName ?? string.Empty), DefaultMultiplier = 1.0, Categories = new CategoryMultipliers { Mining = 1.0, Wood = 1.0, Fishing = 1.0, Pickable = 1.0, MobDrop = 1.0, BossDrop = 1.0, ChestLoot = 1.0, CombatDamage = 1.0, PlantGrowth = 1.0, SkillXP = 1.0 } }; } private static void ValidateUntil(string? until, string source) { if (!string.IsNullOrWhiteSpace(until) && !DateTime.TryParse(until, out var _)) { BoostDropPlugin.Log.LogWarning((object)("BoostDrop: invalid 'until' value '" + until + "' in " + source + "; the rule is disabled until corrected.")); } } private static Dictionary<string, double> ToInsensitive(Dictionary<string, double>? source) { if (source != null) { return new Dictionary<string, double>(source, StringComparer.OrdinalIgnoreCase); } return new Dictionary<string, double>(StringComparer.OrdinalIgnoreCase); } private static Dictionary<string, PlayerPermissionEntry> ToInsensitivePlayers(Dictionary<string, PlayerPermissionEntry>? source) { if (source != null) { return new Dictionary<string, PlayerPermissionEntry>(source, StringComparer.OrdinalIgnoreCase); } return new Dictionary<string, PlayerPermissionEntry>(StringComparer.OrdinalIgnoreCase); } private static Dictionary<string, GroupPermissionEntry> ToInsensitiveGroups(Dictionary<string, GroupPermissionEntry>? source) { if (source != null) { return new Dictionary<string, GroupPermissionEntry>(source, StringComparer.OrdinalIgnoreCase); } return new Dictionary<string, GroupPermissionEntry>(StringComparer.OrdinalIgnoreCase); } private static Dictionary<string, MobRule> ToInsensitiveMob(Dictionary<string, MobRule>? source) { Dictionary<string, MobRule> dictionary = new Dictionary<string, MobRule>(StringComparer.OrdinalIgnoreCase); if (source == null) { return dictionary; } foreach (KeyValuePair<string, MobRule> item in source) { item.Value.Items = ToInsensitive(item.Value.Items); dictionary[NormalizePrefabName(item.Key)] = item.Value; } return dictionary; } } internal static class ClientPolicyCache { private static readonly object Sync = new object(); internal static EffectivePlayerPolicy? Current { get; private set; } internal static void Apply(EffectivePlayerPolicy policy) { lock (Sync) { Current = policy; } } internal static void Clear() { lock (Sync) { Current = null; } } internal static double GetMultiplier(ResourceCategory category) { lock (Sync) { return Current?.GetCategoryMultiplier(category) ?? 1.0; } } } internal static class TemporaryGrantStore { private static readonly object Sync = new object(); private static readonly ISerializer Serializer = ((BuilderSkeleton<SerializerBuilder>)new SerializerBuilder()).WithNamingConvention(CamelCaseNamingConvention.Instance).Build(); private static readonly IDeserializer Deserializer = ((BuilderSkeleton<DeserializerBuilder>)new DeserializerBuilder()).WithNamingConvention(CamelCaseNamingConvention.Instance).IgnoreUnmatchedProperties().Build(); private static GrantData _data = new GrantData(); private static string _path = string.Empty; private static DateTime _nextExpiryCheckUtc = DateTime.MinValue; internal static void Initialize() { lock (Sync) { _data = new GrantData(); } _nextExpiryCheckUtc = DateTime.MinValue; } internal static bool Reload() { if (!DropBoostService.IsServerAuthoritative()) { return true; } _path = Path.Combine(Paths.ConfigPath, ModConfig.GrantsFile.Value); GrantData grantData = new GrantData(); if (File.Exists(_path)) { try { grantData = Deserializer.Deserialize<GrantData>(File.ReadAllText(_path)) ?? new GrantData(); GrantData grantData2 = grantData; if (grantData2.Grants == null) { List<TemporaryGrant> list = (grantData2.Grants = new List<TemporaryGrant>()); } } catch (Exception ex) { BoostDropPlugin.Log.LogError((object)("BoostDrop: failed to read grants file '" + _path + "': " + ex.Message)); BoostDropPlugin.Log.LogWarning((object)"BoostDrop: keeping last-known-good temporary grants; watcher will retry after the file is quiet."); return false; } } lock (Sync) { _data = grantData; CleanupExpiredLocked(save: false); } BoostDropPlugin.Log.LogInfo((object)$"BoostDrop temporary grants loaded: {CountActive()} active."); return true; } internal static bool TickExpiryCleanup() { if (!DropBoostService.IsServerAuthoritative()) { return false; } DateTime utcNow = DateTime.UtcNow; if (utcNow < _nextExpiryCheckUtc) { return false; } _nextExpiryCheckUtc = utcNow.AddSeconds(30.0); lock (Sync) { return CleanupExpiredLocked(save: true) > 0; } } internal static bool Grant(long? userId, string playerName, double multiplier, string category, string duration, string grantedBy, out TemporaryGrant? grant, out string error) { grant = null; error = string.Empty; if (!DropBoostService.IsServerAuthoritative()) { error = "temporary grants can only be changed by the server"; return false; } if (!userId.HasValue && string.IsNullOrWhiteSpace(playerName)) { error = "target requires SteamID/userId or player name"; return false; } if (double.IsNaN(multiplier) || double.IsInfinity(multiplier) || multiplier < 1.0) { error = "multiplier must be at least 1"; return false; } multiplier = PlayerDropPermissions.ClampMultiplier(multiplier); bool valid; string normalizedCategory = NormalizeCategory(category, out valid); if (!valid) { error = "category must be all, mining, wood, fishing, pickable, mobdrop, bossdrop, chestloot, combatdamage, plantgrowth (farming alias), or skillxp"; return false; } if (!DurationParser.TryParseExpiry(duration, DateTime.UtcNow, out var expiryUtc, out error)) { return false; } TemporaryGrant temporaryGrant = new TemporaryGrant { UserId = userId, PlayerName = (playerName?.Trim() ?? string.Empty), Multiplier = multiplier, Category = normalizedCategory, Until = DurationParser.FormatUtc(expiryUtc), GrantedBy = (string.IsNullOrWhiteSpace(grantedBy) ? "server" : grantedBy.Trim()), GrantedAt = DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture) }; lock (Sync) { CleanupExpiredLocked(save: false); _data.Grants.RemoveAll((TemporaryGrant existing) => SameTarget(existing, userId, playerName) && existing.Category.Equals(normalizedCategory, StringComparison.OrdinalIgnoreCase)); _data.Grants.Add(temporaryGrant); SaveLocked(); } grant = temporaryGrant; return true; } internal static int Revoke(long? userId, string playerName, string category) { if (!DropBoostService.IsServerAuthoritative()) { return 0; } bool valid; string normalizedCategory = NormalizeCategory(category, out valid); if (!valid) { return 0; } int num; lock (Sync) { CleanupExpiredLocked(save: false); num = _data.Grants.RemoveAll((TemporaryGrant existing) => SameTarget(existing, userId, playerName) && (normalizedCategory == "all" || existing.Category.Equals(normalizedCategory, StringComparison.OrdinalIgnoreCase))); if (num > 0) { SaveLocked(); } } return num; } internal static int CleanupExpired() { if (!DropBoostService.IsServerAuthoritative()) { return 0; } lock (Sync) { return CleanupExpiredLocked(save: true); } } internal static IReadOnlyList<TemporaryGrant> GetActiveFor(long userId, string playerName) { lock (Sync) { CleanupExpiredLocked(save: true); return _data.Grants.Where((TemporaryGrant g) => Matches(g, userId, playerName) && DurationParser.IsActive(g.Until)).Select(Clone).ToList(); } } internal static string DescribeActive() { lock (Sync) { CleanupExpiredLocked(save: true); if (_data.Grants.Count == 0) { return "No active BoostDrop temporary grants."; } return string.Join(Environment.NewLine, _data.Grants.Select(delegate(TemporaryGrant g) { string text = (g.UserId.HasValue ? $"{g.PlayerName} ({g.UserId.Value})" : g.PlayerName); string text2 = (string.IsNullOrWhiteSpace(g.Until) ? "permanent" : g.Until); return $"{text}: x{g.Multiplier:0.##} [{g.Category}] until {text2} by {g.GrantedBy}"; })); } } internal static int CountActive() { lock (Sync) { CleanupExpiredLocked(save: true); return _data.Grants.Count; } } internal static string NormalizeCategory(string? category, out bool valid) { if (string.IsNullOrWhiteSpace(category) || category.Equals("all", StringComparison.OrdinalIgnoreCase)) { valid = true; return "all"; } if (ResourceCategoryExtensions.TryParse(category, out var category2)) { valid = category2 != ResourceCategory.None; return category2.ToConfigKey(); } valid = false; return category.Trim().ToLowerInvariant(); } private static bool Matches(TemporaryGrant grant, long userId, string playerName) { if (grant.UserId.HasValue) { return grant.UserId.Value == userId; } if (!string.IsNullOrWhiteSpace(grant.PlayerName)) { return grant.PlayerName.Equals(playerName, StringComparison.OrdinalIgnoreCase); } return false; } private static bool SameTarget(TemporaryGrant grant, long? userId, string? playerName) { if (userId.HasValue && grant.UserId.HasValue) { return grant.UserId.Value == userId.Value; } if (!userId.HasValue && !grant.UserId.HasValue) { return grant.PlayerName.Equals(playerName ?? string.Empty, StringComparison.OrdinalIgnoreCase); } if (userId.HasValue && !grant.UserId.HasValue && !string.IsNullOrWhiteSpace(playerName)) { return grant.PlayerName.Equals(playerName, StringComparison.OrdinalIgnoreCase); } return false; } private static int CleanupExpiredLocked(bool save) { int num = _data.Grants.RemoveAll((TemporaryGrant g) => !DurationParser.IsActive(g.Until)); if (num > 0 && save) { SaveLocked(); } return num; } private static void SaveLocked() { _path = Path.Combine(Paths.ConfigPath, ModConfig.GrantsFile.Value); Directory.CreateDirectory(Path.GetDirectoryName(_path) ?? Paths.ConfigPath); string text = _path + ".tmp"; string contents = Serializer.Serialize((object)_data); PermissionFileWatcher.SuppressOwnWrite(_path); File.WriteAllText(text, contents); if (File.Exists(_path)) { File.Delete(_path); } File.Move(text, _path); } private static TemporaryGrant Clone(TemporaryGrant source) { return new TemporaryGrant { UserId = source.UserId, PlayerName = source.PlayerName, Multiplier = source.Multiplier, Category = source.Category, Until = source.Until, GrantedBy = source.GrantedBy, GrantedAt = source.GrantedAt }; } } [BepInPlugin("fonta.boostdrop", "BoostDrop", "2.1.1")] [BepInDependency(/*Could not decode attribute arguments.*/)] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] public sealed class BoostDropPlugin : BaseUnityPlugin { public const string PluginGuid = "fonta.boostdrop"; public const string PluginName = "BoostDrop"; public const string PluginVersion = "2.1.1"; private Harmony? _harmony; private PermissionFileWatcher? _permissionWatcher; private bool _runtimeRefreshPending; private bool _reloadPrivateFilesPending; private bool _restartWatcherPending; private bool _refreshPoliciesPending; private DateTime _runtimeRefreshNotBeforeUtc = DateTime.MinValue; private string _runtimeRefreshReason = string.Empty; internal static BoostDropPlugin Instance { get; private set; } internal static ManualLogSource Log { get; private set; } private void Awake() { Instance = this; Log = ((BaseUnityPlugin)this).Logger; ModConfig.Bind(((BaseUnityPlugin)this).Config); PlayerDropPermissions.Initialize(((BaseUnityPlugin)this).Config); TemporaryGrantStore.Initialize(); _harmony = Harmony.CreateAndPatchAll(typeof(BoostDropPlugin).Assembly, "fonta.boostdrop"); BoostDropConsoleCommands.Register(); _permissionWatcher = new PermissionFileWatcher(); ((BaseUnityPlugin)this).Config.SettingChanged += OnSettingChanged; SynchronizationManager.OnConfigurationSynchronized += OnConfigurationSynchronized; Log.LogInfo((object)"BoostDrop 2.1.1 loaded. Server-authoritative policies, strict player damage attribution, batched contribution checkpoints and delta policy sync enabled."); } internal void StartServerServices() { if (DropBoostService.IsServerAuthoritative()) { DropBoostContext.ForceReset(); DamageContributionTracker.Clear(); RuntimeIdentityDirectory.Clear(); EffectivePolicyCache.Clear(); bool num = PlayerDropPermissions.Reload(reloadFromDisk: true, broadcast: false); bool flag = TemporaryGrantStore.Reload(); _permissionWatcher?.Restart(); PolicySyncService.RefreshAllOnlinePlayers(); if (!num || !flag) { Log.LogWarning((object)"BoostDrop server started with last-known-good/default state for a YAML file that could not be read."); } Log.LogInfo((object)"BoostDrop server authority initialized. Raw permissions/grants stay server-only; damage ledgers remain independent from runtime policy reloads."); } } private void OnSettingChanged(object sender, SettingChangedEventArgs args) { if (!DropBoostService.IsServerAuthoritative()) { return; } ConfigEntryBase val = ((args != null) ? args.ChangedSetting : null); if (val != null && !ModConfig.IsClientOnlySetting(val)) { string text = ((object)val.Definition).ToString(); if (ModConfig.IsServerFileSetting(val)) { ScheduleRuntimeRefresh("server file config changed: " + text, 400, reloadPrivateFiles: true, restartWatcher: true, refreshPolicies: true); } else if (ModConfig.IsWatcherSetting(val)) { ScheduleRuntimeRefresh("watcher config changed: " + text, 150, reloadPrivateFiles: false, restartWatcher: true, refreshPolicies: false); } else if (ModConfig.RequiresPolicyRefresh(val)) { ScheduleRuntimeRefresh("gameplay config changed: " + text, 250, reloadPrivateFiles: false, restartWatcher: false, refreshPolicies: true); } } } private void OnConfigurationSynchronized(object sender, ConfigurationSynchronizationEventArgs args) { if (args == null || (args.UpdatedPluginGUIDs != null && args.UpdatedPluginGUIDs.Count > 0 && !args.UpdatedPluginGUIDs.Contains("fonta.boostdrop"))) { return; } if (DropBoostService.IsServerAuthoritative()) { if (!args.InitialSynchronization) { ScheduleRuntimeRefresh("Jotunn configuration synchronization completed", 100, reloadPrivateFiles: false, restartWatcher: false, refreshPolicies: true); } } else { DropBoostContext.ForceReset(); PolicySyncService.ScheduleOwnPolicyRequest(); } } private void ScheduleRuntimeRefresh(string reason, int delayMilliseconds, bool reloadPrivateFiles, bool restartWatcher, bool refreshPolicies) { _runtimeRefreshPending = true; _reloadPrivateFilesPending |= reloadPrivateFiles; _restartWatcherPending |= restartWatcher; _refreshPoliciesPending |= refreshPolicies; _runtimeRefreshReason = reason; DateTime dateTime = DateTime.UtcNow.AddMilliseconds(Math.Max(50, delayMilliseconds)); if (dateTime > _runtimeRefreshNotBeforeUtc) { _runtimeRefreshNotBeforeUtc = dateTime; } } private void ApplyRuntimeConfiguration() { if (DropBoostService.IsServerAuthoritative()) { bool reloadPrivateFilesPending = _reloadPrivateFilesPending; bool restartWatcherPending = _restartWatcherPending; bool refreshPoliciesPending = _refreshPoliciesPending; _reloadPrivateFilesPending = false; _restartWatcherPending = false; _refreshPoliciesPending = false; bool flag = true; bool flag2 = true; if (reloadPrivateFilesPending) { flag = PlayerDropPermissions.Reload(reloadFromDisk: true, broadcast: false); flag2 = TemporaryGrantStore.Reload(); } if (restartWatcherPending) { _permissionWatcher?.Restart(); } if (refreshPoliciesPending) { DropBoostContext.ForceReset(); PolicySyncService.RefreshAllOnlinePlayers(); } if (flag && flag2) { Log.LogInfo((object)("BoostDrop runtime configuration applied without restart (" + _runtimeRefreshReason + ").")); } else { Log.LogWarning((object)("BoostDrop runtime config applied with last-known-good YAML preserved (" + _runtimeRefreshReason + ").")); } } } private void Update() { PolicySyncService.Tick(); AdminRpcService.EnsureRegistered(); DamageContributionTracker.Tick(); if (TemporaryGrantStore.TickExpiryCleanup()) { PolicySyncService.RefreshAllOnlinePlayers(); } if (_runtimeRefreshPending && DateTime.UtcNow >= _runtimeRefreshNotBeforeUtc) { _runtimeRefreshPending = false; ApplyRuntimeConfiguration(); } _permissionWatcher?.Tick(); AdminGui.Tick(); } private void OnDestroy() { ((BaseUnityPlugin)this).Config.SettingChanged -= OnSettingChanged; SynchronizationManager.OnConfigurationSynchronized -= OnConfigurationSynchronized; _runtimeRefreshPending = false; _reloadPrivateFilesPending = false; _restartWatcherPending = false; _refreshPoliciesPending = false; DropBoostContext.ForceReset(); DamageContributionTracker.Clear(); RuntimeIdentityDirectory.Clear(); EffectivePolicyCache.Clear(); RateLimitedLog.Clear(); AdminGui.Shutdown(); _permissionWatcher?.Dispose(); Harmony? harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } PolicySyncService.ClearClientPolicy(); } } internal enum ResourceCategory { None, Mining, Wood, Fishing, Pickable, MobDrop, BossDrop, ChestLoot, CombatDamage, PlantGrowth, SkillXP } internal static class ResourceCategoryExtensions { internal static string ToConfigKey(this ResourceCategory category) { return category switch { ResourceCategory.Mining => "mining", ResourceCategory.Wood => "wood", ResourceCategory.Fishing => "fishing", ResourceCategory.Pickable => "pickable", ResourceCategory.MobDrop => "mobdrop", ResourceCategory.BossDrop => "bossdrop", ResourceCategory.ChestLoot => "chestloot", ResourceCategory.CombatDamage => "combatdamage", ResourceCategory.PlantGrowth => "plantgrowth", ResourceCategory.SkillXP => "skillxp", _ => "none", }; } internal static bool TryParse(string? value, out ResourceCategory category) { category = ResourceCategory.None; if (string.IsNullOrWhiteSpace(value)) { return false; } switch (value.Trim().ToLowerInvariant().Replace("_", string.Empty) .Replace("-", string.Empty)) { case "mining": case "mine": category = ResourceCategory.Mining; return true; case "wood": case "logging": category = ResourceCategory.Wood; return true; case "fish": case "fishing": category = ResourceCategory.Fishing; return true; case "pickup": case "gather": case "harvest": case "pickable": category = ResourceCategory.Pickable; return true; case "loot": case "mobdrop": case "mobdrops": case "mob": category = ResourceCategory.MobDrop; return true; case "boss": case "bossdrop": case "bossdrops": category = ResourceCategory.BossDrop; return true; case "barrel": case "chestloot": case "chest": category = ResourceCategory.ChestLoot; return true; case "damage": case "combat": case "combatdamage": category = ResourceCategory.CombatDamage; return true; case "plants": case "farm": case "farming": case "plantgrowth": category = ResourceCategory.PlantGrowth; return true; case "skillxp": case "skill": case "experience": case "xp": category = ResourceCategory.SkillXP; return true; default: return false; } } } internal static class ChestLootService { private const string ProcessedKey = "boostdrop.chest.v2.processed"; private static readonly MethodInfo? GetInventoryMethod = AccessTools.Method(typeof(Container), "GetInventory", (Type[])null, (Type[])null); internal static bool TryProcess(Container? container) { if ((Object)(object)container == (Object)null || !ModConfig.Enabled.Value || !ModConfig.EnableChestLoot.Value) { return false; } if (!DropBoostContext.TryGetBoost(out var multiplier)) { return false; } ZNetView val = ((Component)container).GetComponent<ZNetView>() ?? ((Component)container).GetComponentInParent<ZNetView>(); if ((Object)(object)val == (Object)null || !val.IsValid() || !val.IsOwner()) { return false; } ZDO zDO = val.GetZDO(); if (zDO == null || zDO.GetInt("boostdrop.chest.v2.processed", 0) == 1) { return false; } Inventory inventory = GetInventory(container); if (inventory == null) { return false; } List<ItemData> allItems = inventory.GetAllItems(); if (allItems == null || allItems.Count == 0) { return false; } bool flag = false; for (int i = 0; i < allItems.Count; i++) { ItemData val2 = allItems[i]; if (val2 != null && val2.m_stack > 0) { double multiplier2 = DropBoostService.ResolveItemMultiplier(((Object)(object)val2.m_dropPrefab != (Object)null) ? ((Object)val2.m_dropPrefab).name : (val2.m_shared?.m_name ?? string.Empty), multiplier); int num = DropBoostService.ScaleAmount(val2.m_stack, multiplier2); if (num != val2.m_stack) { val2.m_stack = num; flag = true; } } } zDO.Set("boostdrop.chest.v2.processed", 1); if (flag && ModConfig.EnableLogging.Value) { BoostDropPlugin.Log.LogInfo((object)("BoostDrop [ChestLoot] processed " + ((Object)container).name + " for " + (DropBoostContext.AttackerLabel() ?? "unknown") + ".")); } return true; } private static Inventory? GetInventory(Container container) { if (GetInventoryMethod == null) { return null; } try { object? obj = GetInventoryMethod.Invoke(container, null); return (Inventory?)((obj is Inventory) ? obj : null); } catch (Exception ex) { if (ModConfig.EnableLogging.Value) { BoostDropPlugin.Log.LogWarning((object)("BoostDrop: unable to read container inventory: " + ex.Message)); } return null; } } } internal static class DropBoostContext { internal readonly struct ContextToken { internal int Generation { get; } internal bool Entered { get; } internal ContextToken(int generation, bool entered) { Generation = generation; Entered = entered; } } private struct RewardState { internal bool Active; internal Player? Player; internal EffectivePlayerPolicy? Policy; internal double Multiplier; internal ResourceCategory Category; internal string SourceName; internal string AttackerLabel; } [ThreadStatic] private static RewardState _state; [ThreadStatic] private static int _depth; [ThreadStatic] private static int _generation; [ThreadStatic] private static bool _isApplyingExtra; internal static bool IsApplyingExtra { get { return _isApplyingExtra; } set { _isApplyingExtra = value; } } internal static ResourceCategory Category => _state.Category; internal static EffectivePlayerPolicy? Policy => _state.Policy; internal static string SourceName => _state.SourceName ?? string.Empty; internal static ContextToken Begin(HitData? hit, ResourceCategory category, long senderPeerUid = 0L, string? sourceName = null) { bool outermost; ContextToken result = PrepareBegin(out outermost); if (!outermost) { return result; } if (DropBoostService.IsServerAuthoritative() && senderPeerUid != 0L) { ActivatePolicy(EffectivePolicyCache.ResolvePeer(senderPeerUid), category, sourceName, null); return result; } Player val = null; try { if (hit != null) { Character attacker = hit.GetAttacker(); val = (Player)(object)((attacker is Player) ? attacker : null); } } catch { val = null; } if ((Object)(object)val == (Object)null) { return result; } ActivatePlayer(val, category, sourceName); return result; } internal static ContextToken BeginPeer(long senderPeerUid, ResourceCategory category, string? sourceName = null) { bool outermost; ContextToken result = PrepareBegin(out outermost); if (!outermost) { return result; } if (DropBoostService.IsServerAuthoritative()) { EffectivePlayerPolicy effectivePlayerPolicy = EffectivePolicyCache.ResolvePeer(senderPeerUid); ActivatePolicy(effectivePlayerPolicy, category, sourceName, effectivePlayerPolicy.PlayerName); return result; } if (RuntimePolicyCache.TryGetByPeer(senderPeerUid, out EffectivePlayerPolicy policy) && policy != null) { ActivatePolicy(policy, category, sourceName, policy.PlayerName); } else if (senderPeerUid == 0L && (Object)(object)Player.m_localPlayer != (Object)null) { ActivatePlayer(Player.m_localPlayer, category, sourceName); } return result; } internal static ContextToken Begin(Player? player, ResourceCategory category, string? sourceName = null) { bool outermost; ContextToken result = PrepareBegin(out outermost); if (!outermost) { return result; } if ((Object)(object)player != (Object)null && DropBoostService.ShouldApplyDropBoost(player)) { ActivatePlayer(player, category, sourceName); } return result; } internal static ContextToken BeginPolicy(EffectivePlayerPolicy? policy, ResourceCategory category, string? sourceName = null, string? label = null) { bool outermost; ContextToken result = PrepareBegin(out outermost); if (!outermost) { return result; } if (policy != null) { ActivatePolicy(policy, category, sourceName, label ?? policy.PlayerName); } return result; } internal static void End(ContextToken token) { if (token.Entered && token.Generation == _generation && _depth > 0) { _depth--; if (_depth == 0) { ResetAndAdvanceGeneration(); } } } internal static void End() { if (_depth > 0) { _depth--; if (_depth == 0) { ResetAndAdvanceGeneration(); } } } internal static void ForceReset() { _depth = 0; _isApplyingExtra = false; ResetAndAdvanceGeneration(); } internal static bool TryGetBoost(out double multiplier) { multiplier = ((_state.Multiplier <= 0.0) ? 1.0 : _state.Multiplier); return _state.Active; } internal static string? AttackerLabel() { if (!string.IsNullOrWhiteSpace(_state.AttackerLabel)) { return _state.AttackerLabel; } return null; } internal static Exception? RecoverAfterPatchException(Exception? exception, string patchName, ContextToken token = default(ContextToken)) { if (exception == null) { return null; } if (token.Entered) { End(token); } else { ForceReset(); } RateLimitedLog.Error("context:" + patchName, $"BoostDrop context recovered after exception in {patchName}: {exception}"); return exception; } private static ContextToken PrepareBegin(out bool outermost) { if (_depth == 0) { ResetState(); _generation = ((_generation == int.MaxValue) ? 1 : (_generation + 1)); } _depth++; outermost = _depth == 1 && !_state.Active; return new ContextToken(_generation, entered: true); } private static void ActivatePlayer(Player player, ResourceCategory category, string? sourceName) { if (ModConfig.Enabled.Value && ModConfig.IsCategoryEnabled(category)) { EffectivePlayerPolicy policy = (DropBoostService.IsServerAuthoritative() ? EffectivePolicyCache.ResolvePlayer(player) : (ClientPolicyCache.Current ?? PlayerDropPermissions.CreateVanillaPolicyForContext(player.GetPlayerID(), player.GetPlayerName()))); _state.Player = player; ActivatePolicy(policy, category, sourceName, player.GetPlayerName()); } } private static void ActivatePolicy(EffectivePlayerPolicy policy, ResourceCategory category, string? sourceName, string? label) { if (policy != null && ModConfig.Enabled.Value && ModConfig.IsCategoryEnabled(category)) { _state.Policy = policy; _state.Category = category; _state.SourceName = PlayerDropPermissions.NormalizePrefabName(sourceName); _state.Multiplier = PlayerDropPermissions.ClampMultiplier(policy.GetCategoryMultiplier(category)); _state.AttackerLabel = ((!string.IsNullOrWhiteSpace(label)) ? label : ((policy.UserId > 0) ? policy.UserId.ToString() : "unknown")); _state.Active = true; } } private static void ResetAndAdvanceGeneration() { ResetState(); _generation = ((_generation == int.MaxValue) ? 1 : (_generation + 1)); } private static void ResetState() { _state = new RewardState { Active = false, Player = null, Policy = null, Multiplier = 1.0, Category = ResourceCategory.None, SourceName = string.Empty, AttackerLabel = string.Empty }; } } internal static class AdminCommandService { internal static string Execute(string action, string target, double multiplier, string duration, string category, string actor) { if (!DropBoostService.IsServerAuthoritative()) { return "BoostDrop: admin mutation must run on the server."; } switch ((action ?? string.Empty).Trim().ToLowerInvariant()) { case "grant": case "set": return Grant(target, multiplier, duration, category, actor); case "remove": case "revoke": return Revoke(target, category); case "check": return Check(target); case "list": return TemporaryGrantStore.DescribeActive(); case "reload": { bool num2 = PlayerDropPermissions.Reload(reloadFromDisk: true, broadcast: false); bool flag = TemporaryGrantStore.Reload(); DropBoostContext.ForceReset(); PolicySyncService.RefreshAllOnlinePlayers(); if (!(num2 && flag)) { return "BoostDrop: one YAML file could not be parsed; last-known-good live state was preserved and policies were refreshed."; } return "BoostDrop: permissions and temporary grants reloaded; online policies refreshed."; } case "perf": case "performance": return BoostDropDiagnostics.BuildPerformanceReport(); case "cleanup": { int num = TemporaryGrantStore.CleanupExpired(); if (num > 0) { PolicySyncService.RefreshAllOnlinePlayers(); } return $"BoostDrop: removed {num} expired grant(s)."; } default: return "BoostDrop: unknown admin action '" + action + "'."; } } internal static bool TryResolveTarget(string query, out long? userId, out string playerName, out long onlinePeerUid) { userId = null; playerName = string.Empty; onlinePeerUid = 0L; if (string.IsNullOrWhiteSpace(query)) { return false; } string text = query.Trim(); if (RuntimeIdentityDirectory.TryFind(text, out RuntimeIdentityDirectory.Entry entry) && entry != null) { onlinePeerUid = entry.PeerUid; if (entry.PlatformUserId > 0) { userId = entry.PlatformUserId; } playerName = (string.IsNullOrWhiteSpace(entry.PlayerName) ? text : entry.PlayerName); return true; } if (RpcPlayerResolver.TryFindConnectedIdentity(text, out var identity)) { onlinePeerUid = identity.PeerUid; if (identity.PlatformUserId > 0) { userId = identity.PlatformUserId; } playerName = (string.IsNullOrWhiteSpace(identity.PlayerName) ? text : identity.PlayerName); return true; } if (long.TryParse(text, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) && result > 0) { userId = result; return true; } playerName = text; return true; } private static string Grant(string target, double multiplier, string duration, string category, string actor) { if (!TryResolveTarget(target, out long? userId, out string playerName, out long onlinePeerUid)) { return "BoostDrop: target is required (player name or SteamID/userId)."; } if (!TemporaryGrantStore.Grant(userId, playerName, multiplier, category, string.IsNullOrWhiteSpace(duration) ? "7d" : duration, actor, out TemporaryGrant grant, out string error)) { return "BoostDrop: grant failed: " + error + "."; } if (onlinePeerUid != 0L) { PolicySyncService.RefreshPeer(onlinePeerUid, userId.GetValueOrDefault(), playerName); } string text = (userId.HasValue ? string.Format("{0} ({1})", string.IsNullOrWhiteSpace(playerName) ? "user" : playerName, userId.Value) : playerName); string text2 = (string.IsNullOrWhiteSpace(grant.Until) ? "permanent" : grant.Until); return $"BoostDrop: {text} granted x{grant.Multiplier:0.##} [{grant.Category}] until {text2}."; } private static string Revoke(string target, string category) { if (!TryResolveTarget(target, out long? userId, out string playerName, out long onlinePeerUid)) { return "BoostDrop: target is required (player name or SteamID/userId)."; } int num = TemporaryGrantStore.Revoke(userId, playerName, string.IsNullOrWhiteSpace(category) ? "all" : category); if (onlinePeerUid != 0L) { PolicySyncService.RefreshPeer(onlinePeerUid, userId.GetValueOrDefault(), playerName); } return $"BoostDrop: revoked {num} temporary grant(s) for {target}."; } private static string Check(string target) { if (!TryResolveTarget(target, out long? userId, out string playerName, out long onlinePeerUid)) { return "BoostDrop: target is required."; } if (onlinePeerUid != 0L) { return PlayerDropPermissions.DescribeIdentity(userId.GetValueOrDefault(), playerName); } IReadOnlyList<TemporaryGrant> activeFor = TemporaryGrantStore.GetActiveFor(userId.GetValueOrDefault(), playerName); if (activeFor.Count == 0) { return "BoostDrop: '" + target + "' is offline and has no matching active temporary grant. Static effective policy requires the player to be online."; } return string.Join(Environment.NewLine, activeFor.Select((TemporaryGrant g) => string.Format("{0}: x{1:0.##} [{2}] until {3}", target, g.Multiplier, g.Category, string.IsNullOrWhiteSpace(g.Until) ? "permanent" : g.Until))); } } internal static class DropBoostService { internal static bool IsServerAuthoritative() { if ((Object)(object)ZNet.instance != (Object)null) { return ZNet.instance.IsServer(); } return false; } internal static bool ShouldApplyDropBoost(Player? player) { if ((Object)(object)player == (Object)null) { return false; } if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { return true; } return (Object)(object)player == (Object)(object)Player.m_localPlayer; } internal static void ApplyMultiplierToItemDataResult(ref List<ItemData> result) { if (DropBoostContext.Category == ResourceCategory.Pickable || DropBoostContext.IsApplyingExtra || result == null || result.Count == 0 || !DropBoostContext.TryGetBoost(out var multiplier)) { return; } DropBoostContext.IsApplyingExtra = true; try { List<ItemData> list = null; double num = 1.0; for (int i = 0; i < result.Count; i++) { ItemData item = result[i]; double num2 = ResolveItemMultiplier(GetItemDataName(item), multiplier); int num3 = AdditionalCopies(num2); if (num3 <= 0) { list?.Add(item); continue; } num = Math.Max(num, num2); if (list == null) { list = new List<ItemData>(Math.Max(result.Count + num3, (int)Math.Ceiling((double)result.Count * num2))); for (int j = 0; j < i; j++) { list.Add(result[j]); } } list.Add(item); for (int k = 0; k < num3; k++) { list.Add(item); } } if (list != null) { result = list; LogBoost(num, null); } } finally { DropBoostContext.IsApplyingExtra = false; } } internal static void ApplyMultiplierToResult(ref List<GameObject> result) { if (DropBoostContext.Category == ResourceCategory.Pickable || DropBoostContext.IsApplyingExtra || result == null || result.Count == 0 || !DropBoostContext.TryGetBoost(out var multiplier)) { return; } DropBoostContext.IsApplyingExtra = true; try { List<GameObject> list = null; double num = 1.0; for (int i = 0; i < result.Count; i++) { GameObject val = result[i]; if ((Object)(object)val == (Object)null) { list?.Add(val); continue; } double num2 = ResolveItemMultiplier(((Object)val).name, multiplier); int num3 = AdditionalCopies(num2); if (num3 <= 0) { list?.Add(val); continue; } num = Math.Max(num, num2); if (list == null) { list = new List<GameObject>(Math.Max(result.Count + num3, (int)Math.Ceiling((double)result.Count * num2))); for (int j = 0; j < i; j++) { list.Add(result[j]); } } list.Add(val); for (int k = 0; k < num3; k++) { list.Add(val); } } if (list != null) { result = list; LogBoost(num, list); } } finally { DropBoostContext.IsApplyingExtra = false; } } internal static int ScaleAmount(int originalAmount, double multiplier) { if (originalAmount <= 0) { return originalAmount; } multiplier = PlayerDropPermissions.ClampMultiplier(multiplier); double num = (double)originalAmount * multiplier; int num2 = (int)Math.Floor(num); double num3 = num - (double)num2; if (num3 > 0.0 && (double)Random.value < num3) { num2++; } return Math.Max(originalAmount, num2); } internal static double ResolveItemMultiplier(string itemName, double baseMultiplier) { return PlayerDropPermissions.GetItemMultiplier(DropBoostContext.Policy, DropBoostContext.Category, itemName, baseMultiplier, DropBoostContext.SourceName); } private static int AdditionalCopies(double multiplier) { multiplier = PlayerDropPermissions.ClampMultiplier(multiplier); int num = (int)Math.Floor(multiplier); double num2 = multiplier - (double)num; int num3 = Math.Max(0, num - 1); if (num2 > 0.0 && (double)Random.value < num2) { num3++; } return num3; } private static string GetItemDataName(ItemData item) { if (item == null) { return string.Empty; } if ((Object)(object)item.m_dropPrefab != (Object)null) { return ((Object)item.m_dropPrefab).name; } return item.m_shared?.m_name ?? string.Empty; } private static void LogBoost(double multiplier, List<GameObject>? prefabs) { if (ModConfig.EnableLogging.Value) { string text = DropBoostContext.AttackerLabel() ?? "unknown"; string text2 = DropBoostContext.Category.ToString(); string text3 = (string.IsNullOrWhiteSpace(DropBoostContext.SourceName) ? string.Empty : (" source=" + DropBoostContext.SourceName)); string text4 = ((prefabs == null || prefabs.Count == 0) ? "amount/list" : string.Join(", ", GetPrefabNames(prefabs))); BoostDropPlugin.Log.LogInfo((object)$"BoostDrop x{multiplier:0.##} [{text2}] for {text}:{text3} {text4}."); } } private static IEnumerable<string> GetPrefabNames(List<GameObject> prefabs) { HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); foreach (GameObject prefab in prefabs) { if ((Object)(object)prefab != (Object)null) { hashSet.Add(((Object)prefab).name); } } return hashSet; } } internal static class RpcPlayerResolver { internal readonly struct PeerIdentity { internal long PeerUid { get; } internal long PlatformUserId { get; } internal string PlayerName { get; } internal string HostName { get; } internal PeerIdentity(long peerUid, long platformUserId, string playerName, string hostName) { PeerUid = peerUid; PlatformUserId = platformUserId; PlayerName = playerName ?? string.Empty; HostName = hostName ?? string.Empty; } } internal static long GetServerPeerUid() { ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null || instance.IsServer()) { return 0L; } return instance.GetServerPeer()?.m_uid ?? 0; } internal static bool TryGetPeerIdentity(long senderPeerId, out PeerIdentity identity) { identity = default(PeerIdentity); ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null || senderPeerId == 0L) { return false; } ZNetPeer val = FindPeer(instance, senderPeerId); if (val == null) { return false; } identity = IdentityFromPeer(val); return true; } internal static bool TryFindConnectedIdentity(string query, out PeerIdentity identity) { identity = default(PeerIdentity); ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null || string.IsNullOrWhiteSpace(query)) { return false; } string text = query.Trim(); long result; bool flag = long.TryParse(text, NumberStyles.Integer, CultureInfo.InvariantCulture, out result); foreach (ZNetPeer connectedPeer in instance.GetConnectedPeers()) { PeerIdentity peerIdentity = IdentityFromPeer(connectedPeer); if (!string.IsNullOrWhiteSpace(peerIdentity.PlayerName) && peerIdentity.PlayerName.Equals(text, StringComparison.OrdinalIgnoreCase)) { identity = peerIdentity; return true; } if (flag && peerIdentity.PlatformUserId != 0L && peerIdentity.PlatformUserId == result) { identity = peerIdentity; return true; } } return false; } internal static bool TryGetIdentityForPlayer(Player player, out PeerIdentity identity) { identity = default(PeerIdentity); if ((Object)(object)player == (Object)null || (Object)(object)ZNet.instance == (Object)null) { return false; } string value = player.GetPlayerName() ?? string.Empty; if (string.IsNullOrWhiteSpace(value)) { return false; } foreach (ZNetPeer connectedPeer in ZNet.instance.GetConnectedPeers()) { if (!string.IsNullOrWhiteSpace(connectedPeer.m_playerName) && connectedPeer.m_playerName.Equals(value, StringComparison.OrdinalIgnoreCase)) { identity = IdentityFromPeer(connectedPeer); return true; } } return false; } private static ZNetPeer? FindPeer(ZNet znet, long senderPeerId) { ZNetPeer peer = znet.GetPeer(senderPeerId); if (peer != null) { return peer; } foreach (ZNetPeer connectedPeer in znet.GetConnectedPeers()) { if (connectedPeer.m_uid == senderPeerId) { return connectedPeer; } } return null; } private static PeerIdentity IdentityFromPeer(ZNetPeer peer) { string empty = string.Empty; try { ISocket socket = peer.m_socket; empty = ((socket != null) ? socket.GetHostName() : null) ?? string.Empty; } catch { empty = string.Empty; } long platformUserId = ParsePlatformUserId(empty); return new PeerIdentity(peer.m_uid, platformUserId, peer.m_playerName ?? string.Empty, empty); } private static long ParsePlatformUserId(string host) { if (string.IsNullOrWhiteSpace(host)) { return 0L; } string text = host.Trim(); if (long.TryParse(text, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) && result > 0) { return result; } int num = -1; int num2 = 0; int num3 = -1; for (int i = 0; i <= text.Length; i++) { int num4; if (i < text.Length) { num4 = (char.IsDigit(text[i]) ? 1 : 0); if (num4 != 0 && num3 < 0) { num3 = i; } } else { num4 = 0; } if ((num4 == 0 || i == text.Length) && num3 >= 0) { int num5 = i - num3; if (num5 > num2) { num = num3; num2 = num5; } num3 = -1; } } if (num >= 0 && num2 >= 15 && long.TryParse(text.Substring(num, num2), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result2) && result2 > 0) { return result2; } return 0L; } } } namespace BoostDrop.Sync { internal static class AdminRpcService { private const string ActionRpcName = "BoostDrop_AdminAction_v3"; private const string ResponseRpcName = "BoostDrop_AdminResponse_v3"; private static ZRoutedRpc? _registeredOn; private static readonly Dictionary<long, DateTime> LastAction = new Dictionary<long, DateTime>(); internal static void EnsureRegistered() { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance == null) { _registeredOn = null; } else if (instance != _registeredOn) { instance.Register<ZPackage>("BoostDrop_AdminAction_v3", (Action<long, ZPackage>)ServerReceiveAction); instance.Register<ZPackage>("BoostDrop_AdminResponse_v3", (Action<long, ZPackage>)ClientReceiveResponse); _registeredOn = instance; LastAction.Clear(); if (ModConfig.EnableLogging != null && ModConfig.EnableLogging.Value) { BoostDropPlugin.Log.LogInfo((object)"BoostDrop: native admin RPCs registered on current ZRoutedRpc instance."); } } } internal static void SendAction(string action, string target, double multiplier, string duration, string category) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Expected O, but got Unknown if (DropBoostService.IsServerAuthoritative()) { PublishLocal(AdminCommandService.Execute(action, target, multiplier, duration, category, "server")); return; } EnsureRegistered(); if (ZRoutedRpc.instance == null) { PublishLocal("BoostDrop: admin RPC is not ready."); return; } long serverPeerUid = RpcPlayerResolver.GetServerPeerUid(); if (serverPeerUid == 0L) { PublishLocal("BoostDrop: server peer is not ready."); return; } try { ZPackage val = new ZPackage(); val.Write(action ?? string.Empty); val.Write(target ?? string.Empty); val.Write((float)multiplier); val.Write(duration ?? string.Empty); val.Write(category ?? "all"); ZRoutedRpc.instance.InvokeRoutedRPC(serverPeerUid, "BoostDrop_AdminAction_v3", new object[1] { val }); } catch (Exception ex) { PublishLocal("BoostDrop: failed to send admin action: " + ex.Message); } } private static void ServerReceiveAction(long sender, ZPackage package) { if (!DropBoostService.IsServerAuthoritative()) { return; } DateTime utcNow = DateTime.UtcNow; if (LastAction.TryGetValue(sender, out var value) && (utcNow - value).TotalMilliseconds < 200.0) { SendResponse(sender, "BoostDrop: admin command rate-limited."); return; } LastAction[sender] = utcNow; if (!AdminAuthorizer.IsRemoteAdmin(sender)) { BoostDropPlugin.Log.LogWarning((object)$"BoostDrop: rejected unauthorized admin RPC from peer {sender}."); SendResponse(sender, "BoostDrop: permission denied. You are not in the server adminlist.txt."); return; } try { string text = package.ReadString(); string text2 = package.ReadString(); double multiplier = package.ReadSingle(); string duration = package.ReadString(); string category = package.ReadString(); RpcPlayerResolver.PeerIdentity identity; string text3 = ((RpcPlayerResolver.TryGetPeerIdentity(sender, out identity) && !string.IsNullOrWhiteSpace(identity.PlayerName)) ? identity.PlayerName : $"peer:{sender}"); string text4 = AdminCommandService.Execute(text, text2, multiplier, duration, category, text3); BoostDropPlugin.Log.LogInfo((object)("BoostDrop admin: " + text3 + " -> " + text + " " + text2 + ": " + text4)); SendResponse(sender, text4); } catch (Exception ex) { SendResponse(sender, "BoostDrop: admin action failed: " + ex.Message); } } private static void ClientReceiveResponse(long sender, ZPackage package) { if (DropBoostService.IsServerAuthoritative()) { BoostDropPlugin.Log.LogWarning((object)$"BoostDrop: peer {sender} attempted to call admin response RPC; ignored."); return; } long serverPeerUid = RpcPlayerResolver.GetServerPeerUid(); if (serverPeerUid == 0L || sender != serverPeerUid) { BoostDropPlugin.Log.LogWarning((object)$"BoostDrop: ignored admin response from non-server peer {sender} (expected {serverPeerUid})."); return; } try { PublishLocal(package.ReadString()); } catch (Exception ex) { PublishLocal("BoostDrop: invalid admin response: " + ex.Message); } } private static void SendResponse(long target, string message) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Expected O, but got Unknown EnsureRegistered(); if (ZRoutedRpc.instance == null || target == 0L) { return; } try { ZPackage val = new ZPackage(); val.Write(message ?? string.Empty); ZRoutedRpc.instance.InvokeRoutedRPC(target, "BoostDrop_AdminResponse_v3", new object[1] { val }); } catch (Exception ex) { BoostDropPlugin.Log.LogError((object)$"BoostDrop: failed to send admin response to peer {target}: {ex.Message}"); } } private static void PublishLocal(string message) { BoostDropPlugin.Log.LogInfo((object)message); if ((Object)(object)Console.instance != (Object)null) { Console.instance.Print(message); } AdminGui.SetStatus(message); } } internal sealed class PermissionFileWatcher : IDisposable { private static readonly TimeSpan QuietPeriod = TimeSpan.FromMilliseconds(850.0); private static readonly TimeSpan OwnWriteSuppression = TimeSpan.FromSeconds(2.0); private const int MaxReadRetries = 4; private static readonly object SuppressionSync = new object(); private static readonly Dictionary<string, DateTime> SuppressedUntil = new Dictionary<string, DateTime>(StringComparer.OrdinalIgnoreCase); private readonly List<FileSystemWatcher> _watchers = new List<FileSystemWatcher>(); private DateTime _lastSignalUtc = DateTime.MinValue; private volatile bool _pendingReload; private int _retryCount; internal static void SuppressOwnWrite(string fullPath) { if (string.IsNullOrWhiteSpace(fullPath)) { return; } string key; try { key = Path.GetFullPath(fullPath); } catch { key = fullPath; } lock (SuppressionSync) { SuppressedUntil[key] = DateTime.UtcNow + OwnWriteSuppression; } } internal void Start() { if (DropBoostService.IsServerAuthoritative()) { if (!ModConfig.WatchPermissionsFile.Value) { StopWatchers(); } else if (_watchers.Count <= 0) { Watch(ModConfig.PermissionsFile.Value); Watch(ModConfig.GrantsFile.Value); } } } internal void Restart() { StopWatchers(); _retryCount = 0; _pendingReload = false; _lastSignalUtc = DateTime.MinValue; Start(); } internal void Tick() { if (!_pendingReload || !DropBoostService.IsServerAuthoritative() || DateTime.UtcNow - _lastSignalUtc < QuietPeriod) { return; } _pendingReload = false; bool num = PlayerDropPermissions.Reload(reloadFromDisk: true, broadcast: false); bool flag = TemporaryGrantStore.Reload(); if (!num || !flag) { if (_retryCount < 4) { _retryCount++; _lastSignalUtc = DateTime.UtcNow; _pendingReload = true; BoostDropPlugin.Log.LogWarning((object)$"BoostDrop hot-reload read was incomplete; keeping last-known-good state and retrying ({_retryCount}/{4})."); } else { _retryCount = 0; BoostDropPlugin.Log.LogError((object)"BoostDrop hot-reload could not parse the updated YAML after retries. Existing live permissions were preserved."); } } else { _retryCount = 0; DropBoostContext.ForceReset(); PolicySyncService.RefreshAllOnlinePlayers(); BoostDropPlugin.Log.LogInfo((object)"BoostDrop permissions/grants hot-reloaded; personalized policies refreshed."); } } private void Watch(string relativePath) { string path = Path.Combine(Paths.ConfigPath, relativePath); string path2 = Path.GetDirectoryName(path) ?? Paths.ConfigPath; string fileName = Path.GetFileName(path); Directory.CreateDirectory(path2); FileSystemWatcher fileSystemWatcher = new FileSystemWatcher(path2, fileName) { NotifyFilter = (NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.LastWrite | NotifyFilters.CreationTime), EnableRaisingEvents = true }; fileSystemWatcher.Changed += OnChanged; fileSystemWatcher.Created += OnChanged; fileSystemWatcher.Renamed += OnChanged; _watchers.Add(fileSystemWatcher); } private void OnChanged(object sender, FileSystemEventArgs e) { if (!IsSuppressedOwnWrite(e.FullPath)) { _lastSignalUtc = DateTime.UtcNow; _pendingReload = true; } } private static bool IsSuppressedOwnWrite(string fullPath) { string key; try { key = Path.GetFullPath(fullPath); } catch { key = fullPath; } DateTime utcNow = DateTime.UtcNow; lock (SuppressionSync) { if (!SuppressedUntil.TryGetValue(key, out var value)) { return false; } if (utcNow <= value) { return true; } SuppressedUntil.Remove(key); return false; } } private void StopWatchers() { foreach (FileSystemWatcher watcher in _watchers) { watcher.EnableRaisingEvents = false; watcher.Changed -= OnChanged; watcher.Created -= OnChanged; watcher.Renamed -= OnChanged; watcher.Dispose(); } _watchers.Clear(); } public void Dispose() { StopWatchers(); _pendingReload = false; _retryCount = 0; } } internal static class PolicySyncService { private const string PolicyRequestRpcName = "BoostDrop_PolicyRequest_v4"; private const string PolicyPushRpcName = "BoostDrop_PolicyPush_v4"; private const string RuntimeSnapshotRpcName = "BoostDrop_RuntimePolicySnapshot_v2"; private const string RuntimeDeltaRpcName = "BoostDrop_RuntimePolicyDelta_v2"; private static readonly ISerializer Serializer = ((BuilderSkeleton<SerializerBuilder>)new SerializerBuilder()).WithNamingConvention(CamelCaseNamingConvention.Instance).Build(); private static readonly IDeserializer Deserializer = ((BuilderSkeleton<DeserializerBuilder>)new DeserializerBuilder()).WithNamingConvention(CamelCaseNamingConvention.Instance).IgnoreUnmatchedProperties().Build(); private static readonly Dictionary<long, DateTime> LastRequests = new Dictionary<long, DateTime>(); private static ZRoutedRpc? _registeredOn; private static float _nextDirectoryMaintenance; private static long _runtimeRevision = 1L; private static long _snapshotPushes; private static long _deltaPushes; internal static void EnsureRegistered() { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance == null) { _registeredOn = null; } else if (instance != _registeredOn) { instance.Register<ZPackage>("BoostDrop_PolicyRequest_v4", (Action<long, ZPackage>)ServerReceivePolicyRequest); instance.Register<ZPackage>("BoostDrop_PolicyPush_v4", (Action<long, ZPackage>)ClientReceivePolicy); instance.Register<ZPackage>("BoostDrop_RuntimePolicySnapshot_v2", (Action<long, ZPackage>)ClientReceiveRuntimeSnapshot); instance.Register<ZPackage>("BoostDrop_RuntimePolicyDelta_v2", (Action<long, ZPackage>)ClientReceiveRuntimeDelta); _registeredOn = instance; LastRequests.Clear(); _nextDirectoryMaintenance = 0f; if (ModConfig.EnableLogging != null && ModConfig.EnableLogging.Value) { BoostDropPlugin.Log.LogInfo((object)"BoostDrop: policy RPC v4 registered."); } } } internal static void Tick() { EnsureRegistered(); if (!DropBoostService.IsServerAuthoritative() || (Object)(object)ZNet.instance == (Object)null) { return; } float realtimeSinceStartup = Time.realtimeSinceStartup; if (realtimeSinceStartup < _nextDirectoryMaintenance) { return; } _nextDirectoryMaintenance = realtimeSinceStartup + 5f; foreach (RuntimeIdentityDirectory.Entry item in RuntimeIdentityDirectory.PruneDisconnected(ZNet.instance.GetConnectedPeers())) { EffectivePolicyCache.Remove(item); if (item.RuntimePlayerId != 0L) { BroadcastRemoveDelta(item.RuntimePlayerId, item.PlayerName); } } } internal static void ScheduleOwnPolicyRequest() { if (!DropBoostService.IsServerAuthoritative()) { ((MonoBehaviour)BoostDropPlugin.Instance).StartCoroutine(RequestPolicyRoutine()); } } private static IEnumerator RequestPolicyRoutine() { yield return null; RequestOwnPolicy(); yield return (object)new WaitForSeconds(1.5f); if (ClientPolicyCache.Current == null) { RequestOwnPolicy(); } } internal static void RequestOwnPolicy() { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Expected O, but got Unknown EnsureRegistered(); if ((Object)(object)ZNet.instance == (Object)null || ZRoutedRpc.instance == null || ZNet.instance.IsServer()) { return; } long serverPeerUid = RpcPlayerResolver.GetServerPeerUid(); if (serverPeerUid == 0L) { return; } long num = (((Object)(object)Player.m_localPlayer != (Object)null) ? Player.m_localPlayer.GetPlayerID() : 0); if (num == 0L) { return; } try { ZPackage val = new ZPackage(); val.Write(num); ZRoutedRpc.instance.InvokeRoutedRPC(serverPeerUid, "BoostDrop_PolicyRequest_v4", new object[1] { val }); } catch (Exception ex) { BoostDropPlugin.Log.LogWarning((object)("BoostDrop: failed to request policy: " + ex.Message)); } } internal static void RefreshAllOnlinePlayers() { EnsureRegistered(); if (!DropBoostService.IsServerAuthoritative() || (Object)(object)ZNet.instance == (Object)null || ZRoutedRpc.instance == null) { return; } EffectivePolicyCache.InvalidateAll(); BumpRuntimeRevision(); foreach (ZNetPeer connectedPeer in ZNet.instance.GetConnectedPeers()) { if (RpcPlayerResolver.TryGetPeerIdentity(connectedPeer.m_uid, out var identity)) { long runtimePlayerId = 0L; if (RuntimeIdentityDirectory.TryGetByPeer(connectedPeer.m_uid, out RuntimeIdentityDirectory.Entry entry) && entry != null) { runtimePlayerId = entry.RuntimePlayerId; } RuntimeIdentityDirectory.Entry entry2 = RuntimeIdentityDirectory.Upsert(identity, runtimePlayerId); SendPersonalPolicy(policy: EffectivePolicyCache.Resolve(entry2), peerUid: entry2.PeerUid); } } if ((Object)(object)Player.m_localPlayer != (Object)null) { EffectivePolicyCache.Prime(RuntimeIdentityDirectory.UpsertLocal(Player.m_localPlayer)); } BroadcastRuntimeSnapshot(); } internal static void RefreshPlayer(Player player) { if (DropBoostService.IsServerAuthoritative() && !((Object)(object)player == (Object)null)) { RuntimeIdentityDirectory.Entry entry = RuntimeIdentityDirectory.UpsertLocal(player); EffectivePolicyCache.Invalidate(entry); EffectivePlayerPolicy policy = EffectivePolicyCache.Resolve(entry); if (entry.PeerUid != 0L) { SendPersonalPolicy(entry.PeerUid, policy); } BumpRuntimeRevision(); BroadcastUpsertDelta(entry, policy, 0L); } } internal static void RefreshPeer(long peerUid, long platformUserId, string playerName) { EnsureRegistered(); if (!DropBoostService.IsServerAuthoritative() || ZRoutedRpc.instance == null || peerUid == 0L) { return; } RuntimeIdentityDirectory.Entry identity; if (RuntimeIdentityDirectory.TryGetByPeer(peerUid, out RuntimeIdentityDirectory.Entry entry) && entry != null) { identity = entry; } else { if (!RpcPlayerResolver.TryGetPeerIdentity(peerUid, out var identity2)) { return; } identity = RuntimeIdentityDirectory.Upsert(identity2, 0L); } EffectivePolicyCache.Invalidate(identity); EffectivePlayerPolicy policy = EffectivePolicyCache.Resolve(identity); SendPersonalPolicy(peerUid, policy); BumpRuntimeRevision(); BroadcastUpsertDelta(identity, policy, 0L); } internal static void ClearClientPolicy() { ClientPolicyCache.Clear(); RuntimePolicyCache.Clear(); LastRequests.Clear(); _nextDirectoryMaintenance = 0f; } internal static (long Snapshots, long Deltas) GetCounters() { return (Snapshots: _snapshotPushes, Deltas: _deltaPushes); } private static void ServerReceivePolicyRequest(long sender, ZPackage package) { if (!DropBoostService.IsServerAuthoritative()) { return; } DateTime utcNow = DateTime.UtcNow; if (!LastRequests.TryGetValue(sender, out var value) || !((utcNow - value).TotalMilliseconds < 500.0)) { LastRequests[sender] = utcNow; long num = 0L; try { num = package.ReadLong(); } catch { num = 0L; } if (RpcPlayerResolver.TryGetPeerIdentity(sender, out var identity)) { RuntimeIdentityDirectory.Entry identity2 = RuntimeIdentityDirectory.Upsert(identity, num); EffectivePolicyCache.Invalidate(identity2); EffectivePlayerPolicy policy = EffectivePolicyCache.Resolve(identity2); SendPersonalPolicy(sender, policy); SendRuntimeSnapshot(sender); BumpRuntimeRevision(); BroadcastUpsertDelta(identity2, policy, sender); } } } private static void ClientReceivePolicy(long sender, ZPackage package) { if (DropBoostService.IsServerAuthoritative()) { return; } long serverPeerUid = RpcPlayerResolver.GetServerPeerUid(); if (serverPeerUid == 0L || sender != serverPeerUid) { return; } try { string text = package.ReadString(); EffectivePlayerPolicy effectivePlayerPolicy = Deserializer.Deserialize<EffectivePlayerPolicy>(text); if (effectivePlayerPolicy != null) { NormalizePolicy(effectivePlayerPolicy); ClientPolicyCache.Apply(effectivePlayerPolicy); } } catch (Exception ex) { BoostDropPlugin.Log.LogError((object)("BoostDrop: invalid personalized policy: " + ex.Message)); } } private static void ClientReceiveRuntimeSnapshot(long sender, ZPackage package) { if (DropBoostService.IsServerAuthoritative()) { return; } long serverPeerUid = RpcPlayerResolver.GetServerPeerUid(); if (serverPeerUid == 0L || sender != serverPeerUid) { return; } try { RuntimePolicySnapshot runtimePolicySnapshot = Deserializer.Deserialize<RuntimePolicySnapshot>(package.ReadString()); if (runtimePolicySnapshot == null) { return; } foreach (RuntimePolicyEntry player in runtimePolicySnapshot.Players) { NormalizePolicy(player.Policy); } RuntimePolicyCache.ApplySnapshot(runtimePolicySnapshot); } catch (Exception ex) { BoostDropPlugin.Log.LogWarning((object)("BoostDrop: invalid runtime policy snapshot: " + ex.Message)); } } private static void ClientReceiveRuntimeDelta(long sender, ZPackage package) { if (DropBoostService.IsServerAuthoritative()) { return; } long serverPeerUid = RpcPlayerResolver.GetServerPeerUid(); if (serverPeerUid == 0L || sender != serverPeerUid) { return; } try { RuntimePolicyDelta runtimePolicyDelta = Deserializer.Deserialize<RuntimePolicyDelta>(package.ReadString()); if (runtimePolicyDelta != null) { if (runtimePolicyDelta.Entry?.Policy != null) { NormalizePolicy(runtimePolicyDelta.Entry.Policy); } RuntimePolicyCache.ApplyDelta(runtimePolicyDelta); } } catch (Exception ex) { if (ModConfig.EnableLogging.Value) { BoostDropPlugin.Log.LogWarning((object)("BoostDrop: invalid runtime policy delta: " + ex.Message)); } } } private static void SendPersonalPolicy(long peerUid, EffectivePlayerPolicy policy) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Expected O, but got Unknown if (ZRoutedRpc.instance == null || peerUid == 0L || policy == null) { return; } try { ZPackage val = new ZPackage(); val.Write(Serializer.Serialize((object)policy)); ZRoutedRpc.instance.InvokeRoutedRPC(peerUid, "BoostDrop_PolicyPush_v4", new object[1] { val }); } catch (Exception ex) { BoostDropPlugin.Log.LogWarning((object)$"BoostDrop: policy push failed for peer {peerUid}: {ex.Message}"); } } private static RuntimePolicySnapshot BuildSnapshot() { RuntimePolicySnapshot runtimePolicySnapshot = new RuntimePolicySnapshot { Revision = _runtimeRevision }; foreach (RuntimeIdentityDirectory.Entry item in RuntimeIdentityDirectory.Snapshot()) { if (item.RuntimePlayerId != 0L) { EffectivePlayerPolicy policy = EffectivePolicyCache.Resolve(item); runtimePolicySnapshot.Players.Add(ToRuntimeEntry(item, policy)); } } return runtimePolicySnapshot; } private static void SendRuntimeSnapshot(long peerUid) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Expected O, but got Unknown if (ZRoutedRpc.instance == null || peerUid == 0L) { return; } try { ZPackage val = new ZPackage(); val.Write(Serializer.Serialize((object)BuildSnapshot())); ZRoutedRpc.instance.InvokeRoutedRPC(peerUid, "BoostDrop_RuntimePolicySnapshot_v2", new object[1] { val }); _snapshotPushes++; } catch (Exception ex) { if (ModConfig.EnableLogging.Value) { BoostDropPlugin.Log.LogWarning((object)$"BoostDrop: runtime snapshot push failed for peer {peerUid}: {ex.Message}"); } } } private static void BroadcastRuntimeSnapshot() { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Expected O, but got Unknown if ((Object)(object)ZNet.instance == (Object)null || ZRoutedRpc.instance == null) { return; } string text = Serializer.Serialize((object)BuildSnapshot()); foreach (ZNetPeer connectedPeer in ZNet.instance.GetConnectedPeers()) { try { ZPackage val = new ZPackage(); val.Write(text); ZRoutedRpc.instance.InvokeRoutedRPC(connectedPeer.m_uid, "BoostDrop_RuntimePolicySnapshot_v2", new object[1] { val }); _snapshotPushes++; } catch (Exception ex) { if (ModConfig.EnableLogging.Value) { BoostDropPlugin.Log.LogWarning((object)$"BoostDrop: snapshot broadcast failed for peer {connectedPeer.m_uid}: {ex.Message}"); } } } } private static void BroadcastUpsertDelta(RuntimeIdentityDirectory.Entry identity, EffectivePlayerPolicy policy, long exceptPeerUid = 0L) { if (!((Object)(object)ZNet.instance == (Object)null) && ZRoutedRpc.instance != null && identity.RuntimePlayerId != 0L) { BroadcastDelta(new RuntimePolicyDelta { Revision = _runtimeRevision, Operation = "upsert", RuntimePlayerId = identity.RuntimePlayerId, PlayerName = identity.PlayerName, Entry = ToRuntimeEntry(identity, policy) }, exceptPeerUid); } } private static void BroadcastRemoveDelta(long runtimePlayerId, string playerName) { BumpRuntimeRevision(); BroadcastDelta(new RuntimePolicyDelta { Revision = _runtimeRevision, Operation = "remove", RuntimePlayerId = runtimePlayerId, PlayerName = (playerName ?? string.Empty) }, 0L); } private static void BroadcastDelta(RuntimePolicyDelta delta, long exceptPeerUid) { //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Expected O, but got Unknown if ((Object)(object)ZNet.instance == (Object)null || ZRoutedRpc.instance == null) { return; } string text = Serializer.Serialize((object)delta); foreach (ZNetPeer connectedPeer in ZNet.instance.GetConnectedPeers()) { if (exceptPeerUid != 0L && connectedPeer.m_uid == exceptPeerUid) { continue; } try { ZPackage val = new ZPackage(); val.Write(text); ZRoutedRpc.instance.InvokeRoutedRPC(connectedPeer.m_uid, "BoostDrop_RuntimePolicyDelta_v2", new object[1] { val }); _deltaPushes++; } catch (Exception ex) { if (ModConfig.EnableLogging.Value) { BoostDropPlugin.Log.LogWarning((object)$"BoostDrop: delta push failed for peer {connectedPeer.m_uid}: {ex.Message}"); } } } } private static RuntimePolicyEntry ToRuntimeEntry(RuntimeIdentityDirectory.Entry identity, EffectivePlayerPolicy policy) { return new RuntimePolicyEntry { PeerUid = identity.PeerUid, RuntimePlayerId = identity.RuntimePlayerId, PlatformUserId = identity.PlatformUserId, PlayerName = (identity.PlayerName ?? string.Empty), Policy = policy }; } private static void BumpRuntimeRevision() { _runtimeRevision = ((_runtimeRevision == long.MaxValue) ? 1 : (_runtimeRevision + 1)); } private static void NormalizePolicy(EffectivePlayerPolicy policy) { if (policy.Categories == null) { CategoryMultipliers categoryMultipliers = (policy.Categories = new CategoryMultipliers()); } policy.Items = ((policy.Items == null) ? new Dictionary<string, double>(StringComparer.OrdinalIgnoreCase) : new Dictionary<string, double>(policy.Items, StringComparer.OrdinalIgnoreCase)); policy.Mobs = ((policy.Mobs == null) ? new Dictionary<string, MobRule>(StringComparer.OrdinalIgnoreCase) : new Dictionary<string, MobRule>(policy.Mobs, StringComparer.OrdinalIgnoreCase)); foreach (MobRule value in policy.Mobs.Values) { value.Items = ((value.Items == null) ? new Dictionary<string, double>(StringComparer.OrdinalIgnoreCase) : new Dictionary<string, double>(value.Items, StringComparer.OrdinalIgnoreCase)); } } } } namespace BoostDrop.Security { internal static class AdminAuthorizer { internal static bool IsRemoteAdmin(long senderPeerUid) { if (!DropBoostService.IsServerAuthoritative() || (Object)(object)ZNet.instance == (Object)null) { return false; } try { return ZNetExtension.IsAdmin(ZNet.instance, senderPeerUid); } catch (Exception ex) { BoostDropPlugin.Log.LogWarning((object)$"BoostDrop: admin check failed for peer {senderPeerUid}: {ex.Message}"); return false; } } } } namespace BoostDrop.Rewards { internal static class GroundItemCategory { internal static ResourceCategory FromItemData(ItemData? data) { if (data == null) { return ResourceCategory.None; } string text = (((Object)(object)data.m_dropPrefab != (Object)null) ? ((Object)data.m_dropPrefab).name : (data.m_shared?.m_name ?? string.Empty)); if (string.IsNullOrWhiteSpace(text)) { return ResourceCategory.None; } string text2 = text.ToLowerInvariant(); if (text2.Contains("wood") || text2.Contains("bark") || text2.Contains("resin")) { return ResourceCategory.Wood; } if (text2.Contains("ore") || text2.Contains("scrap") || text2.Contains("crystal") || text2.Contains("stone") || text2.Contains("flint")) { return ResourceCategory.Mining; } return ResourceCategory.None; } } internal static class GroundPickupService { internal static void TryApply(Player player, ItemDrop drop) { if ((Object)(object)player == (Object)null || (Object)(object)drop == (Object)null || !ModConfig.Enabled.Value || ProcessedDropMarker.IsProcessed(drop)) { return; } try { drop.Load(); } catch { return; } ResourceCategory resourceCategory = GroundItemCategory.FromItemData(drop.m_itemData); if (resourceCategory == ResourceCategory.None || !ModConfig.IsCategoryEnabled(resourceCategory)) { return; } EffectivePlayerPolicy effectivePlayerPolicy = (DropBoostService.IsServerAuthoritative() ? EffectivePolicyCache.ResolvePlayer(player) : (ClientPolicyCache.Current ?? PlayerDropPermissions.CreateVanillaPolicyForContext(player.GetPlayerID(), player.GetPlayerName()))); double baseMultiplier = PlayerDropPermissions.ClampMultiplier(effectivePlayerPolicy.GetCategoryMultiplier(resourceCategory)); ItemData itemData = drop.m_itemData; if (itemData == null || itemData.m_stack <= 0) { return; } string text = (((Object)(object)itemData.m_dropPrefab != (Object)null) ? ((Object)itemData.m_dropPrefab).name : (itemData.m_shared?.m_name ?? string.Empty)); double itemMultiplier = PlayerDropPermissions.GetItemMultiplier(effectivePlayerPolicy, resourceCategory, text, baseMultiplier); if (!(itemMultiplier <= 1.0) && ProcessedDropMarker.TryMark(drop)) { int stack = itemData.m_stack; itemData.m_stack = DropBoostService.ScaleAmount(stack, itemMultiplier); if (ModConfig.EnableLogging.Value && itemData.m_stack != stack) { BoostDropPlugin.Log.LogInfo((object)$"BoostDrop x{itemMultiplier:0.##} [{resourceCategory}] ground pickup: {text} {stack}->{itemData.m_stack} player={player.GetPlayerName()}."); } } } } internal static class ProcessedDropMarker { private const string ProcessedKey = "boostdrop.drop.v2.processed"; internal static bool IsProcessed(ItemDrop? drop) { ZNetView view = GetView(drop); ZDO val = (((Object)(object)view != (Object)null && view.IsValid()) ? view.GetZDO() : null); if (val != null) { return val.GetInt("boostdrop.drop.v2.processed", 0) == 1; } return false; } internal stat