Decompiled source of FuelCapacity v0.1.0

BepInEx/plugins/FuelCapacity/FuelCapacity.dll

Decompiled a week ago
using System;
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.Runtime.Versioning;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Configuration;
using FuelCapacity.Core;
using HarmonyLib;
using Jotunn.Utils;
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("FuelCapacity")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.1.0.0")]
[assembly: AssemblyInformationalVersion("0.1.0")]
[assembly: AssemblyProduct("FuelCapacity")]
[assembly: AssemblyTitle("FuelCapacity")]
[assembly: AssemblyVersion("0.1.0.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 FuelCapacity
{
	internal sealed class CapacityNetwork
	{
		private const string Query = "FuelCapacity_Query_v1";

		private const string Request = "FuelCapacity_Command_v1";

		private const string State = "FuelCapacity_State_v1";

		private const string Reply = "FuelCapacity_Reply_v1";

		private readonly Plugin plugin;

		private readonly HashSet<ZRpc> registered = new HashSet<ZRpc>();

		private readonly Dictionary<ZRpc, float> lastRequest = new Dictionary<ZRpc, float>();

		private ZNet session;

		private FuelSettings effective;

		private Terminal output;

		private float nextTick;

		private float nextQuery;

		private float nextPrune;

		private long revision;

		private long receivedRevision = -1L;

		internal CapacityNetwork(Plugin plugin)
		{
			this.plugin = plugin;
		}

		internal void Stop()
		{
			session = null;
			effective = null;
			registered.Clear();
			lastRequest.Clear();
			output = null;
			nextTick = (nextQuery = (nextPrune = 0f));
			revision = 0L;
			receivedRevision = -1L;
			plugin.Machines.Restore();
		}

		private void EnsureSession()
		{
			ZNet instance = ZNet.instance;
			if (session != instance)
			{
				Stop();
				session = instance;
				if (Object.op_Implicit((Object)(object)session) && session.IsServer())
				{
					Apply(plugin.Configured);
				}
			}
		}

		internal void Update()
		{
			//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d5: Expected O, but got Unknown
			EnsureSession();
			if (!Object.op_Implicit((Object)(object)session) || Time.unscaledTime < nextTick)
			{
				return;
			}
			nextTick = Time.unscaledTime + 0.25f;
			if (Time.unscaledTime >= nextPrune)
			{
				nextPrune = Time.unscaledTime + 15f;
				plugin.Machines.Prune();
			}
			List<ZNetPeer> peers = session.GetPeers();
			foreach (ZNetPeer item in peers)
			{
				if (item.IsReady() && item.m_rpc != null && registered.Add(item.m_rpc))
				{
					item.m_rpc.Register("FuelCapacity_Query_v1", new Method(OnQuery));
					item.m_rpc.Register<string>("FuelCapacity_Command_v1", (Action<ZRpc, string>)OnRequest);
					item.m_rpc.Register<ZPackage>("FuelCapacity_State_v1", (Action<ZRpc, ZPackage>)OnState);
					item.m_rpc.Register<string>("FuelCapacity_Reply_v1", (Action<ZRpc, string>)OnReply);
				}
			}
			ZRpc[] array = registered.Where((ZRpc r) => !peers.Any((ZNetPeer p) => p.m_rpc == r)).ToArray();
			foreach (ZRpc val in array)
			{
				registered.Remove(val);
				lastRequest.Remove(val);
			}
			if (!session.IsServer() && Time.unscaledTime >= nextQuery)
			{
				nextQuery = Time.unscaledTime + (float)((effective == null) ? 1 : 10);
				ZNetPeer serverPeer = session.GetServerPeer();
				if (serverPeer != null)
				{
					serverPeer.m_rpc.Invoke("FuelCapacity_Query_v1", Array.Empty<object>());
				}
			}
		}

		private void Apply(FuelSettings next)
		{
			effective = next;
			plugin.Machines.Set(next);
		}

		private ZNetPeer Peer(ZRpc rpc)
		{
			return ((IEnumerable<ZNetPeer>)session.GetPeers()).FirstOrDefault((Func<ZNetPeer, bool>)((ZNetPeer p) => p.IsReady() && p.m_rpc == rpc));
		}

		private bool Throttled(ZRpc rpc)
		{
			if (lastRequest.TryGetValue(rpc, out var value) && Time.unscaledTime - value < 0.25f)
			{
				return true;
			}
			lastRequest[rpc] = Time.unscaledTime;
			return false;
		}

		internal static bool IsAdmin(ZNetPeer peer)
		{
			if (Object.op_Implicit((Object)(object)ZNet.instance) && ZNet.instance.IsServer() && peer != null && peer.IsReady() && peer.m_socket != null)
			{
				return ZNet.instance.IsAdmin(peer.m_socket.GetHostName());
			}
			return false;
		}

		private bool FromServer(ZRpc rpc)
		{
			if (Object.op_Implicit((Object)(object)session) && !session.IsServer())
			{
				return session.GetServerPeer()?.m_rpc == rpc;
			}
			return false;
		}

		private void OnQuery(ZRpc rpc)
		{
			if (Object.op_Implicit((Object)(object)session) && session.IsServer() && Peer(rpc) != null && !Throttled(rpc))
			{
				Send(rpc);
			}
		}

		private void OnRequest(ZRpc rpc, string command)
		{
			if (!Object.op_Implicit((Object)(object)session) || !session.IsServer())
			{
				return;
			}
			ZNetPeer val = Peer(rpc);
			if (val != null)
			{
				if (Throttled(rpc))
				{
					rpc.Invoke("FuelCapacity_Reply_v1", new object[1] { "Please wait a moment and retry fuelcapacity." });
				}
				else if (!IsAdmin(val))
				{
					rpc.Invoke("FuelCapacity_Reply_v1", new object[1] { "Only the host or a server admin may change fuel capacity." });
				}
				else if (command == null || command.Length > 256)
				{
					rpc.Invoke("FuelCapacity_Reply_v1", new object[1] { "Command is too long." });
				}
				else
				{
					rpc.Invoke("FuelCapacity_Reply_v1", new object[1] { Execute(command.Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries), val.m_socket.GetHostName()) });
				}
			}
		}

		private void OnReply(ZRpc rpc, string message)
		{
			if (FromServer(rpc) && message != null && message.Length <= 4096 && Object.op_Implicit((Object)(object)output))
			{
				output.AddString(message);
			}
		}

		private void Send(ZRpc rpc)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Expected O, but got Unknown
			ZPackage val = new ZPackage();
			val.Write(1);
			val.Write(revision);
			val.Write(effective.Capacity);
			val.Write(effective.Overrides);
			rpc.Invoke("FuelCapacity_State_v1", new object[1] { val });
		}

		private void Publish()
		{
			foreach (ZNetPeer peer in session.GetPeers())
			{
				if (peer.IsReady())
				{
					Send(peer.m_rpc);
				}
			}
		}

		private void OnState(ZRpc rpc, ZPackage packet)
		{
			if (!FromServer(rpc))
			{
				return;
			}
			try
			{
				long incoming;
				FuelSettings fuelSettings = Decode(packet, receivedRevision, out incoming);
				if (fuelSettings != null)
				{
					Apply(fuelSettings);
					receivedRevision = incoming;
				}
			}
			catch (Exception ex)
			{
				plugin.Warn("Rejected fuel settings: " + ex.Message);
			}
		}

		internal static FuelSettings Decode(ZPackage packet, long previous, out long incoming)
		{
			if (packet == null || packet.Size() > 16384)
			{
				throw new ArgumentException("Invalid settings packet size.");
			}
			if (packet.ReadInt() != 1)
			{
				throw new ArgumentException("Unsupported settings version.");
			}
			incoming = packet.ReadLong();
			if (incoming < 0)
			{
				throw new ArgumentException("Invalid settings revision.");
			}
			FuelSettings result = new FuelSettings(packet.ReadInt(), packet.ReadString());
			if (packet.GetPos() != packet.Size())
			{
				throw new ArgumentException("Trailing settings data.");
			}
			if (incoming >= previous)
			{
				return result;
			}
			return null;
		}

		internal void Command(string[] input, Terminal terminal)
		{
			EnsureSession();
			output = terminal;
			string[] array = input.Skip(1).ToArray();
			if (array.Length == 0 || (array.Length == 1 && array[0].Equals("status", StringComparison.OrdinalIgnoreCase)))
			{
				terminal.AddString(effective?.ToString() ?? "Join a world; waiting for server fuel settings.");
				return;
			}
			if (array.Length == 1 && array[0].Equals("list", StringComparison.OrdinalIgnoreCase))
			{
				string text = plugin.Machines.List();
				terminal.AddString((text.Length == 0) ? "No fuel-fed production units loaded nearby." : text);
				return;
			}
			if (!Object.op_Implicit((Object)(object)session))
			{
				terminal.AddString("Join a world first.");
				return;
			}
			if (session.IsServer())
			{
				terminal.AddString(Execute(array, "local host/console"));
				return;
			}
			if (effective == null)
			{
				terminal.AddString("Waiting for the server's fuel settings. The server must have this mod.");
				return;
			}
			string text2 = string.Join(" ", array);
			if (text2.Length > 256)
			{
				terminal.AddString("Command is too long.");
				return;
			}
			ZNetPeer serverPeer = session.GetServerPeer();
			if (serverPeer != null)
			{
				serverPeer.m_rpc.Invoke("FuelCapacity_Command_v1", new object[1] { text2 });
			}
		}

		private string Execute(string[] args, string actor)
		{
			try
			{
				FuelSettings fuelSettings;
				if (args.Length == 1 && args[0].Equals("reload", StringComparison.OrdinalIgnoreCase))
				{
					fuelSettings = plugin.Reload();
				}
				else
				{
					if (args.Length == 1 && FuelSettings.TryCapacity(args[0], out var result))
					{
						fuelSettings = new FuelSettings(result, effective.Overrides);
					}
					else if (args.Length == 3 && args[0].Equals("set", StringComparison.OrdinalIgnoreCase) && FuelSettings.TryCapacity(args[2], out result))
					{
						fuelSettings = effective.WithOverride(args[1], result);
					}
					else
					{
						if (args.Length != 2 || !args[0].Equals("reset", StringComparison.OrdinalIgnoreCase))
						{
							return "Usage: fuelcapacity <0–100000> | set <prefab> <0–100000> | reset <prefab> | reload | status | list";
						}
						fuelSettings = effective.WithOverride(args[1], null);
					}
					plugin.Save(fuelSettings);
				}
				Apply(fuelSettings);
				revision++;
				Publish();
				plugin.Audit("Fuel settings changed by " + actor + ": " + fuelSettings);
				return fuelSettings?.ToString() + " Applied to loaded units; settings persist in the server config.";
			}
			catch (Exception ex)
			{
				plugin.Warn("Fuel config change failed: " + ex.Message);
				return "Fuel config change failed: " + ex.Message;
			}
		}
	}
	internal sealed class Machines
	{
		private sealed class Entry
		{
			internal Smelter Machine;

			internal int Original;

			internal string Prefab;

			internal int Capacity
			{
				get
				{
					return Machine.m_maxFuel;
				}
				set
				{
					Machine.m_maxFuel = value;
				}
			}
		}

		private readonly Dictionary<Smelter, Entry> entries = new Dictionary<Smelter, Entry>();

		private FuelSettings effective;

		internal void Track(Smelter machine)
		{
			if (entries.ContainsKey(machine))
			{
				return;
			}
			int maxFuel = machine.m_maxFuel;
			if (maxFuel <= 0 || !Object.op_Implicit((Object)(object)machine.m_fuelItem) || !FuelSettings.UsesCoal(((Object)((Component)machine.m_fuelItem).gameObject).name, machine.m_fuelItem.m_itemData?.m_shared?.m_name))
			{
				return;
			}
			string text = ((Object)((Component)machine).gameObject).name;
			ZNetView val = ((Component)machine).GetComponent<ZNetView>() ?? ((Component)machine).GetComponentInParent<ZNetView>();
			if (Object.op_Implicit((Object)(object)val) && val.IsValid() && Object.op_Implicit((Object)(object)ZNetScene.instance))
			{
				GameObject prefab = ZNetScene.instance.GetPrefab(val.GetZDO().GetPrefab());
				if (Object.op_Implicit((Object)(object)prefab))
				{
					text = ((Object)prefab).name;
				}
			}
			if (text.EndsWith("(Clone)"))
			{
				text = text.Substring(0, text.Length - 7);
			}
			Entry entry = new Entry
			{
				Machine = machine,
				Original = maxFuel,
				Prefab = text
			};
			entries.Add(machine, entry);
			Apply(entry);
		}

		private void Apply(Entry entry)
		{
			entry.Capacity = effective?.Resolve(entry.Prefab, entry.Original, usesFuel: true) ?? entry.Original;
		}

		internal void Prune()
		{
			Smelter[] array = entries.Keys.Where((Smelter k) => !Object.op_Implicit((Object)(object)k)).ToArray();
			foreach (Smelter key in array)
			{
				entries.Remove(key);
			}
		}

		internal void Set(FuelSettings settings)
		{
			effective = settings;
			Entry[] array = entries.Values.ToArray();
			foreach (Entry entry in array)
			{
				if (!Object.op_Implicit((Object)(object)entry.Machine))
				{
					entries.Remove(entry.Machine);
				}
				else
				{
					Apply(entry);
				}
			}
		}

		internal void Restore()
		{
			Set(null);
		}

		internal string List()
		{
			return string.Join("\n", from e in entries.Values
				where Object.op_Implicit((Object)(object)e.Machine)
				group e by e.Prefab into g
				orderby g.Key
				select g.Key + ": " + g.First().Capacity + " fuel (original " + g.First().Original + ", " + g.Count() + " loaded)");
		}
	}
	[HarmonyPatch]
	internal static class Patches
	{
		[HarmonyPatch(typeof(Smelter), "Awake")]
		[HarmonyPostfix]
		[HarmonyPriority(0)]
		private static void SmelterAwake(Smelter __instance)
		{
			Plugin.Instance?.Machines.Track(__instance);
		}
	}
	[BepInPlugin("cheyenne.fuelcapacity", "Fuel Capacity", "0.1.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[NetworkCompatibility(/*Could not decode attribute arguments.*/)]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string Guid = "cheyenne.fuelcapacity";

		public const string Version = "0.1.0";

		internal static Plugin Instance;

		internal FuelSettings Configured;

		internal CapacityNetwork Network;

		internal readonly Machines Machines = new Machines();

		private ConfigEntry<int> capacity;

		private ConfigEntry<string> overrides;

		private Harmony harmony;

		private bool stopped;

		private void Awake()
		{
			//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cc: Expected O, but got Unknown
			//IL_00e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fc: Expected O, but got Unknown
			//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
			Instance = this;
			((BaseUnityPlugin)this).Config.SaveOnConfigSet = false;
			capacity = ((BaseUnityPlugin)this).Config.Bind<int>("Fuel", "Capacity", 40, "Coal capacity for coal-fueled smelters and blast furnaces. 0 = original; range 0–100000. Server controls multiplayer settings.");
			overrides = ((BaseUnityPlugin)this).Config.Bind<string>("Fuel", "PrefabOverrides", "", "Optional prefab capacities, e.g. smelter=40;blastfurnace=80. 0 keeps that prefab's original capacity.");
			if (!File.Exists(((BaseUnityPlugin)this).Config.ConfigFilePath))
			{
				((BaseUnityPlugin)this).Config.Save();
			}
			try
			{
				Configured = ReadSettings();
			}
			catch (Exception ex)
			{
				Configured = new FuelSettings(40, "");
				Warn("Invalid config; using 40 until corrected and reloaded: " + ex.Message);
			}
			Network = new CapacityNetwork(this);
			try
			{
				harmony = new Harmony("cheyenne.fuelcapacity");
				harmony.PatchAll();
				new ConsoleCommand("fuelcapacity", "fuelcapacity [status|list|<number>|set <prefab> <number>|reset <prefab>|reload]. Changes require server admin.", (ConsoleEvent)delegate(ConsoleEventArgs a)
				{
					Network.Command(a.Args, a.Context);
				}, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Fuel Capacity 0.1.0 loaded. Default: " + Configured.Capacity + ". Command: fuelcapacity."));
			}
			catch (Exception ex2)
			{
				stopped = true;
				Harmony obj = harmony;
				if (obj != null)
				{
					obj.UnpatchSelf();
				}
				((BaseUnityPlugin)this).Logger.LogError((object)("Fuel Capacity disabled: " + ex2));
			}
		}

		private FuelSettings ReadSettings()
		{
			return new FuelSettings(capacity.Value, overrides.Value);
		}

		internal FuelSettings Reload()
		{
			int value = capacity.Value;
			string value2 = overrides.Value;
			try
			{
				((BaseUnityPlugin)this).Config.Reload();
				return Configured = ReadSettings();
			}
			catch
			{
				capacity.Value = value;
				overrides.Value = value2;
				throw;
			}
		}

		internal void Save(FuelSettings next)
		{
			int value = capacity.Value;
			string value2 = overrides.Value;
			try
			{
				capacity.Value = next.Capacity;
				overrides.Value = next.Overrides;
				((BaseUnityPlugin)this).Config.Save();
				Configured = next;
			}
			catch
			{
				capacity.Value = value;
				overrides.Value = value2;
				throw;
			}
		}

		private void Update()
		{
			if (stopped)
			{
				return;
			}
			try
			{
				Network.Update();
			}
			catch (Exception ex)
			{
				Warn("Fuel networking failed: " + ex.Message);
			}
		}

		private void OnDestroy()
		{
			Network?.Stop();
			Machines.Restore();
			Harmony obj = harmony;
			if (obj != null)
			{
				obj.UnpatchSelf();
			}
			Instance = null;
		}

		internal void Warn(string message)
		{
			((BaseUnityPlugin)this).Logger.LogWarning((object)message);
		}

		internal void Audit(string message)
		{
			((BaseUnityPlugin)this).Logger.LogInfo((object)message);
		}
	}
}
namespace FuelCapacity.Core
{
	public sealed class FuelSettings
	{
		public const int DefaultCapacity = 40;

		public const int Maximum = 100000;

		public const int MaximumOverrides = 64;

		private readonly Dictionary<string, int> overrides;

		public int Capacity { get; }

		public string Overrides => string.Join(";", from p in overrides.OrderBy<KeyValuePair<string, int>, string>((KeyValuePair<string, int> p) => p.Key, StringComparer.OrdinalIgnoreCase)
			select p.Key + "=" + p.Value.ToString(CultureInfo.InvariantCulture));

		public FuelSettings(int capacity, string text)
		{
			if (capacity < 0 || capacity > 100000)
			{
				throw new ArgumentException("Capacity must be 0–100000; 0 keeps the original capacity.");
			}
			Capacity = capacity;
			overrides = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
			if (text == null || text.Length > 8192)
			{
				throw new ArgumentException("Invalid prefab overrides.");
			}
			if (text.Trim().Length == 0)
			{
				return;
			}
			string[] array = text.Split(';');
			for (int i = 0; i < array.Length; i++)
			{
				string[] array2 = array[i].Split('=');
				if (array2.Length != 2 || !ValidName(array2[0].Trim()) || !TryCapacity(array2[1].Trim(), out var result))
				{
					throw new ArgumentException("Use prefab=value entries separated by semicolons, e.g. smelter=40;blastfurnace=80.");
				}
				if (overrides.ContainsKey(array2[0].Trim()))
				{
					throw new ArgumentException("Duplicate prefab override: " + array2[0].Trim());
				}
				overrides.Add(array2[0].Trim(), result);
				if (overrides.Count > 64)
				{
					throw new ArgumentException("At most 64 prefab overrides are supported.");
				}
			}
		}

		public static bool ValidName(string name)
		{
			if (name != null)
			{
				return Regex.IsMatch(name, "\\A[A-Za-z0-9_-]{1,80}\\z");
			}
			return false;
		}

		public static bool UsesCoal(string fuelPrefab, string sharedName)
		{
			if (!string.Equals(fuelPrefab, "Coal", StringComparison.OrdinalIgnoreCase))
			{
				return sharedName == "$item_coal";
			}
			return true;
		}

		public static bool TryCapacity(string value, out int result)
		{
			if (int.TryParse(value, NumberStyles.None, CultureInfo.InvariantCulture, out result) && result >= 0)
			{
				return result <= 100000;
			}
			return false;
		}

		public int Resolve(string prefab, int original, bool usesFuel)
		{
			if (!usesFuel || original <= 0)
			{
				return original;
			}
			int value;
			int num = ((prefab != null && overrides.TryGetValue(prefab, out value)) ? value : Capacity);
			if (num != 0)
			{
				return num;
			}
			return original;
		}

		public FuelSettings WithOverride(string prefab, int? value)
		{
			if (!ValidName(prefab))
			{
				throw new ArgumentException("Use the prefab name (letters, digits, underscores or hyphens).");
			}
			Dictionary<string, int> dictionary = new Dictionary<string, int>(overrides, StringComparer.OrdinalIgnoreCase);
			if (value.HasValue)
			{
				dictionary[prefab] = value.Value;
			}
			else
			{
				dictionary.Remove(prefab);
			}
			return new FuelSettings(Capacity, string.Join(";", dictionary.Select((KeyValuePair<string, int> p) => p.Key + "=" + p.Value.ToString(CultureInfo.InvariantCulture))));
		}

		public override string ToString()
		{
			return "Default fuel capacity: " + Capacity + " (0 = original). Overrides: " + ((overrides.Count == 0) ? "none" : Overrides);
		}
	}
}