Decompiled source of FuelReserve v0.1.5

plugins/ValheimFuelReserve.dll

Decompiled 4 days ago
using 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);
			}
		}
	}
}