Decompiled source of SharedUpgradesPlus compatibilitytweaks v1.3.1

BepInEx/plugins/SharedUpgradesPlus.CompatibilityTweaks/SharedUpgradesPlus.CompatibilityTweaks.dll

Decompiled 2 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.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using ExitGames.Client.Photon;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Photon.Pun;
using Photon.Realtime;
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: AssemblyCompany("ByteSizeMetal")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.3.1.0")]
[assembly: AssemblyInformationalVersion("1.3.1")]
[assembly: AssemblyProduct("SharedUpgradesPlus-compatibilitytweaks")]
[assembly: AssemblyTitle("SharedUpgradesPlus.CompatibilityTweaks")]
[assembly: AssemblyMetadata("Author", "ByteSizeMetal")]
[assembly: AssemblyVersion("1.3.1.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace SharedUpgradesFix
{
	internal enum UpgradeKind
	{
		Vanilla,
		RepoLib,
		StatKey
	}
	internal sealed class UpgradeInfo
	{
		public string Key;

		public string Id;

		public string Source;

		public UpgradeKind Kind;

		public Assembly Owner;

		public object Target;

		public bool DetectedPooled;

		public string PooledReason;

		public string TeamEffect;

		public PropertyInfo MaxLevelProp;

		public string DisplayName => Catalog.Pretty(Id);

		public bool Pooled => UpgradeOptions.Pooled(this);

		public int MaxLevel()
		{
			if (MaxLevelProp == null || Target == null)
			{
				return 0;
			}
			try
			{
				object? value = MaxLevelProp.GetValue(Target);
				ConfigEntryBase val = (ConfigEntryBase)((value is ConfigEntryBase) ? value : null);
				if (val != null)
				{
					object boxedValue = val.BoxedValue;
					if (boxedValue is int)
					{
						return (int)boxedValue;
					}
				}
			}
			catch
			{
			}
			return 0;
		}
	}
	internal static class Catalog
	{
		public const string KeyPrefix = "playerUpgrade";

		private static FieldInfo _repoDict;

		private static FieldInfo _idField;

		private static PropertyInfo _repoProp;

		private static FieldInfo[] _callbackFields;

		private static Type _repoPlayerUpgrade;

		private static MethodInfo _repoGetLevel;

		private static readonly Dictionary<string, UpgradeInfo> ByKey = new Dictionary<string, UpgradeInfo>();

		private static readonly HashSet<Assembly> Scanned = new HashSet<Assembly>();

		private static int _lastRepoCount = -1;

		public static bool RepoLibReady { get; private set; }

		public static IEnumerable<UpgradeInfo> All => ByKey.Values;

		internal static void RefreshForDisplay()
		{
			RefreshRepoLib(force: false);
		}

		public static void InitRepoLib(Assembly repoLib)
		{
			if (repoLib == null)
			{
				Plugin.Log.LogWarning((object)"REPOLib not found - REPOLib upgrades can't be identified; share-once and team-pool detection are off.");
				return;
			}
			Type type = repoLib.GetType("REPOLib.Modules.Upgrades");
			_repoPlayerUpgrade = repoLib.GetType("REPOLib.Modules.PlayerUpgrade");
			if (type == null || _repoPlayerUpgrade == null)
			{
				Plugin.Log.LogWarning((object)"REPOLib: 'REPOLib.Modules.Upgrades' or 'REPOLib.Modules.PlayerUpgrade' not found - REPOLib-specific fixes are off.");
				return;
			}
			_repoDict = type.GetField("_playerUpgrades", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			_repoProp = type.GetProperty("PlayerUpgrades", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			_idField = _repoPlayerUpgrade.GetField("UpgradeId", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			_repoGetLevel = _repoPlayerUpgrade.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == "GetLevel");
			_callbackFields = new FieldInfo[2]
			{
				_repoPlayerUpgrade.GetField("_upgradeAction", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic),
				_repoPlayerUpgrade.GetField("StartAction", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
			}.Where((FieldInfo f) => f != null && typeof(Delegate).IsAssignableFrom(f.FieldType)).ToArray();
			if ((_repoDict == null && _repoProp == null) || _idField == null)
			{
				Plugin.Log.LogWarning((object)"REPOLib: upgrade list ('Upgrades._playerUpgrades'/'PlayerUpgrades') or 'PlayerUpgrade.UpgradeId' not found - REPOLib-specific fixes are off.");
			}
			else
			{
				RepoLibReady = true;
			}
		}

		public static bool TryGet(string key, out UpgradeInfo info)
		{
			info = null;
			if (key == null || !key.StartsWith("playerUpgrade", StringComparison.Ordinal))
			{
				return false;
			}
			RefreshRepoLib(force: false);
			return ByKey.TryGetValue(key, out info);
		}

		public static bool IsRepoLib(string key)
		{
			if (TryGet(key, out var info))
			{
				return info.Kind == UpgradeKind.RepoLib;
			}
			return false;
		}

		public static void Rebuild(IEnumerable<string> supVanilla, IEnumerable<string> supModded)
		{
			foreach (string item in supVanilla ?? Enumerable.Empty<string>())
			{
				if (!ByKey.ContainsKey(item))
				{
					ByKey[item] = new UpgradeInfo
					{
						Key = item,
						Id = Strip(item),
						Source = "R.E.P.O. (vanilla)",
						Kind = UpgradeKind.Vanilla
					};
				}
			}
			RefreshRepoLib(force: true);
			foreach (string item2 in supModded ?? Enumerable.Empty<string>())
			{
				if (!ByKey.ContainsKey(item2))
				{
					ByKey[item2] = new UpgradeInfo
					{
						Key = item2,
						Id = Strip(item2),
						Source = "unknown mod (stat key only)",
						Kind = UpgradeKind.StatKey
					};
				}
			}
			ScanNewOwners();
			UpgradeOptions.Register(ByKey.Values);
		}

		private static void RefreshRepoLib(bool force)
		{
			if (!RepoLibReady)
			{
				return;
			}
			List<object> list;
			try
			{
				if (_repoDict?.GetValue(null) is IDictionary dictionary)
				{
					if (!force && dictionary.Count == _lastRepoCount)
					{
						return;
					}
					_lastRepoCount = dictionary.Count;
					list = dictionary.Values.Cast<object>().ToList();
				}
				else
				{
					list = (_repoProp.GetValue(null) as IEnumerable)?.Cast<object>().ToList() ?? new List<object>();
					if (!force && list.Count == _lastRepoCount)
					{
						return;
					}
					_lastRepoCount = list.Count;
				}
			}
			catch (Exception ex)
			{
				Plugin.Debug("reading REPOLib upgrades failed: " + ex.Message);
				return;
			}
			bool flag = false;
			foreach (object pu in list)
			{
				string text = ((pu == null) ? null : (_idField.GetValue(pu) as string));
				if (string.IsNullOrEmpty(text))
				{
					continue;
				}
				string key = "playerUpgrade" + text;
				if (!ByKey.TryGetValue(key, out var value) || value.Kind != UpgradeKind.RepoLib)
				{
					Delegate? obj = _callbackFields.Select((FieldInfo f) => f.GetValue(pu) as Delegate).FirstOrDefault((Delegate x) => (object)x != null);
					Assembly assembly = obj?.Method?.DeclaringType?.Assembly;
					if (assembly == null)
					{
						assembly = MoreInterop.NativeOwner(pu);
					}
					object obj2 = obj?.Target;
					UpgradeInfo upgradeInfo = new UpgradeInfo
					{
						Key = key,
						Id = text,
						Kind = UpgradeKind.RepoLib,
						Owner = assembly,
						Target = obj2,
						Source = (PluginName(assembly) ?? "unknown REPOLib mod")
					};
					PropertyInfo propertyInfo = obj2?.GetType().GetProperty("MaxLevel", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
					if (propertyInfo != null && typeof(ConfigEntryBase).IsAssignableFrom(propertyInfo.PropertyType))
					{
						upgradeInfo.MaxLevelProp = propertyInfo;
					}
					upgradeInfo.TeamEffect = TeamEffectCompat.Description(upgradeInfo);
					if (upgradeInfo.TeamEffect != null)
					{
						Mark(upgradeInfo, upgradeInfo.TeamEffect);
					}
					ByKey[key] = upgradeInfo;
					flag = true;
				}
			}
			if (flag && !force)
			{
				ScanNewOwners();
				UpgradeOptions.Register(ByKey.Values);
			}
		}

		private static string PluginName(Assembly a)
		{
			if (a == null)
			{
				return null;
			}
			foreach (PluginInfo value in Chainloader.PluginInfos.Values)
			{
				if ((Object)(object)value.Instance != (Object)null && ((object)value.Instance).GetType().Assembly == a)
				{
					return $"{value.Metadata.Name} {value.Metadata.Version}";
				}
			}
			return a.GetName().Name;
		}

		private static void ScanNewOwners()
		{
			foreach (Assembly item in (from u in ByKey.Values
				where u.Kind == UpgradeKind.RepoLib && u.Owner != null
				select u.Owner).Distinct().ToList())
			{
				if (Scanned.Add(item))
				{
					try
					{
						ScanAssembly(item);
					}
					catch (Exception ex)
					{
						Plugin.Log.LogWarning((object)("Team-pool scan of " + item.GetName().Name + " failed: " + ex.Message));
					}
				}
			}
		}

		private static void ScanAssembly(Assembly asm)
		{
			List<UpgradeInfo> source = ByKey.Values.Where((UpgradeInfo u) => u.Owner == asm).ToList();
			Dictionary<string, UpgradeInfo> dictionary = source.GroupBy<UpgradeInfo, string>((UpgradeInfo u) => u.Id, StringComparer.OrdinalIgnoreCase).ToDictionary<IGrouping<string, UpgradeInfo>, string, UpgradeInfo>((IGrouping<string, UpgradeInfo> g) => g.Key, (IGrouping<string, UpgradeInfo> g) => g.First(), StringComparer.OrdinalIgnoreCase);
			Dictionary<Type, List<UpgradeInfo>> dictionary2 = (from u in source
				where u.Target != null
				group u by u.Target.GetType()).ToDictionary((IGrouping<Type, UpgradeInfo> g) => g.Key, (IGrouping<Type, UpgradeInfo> g) => g.ToList());
			int num = 0;
			foreach (Type item in SafeTypes(asm))
			{
				MethodInfo[] methods;
				try
				{
					methods = item.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
				}
				catch
				{
					continue;
				}
				if (dictionary2.TryGetValue(item, out var value) && methods.Any(IsTeamTotalName))
				{
					foreach (UpgradeInfo item2 in value)
					{
						Mark(item2, item.Name + " declares " + methods.First(IsTeamTotalName).Name + "()");
					}
				}
				MethodInfo[] array = methods;
				foreach (MethodInfo methodInfo in array)
				{
					List<KeyValuePair<OpCode, object>> list;
					try
					{
						if (methodInfo.IsAbstract || methodInfo.GetMethodBody() == null)
						{
							continue;
						}
						list = PatchProcessor.ReadMethodBody((MethodBase)methodInfo).ToList();
						num++;
						goto IL_0245;
					}
					catch
					{
					}
					continue;
					IL_0245:
					bool flag = list.Any((KeyValuePair<OpCode, object> i) => IsPlayerCollection(i.Value));
					UpgradeInfo upgradeInfo = null;
					Type type = null;
					for (int num3 = 0; num3 < list.Count; num3++)
					{
						OpCode key = list[num3].Key;
						object value2 = list[num3].Value;
						if (key == OpCodes.Ldstr && value2 is string text)
						{
							string key2 = (text.StartsWith("playerUpgrade", StringComparison.Ordinal) ? text.Substring("playerUpgrade".Length) : text);
							if (dictionary.TryGetValue(key2, out var value3))
							{
								upgradeInfo = value3;
							}
						}
						else if (value2 is FieldInfo fieldInfo && dictionary2.ContainsKey(fieldInfo.FieldType))
						{
							type = fieldInfo.FieldType;
						}
						else
						{
							if (!(value2 is MethodInfo methodInfo2))
							{
								continue;
							}
							if (IsTeamTotalName(methodInfo2))
							{
								Type type2 = (dictionary2.ContainsKey(methodInfo2.DeclaringType) ? methodInfo2.DeclaringType : type);
								if (!(type2 != null))
								{
									continue;
								}
								foreach (UpgradeInfo item3 in dictionary2[type2])
								{
									Mark(item3, item.Name + "." + methodInfo.Name + " calls " + methodInfo2.Name + "()");
								}
							}
							else if (dictionary2.ContainsKey(methodInfo2.ReturnType))
							{
								type = methodInfo2.ReturnType;
							}
							else
							{
								if (!flag || !(_repoGetLevel != null) || !(methodInfo2.DeclaringType == _repoPlayerUpgrade) || !(methodInfo2.Name == "GetLevel") || upgradeInfo == null)
								{
									continue;
								}
								int num4 = num3 + 1;
								int num5 = 0;
								for (; num4 < list.Count; num4++)
								{
									if (num5 >= 3)
									{
										break;
									}
									if (!(list[num4].Key == OpCodes.Nop))
									{
										num5++;
										if (list[num4].Key == OpCodes.Add)
										{
											Mark(upgradeInfo, item.Name + "." + methodInfo.Name + " adds up players' levels");
											break;
										}
									}
								}
							}
						}
					}
				}
			}
			Plugin.Debug($"team-pool scan of {asm.GetName().Name}: {num} methods");
		}

		private static void Mark(UpgradeInfo u, string why)
		{
			if (!u.DetectedPooled)
			{
				u.DetectedPooled = true;
				u.PooledReason = why;
			}
		}

		private static bool IsTeamTotalName(MethodInfo m)
		{
			if (m.Name.StartsWith("GetTotal", StringComparison.Ordinal))
			{
				return m.Name.EndsWith("Levels", StringComparison.Ordinal);
			}
			return false;
		}

		private static bool IsPlayerCollection(object operand)
		{
			if (operand is MethodInfo methodInfo)
			{
				if (!(methodInfo.Name == "PlayerGetAll"))
				{
					return methodInfo.Name == "get_PlayerList";
				}
				return true;
			}
			if (operand is FieldInfo fieldInfo)
			{
				if (!(fieldInfo.Name == "PlayerList"))
				{
					return fieldInfo.Name == "playerGrabbing";
				}
				return true;
			}
			return false;
		}

		private static IEnumerable<Type> SafeTypes(Assembly a)
		{
			try
			{
				return a.GetTypes();
			}
			catch (ReflectionTypeLoadException ex)
			{
				return ex.Types.Where((Type t) => t != null);
			}
		}

		public static string Strip(string key)
		{
			if (!key.StartsWith("playerUpgrade", StringComparison.Ordinal))
			{
				return key;
			}
			return key.Substring("playerUpgrade".Length);
		}

		public static string Pretty(string id)
		{
			if (string.IsNullOrEmpty(id) || id.Contains(' '))
			{
				return id;
			}
			StringBuilder stringBuilder = new StringBuilder();
			for (int i = 0; i < id.Length; i++)
			{
				if (i > 0 && char.IsUpper(id[i]) && !char.IsUpper(id[i - 1]))
				{
					stringBuilder.Append(' ');
				}
				stringBuilder.Append(id[i]);
			}
			return stringBuilder.ToString();
		}

		public static bool MatchesAny(string list, UpgradeInfo u)
		{
			if (string.IsNullOrWhiteSpace(list))
			{
				return false;
			}
			string[] array = list.Split(new char[2] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries);
			for (int i = 0; i < array.Length; i++)
			{
				string text = array[i].Trim();
				int num = text.LastIndexOf(':');
				string a = ((num >= 0) ? text.Substring(num + 1).Trim() : text);
				if (string.Equals(a, u.Id, StringComparison.OrdinalIgnoreCase) || string.Equals(a, u.Key, StringComparison.OrdinalIgnoreCase))
				{
					return true;
				}
			}
			return false;
		}

		public static void LogCatalog()
		{
			if (ByKey.Count == 0)
			{
				return;
			}
			Plugin.Log.LogInfo((object)$"Upgrades SharedUpgradesPlus can share ({ByKey.Count}):");
			foreach (IGrouping<string, UpgradeInfo> item in from u in ByKey.Values
				group u by u.Source into g
				orderby (!(g.Key == "R.E.P.O. (vanilla)")) ? g.Key : ""
				select g)
			{
				IEnumerable<string> values = item.OrderBy((UpgradeInfo u) => u.Id).Select(delegate(UpgradeInfo u)
				{
					List<string> list = new List<string>();
					if (UpgradeOptions.Effective(u) == UpgradeHandling.Consumable)
					{
						list.Add("consumable: no highest-level refill");
					}
					if (!UpgradeOptions.SharingAllowed(u))
					{
						list.Add("sharing disabled in patch options");
					}
					if (u.Pooled && Plugin.SkipTeamPooled.Value)
					{
						list.Add("Team: one contribution per purchase");
					}
					int num = u.MaxLevel();
					if (num > 0 && Plugin.CapAtModMaxLevel.Value)
					{
						list.Add($"max {num}");
					}
					return (list.Count != 0) ? (u.DisplayName + " (" + string.Join(", ", list) + ")") : u.DisplayName;
				});
				Plugin.Log.LogInfo((object)("  [" + item.Key + "] " + string.Join(", ", values)));
			}
			foreach (UpgradeInfo item2 in ByKey.Values.Where((UpgradeInfo u) => u.Pooled && u.PooledReason != null))
			{
				Plugin.Debug(item2.Id + ": team-pooled because " + item2.PooledReason);
			}
		}
	}
	internal static class ConnectedTeam
	{
		private static object _room;

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

		internal static string[] UpgradeIds(Dictionary<string, int> levels)
		{
			if (!SemiFunc.IsMultiplayer())
			{
				PlayerAvatar val = SemiFunc.PlayerAvatarLocal();
				string text = (((Object)(object)val == (Object)null) ? null : SemiFunc.PlayerGetSteamID(val));
				if (!string.IsNullOrEmpty(text))
				{
					return new string[1] { text };
				}
				return Array.Empty<string>();
			}
			if (_room != PhotonNetwork.CurrentRoom)
			{
				_room = PhotonNetwork.CurrentRoom;
				Ids.Clear();
			}
			Player[] playerList = PhotonNetwork.PlayerList;
			HashSet<int> actors = new HashSet<int>(playerList.Select((Player p) => p.ActorNumber));
			int[] array = Ids.Keys.Where((int a) => !actors.Contains(a)).ToArray();
			foreach (int key in array)
			{
				Ids.Remove(key);
			}
			foreach (PlayerAvatar item in SemiFunc.PlayerGetAll())
			{
				if (!((Object)(object)item == (Object)null) && !((Object)(object)item.photonView == (Object)null) && actors.Contains(item.photonView.OwnerActorNr))
				{
					string value = SemiFunc.PlayerGetSteamID(item);
					if (!string.IsNullOrEmpty(value))
					{
						Ids[item.photonView.OwnerActorNr] = value;
					}
				}
			}
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			Player[] array2 = playerList;
			foreach (Player val2 in array2)
			{
				if (Ids.TryGetValue(val2.ActorNumber, out var value2))
				{
					hashSet.Add(value2);
				}
				else if (!string.IsNullOrEmpty(val2.UserId) && levels != null && levels.ContainsKey(val2.UserId))
				{
					hashSet.Add(val2.UserId);
				}
			}
			return hashSet.ToArray();
		}
	}
	internal sealed class HighestSync : MonoBehaviour
	{
		private static MethodInfo _enabled;

		private static MethodInfo _chance;

		private static MethodInfo _modded;

		private static MethodInfo _upgradeEnabled;

		private static MethodInfo _limit;

		private static MethodInfo _trySetLevel;

		private static MethodInfo _repoApply;

		private static FieldInfo _repoDictionary;

		private static bool _ready;

		private static bool _applying;

		private static float _nextCheck;

		private static float _nextHeartbeat;

		private static object _session;

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

		private static readonly Dictionary<int, float> Peers = new Dictionary<int, float>();

		private static readonly Dictionary<string, MethodInfo> Vanilla = new Dictionary<string, MethodInfo>();

		private static readonly HashSet<string> Warned = new HashSet<string>();

		private static readonly HashSet<string> HostOwned = new HashSet<string>();

		private static readonly FieldInfo StatsDictionaries = AccessTools.Field(typeof(StatsManager), "dictionaryOfDictionaries");

		private static SortedDictionary<string, Dictionary<string, int>> Dictionaries => StatsDictionaries.GetValue(StatsManager.instance) as SortedDictionary<string, Dictionary<string, int>>;

		public static bool Active
		{
			get
			{
				if (_ready && Plugin.ReconcileHighest.Value && (bool)_enabled.Invoke(null, null))
				{
					return (int)_chance.Invoke(null, null) == 100;
				}
				return false;
			}
		}

		public static void Initialize(Assembly sup, Assembly repo)
		{
			Type type = sup?.GetType("SharedUpgradesPlus.Services.ConfigService");
			_enabled = (_chance = (_modded = (_upgradeEnabled = (_limit = (_trySetLevel = null)))));
			if (type != null)
			{
				_enabled = AccessTools.Method(type, "IsSharedUpgradesEnabled", (Type[])null, (Type[])null);
				_chance = AccessTools.Method(type, "SharedUpgradesChancePercentage", (Type[])null, (Type[])null);
				_modded = AccessTools.Method(type, "IsModdedUpgradesEnabled", (Type[])null, (Type[])null);
				_upgradeEnabled = AccessTools.Method(type, "IsUpgradeEnabled", new Type[1] { typeof(string) }, (Type[])null);
				_limit = AccessTools.Method(type, "UpgradeShareLimit", new Type[1] { typeof(string) }, (Type[])null);
				_trySetLevel = AccessTools.Method(sup.GetType("SharedUpgradesPlus.Services.RepoLibInterop"), "TrySetLevel", (Type[])null, (Type[])null);
			}
			Type type2 = repo?.GetType("REPOLib.Modules.PlayerUpgrade");
			_repoApply = ((type2 == null) ? null : AccessTools.Method(type2, "ApplyUpgrade", new Type[2]
			{
				typeof(string),
				typeof(int)
			}, (Type[])null));
			Type type3 = repo?.GetType("REPOLib.Modules.Upgrades");
			_repoDictionary = ((type3 == null) ? null : AccessTools.Field(type3, "_playerUpgrades"));
			string[] array = new string[13]
			{
				"CrouchRest", "DeathHeadBattery", "ExtraJump", "Health", "Launch", "MapPlayerCount", "Range", "Speed", "Stamina", "Strength",
				"Throw", "TumbleClimb", "TumbleWings"
			};
			string[] array2 = new string[13]
			{
				"UpgradePlayerCrouchRest", "UpgradeDeathHeadBattery", "UpgradePlayerExtraJump", "UpgradePlayerHealth", "UpgradePlayerTumbleLaunch", "UpgradeMapPlayerCount", "UpgradePlayerGrabRange", "UpgradePlayerSprintSpeed", "UpgradePlayerEnergy", "UpgradePlayerGrabStrength",
				"UpgradePlayerThrowStrength", "UpgradePlayerTumbleClimb", "UpgradePlayerTumbleWings"
			};
			for (int i = 0; i < array.Length; i++)
			{
				Vanilla["playerUpgrade" + array[i]] = AccessTools.Method(typeof(PunManager), array2[i], new Type[2]
				{
					typeof(string),
					typeof(int)
				}, (Type[])null);
			}
			_ready = _enabled != null && _chance != null && _modded != null && _upgradeEnabled != null && _limit != null;
			if (sup != null && !_ready)
			{
				Plugin.Log.LogWarning((object)"Highest-level reconciliation unavailable: SharedUpgradesPlus configuration API changed.");
			}
		}

		public static bool Owns(string key)
		{
			if (Active && !UpgradeOptions.Excluded(key) && Catalog.TryGet(key, out var info) && (!Plugin.SkipTeamPooled.Value || !info.Pooled) && (info.Kind == UpgradeKind.Vanilla || (bool)_modded.Invoke(null, null)) && UpgradeOptions.SharingAllowed(info))
			{
				return (bool)_upgradeEnabled.Invoke(null, new object[1] { key });
			}
			return false;
		}

		internal static bool HostOwns(string key)
		{
			if (!SemiFunc.IsMasterClientOrSingleplayer())
			{
				return HostOwned.Contains(key);
			}
			return Owns(key);
		}

		public static void Request()
		{
			_nextCheck = Math.Min(_nextCheck, Time.unscaledTime + 0.25f);
		}

		private void Update()
		{
			if (Time.unscaledTime < _nextCheck)
			{
				return;
			}
			_nextCheck = Time.unscaledTime + Mathf.Max(1f, Plugin.ReconcileSeconds.Value);
			try
			{
				object currentRoom = PhotonNetwork.CurrentRoom;
				if (_session != currentRoom)
				{
					_session = currentRoom;
					Reports.Clear();
					Peers.Clear();
					HostOwned.Clear();
					_nextHeartbeat = 0f;
				}
				if ((Object)(object)StatsManager.instance == (Object)null || (Object)(object)PunManager.instance == (Object)null || (Object)(object)SemiFunc.PlayerAvatarLocal() == (Object)null)
				{
					return;
				}
				if (SemiFunc.IsMasterClientOrSingleplayer())
				{
					if (!Active)
					{
						if (PhotonNetwork.InRoom && Time.unscaledTime >= _nextHeartbeat)
						{
							_nextHeartbeat = Time.unscaledTime + 5f;
							Net.SendStates(Array.Empty<object>());
						}
						return;
					}
					bool flag = Reconcile();
					if (PhotonNetwork.InRoom && (flag || Time.unscaledTime >= _nextHeartbeat))
					{
						_nextHeartbeat = Time.unscaledTime + 5f;
						SendStates();
					}
				}
				else if (PhotonNetwork.InRoom && Time.unscaledTime >= _nextHeartbeat)
				{
					_nextHeartbeat = Time.unscaledTime + 5f;
					string text = SemiFunc.PlayerGetSteamID(SemiFunc.PlayerAvatarLocal());
					Net.SendLevels("report", text, Snapshot(text), new int[1] { PhotonNetwork.MasterClient.ActorNumber });
				}
			}
			catch (Exception ex)
			{
				Warn("tick", "Highest-level check failed; will retry: " + ex.GetBaseException().Message);
			}
		}

		internal static Dictionary<string, int> Snapshot(string steamId)
		{
			Dictionary<string, int> dictionary = new Dictionary<string, int>();
			if ((Object)(object)StatsManager.instance == (Object)null || string.IsNullOrEmpty(steamId))
			{
				return dictionary;
			}
			foreach (KeyValuePair<string, Dictionary<string, int>> dictionary2 in Dictionaries)
			{
				if (dictionary2.Key.StartsWith("playerUpgrade", StringComparison.Ordinal) && !UpgradeOptions.Excluded(dictionary2.Key))
				{
					dictionary[dictionary2.Key] = (dictionary2.Value.TryGetValue(steamId, out var value) ? value : 0);
				}
			}
			return dictionary;
		}

		internal static void ReceiveReport(int actor, string steamId, Dictionary<string, int> levels)
		{
			if (PhotonNetwork.IsMasterClient && Active && !((Object)(object)((IEnumerable<PlayerAvatar>)SemiFunc.PlayerGetAll()).FirstOrDefault((Func<PlayerAvatar, bool>)((PlayerAvatar p) => (Object)(object)p != (Object)null && SemiFunc.PlayerGetSteamID(p) == steamId && (Object)(object)p.photonView != (Object)null && p.photonView.OwnerActorNr == actor)) == (Object)null))
			{
				Peers[actor] = Time.unscaledTime;
				Reports[actor] = levels;
				Request();
			}
		}

		internal static void ReceiveState(string steamId, Dictionary<string, int> levels)
		{
			if (SemiFunc.IsMasterClientOrSingleplayer() || (Object)(object)StatsManager.instance == (Object)null || (Object)(object)PunManager.instance == (Object)null)
			{
				return;
			}
			foreach (KeyValuePair<string, int> level in levels)
			{
				if (UpgradeOptions.Excluded(level.Key))
				{
					continue;
				}
				HostOwned.Add(level.Key);
				if (!Dictionaries.TryGetValue(level.Key, out var value))
				{
					continue;
				}
				int value2;
				int num = (value.TryGetValue(steamId, out value2) ? value2 : 0);
				if (level.Value > num)
				{
					try
					{
						ApplyLocal(level.Key, steamId, level.Value, num);
					}
					catch (Exception ex)
					{
						Warn(level.Key, "Applying host level for " + level.Key + " failed: " + ex.GetBaseException().Message);
					}
				}
			}
		}

		internal static void BeginHostState()
		{
			HostOwned.Clear();
		}

		private static bool Reconcile()
		{
			if (_applying)
			{
				return false;
			}
			_applying = true;
			bool result = false;
			try
			{
				List<PlayerAvatar> list = (from p in SemiFunc.PlayerGetAll()
					where (Object)(object)p != (Object)null && !string.IsNullOrEmpty(SemiFunc.PlayerGetSteamID(p)) && (Object)(object)p.photonView != (Object)null
					group p by SemiFunc.PlayerGetSteamID(p) into g
					select g.First()).ToList();
				foreach (UpgradeInfo item in Catalog.All.ToList())
				{
					if (!Owns(item.Key) || !Dictionaries.TryGetValue(item.Key, out var value))
					{
						continue;
					}
					List<int> list2 = new List<int>();
					foreach (PlayerAvatar item2 in list)
					{
						list2.Add(value.TryGetValue(SemiFunc.PlayerGetSteamID(item2), out var value2) ? value2 : 0);
						if (Reports.TryGetValue(item2.photonView.OwnerActorNr, out var value3) && value3.TryGetValue(item.Key, out var value4))
						{
							list2.Add(value4);
						}
					}
					int num = SyncPolicy.Target(list2, (int)_limit.Invoke(null, new object[1] { item.Key }));
					foreach (PlayerAvatar item3 in list)
					{
						string text = SemiFunc.PlayerGetSteamID(item3);
						int value5;
						int num2 = (value.TryGetValue(text, out value5) ? value5 : 0);
						if (num <= num2)
						{
							continue;
						}
						try
						{
							if (item.Kind == UpgradeKind.RepoLib && _trySetLevel != null)
							{
								if (!(bool)_trySetLevel.Invoke(null, new object[3] { item.Key, text, num }))
								{
									throw new InvalidOperationException("REPOLib SetLevel returned false");
								}
							}
							else
							{
								ApplyLocal(item.Key, text, num, num2);
								if (PhotonNetwork.InRoom)
								{
									PhotonView component = ((Component)PunManager.instance).GetComponent<PhotonView>();
									Player[] playerListOthers = PhotonNetwork.PlayerListOthers;
									foreach (Player val in playerListOthers)
									{
										if (!Patched(val.ActorNumber))
										{
											component.RPC("TesterUpgradeCommandRPC", val, new object[3]
											{
												text,
												item.Id,
												num - num2
											});
										}
									}
								}
							}
							result = true;
							Plugin.Debug($"Reconciled {SemiFunc.PlayerGetName(item3)}: {item.Id} {num2} -> {num}");
						}
						catch (Exception ex)
						{
							Warn(item.Key, "Reconciliation for " + item.Key + " failed: " + ex.GetBaseException().Message);
						}
					}
				}
				return result;
			}
			finally
			{
				Reports.Clear();
				_applying = false;
			}
		}

		private static bool Patched(int actor)
		{
			return Peers.ContainsKey(actor);
		}

		private static void SendStates()
		{
			List<object> list = new List<object>();
			foreach (PlayerAvatar item in SemiFunc.PlayerGetAll())
			{
				if (!((Object)(object)item == (Object)null) && !string.IsNullOrEmpty(SemiFunc.PlayerGetSteamID(item)))
				{
					string text = SemiFunc.PlayerGetSteamID(item);
					Dictionary<string, int> dictionary = (from kv in Snapshot(text)
						where Owns(kv.Key)
						select kv).ToDictionary((KeyValuePair<string, int> kv) => kv.Key, (KeyValuePair<string, int> kv) => kv.Value);
					if (dictionary.Count > 0)
					{
						list.Add(new object[3]
						{
							text,
							dictionary.Keys.ToArray(),
							dictionary.Values.ToArray()
						});
					}
				}
			}
			Net.SendStates(list.ToArray());
		}

		private static void ApplyLocal(string key, string steamId, int target, int current)
		{
			MethodInfo value;
			if (_repoDictionary?.GetValue(null) is IDictionary dictionary && dictionary.Contains(Catalog.Strip(key)) && _repoApply != null)
			{
				_repoApply.Invoke(dictionary[Catalog.Strip(key)], new object[2] { steamId, target });
			}
			else if (Vanilla.TryGetValue(key, out value) && value != null)
			{
				value.Invoke(PunManager.instance, new object[2]
				{
					steamId,
					SyncPolicy.Delta(current, target)
				});
			}
			else
			{
				StatsManager.instance.DictionaryUpdateValue(key, steamId, target);
			}
		}

		private static void Warn(string key, string message)
		{
			if (Warned.Add(key))
			{
				Plugin.Log.LogWarning((object)message);
			}
		}
	}
	internal static class Hooks
	{
		private const string SupNs = "SharedUpgradesPlus.";

		private const string MoreUpgradesGuid = "bulletbot.moreupgrades";

		private static int _purchaseDepth;

		private static int _supHookDepth;

		private static FieldInfo _supToggles;

		private static FieldInfo _supVanilla;

		private static FieldInfo _supModded;

		private static FieldInfo _statsDicts;

		private static PropertyInfo _ctxPlayerName;

		private static PropertyInfo _ctxSteamId;

		private static readonly HashSet<string> LoggedOnce = new HashSet<string>();

		private static bool _gateAvailable;

		private static bool _hookBracketed;

		private static bool _supHooksRepoLib;

		public static void Apply(Harmony h, Assembly sup, Assembly repoLib)
		{
			Type type = SupType(sup, "Services.ConfigService");
			Type type2 = SupType(sup, "Services.RegistryService");
			Type type3 = SupType(sup, "Services.CompatibilityService");
			Type type4 = SupType(sup, "Patches.PlayerUpgradePatches");
			Type type5 = SupType(sup, "Services.DistributionService");
			Type type6 = SupType(sup, "Models.UpgradeContext");
			_supVanilla = ((type2 != null) ? AccessTools.Field(type2, "vanillaUpgrades") : null);
			_supModded = ((type2 != null) ? AccessTools.Field(type2, "moddedUpgrades") : null);
			Patch(h, type2, "RegisterAll", null, "upgrade list", null, "RegisterAllPostfix");
			Patch(h, type, "IsUpgradeEnabled", new Type[1] { typeof(string) }, "team-pooled skip", null, "IsUpgradeEnabledPostfix");
			Patch(h, type, "UpgradeShareLimit", new Type[1] { typeof(string) }, "max level cap", null, "UpgradeShareLimitPostfix");
			if (Chainloader.PluginInfos.ContainsKey("bulletbot.moreupgrades"))
			{
				Patch(h, type3, "IsSharedByMoreUpgrades", new Type[1] { typeof(string) }, "MoreUpgrades Sync Host fix", null, "IsSharedByMoreUpgradesPostfix");
			}
			MethodInfo methodInfo = AccessTools.Method(typeof(ItemUpgrade), "PlayerUpgrade", (Type[])null, (Type[])null);
			Type type7 = repoLib?.GetType("REPOLib.Modules.Upgrades");
			MethodInfo methodInfo2 = ((type7 != null) ? AccessTools.Method(type7, "HandleUpgradeEvent", (Type[])null, (Type[])null) : null);
			int num = 0;
			if (methodInfo != null && Patch(h, methodInfo, "purchase origin", "PurchasePrefix", null, "PurchaseFinalizer"))
			{
				num++;
			}
			if (methodInfo2 != null && Patch(h, methodInfo2, "purchase origin", "PurchasePrefix", null, "PurchaseFinalizer"))
			{
				num++;
			}
			if (num < 2)
			{
				Plugin.Log.LogWarning((object)("Couldn't hook " + ((methodInfo == null) ? "ItemUpgrade.PlayerUpgrade" : "REPOLib Upgrades.HandleUpgradeEvent") + " - 'only real purchases' is off."));
			}
			MethodInfo applyHook = ((type4 != null) ? AccessTools.Method(type4, "ApplyUpgradePostfix", (Type[])null, (Type[])null) : null);
			if (applyHook == null)
			{
				Warn("SharedUpgradesPlus.Patches.PlayerUpgradePatches.ApplyUpgradePostfix", "'only real purchases' and share-once");
			}
			_gateAvailable = (_hookBracketed = applyHook != null && Patch(h, applyHook, "SUP REPOLib hook", "SupHookPrefix", null, "SupHookFinalizer")) && num == 2;
			MethodInfo methodInfo3 = ((type5 != null) ? AccessTools.Method(type5, "DistributeUpgrade", (Type[])null, (Type[])null) : null);
			Type type8 = repoLib?.GetType("REPOLib.Modules.PlayerUpgrade");
			MethodInfo methodInfo4 = ((type8 != null) ? AccessTools.Method(type8, "ApplyUpgrade", (Type[])null, (Type[])null) : null);
			_supHooksRepoLib = applyHook != null && methodInfo4 != null && (Harmony.GetPatchInfo((MethodBase)methodInfo4)?.Postfixes.Any((Patch p) => p.PatchMethod == applyHook) ?? false);
			if (!_supHooksRepoLib)
			{
				Plugin.Log.LogWarning((object)"SharedUpgradesPlus isn't hooked into REPOLib's PlayerUpgrade.ApplyUpgrade - share-once is off (its fallback paths are the only ones that see modded purchases).");
			}
			_ctxPlayerName = ((type6 != null) ? AccessTools.Property(type6, "PlayerName") : null);
			_ctxSteamId = ((type6 != null) ? AccessTools.Property(type6, "SteamID") : null);
			_statsDicts = AccessTools.Field(typeof(StatsManager), "dictionaryOfDictionaries");
			if (methodInfo3 == null)
			{
				Warn("SharedUpgradesPlus.Services.DistributionService.DistributeUpgrade", "share-once and share pop-ups");
			}
			else
			{
				Patch(h, methodInfo3, "share-once / pop-ups", "DistributePrefix", "DistributePostfix");
			}
			_supToggles = ((type != null) ? AccessTools.Field(type, "upgradeToggles") : null);
			if (_supToggles != null)
			{
				Patch(h, type, "LoadModsIntoConfig", Type.EmptyTypes, "config check", null, "LoadModsIntoConfigPostfix");
			}
		}

		private static void RegisterAllPostfix(object __instance)
		{
			try
			{
				List<string> supVanilla = (_supVanilla?.GetValue(__instance) as IDictionary)?.Keys.Cast<string>().ToList();
				List<string> supModded = (_supModded?.GetValue(__instance) as IDictionary)?.Keys.Cast<string>().ToList();
				Catalog.Rebuild(supVanilla, supModded);
				if (LoggedOnce.Add("catalog"))
				{
					Catalog.LogCatalog();
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Reading SharedUpgradesPlus' upgrade list failed: " + ex.Message));
			}
		}

		private static void IsUpgradeEnabledPostfix(object[] __args, ref bool __result)
		{
			if (!__result || !Catalog.TryGet(Arg(__args, 0), out var u))
			{
				return;
			}
			if (!UpgradeOptions.SharingAllowed(u))
			{
				__result = false;
			}
			else if (Plugin.SkipTeamPooled.Value && u.Pooled)
			{
				__result = false;
				Once("pooled:" + u.Key, delegate
				{
					Plugin.Log.LogInfo((object)(u.DisplayName + " (" + u.Source + ") keeps one team contribution per purchase. Its named compatibility setting controls how it is handled."));
				});
			}
		}

		private static void UpgradeShareLimitPostfix(object[] __args, ref int __result)
		{
			if (Plugin.CapAtModMaxLevel.Value && Catalog.TryGet(Arg(__args, 0), out var info))
			{
				int num = info.MaxLevel();
				if (num > 0)
				{
					__result = ((__result <= 0) ? num : Math.Min(__result, num));
				}
			}
		}

		private static void IsSharedByMoreUpgradesPostfix(object[] __args, ref bool __result)
		{
			if (!Plugin.ShareSyncHostUpgrades.Value)
			{
				return;
			}
			string key = Arg(__args, 0);
			if (key != null && MoreInterop.TryTeamSharing(Catalog.Strip(key), out var enabled))
			{
				__result = enabled;
			}
			else if (__result && key != null && !MoreUpgradesAllowsTeam(Catalog.Strip(key)))
			{
				__result = false;
				Once("synchost:" + key, delegate
				{
					Plugin.Log.LogInfo((object)(Catalog.Strip(key) + ": MoreUpgrades only has 'Sync Host Upgrades' on for it, which resets clients to the host's level, so SharedUpgradesPlus shares it instead (client purchases stick)."));
				});
			}
		}

		private static bool MoreUpgradesAllowsTeam(string id)
		{
			if (MoreInterop.TryTeamSharing(id, out var enabled))
			{
				return enabled;
			}
			try
			{
				if (!Chainloader.PluginInfos.TryGetValue("bulletbot.moreupgrades", out var value) || (Object)(object)value.Instance == (Object)null)
				{
					return true;
				}
				ConfigEntry<bool> val = default(ConfigEntry<bool>);
				foreach (ConfigDefinition key in value.Instance.Config.Keys)
				{
					if (!(key.Key != "Allow Team Upgrades"))
					{
						string text = key.Section;
						int num = text.LastIndexOf(" (", StringComparison.Ordinal);
						if (num >= 0 && text.EndsWith(")", StringComparison.Ordinal))
						{
							text = text.Substring(0, num);
						}
						if (string.Equals(Norm(text), Norm(id), StringComparison.Ordinal))
						{
							return value.Instance.Config.TryGetEntry<bool>(key, ref val) && val.Value;
						}
					}
				}
			}
			catch
			{
				return true;
			}
			return false;
		}

		private static void PurchasePrefix(ref bool __state)
		{
			_purchaseDepth++;
			__state = true;
		}

		private static Exception PurchaseFinalizer(bool __state, Exception __exception)
		{
			if (__state)
			{
				_purchaseDepth = Math.Max(0, _purchaseDepth - 1);
				TeamContributionSync.Request();
			}
			return __exception;
		}

		private static bool SupHookPrefix(object[] __args, ref bool __state)
		{
			__state = false;
			if (TeamContributionSync.Applying)
			{
				return false;
			}
			if (__args.Length != 0)
			{
				string text = MoreInterop.UpgradeKey(__args[0]);
				if (text != null && HighestSync.Owns(text))
				{
					HighestSync.Request();
					return false;
				}
			}
			if (Plugin.OnlyRealPurchases.Value && _gateAvailable && _purchaseDepth == 0)
			{
				Plugin.Debug("REPOLib level change outside a purchase (a mod setting it itself) - not shared.");
				return false;
			}
			_supHookDepth++;
			__state = true;
			return true;
		}

		private static Exception SupHookFinalizer(bool __state, Exception __exception)
		{
			if (__state)
			{
				_supHookDepth = Math.Max(0, _supHookDepth - 1);
			}
			return __exception;
		}

		private static bool DistributePrefix(object[] __args, ref Dictionary<string, int> __state)
		{
			__state = null;
			if (TeamContributionSync.Applying)
			{
				return false;
			}
			string text = Arg(__args, 1);
			if (HighestSync.Owns(text))
			{
				HighestSync.Request();
				return false;
			}
			if (Plugin.ShareOnce.Value && _supHooksRepoLib && _hookBracketed && _supHookDepth == 0 && Catalog.IsRepoLib(text))
			{
				Plugin.Debug(text + ": fallback share skipped, SharedUpgradesPlus' REPOLib hook shares this purchase.");
				return false;
			}
			if (Plugin.SendPopups.Value)
			{
				__state = Levels(text);
			}
			return true;
		}

		private static void DistributePostfix(object[] __args, Dictionary<string, int> __state)
		{
			if (__state == null)
			{
				return;
			}
			try
			{
				string text = Arg(__args, 1);
				Dictionary<string, int> dictionary = Levels(text);
				if (dictionary == null)
				{
					return;
				}
				object obj = ((__args.Length != 0) ? __args[0] : null);
				string text2 = ((obj != null) ? (_ctxPlayerName?.GetValue(obj) as string) : null);
				string text3 = ((obj != null) ? (_ctxSteamId?.GetValue(obj) as string) : null);
				UpgradeInfo info;
				string upgrade = (Catalog.TryGet(text, out info) ? info.DisplayName : Catalog.Pretty(Catalog.Strip(text ?? "?")));
				foreach (KeyValuePair<string, int> item in dictionary)
				{
					if (!(item.Key == text3))
					{
						__state.TryGetValue(item.Key, out var value);
						if (item.Value > value)
						{
							Net.SendShared(item.Key, text2 ?? "A teammate", upgrade, item.Value - value);
						}
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Debug("pop-up send failed: " + ex.Message);
			}
		}

		private static Dictionary<string, int> Levels(string key)
		{
			if (key == null || _statsDicts == null || (Object)(object)StatsManager.instance == (Object)null)
			{
				return null;
			}
			if (!(_statsDicts.GetValue(StatsManager.instance) is IDictionary dictionary) || !dictionary.Contains(key))
			{
				return null;
			}
			if (!(dictionary[key] is Dictionary<string, int> dictionary2))
			{
				return null;
			}
			return new Dictionary<string, int>(dictionary2);
		}

		private static void LoadModsIntoConfigPostfix()
		{
			try
			{
				if (!(_supToggles.GetValue(null) is IDictionary dictionary))
				{
					return;
				}
				foreach (UpgradeInfo u in Catalog.All.Where((UpgradeInfo x) => x.Kind == UpgradeKind.RepoLib))
				{
					if (!dictionary.Contains(u.Key))
					{
						Once("cfg:" + u.Key, delegate
						{
							Plugin.Log.LogWarning((object)(u.DisplayName + " (" + u.Source + ") has no entry in SharedUpgradesPlus' config, so it won't be shared. Is EnableModdedUpgrades on?"));
						});
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Debug("config check failed: " + ex.Message);
			}
		}

		private static Type SupType(Assembly a, string name)
		{
			Type? obj = a?.GetType("SharedUpgradesPlus." + name);
			if (obj == null)
			{
				Plugin.Log.LogWarning((object)("SharedUpgradesPlus: type 'SharedUpgradesPlus." + name + "' not found."));
			}
			return obj;
		}

		private static void Warn(string member, string feature)
		{
			Plugin.Log.LogWarning((object)("'" + member + "' not found - " + feature + " off."));
		}

		private static string Arg(object[] a, int i)
		{
			if (a == null || a.Length <= i)
			{
				return null;
			}
			return a[i] as string;
		}

		private static string Norm(string s)
		{
			return new string((s ?? "").Where(char.IsLetterOrDigit).Select(char.ToLowerInvariant).ToArray());
		}

		private static void Once(string id, Action a)
		{
			if (LoggedOnce.Add(id))
			{
				a();
			}
		}

		private static bool Patch(Harmony h, Type type, string method, Type[] args, string feature, string prefix = null, string postfix = null, string finalizer = null)
		{
			if (type == null)
			{
				return false;
			}
			MethodInfo methodInfo = ((args == null) ? AccessTools.Method(type, method, (Type[])null, (Type[])null) : AccessTools.Method(type, method, args, (Type[])null));
			if (methodInfo == null)
			{
				Warn(type.FullName + "." + method, feature);
				return false;
			}
			return Patch(h, methodInfo, feature, prefix, postfix, finalizer);
		}

		private static bool Patch(Harmony h, MethodBase target, string feature, string prefix = null, string postfix = null, string finalizer = null)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				h.Patch(target, (prefix == null) ? ((HarmonyMethod)null) : new HarmonyMethod(typeof(Hooks), prefix, (Type[])null), (postfix == null) ? ((HarmonyMethod)null) : new HarmonyMethod(typeof(Hooks), postfix, (Type[])null), (HarmonyMethod)null, (finalizer == null) ? ((HarmonyMethod)null) : new HarmonyMethod(typeof(Hooks), finalizer, (Type[])null), (HarmonyMethod)null);
				Plugin.Debug("patched " + target.DeclaringType?.FullName + "." + target.Name + " (" + feature + ")");
				return true;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Patching '" + target.DeclaringType?.FullName + "." + target.Name + "' failed - " + feature + " off. " + ex.Message));
				return false;
			}
		}
	}
	internal static class MoreInterop
	{
		private static FieldInfo _items;

		private static FieldInfo _playerUpgrade;

		private static FieldInfo _entries;

		private static FieldInfo _id;

		private static object _plugin;

		private static readonly HashSet<string> NativeIds = new HashSet<string>(StringComparer.Ordinal) { "Sprint Usage", "Valuable Count", "Map Enemy Tracker", "Map Player Tracker", "Map Cosmetics Tracker", "Item Resist", "Item Value", "Extra Life", "Autoscan", "Map Zoom" };

		internal static Assembly NativeOwner(object registered)
		{
			if (_plugin == null || registered == null || _playerUpgrade == null || !(_items?.GetValue(_plugin) is IEnumerable enumerable))
			{
				return null;
			}
			foreach (object item in enumerable)
			{
				if (_playerUpgrade.GetValue(item) == registered)
				{
					string text = registered.GetType().GetField("UpgradeId")?.GetValue(registered) as string;
					object obj = item.GetType().GetField("upgradeBase")?.GetValue(item);
					string text2 = obj?.GetType().GetField("name")?.GetValue(obj) as string;
					return (NativeIds.Contains(text ?? "") && text == text2) ? _plugin.GetType().Assembly : null;
				}
			}
			return null;
		}

		internal static string UpgradeKey(object upgrade)
		{
			if (upgrade == null)
			{
				return null;
			}
			if (_id == null)
			{
				_id = AccessTools.Field(upgrade.GetType(), "UpgradeId");
			}
			if (_id?.GetValue(upgrade) is string text)
			{
				return "playerUpgrade" + text;
			}
			return null;
		}

		internal static void Apply(Harmony harmony)
		{
			//IL_0126: Unknown result type (might be due to invalid IL or missing references)
			//IL_0132: Expected O, but got Unknown
			//IL_0148: Unknown result type (might be due to invalid IL or missing references)
			//IL_0154: Expected O, but got Unknown
			if (!Chainloader.PluginInfos.TryGetValue("bulletbot.moreupgrades", out var value) || (Object)(object)value.Instance == (Object)null)
			{
				return;
			}
			_plugin = value.Instance;
			Assembly assembly = _plugin.GetType().Assembly;
			Type type = assembly.GetType("MoreUpgrades.Classes.UpgradeItem");
			_items = AccessTools.Field(_plugin.GetType(), "upgradeItems");
			_playerUpgrade = ((type == null) ? null : AccessTools.Field(type, "playerUpgrade"));
			_entries = ((type == null) ? null : AccessTools.Field(type, "configEntries"));
			if (_items == null || _playerUpgrade == null || _entries == null)
			{
				return;
			}
			try
			{
				MethodInfo methodInfo = AccessTools.Method(assembly.GetType("MoreUpgrades.Classes.MoreUpgradesManager"), "FixedUpdate", (Type[])null, (Type[])null);
				MethodInfo methodInfo2 = AccessTools.Method(assembly.GetType("MoreUpgrades.REPOLibPatches.REPOLibItemUpgradePatch"), "Upgrade", (Type[])null, (Type[])null);
				if (methodInfo == null || methodInfo2 == null)
				{
					throw new MissingMethodException("MoreUpgrades sharing methods");
				}
				harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(MoreInterop), "ConfigTranspiler", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(MoreInterop), "ConfigTranspiler", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("MoreUpgrades ownership hooks unavailable: " + ex.GetBaseException().Message));
			}
		}

		internal static bool TryTeamSharing(string id, out bool enabled)
		{
			enabled = false;
			if (!(_items?.GetValue(_plugin) is IEnumerable enumerable))
			{
				return false;
			}
			foreach (object item in enumerable)
			{
				string text = UpgradeKey(_playerUpgrade.GetValue(item));
				if (text != null && !(SyncPolicy.Normalize(Catalog.Strip(text)) != SyncPolicy.Normalize(id)))
				{
					enabled = ConfigBool(item, "Allow Team Upgrades");
					return true;
				}
			}
			return false;
		}

		private static IEnumerable<CodeInstruction> ConfigTranspiler(IEnumerable<CodeInstruction> instructions)
		{
			List<CodeInstruction> list = instructions.ToList();
			int num = 0;
			for (int i = 1; i < list.Count; i++)
			{
				if (list[i].operand is MethodInfo methodInfo && !(methodInfo.Name != "GetConfig") && methodInfo.IsGenericMethod && !(methodInfo.GetGenericArguments()[0] != typeof(bool)) && !(list[i - 1].opcode != OpCodes.Ldstr) && list[i - 1].operand is string text && (!(text != "Sync Host Upgrades") || !(text != "Allow Team Upgrades")))
				{
					list[i].opcode = OpCodes.Call;
					list[i].operand = AccessTools.Method(typeof(MoreInterop), "ConfigBool", (Type[])null, (Type[])null);
					num++;
				}
			}
			if (num != 1)
			{
				throw new InvalidOperationException("Expected one MoreUpgrades sharing setting call, found " + num);
			}
			return list;
		}

		private static bool ConfigBool(object item, string name)
		{
			if (!(_entries.GetValue(item) is IDictionary dictionary) || !(dictionary[name] is ConfigEntry<bool> val) || !val.Value)
			{
				return false;
			}
			string text = UpgradeKey(_playerUpgrade.GetValue(item));
			if (text == null)
			{
				return true;
			}
			if (name == "Sync Host Upgrades" && UpgradeOptions.Excluded(text))
			{
				return false;
			}
			if (Plugin.SkipTeamPooled.Value && Catalog.TryGet(text, out var info) && info.Pooled)
			{
				return false;
			}
			if (HighestSync.HostOwns(text))
			{
				return false;
			}
			return true;
		}
	}
	internal static class Net
	{
		private const byte EventCode = 169;

		private const string Magic = "SUPFIX1";

		private const byte SyncCode = 170;

		private const string SyncMagic = "SUPFIX2";

		public static void Register()
		{
			try
			{
				PhotonNetwork.NetworkingClient.EventReceived += OnEvent;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Couldn't listen for share pop-ups: " + ex.Message));
			}
		}

		public static void SendShared(string steamId, string buyer, string upgrade, int amount)
		{
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Expected O, but got Unknown
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			PlayerAvatar val = SemiFunc.PlayerAvatarGetFromSteamID(steamId);
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			string text = buyer + " shared " + upgrade + ((amount > 1) ? $" (+{amount})" : "");
			if ((Object)(object)val == (Object)(object)SemiFunc.PlayerAvatarLocal())
			{
				if (Plugin.ShowPopups.Value)
				{
					Popups.Show(text);
				}
				return;
			}
			Player val2 = (((Object)(object)val.photonView != (Object)null) ? val.photonView.Owner : null);
			if (val2 != null && PhotonNetwork.InRoom)
			{
				object[] obj = new object[2] { "SUPFIX1", text };
				RaiseEventOptions val3 = new RaiseEventOptions();
				val3.TargetActors = new int[1] { val2.ActorNumber };
				PhotonNetwork.RaiseEvent((byte)169, (object)obj, val3, SendOptions.SendReliable);
			}
		}

		private static void OnEvent(EventData e)
		{
			if (e.Code == 170)
			{
				try
				{
					if (e.CustomData is object[] array && array.Length >= 3 && !(array[0] as string != "SUPFIX2"))
					{
						if (array[1] as string == "report" && array.Length == 5 && array[2] is string steamId)
						{
							HighestSync.ReceiveReport(e.Sender, steamId, Decode(array[3], array[4]));
						}
						else if (array[1] as string == "poolreport" && array.Length == 5 && array[2] is string owner)
						{
							TeamContributionSync.ReceiveReport(e.Sender, owner, Decode(array[3], array[4]));
						}
						else if (array[1] as string == "poolstate" && array.Length == 3 && PhotonNetwork.MasterClient != null && e.Sender == PhotonNetwork.MasterClient.ActorNumber && array[2] is object[] array2 && array2.Length <= 32)
						{
							object[] array3 = array2;
							for (int i = 0; i < array3.Length; i++)
							{
								if (array3[i] is object[] array4 && array4.Length == 3 && array4[0] is string owner2)
								{
									TeamContributionSync.ReceiveState(e.Sender, owner2, Decode(array4[1], array4[2]));
								}
							}
						}
						else if (array[1] as string == "pooled" && array.Length == 4)
						{
							TeamUpgradeDisplay.ReceiveHostTotals(e.Sender, Decode(array[2], array[3]));
						}
						else if (array[1] as string == "state" && PhotonNetwork.MasterClient != null && e.Sender == PhotonNetwork.MasterClient.ActorNumber && array[2] is object[] array5 && array5.Length <= 32)
						{
							HighestSync.BeginHostState();
							object[] array3 = array5;
							for (int i = 0; i < array3.Length; i++)
							{
								if (array3[i] is object[] array6 && array6.Length == 3 && array6[0] is string steamId2)
								{
									HighestSync.ReceiveState(steamId2, Decode(array6[1], array6[2]));
								}
							}
						}
					}
					return;
				}
				catch (Exception ex)
				{
					Plugin.Debug("State message ignored: " + ex.GetBaseException().Message);
					return;
				}
			}
			if (e.Code == 169 && Plugin.ShowPopups.Value && PhotonNetwork.MasterClient != null && e.Sender == PhotonNetwork.MasterClient.ActorNumber && e.CustomData is object[] array7 && array7.Length >= 2 && array7[0] as string == "SUPFIX1" && array7[1] is string text)
			{
				Popups.Show(text);
			}
		}

		internal static void SendLevels(string kind, string id, Dictionary<string, int> levels, int[] actors)
		{
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Expected O, but got Unknown
			if (PhotonNetwork.InRoom && levels.Count != 0)
			{
				PhotonNetwork.RaiseEvent((byte)170, (object)new object[5]
				{
					"SUPFIX2",
					kind,
					id,
					levels.Keys.ToArray(),
					levels.Values.ToArray()
				}, new RaiseEventOptions
				{
					TargetActors = actors
				}, SendOptions.SendReliable);
			}
		}

		internal static void SendStates(object[] rows)
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Expected O, but got Unknown
			if (PhotonNetwork.InRoom && PhotonNetwork.IsMasterClient)
			{
				PhotonNetwork.RaiseEvent((byte)170, (object)new object[3] { "SUPFIX2", "state", rows }, new RaiseEventOptions
				{
					Receivers = (ReceiverGroup)0
				}, SendOptions.SendReliable);
			}
		}

		internal static void SendPooledTotals(Dictionary<string, int> totals)
		{
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Expected O, but got Unknown
			if (PhotonNetwork.InRoom && PhotonNetwork.IsMasterClient)
			{
				PhotonNetwork.RaiseEvent((byte)170, (object)new object[4]
				{
					"SUPFIX2",
					"pooled",
					totals.Keys.ToArray(),
					totals.Values.ToArray()
				}, new RaiseEventOptions
				{
					Receivers = (ReceiverGroup)0
				}, SendOptions.SendReliable);
			}
		}

		internal static void SendPooledStates(object[] rows)
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Expected O, but got Unknown
			if (PhotonNetwork.InRoom && PhotonNetwork.IsMasterClient)
			{
				PhotonNetwork.RaiseEvent((byte)170, (object)new object[3] { "SUPFIX2", "poolstate", rows }, new RaiseEventOptions
				{
					Receivers = (ReceiverGroup)0
				}, SendOptions.SendReliable);
			}
		}

		private static Dictionary<string, int> Decode(object keyData, object valueData)
		{
			Dictionary<string, int> dictionary = new Dictionary<string, int>();
			if (!(keyData is string[] array) || !(valueData is int[] array2) || array.Length != array2.Length || array.Length > 256)
			{
				return dictionary;
			}
			for (int i = 0; i < array.Length; i++)
			{
				if (array[i] != null && array[i].StartsWith("playerUpgrade", StringComparison.Ordinal) && array2[i] >= 0)
				{
					dictionary[array[i]] = array2[i];
				}
			}
			return dictionary;
		}
	}
	[BepInPlugin("nakeem.SharedUpgradesFix", "SharedUpgradesPlus-compatibilitytweaks", "1.3.1")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class Plugin : BaseUnityPlugin
	{
		public const string Guid = "nakeem.SharedUpgradesFix";

		public const string Name = "SharedUpgradesPlus-compatibilitytweaks";

		public const string Version = "1.3.1";

		public const string SupGuid = "Vippy.SharedUpgradesPlus";

		private const string TestedSup = "1.6.2";

		private const string TestedRepoLib = "4.3.0";

		internal static ManualLogSource Log;

		internal static ConfigEntry<bool> OnlyRealPurchases;

		internal static ConfigEntry<bool> ShareOnce;

		internal static ConfigEntry<bool> SkipTeamPooled;

		internal static ConfigEntry<bool> CapAtModMaxLevel;

		internal static ConfigEntry<bool> ShareSyncHostUpgrades;

		internal static ConfigEntry<bool> SendPopups;

		internal static ConfigEntry<bool> ShowPopups;

		internal static ConfigEntry<bool> DebugLogging;

		internal static ConfigEntry<float> PopupSeconds;

		internal static ConfigEntry<bool> ReconcileHighest;

		internal static ConfigEntry<float> ReconcileSeconds;

		internal static ConfigEntry<bool> ShowTeamPooledLevels;

		internal static ConfigEntry<bool> SyncTeamContributions;

		internal static ConfigEntry<bool> RepairSlrTeamIdentity;

		internal static ConfigEntry<bool> RepairMoreItemValue;

		internal static ConfigEntry<bool> RepairMoreItemResist;

		private void Awake()
		{
			//IL_010a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0110: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			UpgradeOptions.Initialize(((BaseUnityPlugin)this).Config);
			BindConfig();
			((Component)this).gameObject.AddComponent<Popups>();
			Net.Register();
			((Component)this).gameObject.AddComponent<HighestSync>();
			if (Chainloader.PluginInfos.ContainsKey("nakeem.SharedUpgrades_SLRPatch"))
			{
				Log.LogWarning((object)"SharedUpgrades_SLRPatch is also installed. This compatibility mod replaces it - please remove SharedUpgrades_SLRPatch.");
			}
			Chainloader.PluginInfos.TryGetValue("Vippy.SharedUpgradesPlus", out var value);
			Chainloader.PluginInfos.TryGetValue("REPOLib", out var value2);
			Assembly assembly = (((Object)(object)((value2 != null) ? value2.Instance : null) != (Object)null) ? ((object)value2.Instance).GetType().Assembly : AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault((Assembly a) => a.GetName().Name == "REPOLib"));
			Catalog.InitRepoLib(assembly);
			HighestSync.Initialize(((Object)(object)((value != null) ? value.Instance : null) != (Object)null) ? ((object)value.Instance).GetType().Assembly : null, assembly);
			Harmony val = new Harmony("nakeem.SharedUpgradesFix");
			TeamEffectCompat.Apply(val);
			TeamContributionSync.Initialize(assembly);
			((Component)this).gameObject.AddComponent<TeamContributionSync>();
			TeamUpgradeDisplay.Apply(val);
			((Component)this).gameObject.AddComponent<TeamUpgradeDisplay>();
			MoreInterop.Apply(val);
			if ((Object)(object)((value != null) ? value.Instance : null) == (Object)null)
			{
				Log.LogInfo((object)"Client support ready. SharedUpgradesPlus is required on the host; this client can report and apply host reconciliation states without it.");
				return;
			}
			WarnVersion("SharedUpgradesPlus", value, "1.6.2");
			if (value2 != null)
			{
				WarnVersion("REPOLib", value2, "4.3.0");
			}
			try
			{
				Hooks.Apply(val, ((object)value.Instance).GetType().Assembly, assembly);
				Log.LogInfo((object)"Ready. Sharing uses events 169/170; team effects keep individual contributions synchronized without multiplying purchases. The list of shareable upgrades is logged when a run starts.");
			}
			catch (Exception ex)
			{
				Log.LogError((object)("Setup failed, SharedUpgradesPlus runs unmodified: " + ex));
			}
		}

		private void Start()
		{
			Catalog.RefreshForDisplay();
		}

		private void BindConfig()
		{
			SyncTeamContributions = ((BaseUnityPlugin)this).Config.Bind<bool>("Team effects", "SynchronizeContributions", true, "Repair each connected player's own permanent team-pooled contribution on every machine. Never copy a team total into individual counts or refill consumable charges. Install on everyone.");
			RepairSlrTeamIdentity = ((BaseUnityPlugin)this).Config.Bind<bool>("Team effects", "SLRUpgradePack", true, "Fix SLR Valuable Density's connected-player pool lookup using avatar upgrade IDs when Photon user IDs are missing or differ. Includes dead connected contributors; retains native value/durability active-player eligibility.");
			RepairMoreItemValue = ((BaseUnityPlugin)this).Config.Bind<bool>("Team effects", "MoreUpgradesItemValue", true, "Fix MoreUpgrades Item Value using the sum of connected contributions rather than the last iterated player's level. Automatically uses TeamPooled handling; one contribution per purchase.");
			RepairMoreItemResist = ((BaseUnityPlugin)this).Config.Bind<bool>("Team effects", "MoreUpgradesItemResist", true, "Handle MoreUpgrades Item Resist as one team effect using connected contributions once, independent of which player grabs it. Preserve native item exclusions and held/last-grab eligibility.");
			ShowTeamPooledLevels = ((BaseUnityPlugin)this).Config.Bind<bool>("Display", "ShowTeamPooledLevels", true, "Show Investor and other detected team-pooled upgrades as '(Team)' totals in the normal upgrade stats panel, including when your personal contribution is zero. Cosmetic only: no upgrade levels or effects are copied.");
			OnlyRealPurchases = ((BaseUnityPlugin)this).Config.Bind<bool>("Sharing", "OnlyShareRealPurchases", true, "Only share a modded (REPOLib) level increase when it comes from someone using an upgrade. Levels a mod sets by itself (starting amounts, MoreUpgrades' host sync, revive costs) aren't treated as purchases.");
			ShareOnce = ((BaseUnityPlugin)this).Config.Bind<bool>("Sharing", "ShareEachPurchaseOnce", true, "Share a modded (REPOLib) purchase only through SharedUpgradesPlus' REPOLib hook. Its stat-diff and item-name fallbacks can see the same purchase again and give teammates +2.");
			SkipTeamPooled = ((BaseUnityPlugin)this).Config.Bind<bool>("Sharing", "SkipTeamPooledUpgrades", true, "Don't share upgrades whose mod already adds up every player's level (found automatically, e.g. SLR Object Value / Object Durability / Valuable Density, Nikki's Investor). Sharing those multiplies the effect by the player count.");
			CapAtModMaxLevel = ((BaseUnityPlugin)this).Config.Bind<bool>("Sharing", "CapAtModMaxLevel", true, "Don't push a teammate past an upgrade's own 'Maximum Level' (e.g. SLR Inventory Slot). Combines with SharedUpgradesPlus' Share Limit; the lower one wins.");
			ShareSyncHostUpgrades = ((BaseUnityPlugin)this).Config.Bind<bool>("Sharing", "ShareMoreUpgradesSyncHostUpgrades", true, "Let SharedUpgradesPlus share MoreUpgrades upgrades that only have 'Sync Host Upgrades' on. That option copies the host's level to everyone, which wipes a client's purchase; SharedUpgradesPlus normally leaves those alone.");
			SendPopups = ((BaseUnityPlugin)this).Config.Bind<bool>("Popups", "SendSharePopups", true, "Host: tell each player when they receive a shared upgrade. Only players who also have this patch see it.");
			ShowPopups = ((BaseUnityPlugin)this).Config.Bind<bool>("Popups", "ShowSharePopups", true, "Show 'X shared Y' pop-ups in the bottom-left stack (Semibot RPG's stack when it's installed).");
			PopupSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Popups", "PopupSeconds", 4f, "How long a built-in pop-up stays on screen.");
			DebugLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "DebugLogging", false, "Log every decision this patch makes.");
			ReconcileHighest = ((BaseUnityPlugin)this).Config.Bind<bool>("Reconciliation", "MatchHighestPermanentLevel", true, "At 100% sharing chance, periodically raise enabled permanent upgrades to the highest connected player's count. Never lowers levels. Team-pooled and excluded consumable upgrades are not reconciled. Install this patch on every player for private client-state repair.");
			ReconcileSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Reconciliation", "CheckSeconds", 2f, "Seconds between bounded host checks (minimum 1). Idle checks send no upgrade writes. Client reports and host snapshots run every 5 seconds.");
		}

		private static void WarnVersion(string name, PluginInfo info, string tested)
		{
			object obj;
			if (info == null)
			{
				obj = null;
			}
			else
			{
				BepInPlugin metadata = info.Metadata;
				obj = ((metadata == null) ? null : metadata.Version?.ToString());
			}
			if (obj == null)
			{
				obj = "?";
			}
			string text = (string)obj;
			if (!text.StartsWith(tested))
			{
				Log.LogWarning((object)(name + " " + text + " is installed; this patch was built against " + tested + ". If something it relies on changed, that part turns itself off and says so here."));
			}
		}

		internal static void Debug(string msg)
		{
			if (DebugLogging != null && DebugLogging.Value)
			{
				Log.LogInfo((object)("[debug] " + msg));
			}
		}
	}
	internal static class PooledDisplayPolicy
	{
		internal static int Total(Dictionary<string, int> levels, IEnumerable<string> connectedIds)
		{
			if (levels == null || connectedIds == null)
			{
				return 0;
			}
			long num = 0L;
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			foreach (string connectedId in connectedIds)
			{
				if (!string.IsNullOrEmpty(connectedId) && hashSet.Add(connectedId) && levels.TryGetValue(connectedId, out var value) && value > 0)
				{
					num = Math.Min(2147483647L, num + value);
				}
			}
			return (int)num;
		}

		internal static Dictionary<string, int> DisplayCopy(Dictionary<string, int> personal, Dictionary<string, int> team)
		{
			Dictionary<string, int> dictionary = ((personal == null) ? new Dictionary<string, int>() : new Dictionary<string, int>(personal));
			foreach (KeyValuePair<string, int> item in team)
			{
				dictionary[item.Key] = Math.Max(0, item.Value);
			}
			return dictionary;
		}

		internal static bool Equal(Dictionary<string, int> left, Dictionary<string, int> right)
		{
			if (left.Count != right.Count)
			{
				return false;
			}
			foreach (KeyValuePair<string, int> item in left)
			{
				if (!right.TryGetValue(item.Key, out var value) || value != item.Value)
				{
					return false;
				}
			}
			return true;
		}

		internal static string Label(string name)
		{
			if (!(name ?? "").EndsWith(" (Team)", StringComparison.Ordinal))
			{
				return name + " (Team)";
			}
			return name;
		}
	}
	internal class Popups : MonoBehaviour
	{
		private const string RpgGuid = "nakeem.semibotrpg";

		private static readonly List<KeyValuePair<string, float>> Items = new List<KeyValuePair<string, float>>();

		private static readonly Color Tint = new Color(0.45f, 0.85f, 1f);

		private static bool _rpgResolved;

		private static PropertyInfo _rpgInstance;

		private static MethodInfo _rpgNotify;

		private static object _rpgKind;

		private static GUIStyle _style;

		private static GUIStyle _shadow;

		public static void Show(string text)
		{
			if (!TryRpg(text))
			{
				Items.Add(new KeyValuePair<string, float>(text, Time.unscaledTime + Mathf.Max(1f, Plugin.PopupSeconds.Value)));
				while (Items.Count > 5)
				{
					Items.RemoveAt(0);
				}
			}
		}

		private static bool TryRpg(string text)
		{
			//IL_0176: Unknown result type (might be due to invalid IL or missing references)
			if (!_rpgResolved)
			{
				_rpgResolved = true;
				try
				{
					if (Chainloader.PluginInfos.TryGetValue("nakeem.semibotrpg", out var value) && (Object)(object)value.Instance != (Object)null)
					{
						Type? type = ((object)value.Instance).GetType().Assembly.GetType("SemibotRPG.RpgRuntime");
						_rpgInstance = type?.GetProperty("Instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
						_rpgNotify = type?.GetMethod("Notify", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
						ParameterInfo[] array = _rpgNotify?.GetParameters();
						if (array != null && array.Length == 4 && array[0].ParameterType.IsEnum)
						{
							_rpgKind = (Enum.IsDefined(array[0].ParameterType, "Other") ? Enum.Parse(array[0].ParameterType, "Other") : Enum.ToObject(array[0].ParameterType, 0));
						}
						else
						{
							_rpgNotify = null;
						}
						if (_rpgInstance == null || _rpgNotify == null)
						{
							Plugin.Log.LogWarning((object)"Semibot RPG is installed but its RpgRuntime.Notify wasn't found - using the built-in pop-up stack.");
						}
					}
				}
				catch (Exception ex)
				{
					_rpgNotify = null;
					Plugin.Debug("Semibot RPG pop-ups unavailable: " + ex.Message);
				}
			}
			if (_rpgNotify == null)
			{
				return false;
			}
			try
			{
				object value2 = _rpgInstance.GetValue(null);
				if (value2 == null)
				{
					return false;
				}
				_rpgNotify.Invoke(value2, new object[4]
				{
					_rpgKind,
					text,
					Tint,
					"sharedupgradesfix:" + text
				});
				return true;
			}
			catch (Exception ex2)
			{
				Plugin.Debug("Semibot RPG Notify failed: " + ex2.Message);
				return false;
			}
		}

		private void OnGUI()
		{
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Expected O, but got Unknown
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Expected O, but got Unknown
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			//IL_0116: Unknown result type (might be due to invalid IL or missing references)
			//IL_0131: Unknown result type (might be due to invalid IL or missing references)
			//IL_0179: Unknown result type (might be due to invalid IL or missing references)
			//IL_019d: Unknown result type (might be due to invalid IL or missing references)
			if (Items.Count != 0)
			{
				float now = Time.unscaledTime;
				Items.RemoveAll((KeyValuePair<string, float> i) => i.Value < now);
				if (_style == null)
				{
					_style = new GUIStyle(GUI.skin.label)
					{
						fontSize = Mathf.RoundToInt((float)Screen.height / 45f),
						fontStyle = (FontStyle)1
					};
					_style.normal.textColor = Tint;
					_shadow = new GUIStyle(_style);
					_shadow.normal.textColor = new Color(0f, 0f, 0f, 0.8f);
				}
				float num = (float)_style.fontSize * 1.5f;
				float num2 = (float)Screen.width * 0.015f;
				float num3 = (float)Screen.height * 0.78f;
				Rect val = default(Rect);
				for (int num4 = Items.Count - 1; num4 >= 0; num4--)
				{
					float num5 = Items[num4].Value - now;
					Color color = GUI.color;
					GUI.color = new Color(1f, 1f, 1f, Mathf.Clamp01(num5));
					((Rect)(ref val))..ctor(num2, num3, (float)Screen.width * 0.5f, num);
					GUI.Label(new Rect(((Rect)(ref val)).x + 2f, ((Rect)(ref val)).y + 2f, ((Rect)(ref val)).width, ((Rect)(ref val)).height), Items[num4].Key, _shadow);
					GUI.Label(val, Items[num4].Key, _style);
					GUI.color = color;
					num3 -= num;
				}
			}
		}
	}
	internal static class SyncPolicy
	{
		public static bool Excluded(string key, string exclusions)
		{
			string id = ((key != null && key.StartsWith("playerUpgrade", StringComparison.Ordinal)) ? key.Substring("playerUpgrade".Length) : key);
			if (Consumable(key))
			{
				return true;
			}
			return (exclusions ?? "").Split(new char[2] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries).Any((string s) => string.Equals(s.Trim(), id, StringComparison.OrdinalIgnoreCase) || string.Equals(s.Trim(), key, StringComparison.OrdinalIgnoreCase));
		}

		public static bool Consumable(string key)
		{
			string text = Normalize((key != null && key.StartsWith("playerUpgrade", StringComparison.Ordinal)) ? key.Substring("playerUpgrade".Length) : key);
			if (!(text == "extralife"))
			{
				return text == "secondchance";
			}
			return true;
		}

		public static int Target(IEnumerable<int> levels, int cap)
		{
			int num = 0;
			foreach (int level in levels)
			{
				if (level > num)
				{
					num = level;
				}
			}
			if (cap <= 0)
			{
				return num;
			}
			return Math.Min(num, cap);
		}

		public static int Delta(int current, int target)
		{
			if (target <= current)
			{
				return 0;
			}
			return target - Math.Max(0, current);
		}

		public static string Normalize(string value)
		{
			return new string((value ?? "").Where(char.IsLetterOrDigit).Select(char.ToLowerInvariant).ToArray());
		}
	}
	internal sealed class TeamContributionSync : MonoBehaviour
	{
		private static FieldInfo _registry;

		private static FieldInfo _levels;

		private static MethodInfo _apply;

		private static object _room;

		private static int _host;

		private static float _nextTick;

		private static float _nextHeartbeat;

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

		private static readonly HashSet<string> Warned = new HashSet<string>();

		internal static bool Applying { get; private set; }

		private static bool Ready
		{
			get
			{
				if (Plugin.SyncTeamContributions.Value && _registry != null && _levels != null)
				{
					return _apply != null;
				}
				return false;
			}
		}

		internal static void Request()
		{
			_nextTick = Math.Min(_nextTick, Time.unscaledTime + 0.25f);
			_nextHeartbeat = 0f;
		}

		internal static void Initialize(Assembly repo)
		{
			_registry = repo?.GetType("REPOLib.Modules.Upgrades")?.GetField("_playerUpgrades", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			Type? obj = repo?.GetType("REPOLib.Modules.PlayerUpgrade");
			_levels = obj?.GetField("PlayerDictionary", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			_apply = obj?.GetMethod("ApplyUpgrade", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[2]
			{
				typeof(string),
				typeof(int)
			}, null);
			Pending.Clear();
			_room = null;
			_host = 0;
			_nextTick = (_nextHeartbeat = 0f);
		}

		private static bool Eligible(string key)
		{
			if (!UpgradeOptions.Excluded(key) && Catalog.TryGet(key, out var info) && info.Kind == UpgradeKind.RepoLib && info.Pooled)
			{
				return UpgradeOptions.SharingAllowed(info);
			}
			return false;
		}

		private static object Upgrade(string key)
		{
			if (!(_registry?.GetValue(null) is IDictionary dictionary) || !dictionary.Contains(Catalog.Strip(key)))
			{
				return null;
			}
			return dictionary[Catalog.Strip(key)];
		}

		internal static Dictionary<string, int> Levels(string key)
		{
			object obj = Upgrade(key);
			if (obj != null)
			{
				return _levels?.GetValue(obj) as Dictionary<string, int>;
			}
			return null;
		}

		private static void Session()
		{
			Player masterClient = PhotonNetwork.MasterClient;
			int num = ((masterClient != null) ? masterClient.ActorNumber : 0);
			if (_room != PhotonNetwork.CurrentRoom || _host != num)
			{
				_room = PhotonNetwork.CurrentRoom;
				_host = num;
				Pending.Clear();
				_nextTick = (_nextHeartbeat = 0f);
			}
		}

		internal static Dictionary<string, int> Snapshot(string owner)
		{
			Dictionary<string, int> dictionary = new Dictionary<string, int>();
			if (!Ready || string.IsNullOrEmpty(owner))
			{
				return dictionary;
			}
			UpgradeInfo[] array = Catalog.All.ToArray();
			foreach (UpgradeInfo upgradeInfo in array)
			{
				if (Eligible(upgradeInfo.Key))
				{
					Dictionary<string, int> dictionary2 = Levels(upgradeInfo.Key);
					if (dictionary2 != null)
					{
						dictionary[upgradeInfo.Key] = (dictionary2.TryGetValue(owner, out var value) ? Math.Max(0, value) : 0);
					}
				}
			}
			return dictionary;
		}

		internal static void ReceiveReport(int sender, string owner, Dictionary<string, int> report)
		{
			if (!Ready || !PhotonNetwork.InRoom || !PhotonNetwork.IsMasterClient)
			{
				return;
			}
			Session();
			if (!SemiFunc.PlayerGetAll().Any((PlayerAvatar p) => (Object)(object)p != (Object)null && (Object)(object)p.photonView != (Object)null && p.photonView.OwnerActorNr == sender && SemiFunc.PlayerGetSteamID(p) == owner) || !PhotonNetwork.PlayerList.Any((Player p) => p.ActorNumber == sender))
			{
				return;
			}
			if (!Pending.TryGetValue(owner, out var value))
			{
				value = (Pending[owner] = new Dictionary<string, int>());
			}
			foreach (KeyValuePair<string, int> item in report)
			{
				if (item.Value >= 0 && Eligible(item.Key))
				{
					value[item.Key] = (value.TryGetValue(item.Key, out var value2) ? Math.Max(value2, item.Value) : item.Value);
				}
			}
			_nextTick = Math.Min(_nextTick, Time.unscaledTime + 0.25f);
		}

		internal static void ReceiveState(int sender, string owner, Dictionary<string, int> state)
		{
			if (!Ready || !PhotonNetwork.InRoom || PhotonNetwork.IsMasterClient)
			{
				return;
			}
			Player masterClient = PhotonNetwork.MasterClient;
			if (masterClient == null || masterClient.ActorNumber != sender)
			{
				return;
			}
			Session();
			foreach (KeyValuePair<string, int> item in state)
			{
				if (item.Value >= 0 && Eligible(item.Key))
				{
					Dictionary<string, int> dictionary = Levels(item.Key);
					if (dictionary != null && ConnectedTeam.UpgradeIds(dictionary).Contains(owner))
					{
						Merge(item.Key, owner, item.Value);
					}
				}
			}
		}

		private static bool Merge(string key, string owner, int proposed)
		{
			Dictionary<string, int> dictionary = Levels(key);
			if (dictionary == null)
			{
				return false;
			}
			int value;
			int num = (dictionary.TryGetValue(owner, out value) ? value : 0);
			UpgradeInfo info;
			int num2 = (Catalog.TryGet(key, out info) ? info.MaxLevel() : 0);
			int num3 = ((num2 > 0) ? Math.Min(proposed, num2) : proposed);
			if (num3 <= num)
			{
				return false;
			}
			try
			{
				Applying = true;
				_apply.Invoke(Upgrade(key), new object[2] { owner, num3 });
				return true;
			}
			catch (Exception ex)
			{
				if (Warned.Add(key))
				{
					Plugin.Log.LogWarning((object)("Team contribution repair failed for " + key + ": " + ex.GetBaseException().Message));
				}
				return false;
			}
			finally
			{
				Applying = false;
			}
		}

		internal static bool Reconcile()
		{
			bool flag = false;
			KeyValuePair<string, Dictionary<string, int>>[] array = Pending.ToArray();
			for (int i = 0; i < array.Length; i++)
			{
				KeyValuePair<string, Dictionary<string, int>> keyValuePair = array[i];
				foreach (KeyValuePair<string, int> item in keyValuePair.Value)
				{
					if (Eligible(item.Key))
					{
						Dictionary<string, int> dictionary = Levels(item.Key);
						if (dictionary != null && ConnectedTeam.UpgradeIds(dictionary).Contains(keyValuePair.Key))
						{
							flag |= Merge(item.Key, keyValuePair.Key, item.Value);
						}
					}
				}
			}
			Pending.Clear();
			return flag;
		}

		private void Update()
		{
			if (Time.unscaledTime < _nextTick)
			{
				return;
			}
			_nextTick = Time.unscaledTime + 2f;
			try
			{
				Session();
				if (!Ready || (Object)(object)StatsManager.instance == (Object)null || (Object)(object)SemiFunc.PlayerAvatarLocal() == (Object)null)
				{
					return;
				}
				Catalog.RefreshForDisplay();
				if (SemiFunc.IsMasterClientOrSingleplayer())
				{
					bool flag = Reconcile();
					if (!PhotonNetwork.InRoom || (!flag && Time.unscaledTime < _nextHeartbeat))
					{
						return;
					}
					List<object> list = new List<object>();
					string[] array = Catalog.All.Where((UpgradeInfo i) => Eligible(i.Key)).SelectMany((UpgradeInfo i) => ConnectedTeam.UpgradeIds(Levels(i.Key))).Distinct()
						.ToArray();
					foreach (string text in array)
					{
						Dictionary<string, int> dictionary = Snapshot(text);
						if (dictionary.Count > 0)
						{
							list.Add(new object[3]
							{
								text,
								dictionary.Keys.ToArray(),
								dictionary.Values.ToArray()
							});
						}
					}
					if (list.Count > 0)
					{
						Net.SendPooledStates(list.ToArray());
					}
					_nextHeartbeat = Time.unscaledTime + 5f;
				}
				else if (PhotonNetwork.InRoom && PhotonNetwork.MasterClient != null && Time.unscaledTime >= _nextHeartbeat)
				{
					string text2 = SemiFunc.PlayerGetSteamID(SemiFunc.PlayerAvatarLocal());
					Net.SendLevels("poolreport", text2, Snapshot(text2), new int[1] { PhotonNetwork.MasterClient.ActorNumber });
					_nextHeartbeat = Time.unscaledTime + 5f;
				}
			}
			catch (Exception ex)
			{
				if (Warned.Add("tick"))
				{
					Plugin.Log.LogWarning((object)("Team contribution check failed; will retry: " + ex.GetBaseException().Message));
				}
			}
		}
	}
	internal static class TeamEffectCompat
	{
		private static bool _moreApplied;

		private static PropertyInfo _slrRegister;

		private static FieldInfo _slrLevels;

		private static FieldInfo _slrId;

		private static FieldInfo _moreRegister;

		private static FieldInfo _moreId;

		internal static bool MoreValueActive { get; private set; }

		internal static bool MoreResistActive { get; private set; }

		internal static void Apply(Harmony harmony)
		{
			//IL_02b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_02bd: Expected O, but got Unknown
			//IL_047d: Unknown result type (might be due to invalid IL or missing references)
			//IL_048b: Expected O, but got Unknown
			//IL_01ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bc: Expected O, but got Unknown
			if (Plugin.RepairSlrTeamIdentity.Value && Chainloader.PluginInfos.TryGetValue("SolarAaron.SLRUpgradePack", out var value) && (Object)(object)value.Instance != (Object)null)
			{
				try
				{
					Type? type = ((object)value.Instance).GetType().Assembly.GetType("SLRUpgradePack.UpgradeManagers.ValuableDensityUpgrade");
					MethodInfo methodInfo = type?.GetMethod("GetTotalConnectedPlayerLevels", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
					List<KeyValuePair<OpCode, object>> source = ((methodInfo == null) ? null : PatchProcessor.ReadMethodBody((MethodBase)methodInfo).ToList());
					_slrRegister = type?.GetProperty("UpgradeRegister", BindingFlags.Instance | BindingFlags.Public);
					_slrLevels = _slrRegister?.PropertyType.GetField("PlayerDictionary");
					_slrId = _slrRegister?.PropertyType.GetField("UpgradeId");
					if (methodInfo?.ReturnType != typeof(int) || _slrLevels?.FieldType != typeof(Dictionary<string, int>) || _slrId?.FieldType != typeof(string) || !source.Any((KeyValuePair<OpCode, object> i) => i.Value is MethodInfo methodInfo4 && methodInfo4.DeclaringType.FullName == "Photon.Pun.PhotonNetwork" && methodInfo4.Name == "get_PlayerList") || !source.Any((KeyValuePair<OpCode, object> i) => i.Value is FieldInfo fieldInfo && fieldInfo == _slrLevels))
					{
						throw new InvalidOperationException("SLR connected-pool layout is unsupported");
					}
					harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(TeamEffectCompat), "ConnectedPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					Plugin.Log.LogInfo((object)"SLR Valuable Density: team pool resolves connected avatar upgrade IDs rather than relying on published Photon user IDs.");
				}
				catch (Exception ex)
				{
					Plugin.Log.LogWarning((object)("SLR team-pool identity repair unavailable: " + ex.GetBaseException().Message));
				}
			}
			if (Plugin.RepairMoreItemValue.Value && Chainloader.PluginInfos.TryGetValue("bulletbot.moreupgrades", out var value2) && (Object)(object)value2.Instance != (Object)null)
			{
				try
				{
					MethodInfo methodInfo2 = ((object)value2.Instance).GetType().Assembly.GetType("MoreUpgrades.Patches.ValuableObjectPatch")?.GetMethod("DollarValueSetLogic", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(float) }, null);
					if (methodInfo2?.ReturnType != typeof(float))
					{
						throw new MissingMethodException("MoreUpgrades Item Value calculation");
					}
					_moreApplied = false;
					harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(TeamEffectCompat), "ItemValueTranspiler", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null);
					MoreValueActive = _moreApplied;
					if (!_moreApplied)
					{
						throw new InvalidOperationException("MoreUpgrades Item Value layout is unsupported; original handling retained");
					}
					Plugin.Log.LogInfo((object)"MoreUpgrades Item Value: one team contribution per purchase; world values use the connected pool instead of the last player's level.");
				}
				catch (Exception ex2)
				{
					Plugin.Log.LogWarning((object)("MoreUpgrades Item Value team adapter unavailable: " + ex2.GetBaseException().Message));
				}
			}
			if (!Plugin.RepairMoreItemResist.Value || !Chainloader.PluginInfos.TryGetValue("bulletbot.moreupgrades", out var value3) || !((Object)(object)value3.Instance != (Object)null))
			{
				return;
			}
			try
			{
				Assembly assembly = ((object)value3.Instance).GetType().Assembly;
				Type type2 = assembly.GetType("MoreUpgrades.Classes.UpgradeItem");
				Type type3 = assembly.GetType("MoreUpgrades.Patches.PhysGrabObjectImpactDetectorPatch");
				_moreRegister = type2?.GetField("playerUpgrade");
				_moreId = _moreRegister?.FieldType.GetField("UpgradeId");
				MethodInfo methodInfo3 = ((type2 == null) ? null : type3?.GetMethod("ReduceValueLost", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[3]
				{
					type2,
					typeof(float),
					typeof(int)
				}, null));
				if (methodInfo3?.ReturnType != typeof(float) || _moreId?.FieldType != typeof(string) || !PatchProcessor.ReadMethodBody((MethodBase)methodInfo3).Any((KeyValuePair<OpCode, object> i) => i.Value is MethodInfo methodInfo4 && methodInfo4.DeclaringType.FullName == "UnityEngine.Mathf" && methodInfo4.Name == "Pow"))
				{
					throw new InvalidOperationException("MoreUpgrades Item Resist layout is unsupported; original handling retained");
				}
				harmony.Patch((MethodBase)methodInfo3, new HarmonyMethod(typeof(TeamEffectCompat), "ResistPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				MoreResistActive = true;
				Plugin.Log.LogInfo((object)"MoreUpgrades Item Resist: connected team contributions apply once, preserving native item and grab eligibility.");
			}
			catch (Exception ex3)
			{
				Plugin.Log.LogWarning((object)("MoreUpgrades Item Resist team adapter unavailable: " + ex3.GetBaseException().Message));
			}
		}

		private static bool ConnectedPrefix(object __instance, ref int __result)
		{
			if (!Plugin.RepairSlrTeamIdentity.Value || _slrRegister == null)
			{
				return true;
			}
			object value = _slrRegister.GetValue(__instance);
			if (value == null || _slrId.GetValue(value) as string != "ValuableDensity" || !(_slrLevels.GetValue(value) is Dictionary<string, int> levels))
			{
				return true;
			}
			__result = PooledDisplayPolicy.Total(levels, ConnectedTeam.UpgradeIds(levels));
			return false;
		}

		internal static int ItemValueLevel(int original)
		{
			if (!Plugin.RepairMoreItemValue.Value || !Catalog.TryGet("playerUpgradeItem Value", out var info) || !info.Pooled)
			{
				return original;
			}
			Dictionary<string, int> dictionary = TeamContributionSync.Levels("playerUpgradeItem Value");
			if (dictionary != null)
			{
				return PooledDisplayPolicy.Total(dictionary, ConnectedTeam.UpgradeIds(dictionary));
			}
			return original;
		}

		private static void ResistPrefix(object __0, ref int __2)
		{
			if (!Plugin.RepairMoreItemResist.Value || __0 == null || !Catalog.TryGet("playerUpgradeItem Resist", out var info) || !info.Pooled)
			{
				return;
			}
			object value = _moreRegister.GetValue(__0);
			if (value != null && !(_moreId.GetValue(value) as string != "Item Resist"))
			{
				Dictionary<string, int> dictionary = TeamContributionSync.Levels("playerUpgradeItem Resist");
				if (dictionary != null)
				{
					__2 = PooledDisplayPolicy.Total(dictionary, ConnectedTeam.UpgradeIds(dictionary));
				}
			}
		}

		internal static IEnumerable<CodeInstruction> ItemValueTranspiler(IEnumerable<CodeInstruction> instructions)
		{
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c0: Expected O, but got Unknown
			List<CodeInstruction> code = instructions.ToList();
			_moreApplied = false;
			if (code.Count((CodeInstruction i) => i.operand is MethodInfo methodInfo && methodInfo.DeclaringType.FullName == "REPOLib.Modules.PlayerUpgrade" && methodInfo.Name == "GetLevel" && methodInfo.ReturnType == typeof(int) && methodInfo.GetParameters().Length == 1 && methodInfo.GetParameters()[0].ParameterType == typeof(PlayerAvatar)) != 1)
			{
				return code;
			}
			List<int> list = (from i in Enumerable.Range(0, Math.Max(0, code.Count - 6))
				where code[i].opcode == OpCodes.Ldarg_0 && code[i + 1].opcode == OpCodes.Ldc_R4 && object.Equals(code[i + 1].operand, 1f) && code[i + 2].opcode == OpCodes.Ldloc_1 && code[i + 3].opcode == OpCodes.Conv_R4 && code[i + 4].opcode == OpCodes.Ldloc_0 && code[i + 5].opcode == OpCodes.Ldstr && object.Equals(code[i + 5].operand, "Scaling Factor") && code[i + 6].operand is MethodInfo methodInfo && methodInfo.DeclaringType.FullName == "MoreUpgrades.Classes.UpgradeItem" && methodInfo.Name == "GetConfig" && methodInfo.IsGenericMethod && methodInfo.GetGenericArguments().SequenceEqual(new Type[1] { typeof(float) })
				select i).ToList();
			if (list.Count != 1)
			{
				return code;
			}
			code.Insert(list[0] + 3, new CodeInstruction(OpCodes.Call, (object)typeof(TeamEffectCompat).GetMethod("ItemValueLevel", BindingFlags.Static | BindingFlags.NonPublic)));
			_moreApplied = true;
			return code;
		}

		internal static string Description(UpgradeInfo info)
		{
			string text = info.Owner?.GetName().Name;
			if (text == "SLRUpgradePack")
			{
				if (info.Id == "ValuableDensity")
				{
					return "Team effect: valuable spawn quantity/value budgets at the next level generation; one contribution per purchase, including connected dead players.";
				}
				if (info.Id == "ObjectValue")
				{
					return "Team effect: world valuable prices; one contribution per purchase, using SLR's active-player pool.";
				}
				if (info.Id == "ObjectDurability")
				{
					return "Team effect: valuable damage resistance; one contribution per purchase, using SLR's active-player pool.";
				}
			}
			if (text == "NikkiUpgrades" && info.Id == "Investor")
			{
				return "Team effect: shared Investor income; one contribution per purchase.";
			}
			if (text == "MoreUpgrades" && info.Id == "Item Value" && MoreValueActive)
			{
				return "Team effect: world valuable prices; one contribution per purchase, summed over connected players.";
			}
			if (text == "MoreUpgrades" && info.Id == "Item Resist" && MoreResistActive)
			{
				return "Team effect: valuable damage resistance under the original held/last-grab rules; connected contributions apply once, regardless of who grabs.";
			}
			return null;
		}
	}
	internal sealed class TeamUpgradeDisplay : MonoBehaviour
	{
		private static readonly FieldInfo StatsDictionaries = AccessTools.Field(typeof(StatsManager), "dictionaryOfDictionaries");

		private static readonly Dictionary<string, Func<int>> TotalReaders = new Dictionary<string, Func<int>>();

		private static Dictionary<string, int> _display = new Dictionary<string, int>();

		private static Dictionary<string, int> _remote = new Dictionary<string, int>();

		private static Dictionary<string, int> _lastSent = new Dictionary<string, int>();

		private static Func<StatsManager, string, Dictionary<string, int>> _fetch;

		private static object _session;

		private static int _host;

		private static bool _remoteValid;

		private static bool _sessionReady;

		private static bool _warned;

		private static float _nextCheck;

		private static float _nextHeartbeat;

		private static float _remoteAt;

		internal static void Apply(Harmony harmony)
		{
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c0: Expected O, but got Unknown
			//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e1: Expected O, but got Unknown
			try
			{
				MethodInfo methodInfo = AccessTools.DeclaredMethod(typeof(StatsManager), "FetchPlayerUpgrades", new Type[1] { typeof(string) }, (Type[])null);
				MethodInfo methodInfo2 = AccessTools.DeclaredMethod(typeof(StatsUI), "Fetch", Type.EmptyTypes, (Type[])null);
				MethodInfo methodInfo3 = AccessTools.DeclaredMethod(typeof(StatsUI), "GetUpgradeDisplayName", new Type[1] { typeof(string) }, (Type[])null);
				if (methodInfo == null || methodInfo2 == null || methodInfo3 == null)
				{
					throw new MissingMethodException("Upgrade stats display API");
				}
				_fetch = AccessTools.MethodDelegate<Func<StatsManager, string, Dictionary<string, int>>>(methodInfo, (object)null, true);
				harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(TeamUpgradeDisplay), "FetchTranspiler", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, new HarmonyMethod(typeof(TeamUpgradeDisplay), "NamePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				Plugin.Log.LogInfo((object)"Team-pooled upgrade display ready: stats show one read-only team total, including Investor, without copying benefits.");
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Team upgrade stats display unavailable; sharing unchanged: " + ex.GetBaseException().Message));
			}
		}

		private static IEnumerable<CodeInstruction> FetchTranspiler(IEnumerable<CodeInstruction> instructions)
		{
			List<CodeInstruction> list = instructions.ToList();
			MethodInfo original = AccessTools.DeclaredMethod(typeof(StatsManager), "FetchPlayerUpgrades", new Type[1] { typeof(string) }, (Type[])null);
			List<CodeInstruction> list2 = list.Where((CodeInstruction i) => CodeInstructionExtensions.Calls(i, original)).ToList();
			if (list2.Count != 1)
			{
				throw new InvalidOperationException("StatsUI.Fetch must have one isolated FetchPlayerUpgrades call");
			}
			list2[0].opcode = OpCodes.Call;
			list2[0].operand = AccessTools.DeclaredMethod(typeof(TeamUpgradeDisplay), "FetchForDisplay", (Type[])null, (Type[])null);
			return list;
		}

		internal static Dictionary<string, int> FetchForDisplay(StatsManager stats, string steamId)
		{
			Dictionary<string, int> dictionary = _fetch(stats, steamId);
			if (!Plugin.ShowTeamPooledLevels.Value || _display.Count <= 0)
			{
				return dictionary;
			}
			return PooledDisplayPolicy.DisplayCopy(dictionary, _display);
		}

		private static void NamePostfix(string __0, ref string __result)
		{
			if (Plugin.ShowTeamPooledLevels.Value && _display.ContainsKey(__0))
			{
				__result = PooledDisplayPolicy.Label(__result);
			}
		}

		private static void ResetSession()
		{
			object currentRoom = PhotonNetwork.CurrentRoom;
			Player masterClient = PhotonNetwork.MasterClient;
			int num = ((masterClient != null) ? masterClient.ActorNumber : 0);
			if (!_sessionReady || _session != currentRoom || _host != num)
			{
				_session = currentRoom;
				_host = num;
				_sessionReady = true;
				_remoteValid = false;
				_display.Clear();
				_remote.Clear();
				_lastSent.Clear();
				_nextHeartbeat = 0f;
			}
		}

		private void Update()
		{
			if (Time.unscaledTime < _nextCheck)
			{
				return;
			}
			_nextCheck = Time.unscaledTime + 1f;
			try
			{
				ResetSession();
				if ((Object)(object)StatsManager.instance == (Object)null || (Object)(object)SemiFunc.PlayerAvatarLocal() == (Object)null)
				{
					_display.Clear();
					return;
				}
				Catalog.RefreshForDisplay();
				if (!SemiFunc.IsMasterClientOrSingleplayer() && _remoteValid && Time.unscaledTime - _remoteAt <= 12f)
				{
					_display = new Dictionary<string, int>(_remote);
					return;
				}
				Dictionary<string, int> dictionary = (_display = BuildLocalTotals());
				if (SemiFunc.IsMasterClientOrSingleplayer() && PhotonNetwork.InRoom && (!PooledDisplayPolicy.Equal(dictionary, _lastSent) || Time.unscaledTime >= _nextHeartbeat))
				{
					Net.SendPooledTotals(dictionary);
					_lastSent = new Dictionary<string, int>(dictionary);
					_nextHeartbeat = Time.unscaledTime + 5f;
				}
			}
			catch (Exception ex)
			{
				if (!_warned)
				{
					_warned = true;
					Plugin.Log.LogWarning((object)("Team level display update failed; sharing unchanged: " + ex.GetBaseException().Message));
				}
			}
		}

		internal static Dictionary<string, int> BuildLocalTotals()
		{
			Dictionary<string, int> dictionary = new Dictionary<string, int>();
			SortedDictionary<string, Dictionary<string, int>> sortedDictionary = (((Object)(object)StatsManager.instance == (Object)null) ? null : (StatsDictionaries.GetValue(StatsManager.instance) as SortedDictionary<string, Dictionary<string, int>>));
			if (sortedDictionary == null)
			{
				return dictionary;
			}
			foreach (UpgradeInfo item in Catalog.All)
			{
				if (!item.Pooled || !sortedDictionary.TryGetValue(item.Key, out var value))
				{
					continue;
				}
				if (!TotalReaders.TryGetValue(item.Key, out var value2))
				{
					value2 = null;
					if (item.Target != null)
					{
						Type type = item.Target.GetType();
						MethodInfo methodInfo = type.GetMethod("GetTotalConnectedPlayerLevels", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null) ?? type.GetMethod("GetTotalActivePlayerLevels", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
						if (methodInfo != null && !methodInfo.IsStatic && methodInfo.ReturnType == typeof(int))
						{
							value2 = Delegate.CreateDelegate(typeof(Func<int>), item.Target, methodInfo, throwOnBindFailure: false) as Func<int>;
						}
					}
					TotalReaders[item.Key] = value2;
				}
				dictionary[item.Key] = ((value2 != null) ? Math.Max(0, value2()) : PooledDisplayPolicy.Total(value, ConnectedTeam.UpgradeIds(value)));
			}
			return dictionary;
		}

		internal static void ReceiveHostTotals(int sender, Dictionary<string, int> totals)
		{
			if (!PhotonNetwork.InRoom || PhotonNetwork.IsMasterClient || PhotonNetwork.MasterClient == null || sender != PhotonNetwork.MasterClient.ActorNumber)
			{
				return;
			}
			ResetSession();
			Catalog.RefreshForDisplay();
			Dictionary<string, int> dictionary = new Dictionary<string, int>();
			foreach (KeyValuePair<string, int> total in totals)
			{
				if (total.Value >= 0 && Catalog.TryGet(total.Key, out var info) && info.Pooled)
				{
					dictionary[total.Key] = total.Value;
				}
			}
			_remote = dictionary;
			_remoteValid = true;
			_remoteAt = Time.unscaledTime;
			_display = new Dictionary<string, int>(dictionary);
		}
	}
	public enum UpgradeHandling
	{
		Automatic,
		SharedPermanent,
		TeamPooled,
		Consumable
	}
	public enum ConsumableHandling
	{
		Consumable
	}
	internal static class UpgradeOptions
	{
		private sealed class Options
		{
			internal ConfigEntry<UpgradeHandling> Handling;

			internal ConfigEntry<bool> AllowSharing;
		}

		private static ConfigFile _config;

		private static string _oldPooled = "";

		private static string _oldIndividual = "";

		private static string _oldExcluded = "";

		private static readonly Dictionary<string, Options> Ent