Decompiled source of CosmeticBoxConfig v1.0.0

CosmeticBoxConfig.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyCompany("CosmeticBoxConfig")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("CosmeticBoxConfig")]
[assembly: AssemblyTitle("CosmeticBoxConfig")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace CosmeticBoxConfig
{
	internal static class PluginInfo
	{
		public const string GUID = "cosmetic.box.config";

		public const string NAME = "CosmeticBoxConfig";

		public const string VERSION = "1.0.0";
	}
	internal enum SpawnMode
	{
		Random,
		Fixed
	}
	internal readonly struct LevelSettings
	{
		public SpawnMode Mode { get; }

		public int BoxCount { get; }

		public int LoopMax { get; }

		public int CompensationLoopMax { get; }

		public LevelSettings(SpawnMode mode, int boxCount)
		{
			Mode = mode;
			BoxCount = Mathf.Clamp(boxCount, 0, 20);
			LoopMax = ((mode == SpawnMode.Fixed) ? GetTimeFormulaLoopMax(BoxCount) : 0);
			CompensationLoopMax = ((mode == SpawnMode.Fixed) ? GetCompensationLoopMax(BoxCount) : 0);
		}

		private static int GetTimeFormulaLoopMax(int boxCount)
		{
			if (boxCount > 0)
			{
				return boxCount - 1;
			}
			return -1;
		}

		private static int GetCompensationLoopMax(int boxCount)
		{
			if (boxCount > 0)
			{
				return Mathf.Max(boxCount - 1, boxCount * 3 - 1);
			}
			return -1;
		}
	}
	[BepInPlugin("cosmetic.box.config", "CosmeticBoxConfig", "1.0.0")]
	public class Plugin : BaseUnityPlugin
	{
		internal static ManualLogSource Log = null;

		private static ConfigEntry<SpawnMode> _mode = null;

		private static ConfigEntry<int> _boxCount = null;

		private static readonly object SettingsLock = new object();

		private static LevelSettings _pendingSettings = new LevelSettings(SpawnMode.Random, 1);

		private static LevelSettings _activeSettings = new LevelSettings(SpawnMode.Random, 1);

		private static int _actualBoxesThisLevel;

		private static bool _patchesEnabled;

		internal static LevelSettings ActiveSettings
		{
			get
			{
				lock (SettingsLock)
				{
					return _activeSettings;
				}
			}
		}

		internal static int ActualBoxesThisLevel
		{
			get
			{
				lock (SettingsLock)
				{
					return _actualBoxesThisLevel;
				}
			}
		}

		private void Awake()
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Expected O, but got Unknown
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Expected O, but got Unknown
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			_mode = ((BaseUnityPlugin)this).Config.Bind<SpawnMode>("General", "Mode", SpawnMode.Random, new ConfigDescription("Spawn mode:\n  Random - At least 1 actual cosmetic box when an original spawn volume is available; remaining rolls use vanilla probability.\n  Fixed  - Targets BoxCount actual cosmetic boxes when original spawn volumes are available, with compensation attempts.\nChanges apply on the next level load, not mid-level.", (AcceptableValueBase)null, Array.Empty<object>()));
			_boxCount = ((BaseUnityPlugin)this).Config.Bind<int>("General", "BoxCount", 1, new ConfigDescription("Fixed mode cosmetic boxes.\n  0 = 0 cosmetic boxes.\n  1-20 = target actual cosmetic box count, limited by original spawn-volume availability.\nIgnored in Random mode. Changes apply on the next level load.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 20), Array.Empty<object>()));
			UpdatePendingSettings();
			ActivatePendingSettings();
			((BaseUnityPlugin)this).Config.SettingChanged += delegate
			{
				UpdatePendingSettings();
			};
			_patchesEnabled = TryPatchAll(new Harmony("cosmetic.box.config"));
			Log.LogInfo((object)("CosmeticBoxConfig v1.0.0 loaded - patches " + (_patchesEnabled ? "enabled" : "disabled") + "; chance gate " + (PatchMethods.ChanceGatePatchActive ? "active" : "inactive") + "; active " + Describe(ActiveSettings)));
		}

		private static bool TryPatchAll(Harmony harmony)
		{
			//IL_01aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b8: Expected O, but got Unknown
			//IL_01cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d9: Expected O, but got Unknown
			//IL_01ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fa: Expected O, but got Unknown
			//IL_0210: Unknown result type (might be due to invalid IL or missing references)
			//IL_021c: Expected O, but got Unknown
			//IL_022f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0244: Unknown result type (might be due to invalid IL or missing references)
			//IL_0251: Expected O, but got Unknown
			//IL_0251: Expected O, but got Unknown
			try
			{
				MethodInfo methodInfo = AccessTools.Method(typeof(ValuableDirector), "SetupHost", (Type[])null, (Type[])null);
				MethodInfo methodInfo2 = AccessTools.Method(typeof(ValuableDirector), "CosmeticWorldObjectLevelLoopsGet", (Type[])null, (Type[])null);
				MethodInfo methodInfo3 = AccessTools.Method(typeof(ValuableDirector), "CosmeticWorldObjectLevelLoopsClampedGet", (Type[])null, (Type[])null);
				MethodInfo methodInfo4 = AccessTools.Method(typeof(ValuableDirector), "SpawnCosmeticWorldObject", (Type[])null, (Type[])null);
				MethodInfo iteratorMoveNext = GetIteratorMoveNext(methodInfo);
				FieldInfo fieldInfo = AccessTools.Field(typeof(ValuableDirector), "cosmeticWorldObjectTargetAmount");
				if (methodInfo == null || iteratorMoveNext == null || methodInfo2 == null || methodInfo3 == null || methodInfo4 == null || fieldInfo == null)
				{
					Log.LogError((object)("Required game methods were not found; CosmeticBoxConfig will not patch anything. SetupHost=" + ((methodInfo != null) ? "ok" : "missing") + ", SetupHost.MoveNext=" + ((iteratorMoveNext != null) ? "ok" : "missing") + ", CosmeticWorldObjectLevelLoopsGet=" + ((methodInfo2 != null) ? "ok" : "missing") + ", CosmeticWorldObjectLevelLoopsClampedGet=" + ((methodInfo3 != null) ? "ok" : "missing") + ", SpawnCosmeticWorldObject=" + ((methodInfo4 != null) ? "ok" : "missing") + ", cosmeticWorldObjectTargetAmount=" + ((fieldInfo != null) ? "ok" : "missing")));
					return false;
				}
				harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(PatchMethods), "SetupHostPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(typeof(PatchMethods), "LoopsGetPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, new HarmonyMethod(typeof(PatchMethods), "LoopsClampedGetPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)iteratorMoveNext, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(PatchMethods), "SetupHostMoveNextTranspiler", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)methodInfo4, new HarmonyMethod(typeof(PatchMethods), "SpawnCosmeticWorldObjectPrefix", (Type[])null), new HarmonyMethod(typeof(PatchMethods), "SpawnCosmeticWorldObjectPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				return true;
			}
			catch (Exception arg)
			{
				Log.LogError((object)$"Failed to register CosmeticBoxConfig patches; attempting to roll back any partial patches. {arg}");
				try
				{
					harmony.UnpatchSelf();
					Log.LogInfo((object)"Rolled back CosmeticBoxConfig patches after registration failure.");
				}
				catch (Exception arg2)
				{
					Log.LogError((object)$"Failed to roll back partial CosmeticBoxConfig patches. {arg2}");
				}
				return false;
			}
		}

		private static MethodInfo? GetIteratorMoveNext(MethodInfo? method)
		{
			Type type = method?.GetCustomAttribute<IteratorStateMachineAttribute>()?.StateMachineType;
			if (!(type == null))
			{
				return AccessTools.Method(type, "MoveNext", (Type[])null, (Type[])null);
			}
			return null;
		}

		internal static void ActivatePendingSettingsForLevel()
		{
			lock (SettingsLock)
			{
				_activeSettings = _pendingSettings;
				_actualBoxesThisLevel = 0;
			}
			Log.LogInfo((object)("CosmeticBoxConfig level settings active: " + Describe(ActiveSettings)));
		}

		internal static void ActivatePendingSettings()
		{
			lock (SettingsLock)
			{
				_activeSettings = _pendingSettings;
			}
			Log.LogInfo((object)("CosmeticBoxConfig settings active: " + Describe(ActiveSettings)));
		}

		internal static void RecordActualBoxSpawned()
		{
			lock (SettingsLock)
			{
				_actualBoxesThisLevel++;
			}
		}

		internal static bool NeedsGuaranteedBox()
		{
			TryGetGuaranteeState(out var settings, out var actual);
			if (settings.Mode != SpawnMode.Fixed)
			{
				return actual < 1;
			}
			return actual < settings.BoxCount;
		}

		internal static bool TryGetGuaranteeState(out LevelSettings settings, out int actual)
		{
			lock (SettingsLock)
			{
				settings = _activeSettings;
				actual = _actualBoxesThisLevel;
			}
			if (settings.Mode != SpawnMode.Fixed)
			{
				return true;
			}
			return settings.BoxCount > 0;
		}

		private static void UpdatePendingSettings()
		{
			LevelSettings levelSettings = ReadSettingsFromConfig();
			lock (SettingsLock)
			{
				_pendingSettings = levelSettings;
			}
			Log.LogInfo((object)("CosmeticBoxConfig config queued for next level: " + Describe(levelSettings)));
		}

		private static LevelSettings ReadSettingsFromConfig()
		{
			return new LevelSettings(_mode.Value, _boxCount.Value);
		}

		private static string Describe(LevelSettings settings)
		{
			if (settings.Mode != SpawnMode.Fixed)
			{
				return "Random, minimum actual boxes=1 then vanilla probability";
			}
			return $"Fixed, target boxes={settings.BoxCount}, loopMax={settings.LoopMax}, compensationLoopMax={settings.CompensationLoopMax}";
		}
	}
	internal static class PatchMethods
	{
		private const int ForcedFailureRoll = 99999;

		private static readonly FieldInfo TargetAmountField = AccessTools.Field(typeof(ValuableDirector), "cosmeticWorldObjectTargetAmount");

		private static bool _chanceGatePatchActive;

		internal static bool ChanceGatePatchActive => _chanceGatePatchActive;

		internal static void SetupHostPrefix()
		{
			Plugin.ActivatePendingSettingsForLevel();
		}

		internal static IEnumerable<CodeInstruction> SetupHostMoveNextTranspiler(IEnumerable<CodeInstruction> instructions)
		{
			List<CodeInstruction> list = instructions.ToList();
			MethodInfo methodInfo = AccessTools.Method(typeof(Random), "Range", new Type[2]
			{
				typeof(int),
				typeof(int)
			}, (Type[])null);
			MethodInfo operand = AccessTools.Method(typeof(PatchMethods), "GuaranteedChanceRoll", (Type[])null, (Type[])null);
			List<int> list2 = new List<int>();
			for (int i = 0; i <= list.Count - 3; i++)
			{
				if (IsLdcI4(list[i], 0) && IsLdcI4(list[i + 1], 100) && CodeInstructionExtensions.Calls(list[i + 2], methodInfo))
				{
					list2.Add(i);
				}
			}
			if (list2.Count != 1)
			{
				_chanceGatePatchActive = false;
				string text = $"Expected exactly one cosmetic Random.Range(0,100) chance gate in SetupHost.MoveNext, found {list2.Count}; rolling back patches.";
				Plugin.Log.LogError((object)text);
				throw new InvalidOperationException(text);
			}
			list[list2[0] + 2].operand = operand;
			_chanceGatePatchActive = true;
			Plugin.Log.LogInfo((object)"Patched SetupHost.MoveNext cosmetic chance gate.");
			return list;
		}

		internal static int GuaranteedChanceRoll(int min, int max)
		{
			int result = Random.Range(min, max);
			Plugin.TryGetGuaranteeState(out var settings, out var actual);
			if (settings.Mode == SpawnMode.Fixed)
			{
				if (actual >= settings.BoxCount)
				{
					return 99999;
				}
				return min;
			}
			if (actual >= 1)
			{
				return result;
			}
			return min;
		}

		internal static void LoopsGetPostfix(ref int __result)
		{
			LevelSettings activeSettings = Plugin.ActiveSettings;
			if (activeSettings.Mode == SpawnMode.Fixed)
			{
				__result = activeSettings.LoopMax;
			}
		}

		internal static void LoopsClampedGetPostfix(ref int __result)
		{
			LevelSettings activeSettings = Plugin.ActiveSettings;
			__result = ((activeSettings.Mode == SpawnMode.Fixed) ? activeSettings.CompensationLoopMax : Mathf.Max(__result, 0));
		}

		internal static void SpawnCosmeticWorldObjectPrefix(ValuableDirector __instance, ref Rarity _cosmeticRarity, int[] _volumeIndex, List<ValuableVolume>[] _volumes, out int __state)
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Expected I4, but got Unknown
			__state = GetTargetAmount(__instance);
			try
			{
				if (Plugin.NeedsGuaranteedBox() && !HasAvailableVolume(__instance, _cosmeticRarity, _volumeIndex, _volumes) && TryFindAvailableRarity(__instance, _volumeIndex, _volumes, out var rarity))
				{
					_cosmeticRarity = (Rarity)(int)rarity;
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"SpawnCosmeticWorldObjectPrefix failed; leaving vanilla spawn logic unchanged. {arg}");
			}
		}

		internal static void SpawnCosmeticWorldObjectPostfix(ValuableDirector __instance, int __state)
		{
			try
			{
				if (GetTargetAmount(__instance) > __state)
				{
					Plugin.RecordActualBoxSpawned();
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"SpawnCosmeticWorldObjectPostfix failed. {arg}");
			}
		}

		private static int GetTargetAmount(ValuableDirector director)
		{
			if ((Object)(object)director == (Object)null || TargetAmountField == null || TargetAmountField.FieldType != typeof(int))
			{
				return 0;
			}
			try
			{
				return (int)TargetAmountField.GetValue(director);
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Failed to read cosmeticWorldObjectTargetAmount. {arg}");
				return 0;
			}
		}

		private static bool TryFindAvailableRarity(ValuableDirector director, int[] volumeIndex, List<ValuableVolume>[] volumes, out Rarity rarity)
		{
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: 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_004c: Expected I4, but got Unknown
			if (director?.cosmeticWorldObjectSetups == null)
			{
				rarity = (Rarity)0;
				return false;
			}
			for (int i = 0; i < director.cosmeticWorldObjectSetups.Count; i++)
			{
				if (Enum.IsDefined(typeof(Rarity), i) && director.cosmeticWorldObjectSetups[i] != null)
				{
					Rarity val = (Rarity)i;
					if (HasAvailableVolume(director, val, volumeIndex, volumes))
					{
						rarity = (Rarity)(int)val;
						return true;
					}
				}
			}
			rarity = (Rarity)0;
			return false;
		}

		private static bool TryGetVolumeIndexForRarity(ValuableDirector director, Rarity rarity, out int volumeIndex)
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			volumeIndex = 0;
			if (director?.cosmeticWorldObjectSetups == null)
			{
				return false;
			}
			int num = Convert.ToInt32(rarity);
			if (num < 0 || num >= director.cosmeticWorldObjectSetups.Count)
			{
				return false;
			}
			CosmeticWorldObjectSetup val = director.cosmeticWorldObjectSetups[num];
			if (val == null)
			{
				return false;
			}
			volumeIndex = Convert.ToInt32(val.volume);
			return true;
		}

		private static bool HasAvailableVolume(ValuableDirector director, Rarity rarity, int[] volumeIndex, List<ValuableVolume>[] volumes)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			if (TryGetVolumeIndexForRarity(director, rarity, out var volumeIndex2) && volumes != null && volumeIndex != null && volumeIndex2 >= 0 && volumeIndex2 < volumes.Length && volumeIndex2 < volumeIndex.Length && volumes[volumeIndex2] != null && volumeIndex[volumeIndex2] >= 0)
			{
				return volumeIndex[volumeIndex2] < volumes[volumeIndex2].Count;
			}
			return false;
		}

		private static bool IsLdcI4(CodeInstruction instruction, int expected)
		{
			if (instruction.opcode == OpCodes.Ldc_I4_M1)
			{
				return expected == -1;
			}
			if (instruction.opcode == OpCodes.Ldc_I4_0)
			{
				return expected == 0;
			}
			if (instruction.opcode == OpCodes.Ldc_I4_1)
			{
				return expected == 1;
			}
			if (instruction.opcode == OpCodes.Ldc_I4_2)
			{
				return expected == 2;
			}
			if (instruction.opcode == OpCodes.Ldc_I4_3)
			{
				return expected == 3;
			}
			if (instruction.opcode == OpCodes.Ldc_I4_4)
			{
				return expected == 4;
			}
			if (instruction.opcode == OpCodes.Ldc_I4_5)
			{
				return expected == 5;
			}
			if (instruction.opcode == OpCodes.Ldc_I4_6)
			{
				return expected == 6;
			}
			if (instruction.opcode == OpCodes.Ldc_I4_7)
			{
				return expected == 7;
			}
			if (instruction.opcode == OpCodes.Ldc_I4_8)
			{
				return expected == 8;
			}
			if (instruction.opcode == OpCodes.Ldc_I4_S || instruction.opcode == OpCodes.Ldc_I4)
			{
				return Convert.ToInt32(instruction.operand) == expected;
			}
			return false;
		}
	}
}