Decompiled source of GunGame Progressions v1.4.1

GunGameProgressionsMetadataExporter.dll

Decompiled 2 months ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using BepInEx;
using FistVR;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyCompany("GunGameProgressionsMetadataExporter")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.4.1.0")]
[assembly: AssemblyInformationalVersion("1.4.1")]
[assembly: AssemblyProduct("GunGameProgressionsMetadataExporter")]
[assembly: AssemblyTitle("GunGameProgressionsMetadataExporter")]
[assembly: AssemblyVersion("1.4.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 HLin.GunGameProgressions
{
	public static class AtlasMenuSceneResolver
	{
		public static object GetSceneInfo(object menuScreen)
		{
			return ReadMember(ReadMember(menuScreen, "m_def"), "CustomSceneInfo");
		}

		public static bool IsGunGameSelection(object menuScreen)
		{
			return GunGameSceneIdentity.IsMatch(ReadMember(GetSceneInfo(menuScreen), "Identifier") as string);
		}

		private static object ReadMember(object instance, string memberName)
		{
			if (instance == null)
			{
				return null;
			}
			Type type = instance.GetType();
			FieldInfo field = type.GetField(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if ((object)field != null)
			{
				return field.GetValue(instance);
			}
			return type.GetProperty(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(instance, null);
		}
	}
	public sealed class EnemySpawnWeight
	{
		public int Value { get; private set; }

		public int Multiplicity { get; private set; }

		public EnemySpawnWeight(int value, int multiplicity)
		{
			Value = value;
			Multiplicity = multiplicity;
		}
	}
	public static class EnemyWeightPolicy
	{
		private enum OperatorTier
		{
			None,
			Standard,
			Advanced,
			Apex
		}

		public static EnemySpawnWeight Resolve(RuntimeEnemyEntry enemy)
		{
			if (enemy == null)
			{
				throw new ArgumentNullException("enemy");
			}
			int value = EnemyValue(enemy);
			return new EnemySpawnWeight(value, SpawnMultiplicity(enemy, value));
		}

		private static int EnemyValue(RuntimeEnemyEntry enemy)
		{
			switch (GetOperatorTier(enemy.EnemyNameString))
			{
			case OperatorTier.Standard:
				return 2;
			case OperatorTier.Advanced:
			case OperatorTier.Apex:
				return 1;
			default:
				switch (enemy.EnemyNameString)
				{
				case "RW_Rot":
					return 8;
				case "M_Swat_Scout":
					return 5;
				case "M_MercWiener_Riflewiener":
					return 3;
				case "M_Swat_SpecOps":
					return 2;
				case "M_Swat_Heavy":
					return 1;
				default:
					if (!IsCoreEnemyFamily(enemy.EnemyNameString))
					{
						return OtherSpawnWeight(enemy.DifficultyScore);
					}
					return CoreSpawnWeight(enemy.DifficultyScore);
				}
			}
		}

		private static int CoreSpawnWeight(int score)
		{
			if (score <= 15)
			{
				return 8;
			}
			if (score <= 40)
			{
				return 5;
			}
			if (score <= 65)
			{
				return 3;
			}
			if (score <= 100)
			{
				return 2;
			}
			return 1;
		}

		private static int OtherSpawnWeight(int score)
		{
			if (score > 40)
			{
				return 1;
			}
			return 2;
		}

		private static int SpawnMultiplicity(RuntimeEnemyEntry enemy, int value)
		{
			switch (GetOperatorTier(enemy.EnemyNameString))
			{
			case OperatorTier.Apex:
				return 1;
			case OperatorTier.Standard:
			case OperatorTier.Advanced:
				return 2;
			default:
				if (!IsCoreEnemyFamily(enemy.EnemyNameString))
				{
					return 1;
				}
				return value switch
				{
					8 => 13, 
					5 => 8, 
					3 => 6, 
					2 => 4, 
					_ => 2, 
				};
			}
		}

		private static bool IsCoreEnemyFamily(string enemyNameString)
		{
			if (!enemyNameString.StartsWith("RW_", StringComparison.Ordinal) && !enemyNameString.StartsWith("M_Swat_", StringComparison.Ordinal) && !enemyNameString.StartsWith("M_MercWiener_", StringComparison.Ordinal))
			{
				return enemyNameString.StartsWith("Comperator_", StringComparison.Ordinal);
			}
			return true;
		}

		private static OperatorTier GetOperatorTier(string enemyNameString)
		{
			if (string.IsNullOrEmpty(enemyNameString) || !enemyNameString.StartsWith("Comperator_", StringComparison.OrdinalIgnoreCase))
			{
				return OperatorTier.None;
			}
			if (ContainsToken(enemyNameString, "Heavy_") || ContainsToken(enemyNameString, "Tier5") || ContainsToken(enemyNameString, "MixedHighTier"))
			{
				return OperatorTier.Apex;
			}
			if (ContainsToken(enemyNameString, "Tier4") || ContainsToken(enemyNameString, "MixedMedTier") || ContainsToken(enemyNameString, "Medium_Tier3"))
			{
				return OperatorTier.Advanced;
			}
			return OperatorTier.Standard;
		}

		private static bool ContainsToken(string value, string token)
		{
			return value.IndexOf(token, StringComparison.OrdinalIgnoreCase) >= 0;
		}
	}
	public enum ExternalContentLoadState
	{
		Unavailable,
		Loading,
		Complete
	}
	public static class GunGameSceneIdentity
	{
		public const string Identifier = "GunGame";

		public static bool IsMatch(string sceneIdentifier)
		{
			return string.Equals(sceneIdentifier, "GunGame", StringComparison.OrdinalIgnoreCase);
		}
	}
	public sealed class GunGameSelectorInstanceTracker
	{
		private object activeSelector;

		public bool Observe(object selector)
		{
			if (selector == null)
			{
				activeSelector = null;
				return false;
			}
			if (activeSelector == selector)
			{
				return false;
			}
			activeSelector = selector;
			return true;
		}
	}
	public static class GunGameSelectorLocator
	{
		public static object Resolve(Type selectorType)
		{
			object obj = ResolveSingleton(selectorType);
			if (IsAlive(obj))
			{
				return obj;
			}
			if ((object)selectorType != null)
			{
				return Object.FindObjectOfType(selectorType);
			}
			return null;
		}

		public static object ResolveSingleton(Type selectorType)
		{
			if ((object)selectorType == null)
			{
				return null;
			}
			PropertyInfo property = selectorType.GetProperty("Instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy);
			if ((object)property != null && property.CanRead)
			{
				return property.GetValue(null, null);
			}
			return null;
		}

		private static bool IsAlive(object candidate)
		{
			if (candidate == null)
			{
				return false;
			}
			Object val = (Object)((candidate is Object) ? candidate : null);
			if (val != null)
			{
				return val != (Object)null;
			}
			return true;
		}
	}
	public static class GunGameSpawnSafetyPolicy
	{
		public static bool HasExpectedCategory(string role, string category)
		{
			if (role == "Gun")
			{
				return category == "Firearm";
			}
			if (role == "Feed")
			{
				switch (category)
				{
				default:
					return category == "Cartridge";
				case "Magazine":
				case "Clip":
				case "SpeedLoader":
					return true;
				}
			}
			if (!(role != "Extra"))
			{
				return category == "Attachment";
			}
			return true;
		}
	}
	public sealed class GunGameSpawnSafety
	{
		private static GunGameSpawnSafety active;

		private readonly MonoBehaviour host;

		private readonly Action<string> trace;

		private bool skipQueued;

		private object queuedProgression;

		private int queuedWeaponId;

		public GunGameSpawnSafety(MonoBehaviour host, Action<string> trace)
		{
			this.host = host;
			this.trace = trace;
		}

		public bool Install(Harmony harmony)
		{
			//IL_010d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0114: Unknown result type (might be due to invalid IL or missing references)
			//IL_011c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0127: Expected O, but got Unknown
			//IL_0127: Expected O, but got Unknown
			//IL_0127: Expected O, but got Unknown
			//IL_012c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0133: Unknown result type (might be due to invalid IL or missing references)
			//IL_013b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0146: Expected O, but got Unknown
			//IL_0146: Expected O, but got Unknown
			//IL_0146: Expected O, but got Unknown
			Type type = AccessTools.TypeByName("GunGame.Scripts.Progression");
			Type type2 = AccessTools.TypeByName("GunGame.Scripts.Weapons.WeaponBuffer");
			MethodInfo methodInfo = type?.GetMethod("SpawnAndEquip", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(bool) }, null);
			MethodInfo methodInfo2 = (((object)type2 == null) ? null : FindMethod(type2, "SpawnAsync", 2));
			MethodInfo methodInfo3 = AccessTools.Method(typeof(GunGameSpawnSafety), "SpawnAndEquipPrefix", (Type[])null, (Type[])null);
			MethodInfo methodInfo4 = AccessTools.Method(typeof(GunGameSpawnSafety), "SpawnAndEquipPostfix", (Type[])null, (Type[])null);
			MethodInfo methodInfo5 = AccessTools.Method(typeof(GunGameSpawnSafety), "SpawnAndEquipFinalizer", (Type[])null, (Type[])null);
			MethodInfo methodInfo6 = AccessTools.Method(typeof(GunGameSpawnSafety), "SpawnAsyncPrefix", (Type[])null, (Type[])null);
			MethodInfo methodInfo7 = AccessTools.Method(typeof(GunGameSpawnSafety), "SpawnAsyncPostfix", (Type[])null, (Type[])null);
			MethodInfo methodInfo8 = AccessTools.Method(typeof(GunGameSpawnSafety), "SpawnAsyncFinalizer", (Type[])null, (Type[])null);
			if ((object)methodInfo == null || (object)methodInfo2 == null || (object)methodInfo3 == null || (object)methodInfo4 == null || (object)methodInfo5 == null || (object)methodInfo6 == null || (object)methodInfo7 == null || (object)methodInfo8 == null)
			{
				return false;
			}
			try
			{
				active = this;
				harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(methodInfo3), new HarmonyMethod(methodInfo4), (HarmonyMethod)null, new HarmonyMethod(methodInfo5), (HarmonyMethod)null);
				harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(methodInfo6), new HarmonyMethod(methodInfo7), (HarmonyMethod)null, new HarmonyMethod(methodInfo8), (HarmonyMethod)null);
				Patches patchInfo = Harmony.GetPatchInfo((MethodBase)methodInfo);
				Patches patchInfo2 = Harmony.GetPatchInfo((MethodBase)methodInfo2);
				return patchInfo != null && patchInfo2 != null;
			}
			catch
			{
				active = null;
				return false;
			}
		}

		public static void Clear()
		{
			active = null;
		}

		private static bool SpawnAndEquipPrefix(object __instance)
		{
			if (active == null)
			{
				return true;
			}
			if (active.TryValidateCurrentLoadout(__instance, out var weaponId, out var reason))
			{
				return true;
			}
			active.QueueSkip(__instance, weaponId, reason);
			return false;
		}

		private static Exception SpawnAndEquipFinalizer(object __instance, Exception __exception)
		{
			if (__exception == null || active == null)
			{
				return __exception;
			}
			active.QueueSkip(__instance, ReadCurrentWeaponId(__instance), "spawn exception " + __exception.GetType().Name);
			return null;
		}

		private static void SpawnAndEquipPostfix(object __instance)
		{
			if (active != null)
			{
				active.TryMountGeneratedOptic(__instance);
			}
		}

		private static bool SpawnAsyncPrefix(object __instance, object __1, ref IEnumerator __result)
		{
			if (active == null)
			{
				return true;
			}
			if (active.TryValidateGunData(__1, out var reason))
			{
				return true;
			}
			active.QueueSkipFromWeaponBuffer(__instance, "invalid pre-buffer: " + reason);
			active.trace("ignored invalid GunGame pre-buffer: " + reason);
			__result = EmptyEnumerator();
			return false;
		}

		private static void SpawnAsyncPostfix(object __instance, ref IEnumerator __result)
		{
			if (active != null && __result != null)
			{
				__result = GuardSpawnAsync(__instance, __result);
			}
		}

		private static Exception SpawnAsyncFinalizer(object __instance, Exception __exception, ref IEnumerator __result)
		{
			if (__exception == null || active == null)
			{
				return __exception;
			}
			active.QueueSkipFromWeaponBuffer(__instance, "buffer spawn exception " + __exception.GetType().Name);
			__result = EmptyEnumerator();
			return null;
		}

		private bool TryValidateCurrentLoadout(object progression, out int weaponId, out string reason)
		{
			weaponId = ReadCurrentWeaponId(progression);
			reason = null;
			try
			{
				object obj = AccessTools.TypeByName("GunGame.Scripts.Options.GameSettings")?.GetProperty("CurrentPool", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).GetValue(null, null);
				MethodInfo methodInfo = ((obj == null) ? null : FindMethod(obj.GetType(), "GetWeapon", 1));
				if (weaponId < 0 || obj == null || (object)methodInfo == null)
				{
					return true;
				}
				object gunData = methodInfo.Invoke(obj, new object[1] { weaponId });
				return TryValidateGunData(gunData, out reason);
			}
			catch
			{
				return true;
			}
		}

		private bool TryValidateGunData(object gunData, out string reason)
		{
			reason = null;
			if (gunData == null)
			{
				reason = "missing gun data";
				return false;
			}
			Dictionary<string, FVRObject> oD;
			try
			{
				oD = IM.OD;
			}
			catch
			{
				return true;
			}
			if (oD == null)
			{
				return true;
			}
			if (!HasExpectedObject(oD, ReadField(gunData, "GunName"), "Gun", out reason))
			{
				return false;
			}
			string text = ReadField(gunData, "MagName");
			if (!string.IsNullOrEmpty(text) && !HasExpectedObject(oD, text, "Feed", out reason))
			{
				return false;
			}
			string text2 = ReadField(gunData, "Extra");
			if (!string.IsNullOrEmpty(text2))
			{
				return HasExpectedObject(oD, text2, "Extra", out reason);
			}
			return true;
		}

		private void TryMountGeneratedOptic(object progression)
		{
			try
			{
				int num = ReadCurrentWeaponId(progression);
				object currentPool = GetCurrentPool();
				if (num < 0 || !IsGeneratedRuntimePool(currentPool))
				{
					return;
				}
				object currentGunData = GetCurrentGunData(currentPool, num);
				string text = ReadField(currentGunData, "Extra");
				if (!string.IsNullOrEmpty(text))
				{
					IList currentEquipment = GetCurrentEquipment(progression);
					FVRFireArm val = FindFirearm(currentEquipment, ReadField(currentGunData, "GunName"));
					FVRFireArmAttachment val2 = FindAttachment(currentEquipment, text);
					if (!((Object)(object)val == (Object)null) && !((Object)(object)val2 == (Object)null) && !((Object)(object)val2.curMount != (Object)null) && !TryAttachOptic((FVRPhysicalObject)(object)val, val2))
					{
						trace("could not mount generated optic for loadout " + num + ".");
					}
				}
			}
			catch (Exception ex)
			{
				trace("could not mount generated optic: " + ex.GetType().Name);
			}
		}

		private static object GetCurrentPool()
		{
			try
			{
				return (AccessTools.TypeByName("GunGame.Scripts.Options.GameSettings")?.GetProperty("CurrentPool", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic))?.GetValue(null, null);
			}
			catch
			{
				return null;
			}
		}

		private static object GetCurrentGunData(object currentPool, int weaponId)
		{
			try
			{
				return ((currentPool == null) ? null : FindMethod(currentPool.GetType(), "GetWeapon", 1))?.Invoke(currentPool, new object[1] { weaponId });
			}
			catch
			{
				return null;
			}
		}

		private static bool IsGeneratedRuntimePool(object currentPool)
		{
			return ReadStringProperty(currentPool, "Name").StartsWith("Runtime ", StringComparison.Ordinal);
		}

		private static string ReadStringProperty(object value, string propertyName)
		{
			try
			{
				PropertyInfo propertyInfo = value?.GetType().GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				return ((object)propertyInfo == null) ? string.Empty : ((propertyInfo.GetValue(value, null) as string) ?? string.Empty);
			}
			catch
			{
				return string.Empty;
			}
		}

		private static IList GetCurrentEquipment(object progression)
		{
			FieldInfo fieldInfo = progression?.GetType().GetField("_currentEquipment", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if ((object)fieldInfo != null)
			{
				return fieldInfo.GetValue(progression) as IList;
			}
			return null;
		}

		private static FVRFireArm FindFirearm(IList equipment, string firearmId)
		{
			foreach (object item in equipment ?? new object[0])
			{
				GameObject val = (GameObject)((item is GameObject) ? item : null);
				FVRFireArm val2 = (((Object)(object)val == (Object)null) ? null : val.GetComponent<FVRFireArm>());
				if ((Object)(object)val2 != (Object)null && HasObjectId((FVRPhysicalObject)(object)val2, firearmId))
				{
					return val2;
				}
			}
			return null;
		}

		private static FVRFireArmAttachment FindAttachment(IList equipment, string opticId)
		{
			foreach (object item in equipment ?? new object[0])
			{
				GameObject val = (GameObject)((item is GameObject) ? item : null);
				FVRFireArmAttachment val2 = (((Object)(object)val == (Object)null) ? null : val.GetComponent<FVRFireArmAttachment>());
				if ((Object)(object)val2 != (Object)null && HasObjectId((FVRPhysicalObject)(object)val2, opticId))
				{
					return val2;
				}
			}
			return null;
		}

		private static bool HasObjectId(FVRPhysicalObject item, string objectId)
		{
			if ((Object)(object)item != (Object)null && (Object)(object)item.ObjectWrapper != (Object)null)
			{
				return string.Equals(item.ObjectWrapper.ItemID, objectId, StringComparison.Ordinal);
			}
			return false;
		}

		private static bool TryAttachOptic(FVRPhysicalObject parent, FVRFireArmAttachment optic)
		{
			FVRFireArmAttachmentMount val = FindCompatibleOpticMount(parent, optic);
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			((FVRPhysicalObject)optic).ClearQuickbeltState();
			optic.AttachToMount(val, false);
			return true;
		}

		private static FVRFireArmAttachmentMount FindCompatibleOpticMount(FVRPhysicalObject parent, FVRFireArmAttachment attachment)
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)parent == (Object)null || (Object)(object)attachment == (Object)null || !IsOpticMountType(attachment.Type))
			{
				return null;
			}
			foreach (FVRFireArmAttachmentMount item in parent.AttachmentMounts ?? new List<FVRFireArmAttachmentMount>())
			{
				if (!((Object)(object)item == (Object)null) && item.Type == attachment.Type && IsOpticMountType(item.Type) && IsTopSightingMount(parent, item) && item.isMountableOn(attachment))
				{
					return item;
				}
			}
			return null;
		}

		private static bool IsTopSightingMount(FVRPhysicalObject parent, FVRFireArmAttachmentMount mount)
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			if (!OpticMountPolicy.RequiresTopSightingOrientation(((object)Unsafe.As<FVRFireArmAttachementMountType, FVRFireArmAttachementMountType>(ref mount.Type)/*cast due to .constrained prefix*/).ToString()))
			{
				return true;
			}
			return Vector3.Angle(((Component)mount).transform.up, ((Component)parent).transform.up) < 46f;
		}

		private unsafe static bool IsOpticMountType(FVRFireArmAttachementMountType mountType)
		{
			return OpticMountPolicy.IsOpticMountType(((object)(*(FVRFireArmAttachementMountType*)(&mountType))/*cast due to .constrained prefix*/).ToString());
		}

		private void QueueSkip(object progression, int weaponId, string reason)
		{
			if (progression != null && (!skipQueued || queuedProgression != progression))
			{
				skipQueued = true;
				queuedProgression = progression;
				queuedWeaponId = weaponId;
				ClearWeaponBuffer(progression);
				trace("skipping invalid GunGame loadout " + DescribeCurrentLoadout(progression, weaponId) + ": " + reason);
				host.StartCoroutine(AdvancePastInvalidWeapon());
			}
		}

		private void QueueSkipFromWeaponBuffer(object weaponBuffer, string reason)
		{
			object obj = FindProgressionForWeaponBuffer(weaponBuffer);
			if (obj == null)
			{
				trace("could not advance invalid GunGame pre-buffer: " + reason);
			}
			else
			{
				QueueSkip(obj, ReadCurrentWeaponId(obj), reason);
			}
		}

		private static object FindProgressionForWeaponBuffer(object weaponBuffer)
		{
			try
			{
				Type type = AccessTools.TypeByName("GunGame.Scripts.Progression");
				if ((object)type == null)
				{
					return null;
				}
				Component val = (Component)((weaponBuffer is Component) ? weaponBuffer : null);
				return ((object)(((Object)(object)val == (Object)null) ? null : val.GetComponentInParent(type))) ?? ((object)Object.FindObjectOfType(type));
			}
			catch
			{
				return null;
			}
		}

		private static IEnumerator GuardSpawnAsync(object weaponBuffer, IEnumerator original)
		{
			using (original as IDisposable)
			{
				object current;
				Exception exception;
				while (TryMoveNext(original, out current, out exception))
				{
					yield return current;
				}
				if (exception != null && active != null)
				{
					active.QueueSkipFromWeaponBuffer(weaponBuffer, "buffer spawn exception " + exception.GetType().Name);
				}
			}
		}

		private static bool TryMoveNext(IEnumerator original, out object current, out Exception exception)
		{
			current = null;
			exception = null;
			try
			{
				if (original == null || !original.MoveNext())
				{
					return false;
				}
				current = original.Current;
				return true;
			}
			catch (Exception ex)
			{
				exception = ex;
				return false;
			}
		}

		private static string DescribeCurrentLoadout(object progression, int weaponId)
		{
			object currentGunData = GetCurrentGunData(GetCurrentPool(), weaponId);
			return "weapon " + weaponId + " (gun=" + ReadField(currentGunData, "GunName") + ", feed=" + ReadField(currentGunData, "MagName") + ", optic=" + ReadField(currentGunData, "Extra") + ")";
		}

		private IEnumerator AdvancePastInvalidWeapon()
		{
			yield return null;
			object obj = queuedProgression;
			int num = queuedWeaponId;
			skipQueued = false;
			queuedProgression = null;
			if (obj == null || ReadCurrentWeaponId(obj) != num)
			{
				yield break;
			}
			MethodInfo methodInfo = FindMethod(obj.GetType(), "Promote", 0);
			if ((object)methodInfo == null)
			{
				trace("could not advance past invalid GunGame loadout.");
				yield break;
			}
			try
			{
				methodInfo.Invoke(obj, null);
			}
			catch (Exception ex)
			{
				trace("could not advance past invalid GunGame loadout: " + ex.GetType().Name);
			}
		}

		private static bool HasExpectedObject(IDictionary<string, FVRObject> objects, string objectId, string role, out string reason)
		{
			reason = null;
			if (string.IsNullOrEmpty(objectId) || !objects.TryGetValue(objectId, out var value) || (Object)(object)value == (Object)null)
			{
				reason = role + " id is unavailable";
				return false;
			}
			if (!GunGameSpawnSafetyPolicy.HasExpectedCategory(role, ((object)Unsafe.As<ObjectCategory, ObjectCategory>(ref value.Category)/*cast due to .constrained prefix*/).ToString()))
			{
				reason = role + " id has category " + ((object)Unsafe.As<ObjectCategory, ObjectCategory>(ref value.Category)/*cast due to .constrained prefix*/).ToString();
				return false;
			}
			return true;
		}

		private static void ClearWeaponBuffer(object progression)
		{
			try
			{
				Component val = (Component)((progression is Component) ? progression : null);
				Type type = AccessTools.TypeByName("GunGame.Scripts.Weapons.WeaponBuffer");
				Component val2 = (((Object)(object)val == (Object)null || (object)type == null) ? null : val.GetComponent(type));
				(((Object)(object)val2 == (Object)null) ? null : FindMethod(((object)val2).GetType(), "ClearBuffer", 0))?.Invoke(val2, null);
			}
			catch
			{
			}
		}

		private static int ReadCurrentWeaponId(object progression)
		{
			try
			{
				PropertyInfo propertyInfo = progression?.GetType().GetProperty("CurrentWeaponId", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				return ((object)propertyInfo == null) ? (-1) : Convert.ToInt32(propertyInfo.GetValue(progression, null));
			}
			catch
			{
				return -1;
			}
		}

		private static string ReadField(object value, string fieldName)
		{
			FieldInfo fieldInfo = value?.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			object obj;
			if ((object)fieldInfo != null)
			{
				obj = fieldInfo.GetValue(value) as string;
				if (obj == null)
				{
					return string.Empty;
				}
			}
			else
			{
				obj = string.Empty;
			}
			return (string)obj;
		}

		private static MethodInfo FindMethod(Type type, string methodName, int parameterCount)
		{
			if ((object)type == null)
			{
				return null;
			}
			MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			foreach (MethodInfo methodInfo in methods)
			{
				if (methodInfo.Name == methodName && methodInfo.GetParameters().Length == parameterCount)
				{
					return methodInfo;
				}
			}
			return null;
		}

		private static IEnumerator EmptyEnumerator()
		{
			yield break;
		}
	}
	public sealed class MountResolution
	{
		public string RawMount { get; private set; }

		public string CanonicalMount { get; private set; }

		public bool IsResolved { get; private set; }

		private MountResolution(string rawMount, string canonicalMount, bool isResolved)
		{
			RawMount = rawMount;
			CanonicalMount = canonicalMount;
			IsResolved = isResolved;
		}

		public static MountResolution Resolve(string rawMount)
		{
			if (string.IsNullOrEmpty(rawMount) || rawMount.Trim().Length == 0)
			{
				return new MountResolution(string.Empty, string.Empty, isResolved: false);
			}
			string text = rawMount.Trim();
			if (string.Equals(text, "99", StringComparison.Ordinal))
			{
				return new MountResolution(text, "RMR", isResolved: true);
			}
			if (int.TryParse(text, out var _))
			{
				return new MountResolution(text, string.Empty, isResolved: false);
			}
			return new MountResolution(text, text, isResolved: true);
		}
	}
	public sealed class OpticMountRule
	{
		public string MountType { get; private set; }

		public List<string> OpticKinds { get; private set; }

		public int Priority { get; private set; }

		public OpticMountRule(string mountType, IEnumerable<string> opticKinds, int priority)
		{
			MountType = mountType;
			OpticKinds = (opticKinds ?? Enumerable.Empty<string>()).Where((string kind) => !string.IsNullOrEmpty(kind)).Distinct<string>(StringComparer.Ordinal).ToList();
			Priority = priority;
		}

		public bool Accepts(string opticKind)
		{
			return OpticKinds.Any((string kind) => string.Equals(kind, opticKind, StringComparison.Ordinal));
		}
	}
	public static class OpticMountPolicy
	{
		private static readonly HashSet<string> ReflexOnlyMounts = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "RMR", "Handgun" };

		private static readonly HashSet<string> RailMounts = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "Picatinny", "MLokRail" };

		private static readonly HashSet<string> ScopeOnlyMounts = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
		{
			"Russian", "MAS4956Scope", "SVTScope", "M16HandleMount", "M1GarandScope", "M1CarbineScope", "MP5RailMount", "PythonScopeMount", "FamasTopRail", "Mini14TopRail",
			"R1022TopRail"
		};

		public static IEnumerable<OpticMountRule> Rank(IEnumerable<string> physicalMountTypes)
		{
			return (from rule in (from resolution in (physicalMountTypes ?? Enumerable.Empty<string>()).Select(MountResolution.Resolve)
					where resolution.IsResolved
					select resolution.CanonicalMount).Distinct<string>(StringComparer.OrdinalIgnoreCase).Select(CreateRule)
				where rule != null
				orderby rule.Priority
				select rule).ThenBy<OpticMountRule, string>((OpticMountRule rule) => rule.MountType, StringComparer.Ordinal);
		}

		public static bool IsOpticMountType(string rawMountType)
		{
			MountResolution mountResolution = MountResolution.Resolve(rawMountType);
			if (mountResolution.IsResolved)
			{
				return GetOpticKinds(mountResolution.CanonicalMount) != null;
			}
			return false;
		}

		public static bool RequiresTopSightingOrientation(string rawMountType)
		{
			MountResolution mountResolution = MountResolution.Resolve(rawMountType);
			if (mountResolution.IsResolved)
			{
				return RailMounts.Contains(mountResolution.CanonicalMount);
			}
			return false;
		}

		private static OpticMountRule CreateRule(string mountType)
		{
			IEnumerable<string> opticKinds = GetOpticKinds(mountType);
			if (opticKinds == null)
			{
				return null;
			}
			return new OpticMountRule(mountType, opticKinds, GetPriority(mountType));
		}

		private static IEnumerable<string> GetOpticKinds(string mountType)
		{
			if (ReflexOnlyMounts.Contains(mountType))
			{
				return new string[1] { "Reflex" };
			}
			if (RailMounts.Contains(mountType))
			{
				return new string[2] { "Scope", "Reflex" };
			}
			if (!IsScopeMount(mountType))
			{
				return null;
			}
			return new string[1] { "Scope" };
		}

		private static int GetPriority(string mountType)
		{
			if (ReflexOnlyMounts.Contains(mountType))
			{
				return 20;
			}
			if (!RailMounts.Contains(mountType))
			{
				return 10;
			}
			return 100;
		}

		private static bool IsScopeMount(string mountType)
		{
			if (!ScopeOnlyMounts.Contains(mountType) && !mountType.StartsWith("Scope_", StringComparison.OrdinalIgnoreCase))
			{
				return mountType.EndsWith("Scope", StringComparison.OrdinalIgnoreCase);
			}
			return true;
		}
	}
	public sealed class OtherLoaderStatusProbe
	{
		private bool initialized;

		private bool failureLogged;

		private MethodInfo progressMethod;

		private FieldInfo startTimeField;

		private PropertyInfo activeLoadersProperty;

		public ExternalContentLoadState Read(Action<string> logDebug)
		{
			Initialize();
			if ((object)progressMethod == null)
			{
				return ExternalContentLoadState.Unavailable;
			}
			try
			{
				float num = Convert.ToSingle(progressMethod.Invoke(null, null));
				float num2 = (((object)startTimeField == null) ? 0f : Convert.ToSingle(startTimeField.GetValue(null)));
				int num3 = (((object)activeLoadersProperty != null) ? Convert.ToInt32(activeLoadersProperty.GetValue(null, null)) : 0);
				if (num >= 1f && num3 <= 0 && ((object)startTimeField != null || (object)activeLoadersProperty != null))
				{
					return ExternalContentLoadState.Complete;
				}
				return (num2 > 0f || num3 > 0) ? ExternalContentLoadState.Loading : ExternalContentLoadState.Unavailable;
			}
			catch (Exception ex)
			{
				LogFailureOnce(logDebug, "Could not read OtherLoader load status: " + ex);
				return ExternalContentLoadState.Unavailable;
			}
		}

		private void Initialize()
		{
			if (!initialized)
			{
				initialized = true;
				Type type = AccessTools.TypeByName("OtherLoader.LoaderStatus");
				if ((object)type != null)
				{
					BindingFlags bindingAttr = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
					progressMethod = type.GetMethod("GetLoaderProgress", bindingAttr);
					startTimeField = type.GetField("LoadStartTime", bindingAttr);
					activeLoadersProperty = type.GetProperty("NumActiveLoaders", bindingAttr);
				}
			}
		}

		private void LogFailureOnce(Action<string> logDebug, string message)
		{
			if (!failureLogged)
			{
				failureLogged = true;
				logDebug?.Invoke(message);
			}
		}
	}
	public static class PipScopeOpticClassifier
	{
		private const string Pso1ScopeObjectId = "MagnifierPSO1";

		public static string ClassifyFromMetadata(string objectId, string attachmentFeature)
		{
			if (IsPso1Scope(objectId) && attachmentFeature == "Magnification")
			{
				return "Scope";
			}
			if (!string.IsNullOrEmpty(objectId) && objectId.IndexOf("magnifier", StringComparison.OrdinalIgnoreCase) >= 0)
			{
				return "Magnifier";
			}
			if (attachmentFeature == "Reflex")
			{
				return "Reflex";
			}
			if (!(attachmentFeature == "Magnification"))
			{
				return string.Empty;
			}
			return "Scope";
		}

		public static string Classify(string objectId, bool hasPipScope, bool hasReflexSight)
		{
			if (IsPso1Scope(objectId) && hasPipScope)
			{
				return "Scope";
			}
			if (!string.IsNullOrEmpty(objectId) && objectId.IndexOf("magnifier", StringComparison.OrdinalIgnoreCase) >= 0)
			{
				return "Magnifier";
			}
			if (hasPipScope)
			{
				return "Scope";
			}
			if (!hasReflexSight)
			{
				return string.Empty;
			}
			return "Reflex";
		}

		private static bool IsPso1Scope(string objectId)
		{
			return string.Equals(objectId, "MagnifierPSO1", StringComparison.Ordinal);
		}
	}
	[BepInPlugin("HLin.GunGameProgressionsMetadataExporter", "GunGame Progressions Metadata Exporter", "1.4.1")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInProcess("h3vr.exe")]
	public sealed class Plugin : BaseUnityPlugin
	{
		private enum RuntimeGenerationPhase
		{
			Vanilla,
			Modded,
			CompatibilityProbe
		}

		private sealed class RuntimeGenerationJob
		{
			private readonly object sync = new object();

			private readonly string packagePath;

			private readonly List<RuntimeMetadataEntry> entries;

			private readonly List<RuntimeEnemyEntry> enemyEntries;

			private readonly RuntimeGenerationPhase phase;

			private readonly bool confirmedEmptySnapshot;

			private readonly bool allowPolicyReplacement;

			private bool isCompleted;

			private Exception error;

			private RuntimeGenerationReport report;

			public bool IsCompleted
			{
				get
				{
					lock (sync)
					{
						return isCompleted;
					}
				}
			}

			public Exception Error
			{
				get
				{
					lock (sync)
					{
						return error;
					}
				}
			}

			public RuntimeGenerationReport Report
			{
				get
				{
					lock (sync)
					{
						return report;
					}
				}
			}

			public RuntimeGenerationJob(string packagePath, List<RuntimeMetadataEntry> entries, List<RuntimeEnemyEntry> enemyEntries, RuntimeGenerationPhase phase, bool confirmedEmptySnapshot, bool allowPolicyReplacement)
			{
				this.packagePath = packagePath;
				this.entries = entries;
				this.enemyEntries = enemyEntries;
				this.phase = phase;
				this.confirmedEmptySnapshot = confirmedEmptySnapshot;
				this.allowPolicyReplacement = allowPolicyReplacement;
			}

			public void Start()
			{
				Thread thread = new Thread(Generate);
				thread.IsBackground = true;
				thread.Priority = ThreadPriority.BelowNormal;
				thread.Start();
			}

			private void Generate()
			{
				Stopwatch stopwatch = Stopwatch.StartNew();
				RuntimeGenerationReport runtimeGenerationReport = null;
				Exception ex = null;
				try
				{
					runtimeGenerationReport = GenerateRuntimeFiles(packagePath, entries, enemyEntries, phase, confirmedEmptySnapshot, allowPolicyReplacement);
					runtimeGenerationReport.ElapsedMilliseconds = stopwatch.ElapsedMilliseconds;
				}
				catch (Exception ex2)
				{
					ex = ex2;
				}
				lock (sync)
				{
					report = runtimeGenerationReport;
					error = ex;
					isCompleted = true;
				}
			}
		}

		private sealed class RuntimeMetadataCapture
		{
			public List<RuntimeMetadataEntry> Entries { get; private set; }

			public long ElapsedMilliseconds { get; private set; }

			public RuntimeMetadataCapture(List<RuntimeMetadataEntry> entries, long elapsedMilliseconds)
			{
				Entries = entries;
				ElapsedMilliseconds = elapsedMilliseconds;
			}
		}

		private sealed class RuntimeEnemyCapture
		{
			public List<RuntimeEnemyEntry> Entries { get; private set; }

			public long ElapsedMilliseconds { get; private set; }

			public RuntimeEnemyCapture(List<RuntimeEnemyEntry> entries, long elapsedMilliseconds)
			{
				Entries = entries;
				ElapsedMilliseconds = elapsedMilliseconds;
			}
		}

		private sealed class RuntimeGenerationReport
		{
			public int EntryCount { get; private set; }

			public int ModdedEntryCount { get; private set; }

			public int EnemyCount { get; private set; }

			public int PoolCount { get; private set; }

			public int EligibleWeaponsPerPool { get; private set; }

			public int SkippedFirearmCount { get; private set; }

			public List<string> PoolFileNames { get; private set; }

			public bool WasWritten { get; private set; }

			public long ElapsedMilliseconds { get; set; }

			public RuntimeGenerationReport(int entryCount, int moddedEntryCount, int enemyCount, int poolCount, int eligibleWeaponsPerPool, int skippedFirearmCount, List<string> poolFileNames, bool wasWritten)
			{
				EntryCount = entryCount;
				ModdedEntryCount = moddedEntryCount;
				EnemyCount = enemyCount;
				PoolCount = poolCount;
				EligibleWeaponsPerPool = eligibleWeaponsPerPool;
				SkippedFirearmCount = skippedFirearmCount;
				PoolFileNames = poolFileNames;
				WasWritten = wasWritten;
			}
		}

		private const long CaptureFrameBudgetMilliseconds = 2L;

		private const float SelectorSubscriptionRetrySeconds = 10f;

		private const string HarmonyId = "HLin.GunGameProgressionsMetadataExporter.KodemanRefresh";

		private static Plugin instance;

		private readonly GunGameSelectorInstanceTracker selectorTracker = new GunGameSelectorInstanceTracker();

		private readonly OtherLoaderStatusProbe otherLoaderStatusProbe = new OtherLoaderStatusProbe();

		private List<RuntimeMetadataEntry> vanillaMetadata;

		private bool vanillaGenerationFinished;

		private bool moddedRefreshRunning;

		private bool moddedRefreshRequested;

		private bool policyReplacementEligible;

		private bool startupWarmupScheduled;

		private Harmony harmony;

		private GunGameSpawnSafety spawnSafety;

		private Type weaponPoolLoaderType;

		private MethodInfo gameManagerOnDestroy;

		private EventInfo weaponPoolLoadedEvent;

		private Delegate weaponPoolLoadedHandler;

		private bool selectorSubscriptionWaitingLogged;

		private bool objectDataUnavailableLogged;

		private void Awake()
		{
			instance = this;
			InstallGunGameRefreshHooks();
			InstallGunGameSpawnSafety();
			ScheduleStartupProfileWarmup();
			((MonoBehaviour)this).StartCoroutine(WaitForWeaponPoolLoaderReadyEvent());
			Trace("plugin awake.");
			if (RuntimeBuildFeatures.CompatibilityProbeEnabled)
			{
				Trace("local Debug build: Runtime 05 compatibility probe enabled.");
			}
			((BaseUnityPlugin)this).Logger.LogInfo((object)"GunGame Progressions: ready.");
		}

		private void Start()
		{
			ScheduleStartupProfileWarmup();
		}

		private void ScheduleStartupProfileWarmup()
		{
			if (!startupWarmupScheduled)
			{
				startupWarmupScheduled = true;
				Trace("starting vanilla and modded profile warmup.");
				((MonoBehaviour)this).StartCoroutine(GenerateVanillaPoolsAtStartup());
				RequestModdedRefresh();
				((MonoBehaviour)this).StartCoroutine(RequestStartupModdedRescan(60f, "startup 1-minute rescan requested.", enablePolicyReplacement: false));
				((MonoBehaviour)this).StartCoroutine(RequestStartupModdedRescan(300f, "startup 5-minute rescan requested.", enablePolicyReplacement: false));
				((MonoBehaviour)this).StartCoroutine(RequestStartupModdedRescan(600f, "startup 10-minute rescan requested; policy replacement eligible.", enablePolicyReplacement: true));
			}
		}

		private void OnDestroy()
		{
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
			GunGameSpawnSafety.Clear();
			if ((object)weaponPoolLoadedEvent != null && (object)weaponPoolLoadedHandler != null)
			{
				weaponPoolLoadedEvent.RemoveEventHandler(null, weaponPoolLoadedHandler);
			}
			if ((Object)(object)instance == (Object)(object)this)
			{
				instance = null;
			}
		}

		private void InstallGunGameRefreshHooks()
		{
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Expected O, but got Unknown
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Expected O, but got Unknown
			Type type = AccessTools.TypeByName("GunGame.Scripts.GameManager");
			gameManagerOnDestroy = (((object)type == null) ? null : AccessTools.Method(type, "OnDestroy", (Type[])null, (Type[])null));
			MethodInfo methodInfo = AccessTools.Method(typeof(Plugin), "GameManagerOnDestroyPostfix", (Type[])null, (Type[])null);
			if ((object)gameManagerOnDestroy == null || (object)methodInfo == null)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)"GunGame Progressions: scene hook unavailable; packaged pools will be used.");
				return;
			}
			harmony = new Harmony("HLin.GunGameProgressionsMetadataExporter.KodemanRefresh");
			harmony.Patch((MethodBase)gameManagerOnDestroy, (HarmonyMethod)null, new HarmonyMethod(methodInfo), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			Patches patchInfo = Harmony.GetPatchInfo((MethodBase)gameManagerOnDestroy);
			if (patchInfo == null || !patchInfo.Postfixes.Any((Patch patch) => patch.owner == "HLin.GunGameProgressionsMetadataExporter.KodemanRefresh"))
			{
				((BaseUnityPlugin)this).Logger.LogError((object)"GunGame Progressions: scene hook unavailable; packaged pools will be used.");
			}
			else
			{
				Trace("GunGame session-exit hook active.");
			}
		}

		private void InstallGunGameSpawnSafety()
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Expected O, but got Unknown
			if (harmony == null)
			{
				harmony = new Harmony("HLin.GunGameProgressionsMetadataExporter.KodemanRefresh");
			}
			spawnSafety = new GunGameSpawnSafety((MonoBehaviour)(object)this, Trace);
			if (spawnSafety.Install(harmony))
			{
				Trace("GunGame invalid-loadout safety active.");
			}
			else
			{
				((BaseUnityPlugin)this).Logger.LogError((object)"GunGame Progressions: spawn safety unavailable.");
			}
		}

		private static void GameManagerOnDestroyPostfix()
		{
			if ((Object)(object)instance != (Object)null)
			{
				instance.Trace("GunGame session ended; background refresh requested.");
				instance.RequestModdedRefresh();
			}
		}

		private IEnumerator WaitForWeaponPoolLoaderReadyEvent()
		{
			while ((object)weaponPoolLoadedHandler == null && !TrySubscribeToWeaponPoolLoaderReadyEvent())
			{
				if (!selectorSubscriptionWaitingLogged)
				{
					selectorSubscriptionWaitingLogged = true;
					Trace("waiting for GunGame selector event.");
				}
				yield return (object)new WaitForSecondsRealtime(10f);
			}
		}

		private bool TrySubscribeToWeaponPoolLoaderReadyEvent()
		{
			weaponPoolLoaderType = AccessTools.TypeByName("GunGame.Scripts.Weapons.WeaponPoolLoader");
			MethodInfo methodInfo = AccessTools.Method(typeof(Plugin), "WeaponPoolLoaderReady", (Type[])null, (Type[])null);
			weaponPoolLoadedEvent = (((object)weaponPoolLoaderType == null) ? null : weaponPoolLoaderType.GetEvent("WeaponLoadedEvent", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic));
			if ((object)weaponPoolLoadedEvent == null || (object)methodInfo == null || (object)weaponPoolLoadedEvent.EventHandlerType == null)
			{
				return false;
			}
			try
			{
				weaponPoolLoadedHandler = Delegate.CreateDelegate(weaponPoolLoadedEvent.EventHandlerType, methodInfo);
				weaponPoolLoadedEvent.AddEventHandler(null, weaponPoolLoadedHandler);
				Trace("GunGame selector ready event subscribed.");
				return true;
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogDebug((object)("Could not subscribe to GunGame selector ready event: " + ex));
				return false;
			}
		}

		private static void WeaponPoolLoaderReady()
		{
			if (!((Object)(object)instance == (Object)null))
			{
				object obj = instance.FindGunGamePoolLoader();
				if (instance.selectorTracker.Observe(obj))
				{
					instance.Trace("live selector ready event received.");
					instance.RestorePersistedRuntimeProfilesForSelector(obj);
				}
				instance.RequestModdedRefresh();
			}
		}

		private object FindGunGamePoolLoader()
		{
			if ((object)weaponPoolLoaderType == null)
			{
				weaponPoolLoaderType = AccessTools.TypeByName("GunGame.Scripts.Weapons.WeaponPoolLoader");
				if ((object)weaponPoolLoaderType != null)
				{
					Trace("selector type resolved.");
				}
			}
			return GunGameSelectorLocator.Resolve(weaponPoolLoaderType);
		}

		private void RestorePersistedRuntimeProfilesForSelector(object weaponPoolLoader)
		{
			int num = AddPersistedRuntimePoolChoices(weaponPoolLoader);
			if (num > 0)
			{
				Trace("selector restored " + num + " persisted runtime profiles.");
			}
		}

		private void Trace(string message)
		{
			((BaseUnityPlugin)this).Logger.LogInfo((object)("GunGame Progressions trace: " + message));
		}

		private void RequestModdedRefresh()
		{
			moddedRefreshRequested = true;
			if (!moddedRefreshRunning)
			{
				((MonoBehaviour)this).StartCoroutine(RefreshModdedPoolsInBackground());
			}
		}

		private IEnumerator RequestStartupModdedRescan(float delaySeconds, string traceMessage, bool enablePolicyReplacement)
		{
			yield return (object)new WaitForSecondsRealtime(delaySeconds);
			if (enablePolicyReplacement)
			{
				policyReplacementEligible = true;
			}
			Trace(traceMessage);
			RequestModdedRefresh();
		}

		private IEnumerator GenerateVanillaPoolsAtStartup()
		{
			Dictionary<string, FVRObject> objects;
			while (!TryGetObjectData(out objects) || objects.Count == 0)
			{
				yield return null;
			}
			RuntimeMetadataCapture metadataCapture = null;
			yield return ((MonoBehaviour)this).StartCoroutine(CaptureRuntimeMetadata(objects, (FVRObject item) => !item.IsModContent, delegate(RuntimeMetadataCapture capture)
			{
				metadataCapture = capture;
			}));
			RuntimeEnemyCapture enemyCapture = null;
			yield return ((MonoBehaviour)this).StartCoroutine(CaptureEnemyEntries(delegate(RuntimeEnemyCapture capture)
			{
				enemyCapture = capture;
			}));
			if (metadataCapture == null)
			{
				vanillaGenerationFinished = true;
				((BaseUnityPlugin)this).Logger.LogWarning((object)"GunGame Progressions: using packaged fallback pools.");
				yield break;
			}
			vanillaMetadata = metadataCapture.Entries;
			string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
			RuntimeGenerationJob job = new RuntimeGenerationJob(directoryName, vanillaMetadata, (enemyCapture == null) ? new List<RuntimeEnemyEntry>() : enemyCapture.Entries, RuntimeGenerationPhase.Vanilla, confirmedEmptySnapshot: false, allowPolicyReplacement: false);
			job.Start();
			while (!job.IsCompleted)
			{
				yield return null;
			}
			vanillaGenerationFinished = true;
			if (job.Error != null)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)"GunGame Progressions: using packaged fallback pools.");
				((BaseUnityPlugin)this).Logger.LogDebug((object)("GunGame vanilla pool generation failed: " + job.Error));
				yield break;
			}
			((BaseUnityPlugin)this).Logger.LogInfo((object)"GunGame Progressions: vanilla pools ready.");
			((BaseUnityPlugin)this).Logger.LogDebug((object)("GunGame vanilla pools: " + job.Report.PoolCount + " pools, " + job.Report.EligibleWeaponsPerPool + " items per pool; capture " + metadataCapture.ElapsedMilliseconds + "ms + background build/write " + job.Report.ElapsedMilliseconds + "ms."));
		}

		private IEnumerator RefreshModdedPoolsInBackground()
		{
			moddedRefreshRunning = true;
			while (moddedRefreshRequested)
			{
				moddedRefreshRequested = false;
				bool allowPolicyReplacement = policyReplacementEligible;
				yield return ((MonoBehaviour)this).StartCoroutine(GenerateModdedPoolsForRefresh(allowPolicyReplacement));
			}
			moddedRefreshRunning = false;
		}

		private IEnumerator GenerateModdedPoolsForRefresh(bool allowPolicyReplacement)
		{
			while (!vanillaGenerationFinished)
			{
				yield return null;
			}
			Stopwatch totalTimer = Stopwatch.StartNew();
			((BaseUnityPlugin)this).Logger.LogInfo((object)"GunGame Progressions: preparing pools.");
			if (!TryGetObjectData(out var objects) || objects.Count == 0)
			{
				Trace("modded capture skipped; object registry unavailable.");
				yield break;
			}
			ExternalContentLoadState externalLoadState = otherLoaderStatusProbe.Read((Action<string>)((BaseUnityPlugin)this).Logger.LogDebug);
			RuntimeMetadataCapture metadataCapture = null;
			Trace("modded capture started.");
			yield return ((MonoBehaviour)this).StartCoroutine(CaptureRuntimeMetadata(objects, (FVRObject item) => item.IsModContent, delegate(RuntimeMetadataCapture capture)
			{
				metadataCapture = capture;
			}));
			if (metadataCapture == null)
			{
				Trace("modded capture failed.");
				yield break;
			}
			Trace("modded capture complete: " + metadataCapture.Entries.Count + " entries.");
			yield return ((MonoBehaviour)this).StartCoroutine(GenerateModdedPoolCandidate(metadataCapture, totalTimer, externalLoadState == ExternalContentLoadState.Complete, allowPolicyReplacement));
		}

		private IEnumerator GenerateModdedPoolCandidate(RuntimeMetadataCapture metadataCapture, Stopwatch totalTimer, bool confirmedEmptySnapshot, bool allowPolicyReplacement, Action<RuntimeGenerationReport> complete = null)
		{
			RuntimeEnemyCapture enemyCapture = null;
			yield return ((MonoBehaviour)this).StartCoroutine(CaptureEnemyEntries(delegate(RuntimeEnemyCapture capture)
			{
				enemyCapture = capture;
			}));
			string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
			List<RuntimeMetadataEntry> entries = MergeRuntimeMetadata(vanillaMetadata ?? new List<RuntimeMetadataEntry>(), metadataCapture.Entries);
			RuntimeGenerationJob job = new RuntimeGenerationJob(directoryName, entries, (enemyCapture == null) ? new List<RuntimeEnemyEntry>() : enemyCapture.Entries, RuntimeGenerationPhase.Modded, confirmedEmptySnapshot, allowPolicyReplacement);
			job.Start();
			while (!job.IsCompleted)
			{
				yield return null;
			}
			if (job.Error != null)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)"GunGame Progressions: using packaged fallback pools.");
				((BaseUnityPlugin)this).Logger.LogDebug((object)("GunGame runtime pool generation failed: " + job.Error));
				complete?.Invoke(null);
				yield break;
			}
			RuntimeGenerationReport report = job.Report;
			if (report.PoolCount == 0 && report.WasWritten)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)"GunGame Progressions: no modded pools available.");
			}
			else if (report.WasWritten)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)"GunGame Progressions: pools ready.");
			}
			else if (report.PoolCount == 0)
			{
				((BaseUnityPlugin)this).Logger.LogDebug((object)"GunGame Modded snapshot was empty but not confirmed complete; saved profiles were retained.");
			}
			else
			{
				((BaseUnityPlugin)this).Logger.LogDebug((object)"GunGame Modded candidate did not exceed the saved profile count.");
			}
			((BaseUnityPlugin)this).Logger.LogDebug((object)("GunGame runtime pools: " + report.PoolCount + " pools, " + report.EntryCount + " items, " + report.EnemyCount + " Sosig types; capture " + metadataCapture.ElapsedMilliseconds + "ms + enemy capture " + ((enemyCapture == null) ? 0 : enemyCapture.ElapsedMilliseconds) + "ms + background build/write " + report.ElapsedMilliseconds + "ms, total " + totalTimer.ElapsedMilliseconds + "ms."));
			((BaseUnityPlugin)this).Logger.LogInfo((object)RuntimeStatusMessages.ModdedScanCompleted(totalTimer.ElapsedMilliseconds, metadataCapture.Entries.Count));
			complete?.Invoke(report);
		}

		private static string RuntimePoolDisplayName(string poolFileName)
		{
			if (poolFileName != null && poolFileName.IndexOf("_02_Modded_Rot_", StringComparison.Ordinal) >= 0)
			{
				return "Runtime 02 - Modded Rot";
			}
			if (poolFileName != null && poolFileName.IndexOf("_04_Modded_Mixed_Enemy_", StringComparison.Ordinal) >= 0)
			{
				return "Runtime 04 - Modded Mixed Enemy";
			}
			return string.Empty;
		}

		private int AddPersistedRuntimePoolChoices(object loader)
		{
			string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
			if (string.IsNullOrEmpty(directoryName) || !Directory.Exists(directoryName))
			{
				return 0;
			}
			List<string> poolFileNames = (from fileName in Directory.GetFiles(directoryName, "GunGameWeaponPool_Runtime_*.json").Select(Path.GetFileName)
				where RuntimeProfileFamily.IsModdedPoolFile(fileName)
				select fileName).OrderBy<string, string>((string fileName) => fileName, StringComparer.Ordinal).ToList();
			return AddPoolChoices(loader, poolFileNames);
		}

		private int AddPoolChoices(object loader, IEnumerable<string> poolFileNames)
		{
			try
			{
				Type type = loader.GetType();
				MethodInfo method = type.GetMethod("LoadWeaponPool", new Type[1] { typeof(string) });
				FieldInfo field = type.GetField("_weaponPools", BindingFlags.Instance | BindingFlags.NonPublic);
				FieldInfo field2 = type.GetField("_choices", BindingFlags.Instance | BindingFlags.NonPublic);
				FieldInfo field3 = type.GetField("ChoicePrefab", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				FieldInfo field4 = type.GetField("ChoicesListParent", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				IList list = (((object)field == null) ? null : (field.GetValue(loader) as IList));
				IList list2 = (((object)field2 == null) ? null : (field2.GetValue(loader) as IList));
				Object val = (Object)(((object)field3 == null) ? null : /*isinst with value type is only supported in some contexts*/);
				Transform val2 = (Transform)(((object)field4 == null) ? null : /*isinst with value type is only supported in some contexts*/);
				if ((object)method == null || list == null || list2 == null || val == (Object)null || (Object)(object)val2 == (Object)null)
				{
					return 0;
				}
				string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
				HashSet<string> hashSet = new HashSet<string>(list.Cast<object>().Select(GunGamePoolName), StringComparer.Ordinal);
				int num = 0;
				foreach (string item in poolFileNames ?? Enumerable.Empty<string>())
				{
					string text = RuntimePoolDisplayName(item);
					if (!string.IsNullOrEmpty(text) && hashSet.Contains(text))
					{
						continue;
					}
					object obj = method.Invoke(loader, new object[1] { Path.Combine(directoryName, item) });
					if (obj == null)
					{
						continue;
					}
					Object val3 = Object.Instantiate(val, val2);
					MethodInfo method2 = ((object)val3).GetType().GetMethod("Initialize", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
					if ((object)method2 == null)
					{
						Object.Destroy(val3);
						continue;
					}
					method2.Invoke(val3, new object[1] { obj });
					int num2 = RuntimePoolInsertionIndex(list, GunGamePoolName(obj));
					list.Insert(num2, obj);
					list2.Insert(num2, val3);
					Component val4 = (Component)(object)((val3 is Component) ? val3 : null);
					if ((Object)(object)val4 != (Object)null)
					{
						val4.transform.SetSiblingIndex(num2);
					}
					hashSet.Add(GunGamePoolName(obj));
					num++;
				}
				return num;
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogDebug((object)("GunGame selector update failed: " + ex));
				return 0;
			}
		}

		private static int RuntimePoolInsertionIndex(IList pools, string generatedPoolName)
		{
			if (generatedPoolName.StartsWith("Runtime 02", StringComparison.Ordinal))
			{
				for (int i = 0; i < pools.Count; i++)
				{
					if (GunGamePoolName(pools[i]).StartsWith("Runtime 03", StringComparison.Ordinal))
					{
						return i;
					}
				}
			}
			return pools.Count;
		}

		private static string GunGamePoolName(object pool)
		{
			if (pool == null)
			{
				return string.Empty;
			}
			MethodInfo method = pool.GetType().GetMethod("GetName", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			object obj;
			if ((object)method != null)
			{
				obj = method.Invoke(pool, null) as string;
				if (obj == null)
				{
					return string.Empty;
				}
			}
			else
			{
				obj = string.Empty;
			}
			return (string)obj;
		}

		private bool TryGetObjectData(out Dictionary<string, FVRObject> objects)
		{
			objects = null;
			try
			{
				objects = IM.OD;
				return objects != null;
			}
			catch (Exception ex)
			{
				if (!objectDataUnavailableLogged)
				{
					objectDataUnavailableLogged = true;
					((BaseUnityPlugin)this).Logger.LogDebug((object)("H3VR object data is not ready: " + ex));
				}
				return false;
			}
		}

		private unsafe IEnumerator CaptureRuntimeMetadata(Dictionary<string, FVRObject> objects, Func<FVRObject, bool> include, Action<RuntimeMetadataCapture> complete)
		{
			Stopwatch timer = Stopwatch.StartNew();
			List<RuntimeMetadataEntry> entries = new List<RuntimeMetadataEntry>();
			List<FVRObject> snapshot;
			try
			{
				snapshot = objects.Values.Where((FVRObject val2) => (Object)(object)val2 != (Object)null).ToList();
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogDebug((object)("Could not snapshot H3VR object data for GunGame: " + ex));
				complete(null);
				yield break;
			}
			long timestamp = Stopwatch.GetTimestamp();
			for (int index = 0; index < snapshot.Count; index++)
			{
				FVRObject val = snapshot[index];
				if (!((Object)(object)val == (Object)null) && !string.IsNullOrEmpty(val.ItemID) && (include == null || include(val)))
				{
					string text = ((object)Unsafe.As<ObjectCategory, ObjectCategory>(ref val.Category)/*cast due to .constrained prefix*/).ToString();
					int roundType = (int)val.RoundType;
					RuntimeMetadataEntry item = new RuntimeMetadataEntry
					{
						ObjectID = val.ItemID,
						Category = text,
						IsModContent = val.IsModContent,
						MagazineType = (int)val.MagazineType,
						ClipType = (int)val.ClipType,
						RoundType = roundType,
						CompatibleMagazines = ObjectIds(val.CompatibleMagazines),
						CompatibleClips = ObjectIds(val.CompatibleClips),
						CompatibleSpeedLoaders = ObjectIds(val.CompatibleSpeedLoaders),
						CompatibleSingleRounds = ObjectIds(val.CompatibleSingleRounds),
						BespokeAttachments = ObjectIds(val.BespokeAttachments),
						FirearmSize = ((object)Unsafe.As<OTagFirearmSize, OTagFirearmSize>(ref val.TagFirearmSize)/*cast due to .constrained prefix*/).ToString(),
						FirearmRoundPower = ((object)Unsafe.As<OTagFirearmRoundPower, OTagFirearmRoundPower>(ref val.TagFirearmRoundPower)/*cast due to .constrained prefix*/).ToString(),
						FirearmAction = ((object)Unsafe.As<OTagFirearmAction, OTagFirearmAction>(ref val.TagFirearmAction)/*cast due to .constrained prefix*/).ToString(),
						FirearmFeedOptions = ((val.TagFirearmFeedOption == null) ? new List<string>() : val.TagFirearmFeedOption.Select((OTagFirearmFeedOption option) => ((object)(*(OTagFirearmFeedOption*)(&option))/*cast due to .constrained prefix*/).ToString()).ToList()),
						FirearmMounts = ((val.TagFirearmMounts == null) ? new List<string>() : val.TagFirearmMounts.Select((OTagFirearmMount mount) => ((object)(*(OTagFirearmMount*)(&mount))/*cast due to .constrained prefix*/).ToString()).ToList()),
						AttachmentMount = ((object)Unsafe.As<OTagFirearmMount, OTagFirearmMount>(ref val.TagAttachmentMount)/*cast due to .constrained prefix*/).ToString(),
						AttachmentFeature = ((object)Unsafe.As<OTagAttachmentFeature, OTagAttachmentFeature>(ref val.TagAttachmentFeature)/*cast due to .constrained prefix*/).ToString(),
						OpticKind = CatalogOpticKind(val, text),
						PhysicalMountTypes = CatalogPhysicalMountTypes(val, text),
						ProvidedMountTypes = new List<string>(),
						IsGunGameRoundDisplaySupported = (text != "Firearm" || HasGunGameRoundDisplayData(roundType)),
						IsVerifiedFirearmPrefab = (text != "Firearm" || HasCatalogFirearmProof(val))
					};
					entries.Add(item);
					if (HasExceededCaptureBudget(timestamp))
					{
						yield return null;
						timestamp = Stopwatch.GetTimestamp();
					}
				}
			}
			entries.Sort((RuntimeMetadataEntry left, RuntimeMetadataEntry right) => string.CompareOrdinal(left.ObjectID, right.ObjectID));
			complete(new RuntimeMetadataCapture(entries, timer.ElapsedMilliseconds));
		}

		private static bool HasGunGameRoundDisplayData(int roundType)
		{
			try
			{
				return AM.SRoundDisplayDataDic != null && AM.SRoundDisplayDataDic.ContainsKey((FireArmRoundType)roundType);
			}
			catch
			{
				return false;
			}
		}

		private static bool HasExceededCaptureBudget(long frameStart)
		{
			return (Stopwatch.GetTimestamp() - frameStart) * 1000 >= Stopwatch.Frequency * 2;
		}

		private static List<string> ObjectIds(IEnumerable<FVRObject> objects)
		{
			if (objects == null)
			{
				return new List<string>();
			}
			return (from item in objects
				where (Object)(object)item != (Object)null && !string.IsNullOrEmpty(item.ItemID)
				select item.ItemID).Distinct().OrderBy<string, string>((string item) => item, StringComparer.Ordinal).ToList();
		}

		private static bool HasCatalogFirearmIdentity(FVRObject item)
		{
			if ((Object)(object)item != (Object)null && ((object)Unsafe.As<OTagFirearmSize, OTagFirearmSize>(ref item.TagFirearmSize)/*cast due to .constrained prefix*/).ToString() != "None")
			{
				return ((object)Unsafe.As<OTagFirearmRoundPower, OTagFirearmRoundPower>(ref item.TagFirearmRoundPower)/*cast due to .constrained prefix*/).ToString() != "None";
			}
			return false;
		}

		private static bool HasCatalogFirearmProof(FVRObject item)
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			if (HasCatalogFirearmIdentity(item))
			{
				return true;
			}
			if ((Object)(object)item == (Object)null)
			{
				return false;
			}
			if (HasDeclaredCompatibleFeed(item) || (int)item.MagazineType != 0 || (int)item.ClipType != 0)
			{
				return true;
			}
			return string.Equals(item.ItemID, "GravitonBeamer", StringComparison.OrdinalIgnoreCase);
		}

		private static bool HasDeclaredCompatibleFeed(FVRObject item)
		{
			if (!HasObjectId(item.CompatibleMagazines) && !HasObjectId(item.CompatibleClips) && !HasObjectId(item.CompatibleSpeedLoaders))
			{
				return HasObjectId(item.CompatibleSingleRounds);
			}
			return true;
		}

		private static bool HasObjectId(IEnumerable<FVRObject> objects)
		{
			return objects?.Any((FVRObject candidate) => (Object)(object)candidate != (Object)null && !string.IsNullOrEmpty(candidate.ItemID)) ?? false;
		}

		private static string CatalogOpticKind(FVRObject item, string category)
		{
			if (!(category == "Attachment"))
			{
				return string.Empty;
			}
			return PipScopeOpticClassifier.ClassifyFromMetadata(item.ItemID, ((object)Unsafe.As<OTagAttachmentFeature, OTagAttachmentFeature>(ref item.TagAttachmentFeature)/*cast due to .constrained prefix*/).ToString());
		}

		private unsafe static List<string> CatalogPhysicalMountTypes(FVRObject item, string category)
		{
			IEnumerable<string> source = ((category == "Firearm") ? ((item.TagFirearmMounts == null) ? Enumerable.Empty<string>() : item.TagFirearmMounts.Select((OTagFirearmMount mount) => ((object)(*(OTagFirearmMount*)(&mount))/*cast due to .constrained prefix*/).ToString())) : ((!(category == "Attachment")) ? Enumerable.Empty<string>() : new string[1] { ((object)Unsafe.As<OTagFirearmMount, OTagFirearmMount>(ref item.TagAttachmentMount)/*cast due to .constrained prefix*/).ToString() }));
			return source.Where((string mount) => !string.IsNullOrEmpty(mount) && mount != "None").Distinct<string>(StringComparer.Ordinal).OrderBy<string, string>((string mount) => mount, StringComparer.Ordinal)
				.ToList();
		}

		private IEnumerator CaptureEnemyEntries(Action<RuntimeEnemyCapture> complete)
		{
			Stopwatch timer = Stopwatch.StartNew();
			List<RuntimeEnemyEntry> entries = new List<RuntimeEnemyEntry>();
			IM val = ManagerSingleton<IM>.Instance;
			if ((Object)(object)val == (Object)null || val.odicSosigObjsByID == null)
			{
				complete(new RuntimeEnemyCapture(entries, timer.ElapsedMilliseconds));
				yield break;
			}
			List<KeyValuePair<SosigEnemyID, SosigEnemyTemplate>> list = val.odicSosigObjsByID.ToList();
			long timestamp = Stopwatch.GetTimestamp();
			foreach (KeyValuePair<SosigEnemyID, SosigEnemyTemplate> item in list)
			{
				SosigEnemyTemplate value = item.Value;
				if (!((Object)(object)value == (Object)null) && (int)item.Key != 0)
				{
					bool flag = Enum.IsDefined(typeof(SosigEnemyID), item.Key);
					string text = (flag ? ((object)item.Key/*cast due to .constrained prefix*/).ToString() : Convert.ToInt32(item.Key).ToString());
					int num = EnemyHealthScore(value);
					int num2 = EnemyArmorScore(value);
					int num3 = EnemyWeaponThreatScore(value);
					int num4 = EnemySpecialThreatScore(value);
					entries.Add(new RuntimeEnemyEntry
					{
						EnemyNameString = text,
						DisplayName = (string.IsNullOrEmpty(value.DisplayName) ? text : value.DisplayName),
						IsModContent = !flag,
						IsSpawnable = true,
						HealthScore = num,
						ArmorScore = num2,
						WeaponThreatScore = num3,
						SpecialThreatScore = num4,
						DifficultyScore = Math.Max(1, num + num2 + num3 + num4)
					});
					if (HasExceededCaptureBudget(timestamp))
					{
						yield return null;
						timestamp = Stopwatch.GetTimestamp();
					}
				}
			}
			complete(new RuntimeEnemyCapture(entries.OrderBy((RuntimeEnemyEntry entry) => entry.DifficultyScore).ThenBy<RuntimeEnemyEntry, string>((RuntimeEnemyEntry entry) => entry.EnemyNameString, StringComparer.Ordinal).ToList(), timer.ElapsedMilliseconds));
		}

		private static int EnemyHealthScore(SosigEnemyTemplate template)
		{
			List<SosigConfigTemplate> list = EnemyConfigs(template).ToList();
			if (list.Count == 0)
			{
				return 1;
			}
			return Math.Max(1, (int)Math.Round(list.Average((SosigConfigTemplate config) => config.TotalMustard) / 25f));
		}

		private static int EnemyArmorScore(SosigEnemyTemplate template)
		{
			float num = 0f;
			foreach (SosigOutfitConfig item in template.OutfitConfig ?? new List<SosigOutfitConfig>())
			{
				num += OutfitSlotScore(item.Headwear, item.Chance_Headwear);
				num += OutfitSlotScore(item.Eyewear, item.Chance_Eyewear) * 0.5f;
				num += OutfitSlotScore(item.Facewear, item.Chance_Facewear) * 0.75f;
				num += OutfitSlotScore(item.Torsowear, item.Chance_Torsowear) * 1.5f;
				num += OutfitSlotScore(item.Pantswear, item.Chance_Pantswear) * 0.5f;
			}
			return (int)Math.Round(num * 3f);
		}

		private static float OutfitSlotScore(List<FVRObject> items, float chance)
		{
			if (items == null || items.Count == 0)
			{
				return 0f;
			}
			if (!(chance > 0f))
			{
				return 1f;
			}
			return chance;
		}

		private static int EnemyWeaponThreatScore(SosigEnemyTemplate template)
		{
			List<FVRObject> list = new List<FVRObject>();
			list.AddRange(template.WeaponOptions ?? new List<FVRObject>());
			list.AddRange(template.WeaponOptions_Secondary ?? new List<FVRObject>());
			list.AddRange(template.WeaponOptions_Tertiary ?? new List<FVRObject>());
			int num = list.Where((FVRObject weapon) => (Object)(object)weapon != (Object)null).Select(WeaponThreatScore).DefaultIfEmpty(0)
				.Max();
			int num2 = Math.Min(10, list.Count((FVRObject weapon) => (Object)(object)weapon != (Object)null));
			return num + num2 + (int)Math.Round((template.SecondaryChance + template.TertiaryChance) * 4f);
		}

		private static int WeaponThreatScore(FVRObject weapon)
		{
			if (((object)Unsafe.As<ObjectCategory, ObjectCategory>(ref weapon.Category)/*cast due to .constrained prefix*/).ToString() != "Firearm")
			{
				return 1;
			}
			return ((object)Unsafe.As<OTagFirearmRoundPower, OTagFirearmRoundPower>(ref weapon.TagFirearmRoundPower)/*cast due to .constrained prefix*/).ToString() switch
			{
				"Tiny" => 2, 
				"Pistol" => 4, 
				"Shotgun" => 6, 
				"Intermediate" => 7, 
				"FullPower" => 9, 
				"AntiMaterial" => 12, 
				"Ordnance" => 14, 
				"Exotic" => 12, 
				_ => 5, 
			};
		}

		private static int EnemySpecialThreatScore(SosigEnemyTemplate template)
		{
			float num = 0f;
			foreach (SosigConfigTemplate item in EnemyConfigs(template))
			{
				num = Math.Max(num, Math.Max(0f, item.RunSpeed - 3f) * 2f);
				num = Math.Max(num, Math.Max(0f, item.ViewDistance - 150f) / 100f);
				num = Math.Max(num, HasBooleanProperty(item, "HasNightVision") ? 4f : 0f);
				num = Math.Max(num, item.AppliesDamageResistToIntegrityLoss ? 3f : 0f);
				num = Math.Max(num, (!item.CanBeGrabbed || !item.CanBeSevered || !item.CanBeStabbed) ? 3f : 0f);
			}
			return (int)Math.Ceiling(num);
		}

		private static IEnumerable<SosigConfigTemplate> EnemyConfigs(SosigEnemyTemplate template)
		{
			return from config in (template.ConfigTemplates ?? new List<SosigConfigTemplate>()).Concat(template.ConfigTemplates_Easy ?? new List<SosigConfigTemplate>())
				where (Object)(object)config != (Object)null
				select config;
		}

		private static bool HasBooleanProperty(object value, string propertyName)
		{
			PropertyInfo property = value.GetType().GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public);
			if ((object)property != null && (object)property.PropertyType == typeof(bool))
			{
				return (bool)property.GetValue(value, null);
			}
			return false;
		}

		private static string RuntimePoolFileName(RuntimeWeaponPool pool)
		{
			return "GunGameWeaponPool_Runtime_" + pool.Family + "_" + pool.EnemyType + ".json";
		}

		private static void RemoveStaleRuntimePools(string runtimePoolsPath, HashSet<string> expectedPoolFiles, Func<string, bool> isOwnedByPhase)
		{
			string[] files = Directory.GetFiles(runtimePoolsPath, "GunGameWeaponPool_Runtime_*.json");
			foreach (string path in files)
			{
				string fileName = Path.GetFileName(path);
				if (isOwnedByPhase(fileName) && !expectedPoolFiles.Contains(fileName))
				{
					File.Delete(path);
				}
			}
		}

		private static void WriteTextAtomically(string outputPath, string contents)
		{
			string text = outputPath + "." + Guid.NewGuid().ToString("N") + ".tmp";
			File.WriteAllText(text, contents, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
			try
			{
				if (File.Exists(outputPath))
				{
					File.Replace(text, outputPath, null);
				}
				else
				{
					File.Move(text, outputPath);
				}
			}
			catch (PlatformNotSupportedException)
			{
				File.Copy(text, outputPath, overwrite: true);
				File.Delete(text);
			}
		}

		private static string SerializeMetadata(List<RuntimeMetadataEntry> entries)
		{
			StringBuilder stringBuilder = new StringBuilder(entries.Count * 256);
			stringBuilder.Append("[\n");
			for (int i = 0; i < entries.Count; i++)
			{
				RuntimeMetadataEntry runtimeMetadataEntry = entries[i];
				stringBuilder.Append("  {\"ObjectID\":\"");
				AppendJsonString(stringBuilder, runtimeMetadataEntry.ObjectID);
				stringBuilder.Append("\",\"Category\":\"");
				AppendJsonString(stringBuilder, runtimeMetadataEntry.Category);
				stringBuilder.Append("\",\"IsModContent\":");
				stringBuilder.Append(runtimeMetadataEntry.IsModContent ? "true" : "false");
				stringBuilder.Append(",\"MagazineType\":");
				stringBuilder.Append(runtimeMetadataEntry.MagazineType);
				stringBuilder.Append(",\"ClipType\":");
				stringBuilder.Append(runtimeMetadataEntry.ClipType);
				stringBuilder.Append(",\"RoundType\":");
				stringBuilder.Append(runtimeMetadataEntry.RoundType);
				stringBuilder.Append(",\"IsGunGameRoundDisplaySupported\":");
				stringBuilder.Append(runtimeMetadataEntry.IsGunGameRoundDisplaySupported ? "true" : "false");
				stringBuilder.Append(",\"IsVerifiedFirearmPrefab\":");
				stringBuilder.Append(runtimeMetadataEntry.IsVerifiedFirearmPrefab ? "true" : "false");
				AppendJsonNamedStringArray(stringBuilder, "CompatibleMagazines", runtimeMetadataEntry.CompatibleMagazines);
				AppendJsonNamedStringArray(stringBuilder, "CompatibleClips", runtimeMetadataEntry.CompatibleClips);
				AppendJsonNamedStringArray(stringBuilder, "CompatibleSpeedLoaders", runtimeMetadataEntry.CompatibleSpeedLoaders);
				AppendJsonNamedStringArray(stringBuilder, "CompatibleSingleRounds", runtimeMetadataEntry.CompatibleSingleRounds);
				AppendJsonNamedStringArray(stringBuilder, "BespokeAttachments", runtimeMetadataEntry.BespokeAttachments);
				AppendJsonNamedString(stringBuilder, "FirearmSize", runtimeMetadataEntry.FirearmSize);
				AppendJsonNamedString(stringBuilder, "FirearmRoundPower", runtimeMetadataEntry.FirearmRoundPower);
				AppendJsonNamedString(stringBuilder, "FirearmAction", runtimeMetadataEntry.FirearmAction);
				AppendJsonNamedStringArray(stringBuilder, "FirearmFeedOptions", runtimeMetadataEntry.FirearmFeedOptions);
				AppendJsonNamedStringArray(stringBuilder, "FirearmMounts", runtimeMetadataEntry.FirearmMounts);
				AppendJsonNamedString(stringBuilder, "AttachmentMount", runtimeMetadataEntry.AttachmentMount);
				AppendJsonNamedString(stringBuilder, "AttachmentFeature", runtimeMetadataEntry.AttachmentFeature);
				AppendJsonNamedString(stringBuilder, "OpticKind", runtimeMetadataEntry.OpticKind);
				AppendJsonNamedStringArray(stringBuilder, "PhysicalMountTypes", runtimeMetadataEntry.PhysicalMountTypes);
				AppendJsonNamedStringArray(stringBuilder, "ProvidedMountTypes", runtimeMetadataEntry.ProvidedMountTypes);
				stringBuilder.Append(",\"OpticMinMagnification\":");
				stringBuilder.Append(runtimeMetadataEntry.OpticMinMagnification.ToString(CultureInfo.InvariantCulture));
				stringBuilder.Append(",\"OpticMaxMagnification\":");
				stringBuilder.Append(runtimeMetadataEntry.OpticMaxMagnification.ToString(CultureInfo.InvariantCulture));
				stringBuilder.Append(",\"IsVariableMagnification\":");
				stringBuilder.Append(runtimeMetadataEntry.IsVariableMagnification ? "true" : "false");
				stringBuilder.Append('}');
				if (i < entries.Count - 1)
				{
					stringBuilder.Append(',');
				}
				stringBuilder.Append('\n');
			}
			stringBuilder.Append(']');
			return stringBuilder.ToString();
		}

		private static string SerializePool(RuntimeWeaponPool pool)
		{
			StringBuilder stringBuilder = new StringBuilder(pool.Guns.Count * 128);
			stringBuilder.Append("{\n  \"WeaponPoolType\": \"Advanced\",\n  \"Description\": \"");
			AppendJsonString(stringBuilder, pool.Description);
			stringBuilder.Append("\",\n  \"EnemyProgressionType\": ");
			stringBuilder.Append(pool.EnemyProgressionType);
			stringBuilder.Append(",\n  \"Enemies\": [");
			for (int i = 0; i < pool.Enemies.Count; i++)
			{
				if (i > 0)
				{
					stringBuilder.Append(',');
				}
				stringBuilder.Append("{\"EnemyName\":0,\"EnemyNameString\":\"");
				AppendJsonString(stringBuilder, pool.Enemies[i].EnemyNameString);
				stringBuilder.Append("\",\"Value\":");
				stringBuilder.Append(pool.Enemies[i].Value);
				stringBuilder.Append('}');
			}
			stringBuilder.Append("],\n  \"Guns\": [");
			for (int j = 0; j < pool.Guns.Count; j++)
			{
				if (j > 0)
				{
					stringBuilder.Append(',');
				}
				RuntimeGun runtimeGun = pool.Guns[j];
				stringBuilder.Append("{\"GunName\":\"");
				AppendJsonString(stringBuilder, runtimeGun.GunName);
				stringBuilder.Append("\",\"MagName\":\"");
				AppendJsonString(stringBuilder, runtimeGun.MagName);
				stringBuilder.Append("\",\"MagNames\":");
				AppendJsonStringArray(stringBuilder, runtimeGun.MagNames);
				stringBuilder.Append(",\"CategoryID\":");
				stringBuilder.Append(runtimeGun.CategoryID);
				stringBuilder.Append(",\"Extra\":\"");
				AppendJsonString(stringBuilder, runtimeGun.Extra);
				stringBuilder.Append("\"}");
			}
			stringBuilder.Append("],\n  \"Name\": \"");
			AppendJsonString(stringBuilder, pool.Name);
			stringBuilder.Append("\",\n  \"OrderType\": ");
			stringBuilder.Append(pool.OrderType);
			stringBuilder.Append("\n}");
			return stringBuilder.ToString();
		}

		private static string SerializeReceipt(List<RuntimeMetadataEntry> entries, List<RuntimeEnemyEntry> enemies, RuntimeGenerationResult result, int randomSeed, string phase, string contentFingerprint)
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append("{\n  \"generatedAtUtc\": \"");
			AppendJsonString(stringBuilder, DateTime.UtcNow.ToString("o"));
			stringBuilder.Append("\",\n  \"randomSeed\": ");
			stringBuilder.Append(randomSeed);
			stringBuilder.Append(",\n  \"phase\": \"");
			AppendJsonString(stringBuilder, phase);
			stringBuilder.Append('"');
			stringBuilder.Append(",\n  \"contentFingerprint\": \"");
			AppendJsonString(stringBuilder, contentFingerprint);
			stringBuilder.Append('"');
			stringBuilder.Append(",\n  \"generationPolicyVersion\": \"");
			AppendJsonString(stringBuilder, "21");
			stringBuilder.Append('"');
			stringBuilder.Append(",\n  \"activeItems\": ");
			stringBuilder.Append(entries.Count);
			stringBuilder.Append(",\n  \"activeModdedItems\": ");
			stringBuilder.Append(entries.Count((RuntimeMetadataEntry entry) => entry.IsModContent));
			stringBuilder.Append(",\n  \"activeSosigTypes\": ");
			stringBuilder.Append(enemies.Count);
			stringBuilder.Append(",\n  \"activeModdedSosigTypes\": ");
			stringBuilder.Append(enemies.Count((RuntimeEnemyEntry enemy) => enemy.IsModContent));
			stringBuilder.Append(",\n  \"eligibleWeaponsPerPool\": ");
			stringBuilder.Append((result.Pools.Count != 0) ? result.Pools[0].Guns.Count : 0);
			stringBuilder.Append(",\n  \"skippedFirearms\": ");
			AppendJsonStringArray(stringBuilder, result.SkippedFirearms);
			stringBuilder.Append(",\n  \"firearmsWithoutOptics\": ");
			AppendJsonStringArray(stringBuilder, result.FirearmsWithoutOptics);
			stringBuilder.Append("\n}");
			return stringBuilder.ToString();
		}

		private static string SerializeEnemyCatalog(List<RuntimeEnemyEntry> enemies)
		{
			StringBuilder stringBuilder = new StringBuilder(enemies.Count * 192);
			stringBuilder.Append("[\n");
			for (int i = 0; i < enemies.Count; i++)
			{
				RuntimeEnemyEntry runtimeEnemyEntry = enemies[i];
				stringBuilder.Append("  {\"EnemyNameString\":\"");
				AppendJsonString(stringBuilder, runtimeEnemyEntry.EnemyNameString);
				stringBuilder.Append("\",\"DisplayName\":\"");
				AppendJsonString(stringBuilder, runtimeEnemyEntry.DisplayName);
				stringBuilder.Append("\",\"IsModContent\":");
				stringBuilder.Append(runtimeEnemyEntry.IsModContent ? "true" : "false");
				stringBuilder.Append(",\"IsSpawnable\":");
				stringBuilder.Append(runtimeEnemyEntry.IsSpawnable ? "true" : "false");
				stringBuilder.Append(",\"DifficultyScore\":");
				stringBuilder.Append(runtimeEnemyEntry.DifficultyScore);
				stringBuilder.Append(",\"HealthScore\":");
				stringBuilder.Append(runtimeEnemyEntry.HealthScore);
				stringBuilder.Append(",\"ArmorScore\":");
				stringBuilder.Append(runtimeEnemyEntry.ArmorScore);
				stringBuilder.Append(",\"WeaponThreatScore\":");
				stringBuilder.Append(runtimeEnemyEntry.WeaponThreatScore);
				stringBuilder.Append(",\"SpecialThreatScore\":");
				stringBuilder.Append(runtimeEnemyEntry.SpecialThreatScore);
				stringBuilder.Append('}');
				if (i < enemies.Count - 1)
				{
					stringBuilder.Append(',');
				}
				stringBuilder.Append('\n');
			}
			stringBuilder.Append(']');
			return stringBuilder.ToString();
		}

		private static void AppendJsonNamedString(StringBuilder json, string name, string value)
		{
			json.Append(",\"");
			AppendJsonString(json, name);
			json.Append("\":\"");
			AppendJsonString(json, value ?? string.Empty);
			json.Append('"');
		}

		private static void AppendJsonNamedStringArray(StringBuilder json, string name, List<string> values)
		{
			json.Append(",\"");
			AppendJsonString(json, name);
			json.Append("\":");
			AppendJsonStringArray(json, values);
		}

		private static void AppendJsonStringArray(StringBuilder json, List<string> values)
		{
			values = values ?? new List<string>();
			json.Append('[');
			for (int i = 0; i < values.Count; i++)
			{
				if (i > 0)
				{
					json.Append(',');
				}
				json.Append('"');
				AppendJsonString(json, values[i]);
				json.Append('"');
			}
			json.Append(']');
		}

		private static void AppendJsonString(StringBuilder json, string value)
		{
			string text = value ?? string.Empty;
			foreach (char c in text)
			{
				switch (c)
				{
				case '\\':
					json.Append("\\\\");
					continue;
				case '"':
					json.Append("\\\"");
					continue;
				case '\n':
					json.Append("\\n");
					continue;
				case '\r':
					json.Append("\\r");
					continue;
				case '\t':
					json.Append("\\t");
					continue;
				}
				if (c < ' ')
				{
					json.Append("\\u");
					int num = c;
					json.Append(num.ToString("x4"));
				}
				else
				{
					json.Append(c);
				}
			}
		}

		private static RuntimeGenerationReport GenerateRuntimeFiles(string packagePath, List<RuntimeMetadataEntry> entries, List<RuntimeEnemyEntry> enemyEntries, RuntimeGenerationPhase phase, bool confirmedEmptySnapshot, bool allowPolicyReplacement)
		{
			ProfileRules rules = ProfileRules.Load(packagePath);
			List<RuntimeMetadataEntry> list = ((phase == RuntimeGenerationPhase.Vanilla) ? entries.Where((RuntimeMetadataEntry entry) => !rules.IsGloballyBlacklisted(entry)).ToList() : entries.Where((RuntimeMetadataEntry entry) => !rules.IsBlacklisted(entry)).ToList());
			string text = phase.ToString().ToLowerInvariant();
			string text2 = ((phase == RuntimeGenerationPhase.CompatibilityProbe) ? RuntimePoolPersistence.CreateFingerprint(list, enemyEntries, rules.CompatibilityProbeFirearms) : RuntimePoolPersistence.CreateFingerprint(list, enemyEntries));
			int num = RuntimePoolPersistence.CreateStableSeed(text2);
			RuntimeGenerationResult runtimeGenerationResult = ((phase == RuntimeGenerationPhase.CompatibilityProbe) ? RuntimeProfileBuilder.BuildCompatibilityProbe(list, enemyEntries, rules.CompatibilityProbeFirearms, new Random(num)) : RuntimeProfileBuilder.BuildWithDiagnostics(list, enemyEntries, new Random(num)));
			List<RuntimeWeaponPool> list2 = runtimeGenerationResult.Pools.Where((RuntimeWeaponPool pool) => (phase != RuntimeGenerationPhase.Vanilla) ? ((phase != RuntimeGenerationPhase.Modded) ? RuntimeProfileFamily.IsCompatibilityProbe(pool.Family) : RuntimeProfileFamily.IsModded(pool.Family)) : RuntimeProfileFamily.IsVanilla(pool.Family)).ToList();
			RuntimeGenerationResult result = new RuntimeGenerationResult
			{
				Pools = list2,
				SkippedFirearms = runtimeGenerationResult.SkippedFirearms,
				FirearmsWithoutOptics = runtimeGenerationResult.FirearmsWithoutOptics
			};
			int eligibleWeaponsPerPool = ((list2.Count != 0) ? list2[0].Guns.Count : 0);
			string text3 = Path.Combine(packagePath, "RuntimePools");
			string text4 = Path.Combine(text3, "runtime-generation-" + text + "-receipt.json");
			if (phase == RuntimeGenerationPhase.Modded && !RuntimePoolPersistence.ShouldPromoteModdedCandidate(list2.Count, eligibleWeaponsPerPool, RuntimePoolPersistence.ReadEligibleWeaponsPerPool(text4), RuntimePoolPersistence.HasCompleteModdedPoolFiles(packagePath), confirmedEmptySnapshot, !string.Equals(RuntimePoolPersistence.ReadGenerationPolicyVersion(text4), "21", StringComparison.Ordinal), allowPolicyReplacement))
			{
				return new RuntimeGenerationReport(list.Count, list.Count((RuntimeMetadataEntry entry) => entry.IsModContent), enemyEntries.Count, list2.Count, eligibleWeaponsPerPool, runtimeGenerationResult.SkippedFirearms.Count, new List<string>(), wasWritten: false);
			}
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			foreach (RuntimeWeaponPool item2 in list2)
			{
				string item = RuntimePoolFileName(item2);
				hashSet.Add(item);
			}
			Func<string, bool> isOwnedByPhase = ((phase == RuntimeGenerationPhase.Vanilla) ? new Func<string, bool>(RuntimeProfileFamily.IsVanillaPoolFile) : ((phase == RuntimeGenerationPhase.Modded) ? new Func<string, bool>(RuntimeProfileFamily.IsModdedPoolFile) : new Func<string, bool>(RuntimeProfileFamily.IsCompatibilityProbePoolFile)));
			string storedFingerprint = RuntimePoolPersistence.ReadFingerprint(text4);
			bool poolFilesMatch = RuntimePoolPersistence.HasExpectedPoolFiles(packagePath, hashSet, isOwnedByPhase);
			if (!RuntimePoolPersistence.ShouldWrite(storedFingerprint, text2, poolFilesMatch))
			{
				return new RuntimeGenerationReport(list.Count, list.Count((RuntimeMetadataEntry entry) => entry.IsModContent), enemyEntries.Count, list2.Count, eligibleWeaponsPerPool, runtimeGenerationResult.SkippedFirearms.Count, hashSet.OrderBy<string, string>((string fileName) => fileName, StringComparer.Ordinal).ToList(), wasWritten: false);
			}
			if (phase != RuntimeGenerationPhase.CompatibilityProbe)
			{
				WriteTextAtomically(Path.Combine(packagePath, "ObjectData.json"), SerializeMetadata(list));
			}
			Directory.CreateDirectory(text3);
			foreach (RuntimeWeaponPool item3 in list2)
			{
				WriteTextAtomically(Path.Combine(packagePath, RuntimePoolFileName(item3)), SerializePool(item3));
			}
			RemoveStaleRuntimePools(packagePath, hashSet, isOwnedByPhase);
			WriteTextAtomically(text4, SerializeReceipt(list, enemyEntries, result, num, text, text2));
			WriteTextAtomically(Path.Combine(text3, "enemy-catalog.json"), SerializeEnemyCatalog(enemyEntries));
			return new RuntimeGenerationReport(list.Count, list.Count((RuntimeMetadataEntry entry) => entry.IsModContent), enemyEntries.Count, list2.Count, eligibleWeaponsPerPool, runtimeGenerationResult.SkippedFirearms.Count, hashSet.OrderBy<string, string>((string fileName) => fileName, StringComparer.Ordinal).ToList(), wasWritten: true);
		}

		private static List<RuntimeMetadataEntry> MergeRuntimeMetadata(IEnumerable<RuntimeMetadataEntry> first, IEnumerable<RuntimeMetadataEntry> second)
		{
			return (from @group in (from entry in (first ?? Enumerable.Empty<RuntimeMetadataEntry>()).Concat(second ?? Enumerable.Empty<RuntimeMetadataEntry>())
					where entry != null && !string.IsNullOrEmpty(entry.ObjectID)
					select entry).GroupBy<RuntimeMetadataEntry, string>((RuntimeMetadataEntry entry) => entry.ObjectID, StringComparer.Ordinal)
				select @group.First()).OrderBy<RuntimeMetadataEntry, string>((RuntimeMetadataEntry entry) => entry.ObjectID, StringComparer.Ordinal).ToList();
		}
	}
	public sealed class ProfileRules
	{
		public string[] FirearmBlacklist { get; set; }

		public string[] RuntimeFirearmBlacklist { get; set; }

		public string[] FeedBlacklist { get; set; }

		public string[] CompatibilityProbeFirearms { get; set; }

		public static ProfileRules Load(string packageDirectory)
		{
			string text = Path.Combine(packageDirectory, "profile-rules.json");
			if (!File.Exists(text))
			{
				throw new FileNotFoundException("GunGame profile rules are missing.", text);
			}
			string json = File.ReadAllText(text);
			string[] array = ReadStringArray(json, "firearmBlacklist");
			string[] array2 = ReadStringArray(json, "runtimeFirearmBlacklist");
			return new ProfileRules
			{
				FirearmBlacklist = array,
				RuntimeFirearmBlacklist = ((array2.Length == 0) ? array : array2),
				FeedBlacklist = ReadStringArray(json, "feedBlacklist"),
				CompatibilityProbeFirearms = ReadStringArray(json, "compatibilityProbeFirearms")
			};
		}

		public bool IsBlacklisted(RuntimeMetadataEntry entry)
		{
			if (!IsGloballyBlacklisted(entry))
			{
				if (entry.Category == "Firearm")
				{
					return Contains(RuntimeFirearmBlacklist, entry.ObjectID);
				}
				return false;
			}
			return true;
		}

		public bool IsGloballyBlacklisted(RuntimeMetadataEntry entry)
		{
			if (!(entry.Category == "Firearm"))
			{
				return Contains(FeedBlacklist, entry.ObjectID);
			}
			return Contains(FirearmBlacklist, entry.ObjectID);
		}

		private static bool Contains(IEnumerable<string> values, string value)
		{
			foreach (string value2 in values)
			{
				if (string.Equals(value2, value, StringComparison.Ordinal))
				{
					return true;
				}
			}
			return false;
		}

		private static string[] ReadStringArray(string json, string propertyName)
		{
			string pattern = "\\\"" + Regex.Escape(propertyName) + "\\\"\\s*:\\s*\\[(?<items>.*?)\\]";
			Match match = Regex.Match(json, pattern, RegexOptions.Singleline);
			if (!match.Success)
			{
				return new string[0];
			}
			List<string> list = new List<string>();
			foreach (Match item in Regex.Matches(match.Groups["items"].Value, "\\\"(?<value>(?:\\\\.|[^\\\"\\\\])*)\\\""))
			{
				list.Add(item.Groups["value"].Value.Replace("\\\\\"", "\"").Replace("\\\\\\\\", "\\"));
			}
			return list.ToArray();
		}
	}
	public static class RuntimeBuildFeatures
	{
		public static bool CompatibilityProbeEnabled => false;
	}
	public static class RuntimeItemRole
	{
		public static string Resolve(string declaredCategory, bool hasFirearm, bool hasMagazine, bool hasClip, bool hasSpeedloader, bool hasRound)
		{
			if (declaredCategory != "Firearm")
			{
				return declaredCategory;
			}
			if (hasFirearm)
			{
				return "Firearm";
			}
			if (hasMagazine)
			{
				return "Magazine";
			}
			if (hasClip)
			{
				return "Clip";
			}
			if (hasSpeedloader)
			{
				return "SpeedLoader";
			}
			if (hasRound)
			{
				return "Cartridge";
			}
			return "Unknown";
		}
	}
	public static class RuntimePoolPersistence
	{
		public const string CurrentGenerationPolicyVersion = "21";

		private const int ExpectedModdedPoolCount = 2;

		public static string CreateFingerprint(IEnumerable<RuntimeMetadataEntry> entries, IEnumerable<RuntimeEnemyEntry> enemies)
		{
			return CreateFingerprint(entries, enemies, Enumerable.Empty<string>());
		}

		public static string CreateFingerprint(IEnumerable<RuntimeMetadataEntry> entries, IEnumerable<RuntimeEnemyEntry> enemies, IEnumerable<string> phaseRules)
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append("generationPolicy|");
			AppendValue(stringBuilder, "21");
			foreach (string item in (phaseRules ?? Enumerable.Empty<string>()).Where((string rule) => !string.IsNullOrEmpty(rule)).Distinct<string>(StringComparer.Ordinal).OrderBy<string, string>((string rule) => rule, StringComparer.Ordinal))
			{
				stringBuilder.Append("phaseRule|");
				AppendValue(stringBuilder, item);
			}
			foreach (RuntimeMetadataEntry item2 in (entries ?? Enumerable.Empty<RuntimeMetadataEntry>()).Where((RuntimeMetadataEntry entry) => entry != null).OrderBy<RuntimeMetadataEntry, string>((RuntimeMetadataEntry entry) => entry.ObjectID ?? string.Empty, StringComparer.Ordinal))
			{
				stringBuilder.Append("item|");
				AppendEntry(stringBuilder, item2);
			}
			foreach (RuntimeEnemyEntry item3 in (enemies ?? Enumerable.Empty<RuntimeEnemyEntry>()).Where((RuntimeEnemyEntry enemy) => enemy != null).OrderBy<RuntimeEnemyEntry, string>((RuntimeEnemyEntry enemy) => enemy.EnemyNameString ?? string.Empty, StringComparer.Ordinal))
			{
				stringBuilder.Append("enemy|");
				AppendValue(stringBuilder, item3.EnemyNameString);
				AppendValue(stringBuilder, item3.DisplayName);
				AppendValue(stringBuilder, item3.IsModContent ? "1" : "0");
				AppendValue(stringBuilder, item3.IsSpawnable ? "1" : "0");
				AppendValue(stringBuilder, item3.DifficultyScore.ToString(CultureInfo.InvariantCulture));
				AppendValue(stringBuilder, item3.HealthScore.ToString(CultureInfo.InvariantCulture));
				AppendValue(stringBuilder, item3.ArmorScore.ToString(CultureInfo.InvariantCulture));
				AppendValue(stringBuilder, item3.WeaponThreatScore.ToString(CultureInfo.InvariantCulture));
				AppendValue(stringBuilder, item3.SpecialThreatScore.ToString(CultureInfo.InvariantCulture));
			}
			using SHA256Managed sHA256Managed = new SHA256Managed();
			StringBuilder stringBuilder2 = new StringBuilder();
			byte[] array = sHA256Managed.ComputeHash(Encoding.UTF8.GetBytes(stringBuilder.ToString()));
			foreach (byte b in array)
			{
				stringBuilder2.Append(b.ToString("x2", CultureInfo.InvariantCulture));
			}
			return stringBuilder2.ToString();
		}

		public static int CreateStableSeed(string fingerprint)
		{
			int num = 17;
			string text = fingerprint ?? string.Empty;
			foreach (char c in text)
			{
				num = num * 31 + c;
			}
			return num & 0x7FFFFFFF;
		}

		public static bool ShouldWrite(string storedFingerprint, string candidateFingerprint, bool poolFilesMatch)
		{
			if (poolFilesMatch && !string.IsNullOrEmpty(storedFingerprint))
			{
				return !string.Equals(storedFingerprint, candidateFingerprint, StringComparison.Ordinal);
			}
			return true;
		}

		public static bool ShouldPromoteModdedCandidate(int candidatePoolCount, int eligibleWeaponsPerPool, int? persistedEligibleWeaponsPerPool, bool hasPersistedPair, bool confirmedEmptySnapshot)
		{
			return ShouldPromoteModdedCandidate(candidatePoolCount, eligibleWeaponsPerPool, persistedEligibleWeaponsPerPool, hasPersistedPair, confirmedEmptySnapshot, generationPolicyChanged: false);
		}

		public static bool ShouldPromoteModdedCandidate(int candidatePoolCount, int eligibleWeaponsPerPool, int? persistedEligibleWeaponsPerPool, bool hasPersistedPair, bool confirmedEmptySnapshot, bool generationPolicyChanged)
		{
			return ShouldPromoteModdedCandidate(candidatePoolCount, eligibleWeaponsPerPool, persistedEligibleWeaponsPerPool, hasPersistedPair, confirmedEmptySnapshot, generationPolicyChanged, policyReplacementEligible: false);
		}

		public static bool ShouldPromoteModdedCandidate(int candidatePoolCount, int eligibleWeaponsPerPool, int? persistedEligibleWeaponsPerPool, bool hasPersistedPair, bool confirmedEmptySnapshot, bool generationPolicyChanged, bool policyReplacementEligible)
		{
			switch (candidatePoolCount)
			{
			case 0:
				return hasPersistedPair && confirmedEmptySnapshot;
			case 2:
				if (eligibleWeaponsPerPool > 0)
				{
					if (!hasPersistedPair)
					{
						return true;
					}
					if (generationPolicyChanged && policyReplacementEligible)
					{
						return true;
					}
					if (persistedEligibleWeaponsPerPool.HasValue)
					{
						return eligibleWeaponsPerPool > persistedEligibleWeaponsPerPool.Value;
					}
					return false;
				}
				goto default;
			default:
				return false;
			}
		}

		public static bool HasCompleteModdedPoolFiles(string packagePath)
		{
			if (string.IsNullOrEmpty(packagePath) || !Directory.Exists(packagePath))
			{
				return false;
			}
			return Directory.GetFiles(packagePath, "GunGameWeaponPool_Runtime_*.json").Select(Path.GetFileName).Count(RuntimeProfileFamily.IsModdedPoolFile) == 2;
		}

		public static string ReadFingerprint(string receiptPath)
		{
			return ReadReceiptString(receiptPath, "contentFingerprint");
		}

		public static string ReadGenerationPolicyVersion(string receiptPath)
		{
			return ReadReceiptString(receiptPath, "generationPolicyVersion");
		}

		private static string ReadReceiptString(string receiptPath, string propertyName)
		{
			if (string.IsNullOrEmpty(receiptPath) || !File.Exists(receiptPath))
			{
				return null;
			}
			string text = File.ReadAllText(receiptPath);
			string value = "\"" + propertyName + "\"";
			int num = text.IndexOf(value, StringComparison.Ordinal);
			if (num < 0)
			{
				return null;
			}
			int num2 = text.IndexOf(':', num);
			if (num2 < 0)
			{
				return null;
			}
			int num3 = text.IndexOf('"', num2 + 1);
			if (num3 < 0)
			{
				return null;
			}
			int num4 = text.IndexOf('"', num3 + 1);
			if (num4 >= 0)
			{
				return text.Substring(num3 + 1, num4 - num3 - 1);
			}
			return null;
		}

		public static int? ReadEligibleWeaponsPerPool(string receiptPath)
		{
			if (string.IsNullOrEmpty(receiptPath) || !File.Exists(receiptPath))
			{
				return null;
			}
			string text = File.ReadAllText(receiptPath);
			int num = text.IndexOf("\"eligibleWeaponsPerPool\"", StringComparison.Ordinal);
			if (num < 0)
			{
				return null;
			}
			int num2 = text.IndexOf(':', num);
			if (num2 < 0)
			{
				return null;
			}
			int i;
			for (i = num2 + 1; i < text.Length && char.IsWhiteSpace(text[i]); i++)
			{
			}
			int j;
			for (j = i; j < text.Length && char.IsDigit(text[j]); j++)
			{
			}
			if (i == j)
			{
				return null;
			}
			if (!int.TryParse(text.Substring(i, j - i), NumberStyles.None, CultureInfo.InvariantCulture, out var result))
			{
				return null;
			}
			return result;
		}

		public static bool HasExpectedPoolFiles(string packagePath, IEnumerable<string> expectedPoolFiles, Func<string, bool> isOwnedByPhase)
		{
			HashSet<string> hashSet = new HashSet<string>((expectedPoolFiles ?? Enumerable.Empty<string>()).Where((string fileName) => !string.IsNullOrEmpty(fileName)), StringComparer.Ordinal);
			if (!Directory.Exists(packagePath))
			{
				return false;
			}
			HashSet<string> hashSet2 = new HashSet<string>(from fileName in Directory.GetFiles(packagePath, "GunGameWeaponPool_Runtime_*.json").Select(Path.GetFileName)
				where isOwnedByPhase(fileName)
				select fileName, StringComparer.Ordinal);
			return hashSet.SetEquals(hashSet2);
		}

		private static void AppendEntry(StringBuilder content, RuntimeMetadataEntry entry)
		{
			AppendValue(content, entry.ObjectID);
			AppendValue(content, entry.Category);
			AppendValue(content, entry.IsModContent ? "1" : "0");
			AppendValue(content, entry.MagazineType.ToString(CultureInfo.InvariantCulture));
			AppendValue(content, entry.ClipType.ToString(CultureInfo.InvariantCulture));
			AppendValue(content, entry.RoundType.ToString(CultureInfo.InvariantCulture));
			AppendList(content, entry.CompatibleMagazines);
			AppendList(content, entry.CompatibleClips);
			AppendList(content, entry.CompatibleSpeedLoaders);
			AppendList(content, entry.CompatibleSingleRounds);
			AppendList(content, entry.BespokeAttachments);
			AppendValue(content, entry.FirearmSize);
			AppendValue(content, entry.FirearmRoundPower);
			AppendValue(content, entry.FirearmAction);
			AppendList(content, entry.FirearmFeedOptions);
			AppendList(content, entry.FirearmMounts);
			AppendValue(content, entry.AttachmentMount);
			AppendValue(content, entry.AttachmentFeature);
			AppendValue(content, entry.OpticKind);
			AppendList(content, entry.PhysicalMountTypes);
			AppendList(content, entry.ProvidedMountTypes);
			AppendValue(content, entry.OpticMinMagnification.ToString("R", CultureInfo.InvariantCulture));
			AppendValue(content, entry.OpticMaxMagnification.ToString("R", CultureInfo.InvariantCulture));
			AppendValue(content, entry.IsVariableMagnification ? "1" : "0");
			AppendValue(content, entry.IsGunGameRoundDisplaySupported ? "1" : "0");
			AppendValue(content, entry.IsVerifiedFirearmPrefab ? "1" : "0");
		}

		private static void AppendList(StringBuilder content, IEnumerable<string> values)
		{
			content.Append('[');
			foreach (string item in (values ?? Enumerable.Empty<string>()).OrderBy<string, string>((string value) => value ?? string.Empty, StringComparer.Ordinal))
			{
				AppendValue(content, item);
			}
			content.Append(']');
		}

		private static void AppendValue(StringBuilder content, string value)
		{
			value = value ?? string.Empty;
			content.Append(value.Length);
			content.Append(':');
			content.Append(value);
			content.Append('|');
		}
	}
	public sealed class RuntimeMetadataEntry
	{
		public string ObjectID { get; set; }

		public string Category { get; set; }

		public bool IsModContent { get; set; }

		public int MagazineType { get; set; }

		public int ClipType { get; set; }

		public int RoundType { get; set; }

		public List<string> CompatibleMagazines { get; set; }

		public List<string> CompatibleClips { get; set; }

		public List<string> CompatibleSpeedLoaders { get; set; }

		public List<string> CompatibleSingleRounds { get; set; }

		public List<string> BespokeAttachments { get; set; }

		public string FirearmSize { get; set; }

		public string FirearmRoundPower { get; set; }

		public string FirearmAction { get; set; }

		public List<string> FirearmFeedOptions { get; set; }

		public List<string> FirearmMounts { get; set; }

		public string AttachmentMount { get; set; }

		public string AttachmentFeature { get; set; }

		public string OpticKind { get; set; }

		public List<string> PhysicalMountTypes { get; set; }

		public List<string> ProvidedMountTypes { get; set; }

		public float OpticMinMagnification { get; set; }

		public float OpticMaxMagnification { get; set; }

		public bool IsVariableMagnification { get; set; }

		public bool IsGunGameRoundDisplaySupported { get; set; } = true;

		public bool IsVerifiedFirearmPrefab { get; set; } = true;
	}
	public sealed class RuntimeEnemyEntry
	{
		public string EnemyNameString { get; set; }

		public string DisplayName { get; set; }

		public bool IsModContent { get; set; }

		public bool IsSpawnable { get; set; }

		public int DifficultyScore { get; set; }

		public int HealthScore { get; set; }

		public int ArmorScore { get; set; }

		public int WeaponThreatScore { get; set; }

		public int SpecialThreatScore { get; set; }
	}
	public sealed class RuntimeGun
	{
		public string GunName { get; set; }

		public string MagName { get; set; }

		public List<string> MagNames { get; set; }

		public string Extra { get; set; }

		public int CategoryID { get; set; }
	}
	public sealed class RuntimeEnemy
	{
		public string EnemyNameString { get; set; }

		public int Value { get; set; }
	}
	public sealed class RuntimeWeaponPool
	{
		public string Name { get; set; }

		public string Description { get; set; }

		public int OrderType { get; set; }

		public string WeaponPoolType { get; set; }

		public int EnemyProgressionType { get; set; }

		public string Family { get; set; }

		public string EnemyType { get; set; }

		public List<RuntimeEnemy> Enemie