using System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("Codex")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("4.0.1.0")] [assembly: AssemblyInformationalVersion("4.0.1")] [assembly: AssemblyProduct("EnergyCrystalShopStockFix")] [assembly: AssemblyTitle("EnergyCrystalShopStockFix")] [assembly: AssemblyVersion("4.0.1.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace REPOJP.EnergyCrystalShopStockFix { [BepInPlugin("REPOJP.EnergyCrystalShopStockFix", "Energy Crystal Auto Refill - Shop Stock Fix", "4.0.1")] [BepInDependency("REPOJP.EnergyCrystalAutoRefill", "4.0.0")] public sealed class EnergyCrystalShopStockFixPlugin : BaseUnityPlugin { public const string PluginGuid = "REPOJP.EnergyCrystalShopStockFix"; public const string PluginName = "Energy Crystal Auto Refill - Shop Stock Fix"; public const string PluginVersion = "4.0.1"; internal const string TargetPluginGuid = "REPOJP.EnergyCrystalAutoRefill"; internal const string PowerCrystalItemName = "Item Power Crystal"; internal const string ChargeTotalStatName = "chargingStationChargeTotal"; internal const string SaveStateDictionaryName = "EnergyCrystalShopStockFix"; private Harmony? _harmony; internal static ManualLogSource ModLogger { get; private set; } private void Awake() { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Expected O, but got Unknown ModLogger = ((BaseUnityPlugin)this).Logger; VirtualCrystalTracker.InitializeRuntime(); _harmony = new Harmony("REPOJP.EnergyCrystalShopStockFix"); try { _harmony.PatchAll(typeof(ShopInventoryScopePatch)); _harmony.PatchAll(typeof(CurrentPurchaseCountPatch)); _harmony.PatchAll(typeof(TotalPurchaseCountPatch)); _harmony.PatchAll(typeof(CrystalConsumedPatch)); _harmony.PatchAll(typeof(NewRunPatch)); _harmony.PatchAll(typeof(StatsManagerStartPatch)); _harmony.PatchAll(typeof(LoadGamePatch)); PatchAutoRefillMethod(_harmony); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Shop stock fix loaded for EnergyCrystalAutoRefill 4.0.0."); } catch (Exception arg) { _harmony.UnpatchSelf(); ((BaseUnityPlugin)this).Logger.LogError((object)$"Failed to apply shop stock fix: {arg}"); } } private void OnDestroy() { Harmony? harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } VirtualCrystalTracker.ClearTransientState(); } private static void PatchAutoRefillMethod(Harmony harmony) { //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Expected O, but got Unknown //IL_00e0: Expected O, but got Unknown if (!Chainloader.PluginInfos.TryGetValue("REPOJP.EnergyCrystalAutoRefill", out var value)) { throw new InvalidOperationException("Required plugin 'REPOJP.EnergyCrystalAutoRefill' is not loaded."); } Type type = AccessTools.Inner(((object)value.Instance).GetType(), "ChargingStationUpdatePostfixPatch"); if ((object)type == null) { throw new MissingMemberException(((object)value.Instance).GetType().FullName, "ChargingStationUpdatePostfixPatch"); } MethodInfo methodInfo = AccessTools.Method(type, "TryAddPowerCrystalPurchase", new Type[3] { typeof(ChargingStation), typeof(int), typeof(int) }, (Type[])null); if ((object)methodInfo == null) { throw new MissingMethodException(type.FullName, "TryAddPowerCrystalPurchase(ChargingStation, int, int)"); } harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(AccessTools.Method(typeof(AutoRefillTrackingPatch), "Prefix", (Type[])null, (Type[])null)), new HarmonyMethod(AccessTools.Method(typeof(AutoRefillTrackingPatch), "Postfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } internal readonly struct PurchaseCountSnapshot { internal int Current { get; } internal int Total { get; } internal PurchaseCountSnapshot(int current, int total) { Current = current; Total = total; } internal static PurchaseCountSnapshot Capture() { StatsManager instance = StatsManager.instance; if (instance == null) { return default(PurchaseCountSnapshot); } instance.itemsPurchased.TryGetValue("Item Power Crystal", out var value); instance.itemsPurchasedTotal.TryGetValue("Item Power Crystal", out var value2); return new PurchaseCountSnapshot(value, value2); } } internal static class VirtualCrystalTracker { private const string RecordVersionKey = "RecordVersion"; private const string CurrentOffsetKey = "CurrentAutoRefillCrystals"; private const string TotalOffsetKey = "TotalAutoRefillCrystals"; private const int CurrentRecordVersion = 1; private static readonly Dictionary SaveState = new Dictionary(); private static readonly FieldInfo DictionaryRegistryField = AccessTools.Field(typeof(StatsManager), "dictionaryOfDictionaries"); private static readonly FieldInfo EnergyPerCrystalField = AccessTools.Field(typeof(ChargingStation), "energyPerCrystal"); private static readonly FieldInfo MaxCrystalsField = AccessTools.Field(typeof(ChargingStation), "maxCrystals"); private static int _currentAutoRefillCrystals; private static int _totalAutoRefillCrystals; private static int _shopInventoryQueryDepth; private static bool _legacyRepairPending; internal static bool IsShopInventoryQuery => _shopInventoryQueryDepth > 0; internal static void InitializeRuntime() { SaveState.Clear(); _currentAutoRefillCrystals = 0; _totalAutoRefillCrystals = 0; _shopInventoryQueryDepth = 0; _legacyRepairPending = false; } internal static void RegisterSaveState(StatsManager stats) { if (!(DictionaryRegistryField.GetValue(stats) is SortedDictionary> sortedDictionary)) { throw new InvalidOperationException("StatsManager dictionary registry is unavailable."); } if (sortedDictionary.TryGetValue("EnergyCrystalShopStockFix", out var value)) { if (value == SaveState) { return; } SaveState.Clear(); foreach (KeyValuePair item in value) { SaveState[item.Key] = item.Value; } sortedDictionary["EnergyCrystalShopStockFix"] = SaveState; } else { sortedDictionary.Add("EnergyCrystalShopStockFix", SaveState); } } internal static void BeginLoad(StatsManager stats) { RegisterSaveState(stats); SaveState.Clear(); _currentAutoRefillCrystals = 0; _totalAutoRefillCrystals = 0; _shopInventoryQueryDepth = 0; _legacyRepairPending = false; } internal static void FinishLoad() { if (SaveState.TryGetValue("RecordVersion", out var value) && value > 0) { _currentAutoRefillCrystals = GetSavedNonNegative("CurrentAutoRefillCrystals"); _totalAutoRefillCrystals = GetSavedNonNegative("TotalAutoRefillCrystals"); _legacyRepairPending = false; EnergyCrystalShopStockFixPlugin.ModLogger.LogDebug((object)($"Loaded per-save refill offsets: current={_currentAutoRefillCrystals}, " + $"total={_totalAutoRefillCrystals}.")); } else { _currentAutoRefillCrystals = 0; _totalAutoRefillCrystals = 0; _legacyRepairPending = true; EnergyCrystalShopStockFixPlugin.ModLogger.LogInfo((object)"Loaded a save without shop-stock-fix records. Legacy repair will run before the shop inventory is generated."); } } internal static void NormalizePurchaseCountForShop() { StatsManager instance = StatsManager.instance; ChargingStation instance2 = ChargingStation.instance; if (instance == null || instance2 == null || !instance.itemDictionary.TryGetValue("Item Power Crystal", out var value)) { return; } int num = Math.Max(0, value.maxAmountInShop); instance.itemsPurchased.TryGetValue("Item Power Crystal", out var value2); bool flag = false; if (value2 >= num) { instance.runStats.TryGetValue("chargingStationChargeTotal", out var value3); int num2 = Math.Max(1, (int)EnergyPerCrystalField.GetValue(instance2)); int num3 = Math.Max(0, (int)MaxCrystalsField.GetValue(instance2)); int num4 = Mathf.Clamp(Mathf.CeilToInt((float)value3 / (float)num2), 0, num3); int num5 = num4; int val = Math.Max(0, value2 - num5); if (num5 != value2) { instance.SetItemPurchase(value, num5); if ((Object)(object)PunManager.instance != (Object)null && SemiFunc.IsMasterClientOrSingleplayer()) { PunManager.instance.UpdateStat("itemsPurchased", "Item Power Crystal", num5); } flag = true; } _currentAutoRefillCrystals = 0; if (_legacyRepairPending) { _totalAutoRefillCrystals = Math.Max(_totalAutoRefillCrystals, val); } EnergyCrystalShopStockFixPlugin.ModLogger.LogWarning((object)($"Normalized Power Crystal purchase count for shop: {value2} -> " + $"{num5}. Remaining charge={value3}, " + $"rendered crystals={num4}, " + $"energyPerCrystal={num2}, maxCrystals={num3}, " + $"effective maxAmountInShop={num}.")); } else if (_legacyRepairPending) { EnergyCrystalShopStockFixPlugin.ModLogger.LogInfo((object)("Legacy save did not require repair. Power Crystal purchases=" + $"{value2}, effective maxAmountInShop={num}.")); } if (_legacyRepairPending || flag) { _legacyRepairPending = false; Persist(); } } internal static void RecordRefill(PurchaseCountSnapshot before, PurchaseCountSnapshot after) { int num = Math.Max(0, after.Current - before.Current); int num2 = Math.Max(0, after.Total - before.Total); _currentAutoRefillCrystals += num; _totalAutoRefillCrystals += num2; Persist(); if (num > 0 || num2 > 0) { EnergyCrystalShopStockFixPlugin.ModLogger.LogDebug((object)($"Tracked auto-refill crystals: current +{num}, total +{num2}. " + $"Offsets are now current={_currentAutoRefillCrystals}, total={_totalAutoRefillCrystals}.")); } } internal static void RecordConsumption(PurchaseCountSnapshot before, PurchaseCountSnapshot after) { int num = Math.Max(0, before.Current - after.Current); if (num != 0) { _currentAutoRefillCrystals = Math.Max(0, _currentAutoRefillCrystals - num); Persist(); } } internal static int RemoveCurrentAutoRefills(int purchaseCount) { return Math.Max(0, purchaseCount - _currentAutoRefillCrystals); } internal static int RemoveTotalAutoRefills(int purchaseCount) { return Math.Max(0, purchaseCount - _totalAutoRefillCrystals); } internal static void BeginShopInventoryQuery() { _shopInventoryQueryDepth++; } internal static void EndShopInventoryQuery() { _shopInventoryQueryDepth = Math.Max(0, _shopInventoryQueryDepth - 1); } internal static void Reset() { _currentAutoRefillCrystals = 0; _totalAutoRefillCrystals = 0; _shopInventoryQueryDepth = 0; _legacyRepairPending = false; Persist(); } internal static void ClearTransientState() { _shopInventoryQueryDepth = 0; } private static void Persist() { SaveState["RecordVersion"] = 1; SaveState["CurrentAutoRefillCrystals"] = Math.Max(0, _currentAutoRefillCrystals); SaveState["TotalAutoRefillCrystals"] = Math.Max(0, _totalAutoRefillCrystals); } private static int GetSavedNonNegative(string key) { if (!SaveState.TryGetValue(key, out var value)) { return 0; } return Math.Max(0, value); } } internal static class AutoRefillTrackingPatch { internal static void Prefix(out PurchaseCountSnapshot __state) { __state = PurchaseCountSnapshot.Capture(); } internal static void Postfix(PurchaseCountSnapshot __state) { VirtualCrystalTracker.RecordRefill(__state, PurchaseCountSnapshot.Capture()); } } [HarmonyPatch(typeof(ShopManager), "GetAllItemsFromStatsManager")] internal static class ShopInventoryScopePatch { [HarmonyPrefix] private static void Prefix() { VirtualCrystalTracker.NormalizePurchaseCountForShop(); VirtualCrystalTracker.BeginShopInventoryQuery(); } [HarmonyFinalizer] private static Exception? Finalizer(Exception? __exception) { VirtualCrystalTracker.EndShopInventoryQuery(); return __exception; } } [HarmonyPatch(typeof(SemiFunc), "StatGetItemsPurchased", new Type[] { typeof(string) })] internal static class CurrentPurchaseCountPatch { [HarmonyPostfix] private static void Postfix(string __0, ref int __result) { if (VirtualCrystalTracker.IsShopInventoryQuery && string.Equals(__0, "Item Power Crystal", StringComparison.Ordinal)) { __result = VirtualCrystalTracker.RemoveCurrentAutoRefills(__result); } } } [HarmonyPatch(typeof(StatsManager), "GetItemsUpgradesPurchasedTotal", new Type[] { typeof(string) })] internal static class TotalPurchaseCountPatch { [HarmonyPostfix] private static void Postfix(string __0, ref int __result) { if (VirtualCrystalTracker.IsShopInventoryQuery && string.Equals(__0, "Item Power Crystal", StringComparison.Ordinal)) { __result = VirtualCrystalTracker.RemoveTotalAutoRefills(__result); } } } [HarmonyPatch(typeof(ChargingStation), "ChargingStationCrystalBrokenRPC")] internal static class CrystalConsumedPatch { [HarmonyPrefix] private static void Prefix(out PurchaseCountSnapshot __state) { __state = PurchaseCountSnapshot.Capture(); } [HarmonyPostfix] private static void Postfix(PurchaseCountSnapshot __state) { VirtualCrystalTracker.RecordConsumption(__state, PurchaseCountSnapshot.Capture()); } } [HarmonyPatch(typeof(StatsManager), "RunStartStats")] internal static class NewRunPatch { [HarmonyPostfix] private static void Postfix(StatsManager __instance) { VirtualCrystalTracker.RegisterSaveState(__instance); VirtualCrystalTracker.Reset(); } } [HarmonyPatch(typeof(StatsManager), "Start")] internal static class StatsManagerStartPatch { [HarmonyPostfix] [HarmonyPriority(0)] private static void Postfix(StatsManager __instance) { VirtualCrystalTracker.RegisterSaveState(__instance); } } [HarmonyPatch(typeof(StatsManager), "LoadGame", new Type[] { typeof(string), typeof(List) })] internal static class LoadGamePatch { [HarmonyPrefix] [HarmonyPriority(800)] private static void Prefix(StatsManager __instance) { VirtualCrystalTracker.BeginLoad(__instance); } [HarmonyPostfix] [HarmonyPriority(0)] private static void Postfix() { VirtualCrystalTracker.FinishLoad(); } } }