Decompiled source of BoostDrop v2.1.1

BoostDrop.dll

Decompiled a month ago
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