Decompiled source of SharedUpgradesPlus v1.4.2

SharedUpgradesPlus.dll

Decompiled 17 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using ExitGames.Client.Photon;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Photon.Pun;
using Photon.Realtime;
using SharedUpgradesPlus.Configuration;
using SharedUpgradesPlus.Models;
using SharedUpgradesPlus.Services;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: IgnoresAccessChecksTo("Assembly-CSharp")]
[assembly: AssemblyCompany("Vippy")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.4.2.0")]
[assembly: AssemblyInformationalVersion("1.4.2+5c18fd22269d6d7c1e3e137162f6425d54660de2")]
[assembly: AssemblyProduct("SharedUpgradesPlus")]
[assembly: AssemblyTitle("SharedUpgradesPlus")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.4.2.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.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]
	[Microsoft.CodeAnalysis.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]
	[Microsoft.CodeAnalysis.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 SharedUpgradesPlus
{
	internal static class BuildInfo
	{
		public const string Version = "1.4.2";
	}
	[BepInPlugin("Vippy.SharedUpgradesPlus", "SharedUpgradesPlus", "1.4.2")]
	public class SharedUpgradesPlus : BaseUnityPlugin
	{
		internal static SharedUpgradesPlus Instance { get; private set; }

		internal static ManualLogSource Logger => Instance.BaseLogger;

		private ManualLogSource BaseLogger => ((BaseUnityPlugin)this).Logger;

		internal Harmony? Harmony { get; set; }

		private void Awake()
		{
			Instance = this;
			((Component)this).gameObject.transform.parent = null;
			((Object)((Component)this).gameObject).hideFlags = (HideFlags)61;
			PluginConfig.Init(((BaseUnityPlugin)this).Config);
			Patch();
			Logger.LogInfo((object)$"{((BaseUnityPlugin)this).Info.Metadata.GUID} v{((BaseUnityPlugin)this).Info.Metadata.Version} has loaded! LogLevel={PluginConfig.LoggingLevel.Value}");
		}

		internal static void LogAlways(string msg)
		{
			Logger.LogInfo((object)msg);
		}

		internal static void LogInfo(string msg)
		{
			if (ConfigService.IsDebugLoggingEnabled())
			{
				Logger.LogInfo((object)msg);
			}
		}

		internal static void LogVerbose(string msg)
		{
			if (ConfigService.IsVerboseLoggingEnabled())
			{
				Logger.LogDebug((object)msg);
			}
		}

		internal void Patch()
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Expected O, but got Unknown
			//IL_0025: Expected O, but got Unknown
			if (Harmony == null)
			{
				Harmony val = new Harmony(((BaseUnityPlugin)this).Info.Metadata.GUID);
				Harmony val2 = val;
				Harmony = val;
			}
			Harmony.PatchAll();
		}
	}
}
namespace SharedUpgradesPlus.Services
{
	public static class ConfigService
	{
		private static readonly Dictionary<string, ConfigEntry<bool>> upgradeToggles = new Dictionary<string, ConfigEntry<bool>>();

		private static readonly Dictionary<string, ConfigEntry<int>> limitSliders = new Dictionary<string, ConfigEntry<int>>();

		private static readonly HashSet<string> _disabledByDefault = new HashSet<string> { "playerUpgradeObjectValue", "playerUpgradeObjectDurability" };

		public static bool IsSharedUpgradesEnabled()
		{
			return PluginConfig.EnableSharedUpgrades.Value;
		}

		public static bool IsModdedUpgradesEnabled()
		{
			return PluginConfig.EnableModdedUpgrades.Value;
		}

		public static bool IsLateJoinSyncEnabled()
		{
			return PluginConfig.EnableLateJoinSync.Value;
		}

		public static bool IsSharedUpgradeHealEnabled()
		{
			return PluginConfig.EnableSharedUpgradeHeal.Value;
		}

		public static bool IsShareNotificationEnabled()
		{
			return PluginConfig.EnableShareNotification.Value;
		}

		public static int SharedUpgradesChancePercentage()
		{
			return PluginConfig.SharedUpgradeChance.Value;
		}

		public static bool RollSharedUpgradesChance()
		{
			return Roll(PluginConfig.SharedUpgradeChance.Value);
		}

		private static bool Roll(int chance)
		{
			return Random.Range(0, 100) < chance;
		}

		public static bool IsDebugLoggingEnabled()
		{
			return PluginConfig.LoggingLevel.Value >= VerbosityLevel.Debug;
		}

		public static bool IsVerboseLoggingEnabled()
		{
			return PluginConfig.LoggingLevel.Value >= VerbosityLevel.Verbose;
		}

		public static bool IsUpgradeEnabled(string upgradeKey)
		{
			if (upgradeToggles.TryGetValue(upgradeKey, out ConfigEntry<bool> value))
			{
				return value.Value;
			}
			return true;
		}

		public static int UpgradeShareLimit(string upgradeKey)
		{
			if (limitSliders.TryGetValue(upgradeKey, out ConfigEntry<int> value))
			{
				return value.Value;
			}
			return 0;
		}

		public static void LoadModsIntoConfig()
		{
			if (PluginConfig.ConfigFile != null)
			{
				RegisterToggles(RegistryService.Instance.VanillaUpgrades, "Vanilla Upgrades");
				RegisterToggles(RegistryService.Instance.ModdedUpgrades, "Modded Upgrades");
			}
		}

		private static void RegisterToggles(IEnumerable<Upgrade> upgrades, string section)
		{
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Expected O, but got Unknown
			foreach (Upgrade upgrade in upgrades)
			{
				if (!upgradeToggles.ContainsKey(upgrade.Name))
				{
					upgradeToggles[upgrade.Name] = PluginConfig.ConfigFile.Bind<bool>(section, upgrade.CleanName, !_disabledByDefault.Contains(upgrade.Name), "Enable sharing for " + upgrade.CleanName);
					limitSliders[upgrade.Name] = PluginConfig.ConfigFile.Bind<int>(section, upgrade.CleanName + " Share Limit", 0, new ConfigDescription("Others won't receive this upgrade past this level (0 = unlimited)", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100), Array.Empty<object>()));
				}
			}
		}
	}
	public static class DiscoveryService
	{
		public static DiscoveredUpgradesResult DiscoveredUpgrades(StatsManager statsManager)
		{
			HashSet<string> hashSet = new HashSet<string>();
			HashSet<string> hashSet2 = new HashSet<string>();
			foreach (string item in statsManager.dictionaryOfDictionaries.Keys.Where((string key) => key.StartsWith("playerUpgrade")))
			{
				if (AccessTools.Field(typeof(StatsManager), item) != null)
				{
					hashSet.Add(item);
				}
				else
				{
					hashSet2.Add(item);
				}
			}
			foreach (string moddedUpgradeKey in RepoLibInterop.GetModdedUpgradeKeys())
			{
				if (!hashSet.Contains(moddedUpgradeKey))
				{
					hashSet2.Add(moddedUpgradeKey);
				}
			}
			return new DiscoveredUpgradesResult(hashSet, hashSet2);
		}
	}
	public static class DistributionService
	{
		public static bool IsDistributing { get; private set; }

		public static void DistributeUpgrade(UpgradeContext context, string upgradeKey, int difference)
		{
			SharedUpgradesPlus.LogVerbose($"[Distribute] {context.PlayerName} bought {upgradeKey} (+{difference})");
			int num = ConfigService.UpgradeShareLimit(upgradeKey);
			PhotonView component = ((Component)PunManager.instance).GetComponent<PhotonView>();
			if ((Object)(object)component == (Object)null)
			{
				SharedUpgradesPlus.Logger.LogWarning((object)"[Distribute] PhotonView not found on PunManager, can't distribute.");
				return;
			}
			bool flag = RegistryService.Instance.IsVanilla(upgradeKey);
			if (!flag && !ConfigService.IsModdedUpgradesEnabled())
			{
				SharedUpgradesPlus.LogInfo("[Distribute] " + upgradeKey + " is modded and modded upgrades are off, skipping.");
				return;
			}
			if (!ConfigService.IsUpgradeEnabled(upgradeKey))
			{
				SharedUpgradesPlus.LogInfo("[Distribute] " + upgradeKey + " is disabled in config, skipping.");
				return;
			}
			string text = (flag ? new Upgrade(upgradeKey).CleanName : null);
			List<PlayerAvatar> list = SemiFunc.PlayerGetAll();
			int num2 = ConfigService.SharedUpgradesChancePercentage();
			SharedUpgradesPlus.LogVerbose($"[Distribute] {upgradeKey} (+{difference}): {list.Count} player(s), limit={num}, chance={num2}%");
			IsDistributing = true;
			int num3 = 0;
			int num4 = 0;
			try
			{
				foreach (PlayerAvatar item in list)
				{
					if ((Object)(object)item == (Object)null || (Object)(object)item.photonView == (Object)null || item.photonView.ViewID == context.ViewID)
					{
						continue;
					}
					string steamID = item.steamID;
					if (string.IsNullOrEmpty(steamID))
					{
						continue;
					}
					int value = 0;
					if (StatsManager.instance.dictionaryOfDictionaries.TryGetValue(upgradeKey, out var value2))
					{
						value2.TryGetValue(steamID, out value);
					}
					SharedUpgradesPlus.LogVerbose($"[Distribute]   {item.playerName}: level={value}, limit={num}");
					if (num > 0 && num <= value)
					{
						SharedUpgradesPlus.LogInfo($"[Distribute]   {item.playerName} hit share limit ({num}), skipping.");
						num4++;
						continue;
					}
					if (!ConfigService.RollSharedUpgradesChance())
					{
						SharedUpgradesPlus.LogInfo($"[Distribute]   {item.playerName} roll failed ({num2}%), skipping.");
						num4++;
						continue;
					}
					int num5 = value + difference;
					SharedUpgradesPlus.LogAlways($"[Distribute]   {item.playerName}: {value} -> {num5} (+{difference})");
					if (flag)
					{
						component.RPC("TesterUpgradeCommandRPC", (RpcTarget)0, new object[3] { steamID, text, difference });
					}
					else
					{
						component.RPC("UpdateStatRPC", (RpcTarget)0, new object[3] { upgradeKey, steamID, num5 });
					}
					num3++;
				}
			}
			catch (Exception ex)
			{
				SharedUpgradesPlus.Logger.LogError((object)("[Distribute] exception distributing " + upgradeKey + " for " + context.PlayerName + ": " + ex.Message));
			}
			finally
			{
				IsDistributing = false;
			}
			SharedUpgradesPlus.LogVerbose($"[Distribute] done {upgradeKey}: sent={num3}, skipped={num4}");
			HealBuyer(context, upgradeKey, difference);
		}

		private static void HealBuyer(UpgradeContext context, string upgradeKey, int difference)
		{
			if (upgradeKey != "playerUpgradeHealth" || !ConfigService.IsSharedUpgradeHealEnabled())
			{
				return;
			}
			PlayerAvatar val = SemiFunc.PlayerAvatarGetFromSteamID(context.SteamID);
			if (!((Object)(object)val == (Object)null))
			{
				int num = val.playerHealth.maxHealth + 20 * difference - val.playerHealth.health;
				SharedUpgradesPlus.LogVerbose($"[Distribute] healing {context.PlayerName}: max={val.playerHealth.maxHealth}, current={val.playerHealth.health}, healing={num}");
				if (num > 0)
				{
					val.playerHealth.HealOther(num, false);
				}
			}
		}
	}
	public class NetworkCallbackService : MonoBehaviourPunCallbacks
	{
		[CompilerGenerated]
		private sealed class <LateSyncPlayer>d__9 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public PlayerAvatar avatar;

			public string steamID;

			public Dictionary<string, int> teamSnapshot;

			public NetworkCallbackService <>4__this;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <LateSyncPlayer>d__9(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				int num = <>1__state;
				NetworkCallbackService networkCallbackService = <>4__this;
				switch (num)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					<>2__current = SyncService.ApplyTeamSnapshot(avatar, steamID, teamSnapshot);
					<>1__state = 1;
					return true;
				case 1:
					<>1__state = -1;
					networkCallbackService._pendingSync.Remove(avatar.photonView.Owner);
					return false;
				}
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		private readonly HashSet<Player> _pendingSync = new HashSet<Player>();

		public static NetworkCallbackService? Instance { get; private set; }

		public override void OnJoinedRoom()
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Expected O, but got Unknown
			//IL_0038: Expected O, but got Unknown
			SharedUpgradesPlus.LogVerbose($"OnJoinedRoom (isMaster={PhotonNetwork.IsMasterClient})");
			try
			{
				if (PhotonNetwork.IsMasterClient)
				{
					Hashtable val = new Hashtable();
					((Dictionary<object, object>)val).Add((object)"su__v1", (object)"1.4.2");
					Hashtable val2 = val;
					PhotonNetwork.CurrentRoom.SetCustomProperties(val2, (Hashtable)null, (WebFlags)null);
					SharedUpgradesPlus.LogVerbose("Set room property: su__v1=1.4.2");
				}
			}
			catch (Exception ex)
			{
				SharedUpgradesPlus.Logger.LogError((object)("Couldn't set room properties: " + ex.Message));
			}
		}

		public override void OnPlayerEnteredRoom(Player newPlayer)
		{
			SharedUpgradesPlus.LogVerbose($"OnPlayerEnteredRoom: {newPlayer.NickName} (isMaster={SemiFunc.IsMasterClientOrSingleplayer()} lateJoin={ConfigService.IsLateJoinSyncEnabled()})");
			if (ConfigService.IsLateJoinSyncEnabled() && ConfigService.IsSharedUpgradesEnabled() && SemiFunc.IsMasterClientOrSingleplayer())
			{
				_pendingSync.Add(newPlayer);
				SharedUpgradesPlus.LogAlways($"Deferred sync: {newPlayer.NickName} joined, queued. ({_pendingSync.Count} pending)");
			}
		}

		public override void OnPlayerLeftRoom(Player otherPlayer)
		{
			bool flag = _pendingSync.Remove(otherPlayer);
			SharedUpgradesPlus.LogVerbose($"OnPlayerLeftRoom: {otherPlayer.NickName} (was pending: {flag}, pending count: {_pendingSync.Count})");
		}

		public static bool IsPlayerPendingSync(Player player)
		{
			if ((Object)(object)Instance != (Object)null)
			{
				return Instance._pendingSync.Contains(player);
			}
			return false;
		}

		[IteratorStateMachine(typeof(<LateSyncPlayer>d__9))]
		public IEnumerator LateSyncPlayer(PlayerAvatar avatar, string steamID, Dictionary<string, int> teamSnapshot)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <LateSyncPlayer>d__9(0)
			{
				<>4__this = this,
				avatar = avatar,
				steamID = steamID,
				teamSnapshot = teamSnapshot
			};
		}

		private void Awake()
		{
			Instance = this;
			CatchUpExistingPlayers();
		}

		private void CatchUpExistingPlayers()
		{
			if (ConfigService.IsLateJoinSyncEnabled() && ConfigService.IsSharedUpgradesEnabled() && SemiFunc.IsMasterClientOrSingleplayer())
			{
				Player[] playerListOthers = PhotonNetwork.PlayerListOthers;
				foreach (Player val in playerListOthers)
				{
					_pendingSync.Add(val);
					SharedUpgradesPlus.LogAlways($"Catch-up sync: {val.NickName} was already in room, queued. ({_pendingSync.Count} pending)");
				}
			}
		}
	}
	public sealed class RegistryService
	{
		private readonly HashSet<Upgrade> vanillaUpgrades;

		private readonly HashSet<Upgrade> moddedUpgrades;

		private static readonly RegistryService instance = new RegistryService();

		public IReadOnlyCollection<Upgrade> VanillaUpgrades => vanillaUpgrades;

		public IReadOnlyCollection<Upgrade> ModdedUpgrades => moddedUpgrades;

		public static RegistryService Instance => instance;

		private RegistryService()
		{
			vanillaUpgrades = new HashSet<Upgrade>();
			moddedUpgrades = new HashSet<Upgrade>();
		}

		public void RegisterAll(DiscoveredUpgradesResult result)
		{
			vanillaUpgrades.UnionWith(result.Vanilla.Select(MakeUpgradeFromKey));
			moddedUpgrades.UnionWith(result.Modded.Select(MakeUpgradeFromKey));
			SharedUpgradesPlus.Logger.LogInfo((object)$"Discovered {vanillaUpgrades.Count} vanilla and {moddedUpgrades.Count} modded upgrade(s).");
			if (result.Vanilla.Count > 0)
			{
				SharedUpgradesPlus.LogVerbose("Vanilla: " + string.Join(", ", result.Vanilla));
			}
			if (result.Modded.Count > 0)
			{
				SharedUpgradesPlus.LogVerbose("Modded: " + string.Join(", ", result.Modded));
			}
		}

		public void Clear()
		{
			SharedUpgradesPlus.LogVerbose($"Registry cleared ({vanillaUpgrades.Count} vanilla, {moddedUpgrades.Count} modded).");
			vanillaUpgrades.Clear();
			moddedUpgrades.Clear();
		}

		public bool IsVanilla(string key)
		{
			string key2 = key;
			return vanillaUpgrades.Any((Upgrade upgrade) => upgrade.Name.Equals(key2));
		}

		public bool IsRegistered(string key)
		{
			string key2 = key;
			if (!IsVanilla(key2))
			{
				return moddedUpgrades.Any((Upgrade upgrade) => upgrade.Name.Equals(key2));
			}
			return true;
		}

		private Upgrade MakeUpgradeFromKey(string key)
		{
			return new Upgrade(key);
		}
	}
	internal static class RepoLibInterop
	{
		private const BindingFlags MemberFlags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

		private static bool _resolved;

		private static PropertyInfo? _playerUpgradesProp;

		private static PropertyInfo? _upgradeIdProp;

		public static HashSet<string> GetModdedUpgradeKeys()
		{
			HashSet<string> hashSet = new HashSet<string>();
			if (!ResolveReflection())
			{
				return hashSet;
			}
			try
			{
				if (!(_playerUpgradesProp.GetValue(null) is IEnumerable enumerable))
				{
					return hashSet;
				}
				foreach (object item in enumerable)
				{
					if (item != null && _upgradeIdProp.GetValue(item) is string text && !string.IsNullOrEmpty(text))
					{
						hashSet.Add("playerUpgrade" + text);
					}
				}
			}
			catch (Exception ex)
			{
				SharedUpgradesPlus.LogVerbose("[RepoLibInterop] reflection failed: " + ex.Message);
			}
			return hashSet;
		}

		private static bool ResolveReflection()
		{
			if (_resolved)
			{
				if (_playerUpgradesProp != null)
				{
					return _upgradeIdProp != null;
				}
				return false;
			}
			_resolved = true;
			Type type = AccessTools.TypeByName("REPOLib.Modules.Upgrades");
			if (type == null)
			{
				return false;
			}
			_playerUpgradesProp = type.GetProperty("PlayerUpgrades", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			if (_playerUpgradesProp == null)
			{
				return false;
			}
			Type type2 = AccessTools.TypeByName("REPOLib.Modules.PlayerUpgrade");
			if (type2 == null)
			{
				return false;
			}
			_upgradeIdProp = type2.GetProperty("UpgradeId", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			return _upgradeIdProp != null;
		}
	}
	public static class SnapshotService
	{
		public static Dictionary<string, int> SnapshotPlayerStats(string steamID)
		{
			string steamID2 = steamID;
			if (string.IsNullOrEmpty(steamID2) || StatsManager.instance == null)
			{
				return new Dictionary<string, int>();
			}
			Dictionary<string, int> dictionary = StatsManager.instance.dictionaryOfDictionaries.Where((KeyValuePair<string, Dictionary<string, int>> kvp) => RegistryService.Instance.IsRegistered(kvp.Key)).ToDictionary((KeyValuePair<string, Dictionary<string, int>> kvp) => kvp.Key, (KeyValuePair<string, Dictionary<string, int>> kvp) => kvp.Value.GetValueOrDefault(steamID2, 0));
			SharedUpgradesPlus.LogVerbose($"[Snapshot] Player snapshot for {steamID2}: {dictionary.Count} upgrade(s).");
			return dictionary;
		}

		public static Dictionary<string, int> SnapshotTeamMaxLevels(string? excludeSteamID = null)
		{
			string excludeSteamID2 = excludeSteamID;
			Dictionary<string, int> dictionary = new Dictionary<string, int>();
			if (StatsManager.instance == null)
			{
				return dictionary;
			}
			foreach (KeyValuePair<string, Dictionary<string, int>> item in StatsManager.instance.dictionaryOfDictionaries.Where((KeyValuePair<string, Dictionary<string, int>> k) => RegistryService.Instance.IsRegistered(k.Key)))
			{
				IEnumerable<int> enumerable;
				if (!string.IsNullOrEmpty(excludeSteamID2))
				{
					enumerable = from p in item.Value
						where p.Key != excludeSteamID2
						select p.Value;
				}
				else
				{
					IEnumerable<int> values = item.Value.Values;
					enumerable = values;
				}
				IEnumerable<int> source = enumerable;
				dictionary[item.Key] = source.DefaultIfEmpty(0).Max();
			}
			SharedUpgradesPlus.LogVerbose(string.Format("[Snapshot] Team snapshot (exclude={0}): {1} upgrade(s).", excludeSteamID2 ?? "none", dictionary.Count));
			return dictionary;
		}
	}
	public static class SyncService
	{
		[CompilerGenerated]
		private sealed class <ApplyTeamSnapshot>d__0 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public PlayerAvatar player;

			public Dictionary<string, int> teamSnapshot;

			public string steamID;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <ApplyTeamSnapshot>d__0(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				if (<>1__state != 0)
				{
					return false;
				}
				<>1__state = -1;
				if ((Object)(object)StatsManager.instance == (Object)null || (Object)(object)PunManager.instance == (Object)null)
				{
					return false;
				}
				PhotonView component = ((Component)PunManager.instance).GetComponent<PhotonView>();
				if ((Object)(object)component == (Object)null)
				{
					SharedUpgradesPlus.Logger.LogWarning((object)"[LateJoin] PhotonView not found on PunManager, skipping sync.");
					return false;
				}
				string playerName = player.playerName;
				int num = ConfigService.SharedUpgradesChancePercentage();
				SharedUpgradesPlus.LogAlways($"[LateJoin] syncing {playerName}: {teamSnapshot.Count} upgrade(s), chance={num}%");
				int num2 = 0;
				int num3 = 0;
				foreach (KeyValuePair<string, int> item in teamSnapshot)
				{
					int num4 = ConfigService.UpgradeShareLimit(item.Key);
					bool flag = RegistryService.Instance.IsVanilla(item.Key);
					SharedUpgradesPlus.LogVerbose($"[LateJoin]   {item.Key}: teamMax={item.Value}, isVanilla={flag}, limit={num4}");
					if (!flag && !ConfigService.IsModdedUpgradesEnabled())
					{
						SharedUpgradesPlus.LogVerbose("[LateJoin]   " + item.Key + ": skipped (modded upgrades disabled).");
						num3++;
						continue;
					}
					if (!ConfigService.IsUpgradeEnabled(item.Key))
					{
						SharedUpgradesPlus.LogVerbose("[LateJoin]   " + item.Key + ": skipped (disabled in config).");
						num3++;
						continue;
					}
					Dictionary<string, int> value;
					int num5 = (StatsManager.instance.dictionaryOfDictionaries.TryGetValue(item.Key, out value) ? value.GetValueOrDefault(steamID, 0) : 0);
					if (num4 > 0 && num4 <= num5)
					{
						SharedUpgradesPlus.LogInfo($"[LateJoin]   {item.Key}: {playerName} hit share limit ({num4}), skipping.");
						num3++;
						continue;
					}
					int value2 = item.Value;
					int num6 = value2 - num5;
					if (num4 > 0)
					{
						num6 = Math.Min(num6, num4 - num5);
					}
					SharedUpgradesPlus.LogVerbose($"[LateJoin]   {item.Key}: level={num5}, teamMax={item.Value}, diff={num6} (pre-roll)");
					num6 = SimulateRealisticLevelling(num6);
					value2 = num5 + num6;
					if (num6 <= 0)
					{
						SharedUpgradesPlus.LogInfo("[LateJoin]   " + item.Key + ": rolled 0 after chance simulation, skipping.");
						num3++;
						continue;
					}
					if (flag)
					{
						SharedUpgradesPlus.LogVerbose("[LateJoin]   " + item.Key + ": sending TesterUpgradeCommandRPC to " + playerName);
						component.RPC("TesterUpgradeCommandRPC", (RpcTarget)0, new object[3]
						{
							steamID,
							new Upgrade(item.Key).CleanName,
							num6
						});
					}
					else
					{
						SharedUpgradesPlus.LogVerbose("[LateJoin]   " + item.Key + ": sending UpdateStatRPC to " + playerName);
						component.RPC("UpdateStatRPC", (RpcTarget)0, new object[3] { item.Key, steamID, value2 });
					}
					SharedUpgradesPlus.LogVerbose($"[LateJoin]   sent {item.Key} (+{num6}) to {playerName}.");
					num2++;
				}
				SharedUpgradesPlus.LogAlways($"[LateJoin] done {playerName}: sent={num2}, skipped={num3}");
				return false;
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		[IteratorStateMachine(typeof(<ApplyTeamSnapshot>d__0))]
		public static IEnumerator ApplyTeamSnapshot(PlayerAvatar player, string steamID, Dictionary<string, int> teamSnapshot)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <ApplyTeamSnapshot>d__0(0)
			{
				player = player,
				steamID = steamID,
				teamSnapshot = teamSnapshot
			};
		}

		private static int SimulateRealisticLevelling(int value)
		{
			int num = ConfigService.SharedUpgradesChancePercentage();
			if (num >= 100 || value <= 0)
			{
				return value;
			}
			int num2 = 0;
			for (int i = 0; i < value; i++)
			{
				if (ConfigService.RollSharedUpgradesChance())
				{
					num2++;
				}
			}
			SharedUpgradesPlus.LogVerbose($"[LateJoin] roll simulation: input={value}, chance={num}%, result={num2}");
			return num2;
		}
	}
	internal class WatermarkService : MonoBehaviour
	{
		[CompilerGenerated]
		private sealed class <Poll>d__10 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public WatermarkService <>4__this;

			private float <elapsed>5__2;

			object? IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object? IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <Poll>d__10(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				//IL_003b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0045: Expected O, but got Unknown
				int num = <>1__state;
				WatermarkService watermarkService = <>4__this;
				if (num != 0)
				{
					if (num != 1)
					{
						return false;
					}
					<>1__state = -1;
					<elapsed>5__2 += 1f;
					try
					{
						if (PhotonNetwork.InRoom)
						{
							if (string.IsNullOrEmpty(OwnerID))
							{
								goto IL_0171;
							}
							PlayerAvatar val = null;
							foreach (PlayerAvatar item in SemiFunc.PlayerGetAll())
							{
								if ((Object)(object)item?.photonView != (Object)null && item.photonView.IsMine)
								{
									val = item;
									break;
								}
							}
							if (!((Object)(object)val == (Object)null))
							{
								if (val.steamID != OwnerID)
								{
									goto IL_0171;
								}
								bool isMasterClient = PhotonNetwork.IsMasterClient;
								bool flag = ((Dictionary<object, object>)(object)((RoomInfo)PhotonNetwork.CurrentRoom).CustomProperties).ContainsKey((object)"su__v1");
								if (isMasterClient || flag)
								{
									if (((Dictionary<object, object>)(object)((RoomInfo)PhotonNetwork.CurrentRoom).CustomProperties).TryGetValue((object)"su__v1", out object value))
									{
										Version = (value as string) ?? "UNKNOWN";
									}
									watermarkService.show = !isMasterClient && flag;
									goto IL_0171;
								}
							}
						}
					}
					catch
					{
					}
				}
				else
				{
					<>1__state = -1;
					watermarkService.polling = true;
					<elapsed>5__2 = 0f;
				}
				if (<elapsed>5__2 < 10f)
				{
					<>2__current = (object)new WaitForSeconds(1f);
					<>1__state = 1;
					return true;
				}
				goto IL_0171;
				IL_0171:
				watermarkService.polling = false;
				return false;
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		internal const string RoomKey = "su__v1";

		private static string Version = "UNKNOWN";

		private static readonly string? OwnerID = LoadOwnerID();

		private bool show;

		private bool polling;

		private Object? lastLevel;

		private string? lastRoom;

		private GUIStyle? style;

		private static string? LoadOwnerID()
		{
			try
			{
				string path = Path.Combine(Paths.ConfigPath, "SharedUpgradesPlus.owner");
				if (!File.Exists(path))
				{
					return null;
				}
				return File.ReadAllText(path).Trim();
			}
			catch
			{
				return null;
			}
		}

		private void Update()
		{
			if ((Object)(object)RunManager.instance == (Object)null)
			{
				return;
			}
			Level levelCurrent = RunManager.instance.levelCurrent;
			Room currentRoom = PhotonNetwork.CurrentRoom;
			string text = ((currentRoom != null) ? currentRoom.Name : null);
			if ((Object)(object)levelCurrent == lastLevel && text == lastRoom)
			{
				return;
			}
			lastLevel = (Object?)(object)levelCurrent;
			lastRoom = text;
			if (text == null)
			{
				show = false;
			}
			if ((Object)(object)levelCurrent == (Object)(object)RunManager.instance.levelLobbyMenu)
			{
				show = false;
				if (!polling)
				{
					((MonoBehaviour)this).StartCoroutine(Poll());
				}
			}
		}

		[IteratorStateMachine(typeof(<Poll>d__10))]
		private IEnumerator Poll()
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <Poll>d__10(0)
			{
				<>4__this = this
			};
		}

		private void OnGUI()
		{
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Expected O, but got Unknown
			if (!show)
			{
				return;
			}
			try
			{
				if (style == null)
				{
					GUIStyle val = new GUIStyle(GUI.skin.label)
					{
						fontSize = 24
					};
					val.normal.textColor = new Color(1f, 1f, 1f, 0.15f);
					style = val;
				}
				GUI.Label(new Rect(6f, (float)(Screen.height - 72), 160f, 56f), "SUP: " + Version, style);
			}
			catch
			{
			}
		}
	}
}
namespace SharedUpgradesPlus.Patches
{
	[HarmonyPatch(typeof(PunManager), "UpdateStatRPC")]
	internal class ModdedUpgradesPatch
	{
		[HarmonyPrefix]
		public static void Prefix(string dictionaryName, string key, out int __state)
		{
			__state = 0;
			if (!((Object)(object)StatsManager.instance == (Object)null) && StatsManager.instance.dictionaryOfDictionaries.TryGetValue(dictionaryName, out var value))
			{
				value.TryGetValue(key, out __state);
			}
		}

		[HarmonyPostfix]
		public static void Postfix(string dictionaryName, string key, int value, int __state)
		{
			//IL_0123: Unknown result type (might be due to invalid IL or missing references)
			if (!ConfigService.IsSharedUpgradesEnabled() || !ConfigService.IsModdedUpgradesEnabled() || !RegistryService.Instance.IsRegistered(dictionaryName) || RegistryService.Instance.IsVanilla(dictionaryName) || !ConfigService.IsUpgradeEnabled(dictionaryName))
			{
				return;
			}
			PlayerAvatar val = SemiFunc.PlayerAvatarGetFromSteamID(key);
			int num = value - __state;
			SharedUpgradesPlus.LogVerbose(string.Format("[ModdedPatch] {0} ({1}): {2} -> {3} (+{4}), player={5}, distributing={6}", dictionaryName, key, __state, value, num, val?.playerName ?? "not found", DistributionService.IsDistributing));
			if ((Object)(object)val != (Object)null && ConfigService.IsShareNotificationEnabled())
			{
				SharedUpgradesPlus.LogVerbose("[ModdedPatch] running effects for " + val.playerName);
				if (val.isLocal)
				{
					SharedUpgradesPlus.LogVerbose("[ModdedPatch] local player, triggering StatsUI + CameraGlitch.");
					StatsUI.instance.Fetch();
					StatsUI.instance.ShowStats();
					CameraGlitch.Instance.PlayUpgrade();
				}
				else
				{
					SharedUpgradesPlus.LogVerbose("[ModdedPatch] remote player, shaking camera.");
					GameDirector.instance.CameraImpact.ShakeDistance(5f, 1f, 6f, ((Component)val).transform.position, 0.2f);
				}
				if (!GameManager.Multiplayer() || PhotonNetwork.IsMasterClient)
				{
					SharedUpgradesPlus.LogVerbose("[ModdedPatch] applying upgrade material effect to " + val.playerName + ".");
					val.playerHealth.MaterialEffectOverride((Effect)0);
				}
			}
			if (num > 0 && SemiFunc.IsMasterClientOrSingleplayer())
			{
				if (DistributionService.IsDistributing)
				{
					SharedUpgradesPlus.LogVerbose("[ModdedPatch] already distributing, skipping " + dictionaryName + ".");
				}
				else if ((Object)(object)val == (Object)null || (Object)(object)val.photonView == (Object)null)
				{
					SharedUpgradesPlus.Logger.LogWarning((object)("[ModdedPatch] no PlayerAvatar found for " + key + ", can't distribute " + dictionaryName + "."));
				}
				else
				{
					string playerName = val.playerName;
					SharedUpgradesPlus.LogAlways($"[ModdedPatch] {playerName} bought {dictionaryName}: {__state} -> {value} (+{num}), distributing...");
					DistributionService.DistributeUpgrade(new UpgradeContext(key, val.photonView.ViewID, playerName, new Dictionary<string, int>()), dictionaryName, num);
				}
			}
		}
	}
	[HarmonyPatch(typeof(PlayerTumble), "SetupDone")]
	internal class PlayerTumbleSetupDonePatch
	{
		[HarmonyPostfix]
		public static void Postfix(PlayerTumble __instance)
		{
			if (SemiFunc.IsMasterClientOrSingleplayer() && !string.IsNullOrEmpty(__instance.playerAvatar.steamID) && NetworkCallbackService.IsPlayerPendingSync(__instance.playerAvatar.photonView.Owner) && ConfigService.IsLateJoinSyncEnabled() && ConfigService.IsSharedUpgradesEnabled())
			{
				SharedUpgradesPlus.LogAlways("[LateJoin] SetupDone fired for " + __instance.playerAvatar.steamID + ", triggering sync.");
				Dictionary<string, int> teamSnapshot = SnapshotService.SnapshotTeamMaxLevels(__instance.playerAvatar.steamID);
				if ((Object)(object)NetworkCallbackService.Instance == (Object)null)
				{
					SharedUpgradesPlus.Logger.LogError((object)"NetworkCallbackService instance is null. Cannot sync player stats.");
				}
				else
				{
					((MonoBehaviour)NetworkCallbackService.Instance).StartCoroutine(NetworkCallbackService.Instance.LateSyncPlayer(__instance.playerAvatar, __instance.playerAvatar.steamID, teamSnapshot));
				}
			}
		}
	}
	[HarmonyPatch(typeof(PunManager), "TesterUpgradeCommandRPC")]
	internal class PlayerUpgradeEffectPatch
	{
		[HarmonyPostfix]
		public static void Postfix(string _steamID, string upgradeName, int upgradeNum, PhotonMessageInfo _info)
		{
			//IL_00f5: Unknown result type (might be due to invalid IL or missing references)
			bool flag = upgradeName == "Health";
			PlayerAvatar val = SemiFunc.PlayerAvatarGetFromSteamID(_steamID);
			if ((Object)(object)val == (Object)null)
			{
				SharedUpgradesPlus.Logger.LogError((object)("[Effects] TesterUpgradeCommandRPC fired for " + _steamID + " but no PlayerAvatar found, skipping effects."));
				return;
			}
			SharedUpgradesPlus.LogVerbose($"[Effects] {val.playerName} got {upgradeName} x{upgradeNum} (local={val.isLocal})");
			if (ConfigService.IsShareNotificationEnabled())
			{
				if (val.isLocal)
				{
					SharedUpgradesPlus.LogVerbose("[Effects] " + val.playerName + " is local, StatsUI + CameraGlitch.");
					StatsUI.instance.Fetch();
					StatsUI.instance.ShowStats();
					CameraGlitch.Instance.PlayUpgrade();
				}
				else
				{
					SharedUpgradesPlus.LogVerbose("[Effects] " + val.playerName + " is remote, camera shake.");
					GameDirector.instance.CameraImpact.ShakeDistance(5f, 1f, 6f, ((Component)val).transform.position, 0.2f);
				}
				if (!GameManager.Multiplayer() || PhotonNetwork.IsMasterClient)
				{
					SharedUpgradesPlus.LogVerbose("[Effects] applying upgrade material effect to " + val.playerName + ".");
					val.playerHealth.MaterialEffectOverride((Effect)0);
				}
			}
			SharedUpgradesPlus.LogVerbose("[Effects] " + val.playerName + " effects done.");
			if (flag && SemiFunc.IsMasterClientOrSingleplayer() && ConfigService.IsSharedUpgradeHealEnabled())
			{
				int num = val.playerHealth.maxHealth + 20 * upgradeNum - val.playerHealth.health;
				SharedUpgradesPlus.LogVerbose($"[Effects] healing {val.playerName}: max={val.playerHealth.maxHealth}, current={val.playerHealth.health}, healing={num}");
				if (num > 0)
				{
					val.playerHealth.HealOther(num, false);
				}
			}
		}
	}
	[HarmonyPatch(typeof(StatsManager), "RunStartStats")]
	internal class REPOLibSyncPatch
	{
		private static readonly Type _upgradeType = AccessTools.TypeByName("REPOLib.Modules.Upgrades");

		private static readonly Type _playerUpgradeType = AccessTools.TypeByName("REPOLib.Modules.PlayerUpgrade");

		private static readonly FieldInfo? _playerDictionaryField = ((_playerUpgradeType != null) ? AccessTools.Field(_playerUpgradeType, "PlayerDictionary") : null);

		private static readonly FieldInfo? _playerUpgradesField = ((_upgradeType != null) ? AccessTools.Field(_upgradeType, "_playerUpgrades") : null);

		[HarmonyPostfix]
		[HarmonyPriority(0)]
		public static void Postfix()
		{
			if (_playerDictionaryField == null || _playerUpgradesField == null || (Object)(object)StatsManager.instance == (Object)null || !(_playerUpgradesField.GetValue(null) is IDictionary dictionary))
			{
				return;
			}
			SharedUpgradesPlus.LogVerbose($"Syncing {dictionary.Count} REPOLib upgrade(s) to StatsManager.");
			int num = 0;
			foreach (DictionaryEntry item in dictionary)
			{
				if (item.Value != null)
				{
					string text = $"playerUpgrade{item.Key}";
					if (StatsManager.instance.dictionaryOfDictionaries.TryGetValue(text, out var value))
					{
						_playerDictionaryField.SetValue(item.Value, value);
						SharedUpgradesPlus.LogInfo("Synced PlayerDictionary for " + text + ".");
						num++;
					}
				}
			}
			SharedUpgradesPlus.LogVerbose($"REPOLib sync done: {num}/{dictionary.Count} upgrade(s).");
		}
	}
	[HarmonyPatch(typeof(ItemUpgrade), "PlayerUpgrade")]
	internal class SharedUpgradesPatch
	{
		private static PlayerAvatar? GetUpgradePlayer(ItemUpgrade instance, out int viewID)
		{
			viewID = 0;
			ItemToggle itemToggle = instance.itemToggle;
			if (itemToggle == null || !itemToggle.toggleState)
			{
				return null;
			}
			viewID = instance.itemToggle.playerTogglePhotonID;
			return SemiFunc.PlayerAvatarGetFromPhotonID(viewID);
		}

		[HarmonyPrefix]
		public static void Prefix(ItemUpgrade __instance, out UpgradeContext? __state)
		{
			__state = null;
			if (!ConfigService.IsSharedUpgradesEnabled() || !SemiFunc.IsMasterClientOrSingleplayer())
			{
				return;
			}
			int viewID;
			PlayerAvatar upgradePlayer = GetUpgradePlayer(__instance, out viewID);
			if (upgradePlayer == null)
			{
				SharedUpgradesPlus.LogVerbose("[Purchase] upgrade interaction fired but couldn't find a player, skipping.");
				return;
			}
			string steamID = upgradePlayer.steamID;
			if (!string.IsNullOrEmpty(steamID))
			{
				string text = null;
				ItemAttributes itemAttributes = __instance.itemAttributes;
				if (itemAttributes != null && (Object)(object)itemAttributes.item != (Object)null)
				{
					text = ((Object)itemAttributes.item).name;
				}
				SharedUpgradesPlus.LogVerbose("[Purchase] " + upgradePlayer.playerName + " is buying '" + text + "'");
				__state = new UpgradeContext(steamID, playerName: upgradePlayer.playerName, viewID: viewID, levelsBefore: SnapshotService.SnapshotPlayerStats(steamID), itemName: text);
			}
		}

		[HarmonyPostfix]
		public static void Postfix(UpgradeContext? __state)
		{
			if (!SemiFunc.IsMasterClientOrSingleplayer() || __state == null)
			{
				return;
			}
			SharedUpgradesPlus.LogVerbose("[Purchase] checking what " + __state.PlayerName + " just bought (item='" + __state.ItemName + "')");
			bool flag = false;
			foreach (KeyValuePair<string, Dictionary<string, int>> item in StatsManager.instance.dictionaryOfDictionaries.Where((KeyValuePair<string, Dictionary<string, int>> key) => RegistryService.Instance.IsRegistered(key.Key)))
			{
				item.Value.TryGetValue(__state.SteamID, out var value);
				__state.LevelsBefore.TryGetValue(item.Key, out var value2);
				SharedUpgradesPlus.LogVerbose($"[Purchase]   {item.Key}: {value2} -> {value}");
				if (value > value2)
				{
					int num = value - value2;
					flag = true;
					SharedUpgradesPlus.LogAlways($"[Purchase] {__state.PlayerName} bought {item.Key} (+{num}), distributing...");
					DistributionService.DistributeUpgrade(__state, item.Key, num);
				}
			}
			SharedUpgradesPlus.LogVerbose($"[Purchase] vanilla scan done, distributed={flag}");
			if (!flag && __state.ItemName != null && ConfigService.IsModdedUpgradesEnabled())
			{
				SharedUpgradesPlus.LogVerbose("[Purchase] no vanilla upgrades changed, checking modded match for '" + __state.ItemName + "'");
				string text = MatchItemNameToModdedUpgrade(__state.ItemName);
				if (text != null)
				{
					SharedUpgradesPlus.LogInfo("[Purchase] " + __state.PlayerName + " (" + __state.SteamID + ") bought modded " + text + " (+1), distributing...");
					DistributionService.DistributeUpgrade(__state, text, 1);
				}
				else
				{
					SharedUpgradesPlus.LogVerbose("[Purchase] no match for '" + __state.ItemName + "', nothing to distribute.");
				}
			}
		}

		private static string? MatchItemNameToModdedUpgrade(string itemName)
		{
			string text = itemName.Replace(" ", "");
			foreach (Upgrade moddedUpgrade in RegistryService.Instance.ModdedUpgrades)
			{
				string value = moddedUpgrade.CleanName.Replace(" ", "");
				if (text.EndsWith(value, StringComparison.OrdinalIgnoreCase))
				{
					return moddedUpgrade.Name;
				}
			}
			return null;
		}
	}
	[HarmonyPatch(typeof(StatsManager), "Start")]
	internal class StatsManagerPatch
	{
		private static NetworkCallbackService? _callbackService;

		[HarmonyPostfix]
		public static void Postfix(StatsManager __instance)
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Expected O, but got Unknown
			SharedUpgradesPlus.LogVerbose("StatsManager.Start: initial discovery.");
			RefreshRegistry(__instance);
			if ((Object)(object)_callbackService == (Object)null)
			{
				GameObject val = new GameObject("SharedUpgradesPlus_Services");
				_callbackService = val.AddComponent<NetworkCallbackService>();
				val.AddComponent<WatermarkService>();
				Object.DontDestroyOnLoad((Object)val);
				SharedUpgradesPlus.LogVerbose("Created NetworkCallbackService and WatermarkService.");
			}
			else
			{
				SharedUpgradesPlus.LogVerbose("NetworkCallbackService already exists, skipping.");
			}
		}

		internal static void RefreshRegistry(StatsManager statsManager)
		{
			DiscoveredUpgradesResult discoveredUpgradesResult = DiscoveryService.DiscoveredUpgrades(statsManager);
			SharedUpgradesPlus.LogVerbose($"Found {discoveredUpgradesResult.Vanilla.Count} vanilla and {discoveredUpgradesResult.Modded.Count} modded upgrade(s).");
			RegistryService.Instance.Clear();
			RegistryService.Instance.RegisterAll(discoveredUpgradesResult);
			ConfigService.LoadModsIntoConfig();
		}
	}
	[HarmonyPatch(typeof(StatsManager), "RunStartStats")]
	[HarmonyAfter(new string[] { "REPOLib" })]
	internal class StatsManagerRunStartStatsPatch
	{
		[HarmonyPostfix]
		public static void Postfix(StatsManager __instance)
		{
			SharedUpgradesPlus.LogVerbose("StatsManager.RunStartStats: re-discovering after REPOLib.");
			StatsManagerPatch.RefreshRegistry(__instance);
		}
	}
	[HarmonyPatch(typeof(StatsManager), "LoadGame")]
	internal class StatsManagerLoadGamePatch
	{
		[HarmonyPostfix]
		public static void Postfix(StatsManager __instance)
		{
			SharedUpgradesPlus.LogVerbose("StatsManager.LoadGame: re-discovering after save load.");
			StatsManagerPatch.RefreshRegistry(__instance);
		}
	}
}
namespace SharedUpgradesPlus.Models
{
	public sealed class DiscoveredUpgradesResult
	{
		public HashSet<string> Vanilla { get; }

		public HashSet<string> Modded { get; }

		public DiscoveredUpgradesResult(HashSet<string> vanilla, HashSet<string> modded)
		{
			Vanilla = vanilla;
			Modded = modded;
			base..ctor();
		}
	}
	public sealed class Upgrade : IEquatable<Upgrade>
	{
		public string Name { get; }

		public string CleanName
		{
			get
			{
				if (!Name.StartsWith("playerUpgrade"))
				{
					return Name;
				}
				string name = Name;
				int length = "playerUpgrade".Length;
				return name.Substring(length, name.Length - length);
			}
		}

		public Upgrade(string Name)
		{
			this.Name = Name;
			base..ctor();
		}

		public bool Equals(Upgrade? other)
		{
			if (other != null)
			{
				return other.Name == Name;
			}
			return false;
		}

		public override bool Equals(object? obj)
		{
			return Equals(obj as Upgrade);
		}

		public override int GetHashCode()
		{
			return Name.GetHashCode();
		}
	}
	public sealed class UpgradeContext
	{
		public string SteamID { get; }

		public int ViewID { get; }

		public string PlayerName { get; }

		public Dictionary<string, int> LevelsBefore { get; }

		public string? ItemName { get; }

		public UpgradeContext(string steamID, int viewID, string playerName, Dictionary<string, int> levelsBefore, string? itemName = null)
		{
			SteamID = steamID;
			ViewID = viewID;
			PlayerName = playerName;
			LevelsBefore = levelsBefore;
			ItemName = itemName;
			base..ctor();
		}
	}
}
namespace SharedUpgradesPlus.Configuration
{
	public enum VerbosityLevel
	{
		Off,
		Debug,
		Verbose
	}
	internal static class PluginConfig
	{
		public static ConfigEntry<bool> EnableSharedUpgrades;

		public static ConfigEntry<int> SharedUpgradeChance;

		public static ConfigEntry<bool> EnableLateJoinSync;

		public static ConfigEntry<bool> EnableModdedUpgrades;

		public static ConfigEntry<bool> EnableSharedUpgradeHeal;

		public static ConfigEntry<bool> EnableShareNotification;

		public static ConfigEntry<VerbosityLevel> LoggingLevel;

		public static ConfigFile? ConfigFile;

		public static void Init(ConfigFile config)
		{
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Expected O, but got Unknown
			ConfigFile = config;
			EnableSharedUpgrades = config.Bind<bool>("General", "EnableSharedUpgrades", true, "Enable or disable all upgrade sharing");
			SharedUpgradeChance = config.Bind<int>("General", "SharedUpgradesChance", 100, new ConfigDescription("Chance per upgrade level to be shared with each player", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100), Array.Empty<object>()));
			EnableLateJoinSync = config.Bind<bool>("General", "LateJoinSync", true, "Sync upgrades to players who join mid-run");
			EnableModdedUpgrades = config.Bind<bool>("General", "EnableModdedUpgrades", true, "Sync upgrades added by other mods");
			EnableSharedUpgradeHeal = config.Bind<bool>("Effects", "EnableSharedUpgradeHeal", false, "Heal players to full HP when receiving a shared health upgrade");
			EnableShareNotification = config.Bind<bool>("Effects", "EnableShareNotification", true, "Provide a visual effect when upgrades are shared with you");
			LoggingLevel = config.Bind<VerbosityLevel>("General", "LogLevel", VerbosityLevel.Off, "Off: key events only (sync start/result, purchases). Debug: per-player distribution results and skips. Verbose: full trace of every step.");
		}
	}
}