Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of FuelReserve v0.1.5
plugins/ValheimFuelReserve.dll
Decompiled 4 days agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Bootstrap; 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("ValheimFuelReserve")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.1.5.0")] [assembly: AssemblyInformationalVersion("0.1.5")] [assembly: AssemblyProduct("ValheimFuelReserve")] [assembly: AssemblyTitle("ValheimFuelReserve")] [assembly: InternalsVisibleTo("ReserveCheck")] [assembly: AssemblyVersion("0.1.5.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 ValheimFuelReserve { internal static class AutomaticFuelHooks { private sealed class Target { public string What; public string MethodName; public int Expected; public int Untouched; public (Version Since, string TypeName)[] Locations; public MethodInfo MoveNext; public int Replaced; public string TypeNameFor(Version version) { return Locations.LastOrDefault(((Version Since, string TypeName) l) => version >= l.Since).TypeName; } } internal const string Guid = "TastyChickenLegs.AutomaticFuel"; internal static readonly Version[] TestedVersions = new Version[5] { new Version(1, 5, 3), new Version(1, 5, 5), new Version(1, 5, 7), new Version(1, 6, 1), new Version(1, 6, 2) }; private static readonly Version V153 = new Version(1, 5, 3); private static readonly Version V157 = new Version(1, 5, 7); private static readonly Version V161 = new Version(1, 6, 1); private static readonly MethodInfo GetAllItems = AccessTools.Method(typeof(Inventory), "GetAllItems", new Type[2] { typeof(string), typeof(List<ItemData>) }, (Type[])null); private static readonly MethodInfo Replacement = AccessTools.Method(typeof(Reserve), "GetAllItemsAboveReserve", (Type[])null, (Type[])null); private static readonly Target[] Targets = new Target[3] { new Target { What = "fires and lights", MethodName = "RefuelTorch", Expected = 1, Locations = new(Version, string)[3] { (V153, "AutomaticFuel.GameClasses.Fireplace_Patches"), (V157, null), (V161, "AutomaticFuel.GameClasses.Fireplace_Patches+Fireplace_UpdateFireplace_Patch") } }, new Target { What = "smelters, kiln, windmill, spinning wheel, refinery", MethodName = "RefuelSmelter", Expected = 3, Untouched = 1, Locations = new(Version, string)[3] { (V153, "AutomaticFuel.GameClasses.Smelters+BlastFurnacePatch"), (V157, "AutomaticFuel.GameClasses.Smelters"), (V161, "AutomaticFuel.Patches.Smelters") } }, new Target { What = "shield generator", MethodName = "RefuelGen", Expected = 1, Locations = new(Version, string)[2] { (V153, "AutomaticFuel.GameClasses.Sgenerator_Patches"), (V161, "AutomaticFuel.Patches.Sgenerator_Patches") } } }; internal static IEnumerable<string> RoutinesFor(Version version) { return from t in TargetsFor(version) select t.MethodName; } internal static IEnumerable<string> QualifiedRoutinesFor(Version version) { return from t in TargetsFor(version) select t.TypeNameFor(version) + ":" + t.MethodName; } private static IEnumerable<Target> TargetsFor(Version version) { return Targets.Where((Target t) => t.TypeNameFor(version) != null); } internal static bool Patch(Harmony harmony, Version version, List<string> report) { //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Expected O, but got Unknown if (GetAllItems == null) { report.Add("Inventory.GetAllItems(string, List) no longer exists in the game."); return false; } Target[] array = TargetsFor(version).ToArray(); Target[] array2 = array; foreach (Target target in array2) { string text = Resolve(target, version); if (text != null) { report.Add(target.MethodName + " (" + target.What + "): " + text); return false; } } array2 = array; foreach (Target target2 in array2) { harmony.Patch((MethodBase)target2.MoveNext, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(AutomaticFuelHooks), "Transpiler", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null); report.Add($"{target2.What}: {target2.Replaced} of {target2.Expected} chest reads under reserve"); } return true; } private static string Resolve(Target target, Version version) { string text = target.TypeNameFor(version); Type type = AccessTools.TypeByName(text); if (type == null) { return "type " + text + " not found"; } MethodInfo methodInfo = AccessTools.Method(type, target.MethodName, (Type[])null, (Type[])null); if (methodInfo == null) { return "method not found"; } AsyncStateMachineAttribute customAttribute = methodInfo.GetCustomAttribute<AsyncStateMachineAttribute>(); MethodInfo methodInfo2 = ((customAttribute == null) ? null : AccessTools.Method(customAttribute.StateMachineType, "MoveNext", (Type[])null, (Type[])null)); if (methodInfo2 == null) { return "no longer an async method"; } int num = PatchProcessor.GetOriginalInstructions((MethodBase)methodInfo2, (ILGenerator)null).Count((CodeInstruction i) => CodeInstructionExtensions.Calls(i, GetAllItems)); if (num != target.Expected) { return $"{num} chest reads found, {target.Expected} expected"; } target.MoveNext = methodInfo2; return null; } private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, MethodBase original) { Target target = Targets.FirstOrDefault((Target t) => t.MoveNext != null && t.MoveNext.MetadataToken == original.MetadataToken && t.MoveNext.Module == original.Module); int seen = 0; int replaced = 0; foreach (CodeInstruction instruction in instructions) { if (target != null && CodeInstructionExtensions.Calls(instruction, GetAllItems) && seen++ >= target.Untouched) { instruction.opcode = OpCodes.Call; instruction.operand = Replacement; replaced++; } yield return instruction; } if (target != null) { target.Replaced = replaced; } } internal static bool ShareWithServerSync(List<string> report, params ConfigEntryBase[] entries) { try { Type type = AccessTools.TypeByName("AutomaticFuel.AutomaticFuelPlugin"); object obj = ((type == null) ? null : AccessTools.Field(type, "ConfigSync")?.GetValue(null)); MethodInfo methodInfo = ((obj == null) ? null : AccessTools.Method(obj.GetType(), "AddConfigEntry", (Type[])null, (Type[])null)); if (methodInfo == null) { report.Add("AutomaticFuel's ServerSync channel not found."); return false; } foreach (ConfigEntryBase val in entries) { methodInfo.MakeGenericMethod(val.SettingType).Invoke(obj, new object[1] { val }); } return true; } catch (Exception ex) { report.Add("Could not join AutomaticFuel's ServerSync channel: " + (ex.InnerException ?? ex).Message); return false; } } } [BepInPlugin("moochi.valheim.fuelreserve", "FuelReserve", "0.1.5")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { public const string PluginGuid = "moochi.valheim.fuelreserve"; public const string PluginName = "FuelReserve"; public const string PluginVersion = "0.1.5"; private Harmony _harmony; private FileSystemWatcher _watcher; internal static ManualLogSource Log { get; private set; } private void Awake() { //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; Reserve.Bind(((BaseUnityPlugin)this).Config); List<string> list = new List<string>(); bool flag = AutomaticFuelHooks.ShareWithServerSync(list, (ConfigEntryBase)Reserve.Enabled, (ConfigEntryBase)Reserve.Amount, (ConfigEntryBase)Reserve.Exceptions); WatchConfigFile(); PluginInfo value; Version version = (Chainloader.PluginInfos.TryGetValue("TastyChickenLegs.AutomaticFuel", out value) ? value.Metadata.Version : null); if (version == null || Array.IndexOf(AutomaticFuelHooks.TestedVersions, version) < 0) { Log.LogError((object)(string.Format("AutomaticFuel {0} found, but this mod was written against {1} only. ", version, string.Join(", ", AutomaticFuelHooks.TestedVersions.Select((Version v) => v.ToString()))) + "The reserve is OFF: AutomaticFuel will empty chests as it normally does.")); return; } _harmony = new Harmony("moochi.valheim.fuelreserve"); bool flag2 = AutomaticFuelHooks.Patch(_harmony, version, list); foreach (string item in list) { if (flag2) { Log.LogInfo((object)item); } else { Log.LogError((object)item); } } if (!flag2) { Log.LogError((object)"AutomaticFuel does not look like the version this mod was written for. The reserve is OFF: AutomaticFuel will empty chests as it normally does."); return; } string[] array = AutomaticFuelHooks.RoutinesFor(version).ToArray(); Log.LogInfo((object)(string.Format("{0} v{1} loaded: AutomaticFuel {2}, {3} routines patched ({4}). ", "FuelReserve", "0.1.5", version, array.Length, string.Join(", ", array)) + $"AutomaticFuel leaves {Reserve.Amount.Value} of each item in every chest" + (flag ? ", or what the server imposes." : ". Not synced with the server: each player uses their own setting."))); } private void WatchConfigFile() { _watcher = new FileSystemWatcher(Path.GetDirectoryName(((BaseUnityPlugin)this).Config.ConfigFilePath), Path.GetFileName(((BaseUnityPlugin)this).Config.ConfigFilePath)); _watcher.Changed += delegate { ReloadConfig(); }; _watcher.Created += delegate { ReloadConfig(); }; _watcher.Renamed += delegate { ReloadConfig(); }; _watcher.SynchronizingObject = ThreadingHelper.SynchronizingObject; _watcher.EnableRaisingEvents = true; } private void ReloadConfig() { if (!File.Exists(((BaseUnityPlugin)this).Config.ConfigFilePath)) { return; } try { ((BaseUnityPlugin)this).Config.Reload(); } catch (Exception ex) { Log.LogError((object)("Could not read " + Path.GetFileName(((BaseUnityPlugin)this).Config.ConfigFilePath) + ": " + ex.Message)); } } internal static void Warn(string message) { if (Log != null) { Log.LogWarning((object)message); } } private void OnDestroy() { if (_harmony != null) { _harmony.UnpatchSelf(); } if (_watcher != null) { _watcher.Dispose(); } } } internal static class Reserve { private const string Section = "Reserve"; internal static ConfigEntry<bool> Enabled; internal static ConfigEntry<int> Amount; internal static ConfigEntry<string> Exceptions; private static Dictionary<string, int> _exceptions = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase); internal static void Bind(ConfigFile config) { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Expected O, but got Unknown Enabled = config.Bind<bool>("Reserve", "Enabled", true, "Make AutomaticFuel leave a reserve in every chest it draws from. Off: AutomaticFuel behaves as if this mod were not installed. Imposed by the server when the server has this mod."); Amount = config.Bind<int>("Reserve", "Amount", 100, new ConfigDescription("How many of each item AutomaticFuel must leave in every chest it draws from. A chest holding this many or fewer is left alone. Items lying on the ground are not covered. Imposed by the server when the server has this mod.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 10000), Array.Empty<object>())); Exceptions = config.Bind<string>("Reserve", "Exceptions", "", "Per-item reserves that replace the amount above, by prefab name, comma-separated: Resin:20,Coal:0. Unreadable entries are ignored and reported in the log. Imposed by the server when the server has this mod."); Exceptions.SettingChanged += delegate { ParseExceptions(); }; ParseExceptions(); } internal static int For(string prefabName) { if (prefabName != null && _exceptions.TryGetValue(prefabName, out var value)) { return value; } return Amount.Value; } internal static void ParseExceptions() { Dictionary<string, int> dictionary = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase); string[] array = (Exceptions.Value ?? "").Split(',', ';'); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length != 0) { int num = text.IndexOfAny(new char[2] { ':', '=' }); string text2 = ((num > 0) ? text.Substring(0, num).Trim() : ""); string s = ((num > 0) ? text.Substring(num + 1).Trim() : ""); if (text2.Length == 0 || !int.TryParse(s, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) || result < 0) { Plugin.Warn("Reserve exception '" + text + "' ignored: expected PrefabName:Amount, with an amount of 0 or more."); } else { dictionary[text2] = result; } } } _exceptions = dictionary; } public static void GetAllItemsAboveReserve(Inventory inventory, string name, List<ItemData> items) { int count = items.Count; inventory.GetAllItems(name, items); int num = items.Count - count; if (num == 0 || Enabled == null || !Enabled.Value) { return; } int num2 = 0; string text = null; for (int i = count; i < items.Count; i++) { ItemData val = items[i]; num2 += val.m_stack; if (text == null && val.m_dropPrefab != null && Object.op_Implicit((Object)(object)val.m_dropPrefab)) { text = ((Object)val.m_dropPrefab).name; } } int num3 = num2 - For(text); int num4 = ((num3 > 0) ? Math.Min(num3, num) : 0); if (num4 < num) { items.RemoveRange(count + num4, num - num4); } } } }