Decompiled source of WorkshopRepair v1.0.0

plugins/WorkshopRepair/WorkshopRepair.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Muindor.ServerConfig;
using UnityEngine;
using WorkshopRepair.Repair;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("assembly_valheim")]
[assembly: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")]
[assembly: AssemblyCompany("Egor Fadeev")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("One click on the repair button mends every damaged item, and a crafting station also repairs what belongs to the other stations standing within 30 metres.")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+b6da8f9d6aa103ba51cbd0a8ad70c07a2089e5f9")]
[assembly: AssemblyProduct("Workshop Repair")]
[assembly: AssemblyTitle("WorkshopRepair")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[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 Muindor.ServerConfig
{
	public static class ConfigChannel
	{
		private static ConfigSync? _sync;

		private static ManualLogSource? _log;

		private static string _rpcName = string.Empty;

		public static void Activate(ConfigSync sync, ManualLogSource log)
		{
			_sync = sync;
			_log = log;
			_rpcName = sync.PluginGuid + ".ServerConfig";
			sync.Changed += Broadcast;
		}

		internal static void OnNewConnection(ZNet net, ZNetPeer peer)
		{
			if (_sync != null && !net.IsServer() && peer != null && peer.m_server && peer.m_rpc != null)
			{
				_sync.Clear();
				peer.m_rpc.Register<string>(_rpcName, (Action<ZRpc, string>)Receive);
			}
		}

		internal static void OnPeerInfo(ZNet net, ZRpc rpc)
		{
			if (_sync == null || !net.IsServer())
			{
				return;
			}
			ZNetPeer peer = net.GetPeer(rpc);
			if (peer != null && peer.IsReady() && peer.m_rpc != null)
			{
				peer.m_rpc.Invoke(_rpcName, new object[1] { _sync.BuildPayload().Serialize() });
				ManualLogSource? log = _log;
				if (log != null)
				{
					log.LogInfo((object)("Sent the configuration to " + peer.m_playerName + " (ConfigPriority " + (_sync.Priority.Value ? "on" : "off") + ")"));
				}
			}
		}

		internal static void OnSessionEnd()
		{
			_sync?.Clear();
		}

		internal static void PatchFailed(string patch, Exception exception)
		{
			ManualLogSource? log = _log;
			if (log != null)
			{
				log.LogError((object)$"{patch} failed, the configuration channel may be inactive: {exception}");
			}
		}

		private static void Broadcast()
		{
			ZNet instance = ZNet.instance;
			if (_sync == null || (Object)(object)instance == (Object)null || !instance.IsServer())
			{
				return;
			}
			string text = _sync.BuildPayload().Serialize();
			int num = 0;
			foreach (ZNetPeer peer in instance.GetPeers())
			{
				if (peer.IsReady() && peer.m_rpc != null)
				{
					peer.m_rpc.Invoke(_rpcName, new object[1] { text });
					num++;
				}
			}
			if (num > 0)
			{
				ManualLogSource? log = _log;
				if (log != null)
				{
					log.LogInfo((object)$"A setting changed; sent the configuration to {num} player(s)");
				}
			}
		}

		private static void Receive(ZRpc rpc, string text)
		{
			if (_sync == null)
			{
				return;
			}
			ZNet instance = ZNet.instance;
			ConfigPayload payload;
			string error;
			if ((Object)(object)instance != (Object)null && instance.IsServer())
			{
				ManualLogSource? log = _log;
				if (log != null)
				{
					log.LogWarning((object)"Ignored a configuration sent to this server by a client");
				}
			}
			else if (!ConfigPayload.TryParse(text, out payload, out error))
			{
				ManualLogSource? log2 = _log;
				if (log2 != null)
				{
					log2.LogWarning((object)("Ignored the configuration from the server, keeping the local settings: " + error));
				}
			}
			else
			{
				_sync.Apply(payload);
			}
		}
	}
	[HarmonyPatch(typeof(ZNet), "OnNewConnection")]
	internal static class ZNet_OnNewConnection_Patch
	{
		private static void Postfix(ZNet __instance, ZNetPeer peer)
		{
			try
			{
				ConfigChannel.OnNewConnection(__instance, peer);
			}
			catch (Exception exception)
			{
				ConfigChannel.PatchFailed("OnNewConnection", exception);
			}
		}
	}
	[HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")]
	internal static class ZNet_RPC_PeerInfo_Patch
	{
		private static void Postfix(ZNet __instance, ZRpc rpc)
		{
			try
			{
				ConfigChannel.OnPeerInfo(__instance, rpc);
			}
			catch (Exception exception)
			{
				ConfigChannel.PatchFailed("RPC_PeerInfo", exception);
			}
		}
	}
	[HarmonyPatch(typeof(ZNet), "OnDestroy")]
	internal static class ZNet_OnDestroy_Patch
	{
		private static void Postfix()
		{
			try
			{
				ConfigChannel.OnSessionEnd();
			}
			catch (Exception exception)
			{
				ConfigChannel.PatchFailed("OnDestroy", exception);
			}
		}
	}
	public sealed class ConfigPayload
	{
		public const string Header = "muindor.config/1";

		public string ModVersion { get; }

		public bool Priority { get; }

		public IList<KeyValuePair<string, string>> Values { get; }

		public ConfigPayload(string modVersion, bool priority, IEnumerable<KeyValuePair<string, string>>? values = null)
		{
			ModVersion = modVersion ?? string.Empty;
			Priority = priority;
			List<KeyValuePair<string, string>> list = new List<KeyValuePair<string, string>>();
			if (values != null)
			{
				list.AddRange(values);
			}
			Values = list;
		}

		public string Serialize()
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append("muindor.config/1").Append('\n');
			stringBuilder.Append(Escape(ModVersion, escapeEquals: false)).Append('\n');
			stringBuilder.Append(Priority ? "true" : "false").Append('\n');
			foreach (KeyValuePair<string, string> value in Values)
			{
				stringBuilder.Append(Escape(value.Key, escapeEquals: true)).Append('=').Append(Escape(value.Value ?? string.Empty, escapeEquals: false))
					.Append('\n');
			}
			return stringBuilder.ToString();
		}

		public static bool TryParse(string? text, out ConfigPayload? payload, out string error)
		{
			payload = null;
			if (string.IsNullOrEmpty(text))
			{
				error = "the payload is empty";
				return false;
			}
			string[] array = text.Split(new char[1] { '\n' });
			if (array.Length < 3)
			{
				error = "the payload is truncated";
				return false;
			}
			if (Trim(array[0]) != "muindor.config/1")
			{
				error = "unknown format \"" + Trim(array[0]) + "\", expected \"muindor.config/1\"";
				return false;
			}
			string modVersion = Unescape(Trim(array[1]));
			string text2 = Trim(array[2]);
			if (text2 != "true" && text2 != "false")
			{
				error = "\"" + text2 + "\" is not a valid priority flag";
				return false;
			}
			List<KeyValuePair<string, string>> list = new List<KeyValuePair<string, string>>();
			for (int i = 3; i < array.Length; i++)
			{
				string text3 = Trim(array[i]);
				if (text3.Length != 0)
				{
					int num = IndexOfSeparator(text3);
					if (num < 0)
					{
						error = $"line {i + 1} has no key/value separator";
						return false;
					}
					string text4 = Unescape(text3.Substring(0, num));
					if (text4.Length == 0)
					{
						error = $"line {i + 1} has an empty key";
						return false;
					}
					list.Add(new KeyValuePair<string, string>(text4, Unescape(text3.Substring(num + 1))));
				}
			}
			payload = new ConfigPayload(modVersion, text2 == "true", list);
			error = string.Empty;
			return true;
		}

		private static string Trim(string line)
		{
			if (!line.EndsWith("\r", StringComparison.Ordinal))
			{
				return line;
			}
			return line.Substring(0, line.Length - 1);
		}

		private static int IndexOfSeparator(string line)
		{
			for (int i = 0; i < line.Length; i++)
			{
				if (line[i] == '\\')
				{
					i++;
				}
				else if (line[i] == '=')
				{
					return i;
				}
			}
			return -1;
		}

		private static string Escape(string value, bool escapeEquals)
		{
			StringBuilder stringBuilder = new StringBuilder(value.Length);
			foreach (char c in value)
			{
				switch (c)
				{
				case '\\':
					stringBuilder.Append("\\\\");
					continue;
				case '\n':
					stringBuilder.Append("\\n");
					continue;
				case '\r':
					stringBuilder.Append("\\r");
					continue;
				case '=':
					if (escapeEquals)
					{
						stringBuilder.Append("\\=");
						continue;
					}
					break;
				}
				stringBuilder.Append(c);
			}
			return stringBuilder.ToString();
		}

		private static string Unescape(string value)
		{
			if (value.IndexOf('\\') < 0)
			{
				return value;
			}
			StringBuilder stringBuilder = new StringBuilder(value.Length);
			for (int i = 0; i < value.Length; i++)
			{
				if (value[i] != '\\' || i + 1 >= value.Length)
				{
					stringBuilder.Append(value[i]);
					continue;
				}
				char c = value[++i];
				switch (c)
				{
				case 'n':
					stringBuilder.Append('\n');
					break;
				case 'r':
					stringBuilder.Append('\r');
					break;
				default:
					stringBuilder.Append(c);
					break;
				}
			}
			return stringBuilder.ToString();
		}
	}
	public sealed class ConfigSync
	{
		private const string SyncedNote = "[synced] ";

		private readonly Dictionary<string, ISyncedEntry> _byKey = new Dictionary<string, ISyncedEntry>(StringComparer.Ordinal);

		private readonly List<ISyncedEntry> _entries = new List<ISyncedEntry>();

		private readonly HashSet<ConfigEntryBase> _watched = new HashSet<ConfigEntryBase>();

		private readonly ConfigFile _config;

		private readonly ManualLogSource _log;

		public string PluginGuid { get; }

		public string ModName { get; }

		public string ModVersion { get; }

		public ConfigEntry<bool> Priority { get; }

		public bool Locked { get; private set; }

		public string? ServerVersion { get; private set; }

		public int LockedCount { get; private set; }

		public event Action? Changed;

		public ConfigSync(ConfigFile config, string pluginGuid, string modName, string modVersion, ManualLogSource log)
		{
			_config = config;
			_log = log;
			PluginGuid = pluginGuid;
			ModName = modName;
			ModVersion = modVersion;
			Priority = config.Bind<bool>("Server", "ConfigPriority", false, "Server only, and off by default. Turn it on in the configuration of a dedicated server, or of the player hosting the session, to have every client that also has the mod run on the settings marked [synced] below instead of its own. The clients' own config files are never touched: their settings come back the moment they disconnect. This never makes the mod required on either side - players without it are unaffected, and a player with it can still join a server that does not have it.");
			_watched.Add((ConfigEntryBase)(object)Priority);
			config.SettingChanged += OnSettingChanged;
		}

		public SyncedEntry<T> Bind<T>(string section, string key, T defaultValue, string description)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Expected O, but got Unknown
			return Bind(section, key, defaultValue, new ConfigDescription(description, (AcceptableValueBase)null, Array.Empty<object>()));
		}

		public SyncedEntry<T> Bind<T>(string section, string key, T defaultValue, ConfigDescription description)
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Expected O, but got Unknown
			ConfigDescription val = new ConfigDescription("[synced] " + description.Description, description.AcceptableValues, description.Tags);
			ConfigEntry<T> val2 = _config.Bind<T>(section, key, defaultValue, val);
			SyncedEntry<T> syncedEntry = new SyncedEntry<T>(val2);
			_byKey.Add(syncedEntry.Key, syncedEntry);
			_entries.Add(syncedEntry);
			_watched.Add((ConfigEntryBase)(object)val2);
			return syncedEntry;
		}

		public ConfigPayload BuildPayload()
		{
			bool value = Priority.Value;
			List<KeyValuePair<string, string>> list = new List<KeyValuePair<string, string>>(value ? _entries.Count : 0);
			if (value)
			{
				foreach (ISyncedEntry entry in _entries)
				{
					try
					{
						list.Add(new KeyValuePair<string, string>(entry.Key, entry.LocalAsString()));
					}
					catch (Exception ex)
					{
						_log.LogWarning((object)(entry.Key + " could not be sent to the clients: " + ex.Message));
					}
				}
			}
			return new ConfigPayload(ModVersion, value, list);
		}

		public void Apply(ConfigPayload payload)
		{
			ClearValues();
			if (!payload.Priority)
			{
				Locked = false;
				ServerVersion = null;
				LockedCount = 0;
				_log.LogInfo((object)("The server runs " + ModName + " " + payload.ModVersion + " with ConfigPriority off; keeping the local settings"));
				return;
			}
			int num = 0;
			List<string> list = new List<string>();
			foreach (KeyValuePair<string, string> value2 in payload.Values)
			{
				string error;
				if (!_byKey.TryGetValue(value2.Key, out ISyncedEntry value))
				{
					list.Add(value2.Key);
				}
				else if (value.ApplyServerValue(value2.Value, out error))
				{
					num++;
				}
				else
				{
					_log.LogWarning((object)("Keeping the local " + value2.Key + ": the server sent a value this version cannot read, " + error));
				}
			}
			Locked = true;
			ServerVersion = payload.ModVersion;
			LockedCount = num;
			int num2 = _entries.Count - num;
			string text = ((list.Count > 0) ? string.Format(", {0} unknown here ({1})", list.Count, string.Join(", ", list.ToArray())) : string.Empty);
			string text2 = ((num2 > 0) ? $", {num2} kept local" : string.Empty);
			_log.LogInfo((object)$"The server ({ModName} {payload.ModVersion}) decides the settings: {num} applied{text2}{text}");
		}

		public void Clear()
		{
			if (Locked || LockedCount != 0)
			{
				ClearValues();
				Locked = false;
				ServerVersion = null;
				LockedCount = 0;
				_log.LogInfo((object)"Back to the local settings");
			}
		}

		public string Describe()
		{
			if (!Locked)
			{
				return "local settings";
			}
			return $"settings from the server ({ModName} {ServerVersion}, {LockedCount} values)";
		}

		private void ClearValues()
		{
			foreach (ISyncedEntry entry in _entries)
			{
				entry.ClearServerValue();
			}
		}

		private void OnSettingChanged(object sender, SettingChangedEventArgs args)
		{
			if (this.Changed != null)
			{
				ConfigEntryBase changedSetting = args.ChangedSetting;
				if (changedSetting != null && _watched.Contains(changedSetting))
				{
					this.Changed();
				}
			}
		}
	}
	public interface ISyncedEntry
	{
		string Key { get; }

		string LocalAsString();

		bool ApplyServerValue(string text, out string error);

		void ClearServerValue();
	}
	public sealed class SyncedEntry<T> : ISyncedEntry
	{
		private T _serverValue;

		private bool _hasServerValue;

		public ConfigEntry<T> Local { get; }

		public string Key { get; }

		public T Value
		{
			get
			{
				if (!_hasServerValue)
				{
					return Local.Value;
				}
				return _serverValue;
			}
		}

		public T LocalValue
		{
			get
			{
				return Local.Value;
			}
			set
			{
				Local.Value = value;
			}
		}

		public bool IsLocked => _hasServerValue;

		internal SyncedEntry(ConfigEntry<T> local)
		{
			Local = local;
			Key = ((ConfigEntryBase)local).Definition.Section + "." + ((ConfigEntryBase)local).Definition.Key;
		}

		public string LocalAsString()
		{
			return TomlTypeConverter.ConvertToString((object)Local.Value, typeof(T));
		}

		public bool ApplyServerValue(string text, out string error)
		{
			object obj;
			try
			{
				obj = TomlTypeConverter.ConvertToValue(text, typeof(T));
			}
			catch (Exception ex)
			{
				error = "\"" + text + "\" is not a valid " + typeof(T).Name + " (" + ex.Message + ")";
				return false;
			}
			ConfigDescription description = ((ConfigEntryBase)Local).Description;
			AcceptableValueBase val = ((description != null) ? description.AcceptableValues : null);
			if (val != null && !val.IsValid(obj))
			{
				obj = val.Clamp(obj);
			}
			_serverValue = (T)obj;
			_hasServerValue = true;
			error = string.Empty;
			return true;
		}

		public void ClearServerValue()
		{
			_hasServerValue = false;
			_serverValue = default(T);
		}

		public override string ToString()
		{
			T value = Value;
			return ((value != null) ? value.ToString() : null) ?? string.Empty;
		}
	}
}
namespace WorkshopRepair
{
	public sealed class ModConfig
	{
		public ConfigSync Sync { get; }

		public SyncedEntry<bool> Enabled { get; }

		public ConfigEntry<bool> IsDebug { get; }

		public SyncedEntry<bool> RepairAllAtOnce { get; }

		public SyncedEntry<bool> UseNearbyStations { get; }

		public SyncedEntry<float> NearbyRadius { get; }

		public ModConfig(ConfigFile config)
		{
			//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Expected O, but got Unknown
			Sync = new ConfigSync(config, "muindor.WorkshopRepair", "Workshop Repair", "1.0.0", Plugin.Log);
			Enabled = Sync.Bind("General", "Enabled", defaultValue: true, "Turn the mod on. With this off the repair button works exactly as in vanilla.");
			IsDebug = config.Bind<bool>("General", "IsDebug", false, "Log every repaired item and the station that repaired it.");
			RepairAllAtOnce = Sync.Bind("Repair", "RepairAllAtOnce", defaultValue: true, "One click on the repair button repairs every item that can be repaired here. Off: one item per click, as in vanilla.");
			UseNearbyStations = Sync.Bind("Repair", "UseNearbyStations", defaultValue: true, "A crafting station also repairs the items that belong to another station standing nearby: at the workbench, a forge within reach repairs the bronze and iron gear too. That station must be usable as if you walked up to it - upgraded enough, under a roof, with its fire lit.");
			NearbyRadius = Sync.Bind("Repair", "NearbyRadius", 30f, new ConfigDescription("How far from you, in metres, another station may stand and still repair your items.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(2f, 100f), Array.Empty<object>()));
		}

		public string Describe()
		{
			string text = (UseNearbyStations.Value ? ("stations within " + NearbyRadius.Value.ToString("0.#", CultureInfo.InvariantCulture) + " m help") : "only the station in use repairs");
			return (Enabled.Value ? "enabled" : "disabled") + ", " + (RepairAllAtOnce.Value ? "all items per click" : "one item per click") + ", " + text + ", " + Sync.Describe();
		}
	}
	[BepInPlugin("muindor.WorkshopRepair", "Workshop Repair", "1.0.0")]
	public sealed class Plugin : BaseUnityPlugin
	{
		private Harmony? _harmony;

		public static ManualLogSource Log { get; private set; }

		public static ModConfig Settings { get; private set; }

		public static bool Enabled
		{
			get
			{
				if (Settings != null)
				{
					return Settings.Enabled.Value;
				}
				return false;
			}
		}

		private void Awake()
		{
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			Settings = new ModConfig(((BaseUnityPlugin)this).Config);
			ConfigChannel.Activate(Settings.Sync, Log);
			_harmony = new Harmony("muindor.WorkshopRepair");
			_harmony.PatchAll(typeof(Plugin).Assembly);
			Log.LogInfo((object)("Workshop Repair 1.0.0 loaded: " + Settings.Describe()));
		}

		private void OnDestroy()
		{
			Harmony? harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}

		public static void Debug(string message)
		{
			if (Settings != null && Settings.IsDebug.Value)
			{
				Log.LogInfo((object)message);
			}
		}
	}
	public static class MyPluginInfo
	{
		public const string PLUGIN_GUID = "muindor.WorkshopRepair";

		public const string PLUGIN_NAME = "Workshop Repair";

		public const string PLUGIN_VERSION = "1.0.0";
	}
}
namespace WorkshopRepair.Repair
{
	internal static class NearbyStations
	{
		private static readonly Dictionary<CraftingStation, bool> UsableThisFrame = new Dictionary<CraftingStation, bool>();

		private static int _usableFrame = -1;

		public static CraftingStation? Find(Player player, CraftingStation current, Recipe recipe)
		{
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			string text = (((Object)(object)recipe.m_craftingStation != (Object)null) ? recipe.m_craftingStation.m_name : null);
			string text2 = (((Object)(object)recipe.m_repairStation != (Object)null) ? recipe.m_repairStation.m_name : null);
			if (text == null && text2 == null)
			{
				return null;
			}
			Vector3 position = ((Component)player).transform.position;
			float value = Plugin.Settings.NearbyRadius.Value;
			CraftingStation result = null;
			float num = float.MaxValue;
			foreach (CraftingStation allStation in CraftingStation.m_allStations)
			{
				if (!((Object)(object)allStation == (Object)null) && !((Object)(object)allStation == (Object)(object)current) && allStation.m_canRepair)
				{
					float num2 = Vector3.Distance(position, ((Component)allStation).transform.position);
					if (!(num2 > value) && !(num2 >= num) && RepairRules.StationRepairs(allStation.m_name, allStation.GetLevel(true), text, text2, recipe.m_minStationLevel) && IsUsable(allStation, player))
					{
						result = allStation;
						num = num2;
					}
				}
			}
			return result;
		}

		private static bool IsUsable(CraftingStation station, Player player)
		{
			if (_usableFrame != Time.frameCount)
			{
				_usableFrame = Time.frameCount;
				UsableThisFrame.Clear();
			}
			if (!UsableThisFrame.TryGetValue(station, out var value))
			{
				value = station.CheckUsable(player, false);
				UsableThisFrame[station] = value;
			}
			return value;
		}
	}
	public static class RepairRules
	{
		public const int MaxCountedLevel = 4;

		public static bool StationRepairs(string stationName, int stationLevel, string? craftingStation, string? repairStation, int minStationLevel)
		{
			if (string.IsNullOrEmpty(stationName))
			{
				return false;
			}
			if ((repairStation != null && repairStation == stationName) || (craftingStation != null && craftingStation == stationName))
			{
				return CountedLevel(stationLevel) >= minStationLevel;
			}
			return false;
		}

		public static int CountedLevel(int stationLevel)
		{
			if (stationLevel >= 4)
			{
				return 4;
			}
			return stationLevel;
		}

		public static string SeveralRepairedMessage(int count)
		{
			return $"Repaired {count} items";
		}
	}
}
namespace WorkshopRepair.Patches
{
	[HarmonyPatch(typeof(InventoryGui))]
	internal static class RepairPatches
	{
		private static readonly List<ItemData> WornItems = new List<ItemData>();

		private static readonly List<CraftingStation> UsedStations = new List<CraftingStation>();

		private static CraftingStation? _lastNearbyStation;

		[HarmonyPostfix]
		[HarmonyPatch("CanRepair")]
		private static void CanRepairPostfix(ItemData item, ref bool __result)
		{
			_lastNearbyStation = null;
			if (!__result && Plugin.Enabled && Plugin.Settings.UseNearbyStations.Value)
			{
				_lastNearbyStation = FindNearbyStation(item);
				__result = (Object)(object)_lastNearbyStation != (Object)null;
			}
		}

		[HarmonyPrefix]
		[HarmonyPatch("RepairOneItem")]
		private static bool RepairOneItemPrefix(InventoryGui __instance)
		{
			if (!Plugin.Enabled || !Plugin.Settings.RepairAllAtOnce.Value)
			{
				return true;
			}
			RepairAll(__instance);
			return false;
		}

		private static CraftingStation? FindNearbyStation(ItemData item)
		{
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null || item == null || !item.m_shared.m_canBeReparied)
			{
				return null;
			}
			CraftingStation currentCraftingStation = localPlayer.GetCurrentCraftingStation();
			if ((Object)(object)currentCraftingStation == (Object)null || !currentCraftingStation.m_canRepair)
			{
				return null;
			}
			Recipe val = (((Object)(object)ObjectDB.instance != (Object)null) ? ObjectDB.instance.GetRecipe(item) : null);
			if (!((Object)(object)val != (Object)null))
			{
				return null;
			}
			return NearbyStations.Find(localPlayer, currentCraftingStation, val);
		}

		private static void RepairAll(InventoryGui gui)
		{
			//IL_0171: Unknown result type (might be due to invalid IL or missing references)
			//IL_0176: Unknown result type (might be due to invalid IL or missing references)
			//IL_0184: Unknown result type (might be due to invalid IL or missing references)
			//IL_018a: Unknown result type (might be due to invalid IL or missing references)
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				return;
			}
			CraftingStation currentCraftingStation = localPlayer.GetCurrentCraftingStation();
			if (((Object)(object)currentCraftingStation == (Object)null && !localPlayer.NoCostCheat()) || ((Object)(object)currentCraftingStation != (Object)null && !currentCraftingStation.CheckUsable(localPlayer, false)))
			{
				return;
			}
			WornItems.Clear();
			UsedStations.Clear();
			((Humanoid)localPlayer).GetInventory().GetWornItems(WornItems);
			int num = 0;
			ItemData val = null;
			foreach (ItemData wornItem in WornItems)
			{
				if (gui.CanRepair(wornItem))
				{
					CraftingStation val2 = (CraftingStation)(((Object)(object)_lastNearbyStation != (Object)null) ? ((object)_lastNearbyStation) : ((object)currentCraftingStation));
					((Character)localPlayer).RaiseSkill((SkillType)107, 1f - wornItem.m_durability / wornItem.GetMaxDurability());
					wornItem.m_durability = wornItem.GetMaxDurability();
					if ((Object)(object)val2 != (Object)null && !UsedStations.Contains(val2))
					{
						UsedStations.Add(val2);
					}
					Plugin.Debug("Repaired " + wornItem.m_shared.m_name + " at " + (((Object)(object)val2 != (Object)null) ? val2.m_name : "no station (no-cost mode)"));
					num++;
					val = wornItem;
				}
			}
			foreach (CraftingStation usedStation in UsedStations)
			{
				usedStation.m_repairItemDoneEffects.Create(((Component)usedStation).transform.position, Quaternion.identity, (Transform)null, 1f, -1, default(ZDOID));
			}
			UsedStations.Clear();
			WornItems.Clear();
			switch (num)
			{
			case 0:
				((Character)localPlayer).Message((MessageType)2, "No more item to repair", 0, (Sprite)null, false);
				break;
			case 1:
				((Character)localPlayer).Message((MessageType)2, Localization.instance.Localize("$msg_repaired", new string[1] { val.m_shared.m_name }), 0, (Sprite)null, false);
				break;
			default:
				((Character)localPlayer).Message((MessageType)2, RepairRules.SeveralRepairedMessage(num), 0, (Sprite)null, false);
				break;
			}
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}