Decompiled source of LocalizedContainerSigns v1.1.1

plugins/LocalizedContainerSigns.dll

Decompiled 2 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx;
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.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")]
[assembly: AssemblyCompany("LocalizedContainerSigns")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+71ededdf17f298220b5c52a6a964ab28c9b9e635")]
[assembly: AssemblyProduct("LocalizedContainerSigns")]
[assembly: AssemblyTitle("LocalizedContainerSigns")]
[assembly: AssemblyVersion("1.0.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 LocalizedContainerSigns
{
	internal static class ChestFill
	{
		private const string ListStart = "<i ls></i>";

		private const string ListEnd = "<i le></i>";

		private static ConfigEntry<bool> _enabled;

		private static ConfigEntry<string> _format;

		private static ConfigEntry<string> _separator;

		private static FieldInfo _stateField;

		private static MethodInfo _getInventory;

		private static PropertyInfo _inventoryProperty;

		private static bool _errorLogged;

		internal static void Init(ConfigFile config, Harmony harmony, Type processor)
		{
			//IL_01d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01de: Expected O, but got Unknown
			_enabled = config.Bind<bool>("Fill", "Enabled", true, "Показывать заполненность сундука последней строкой списка на автотабличке.");
			_format = config.Bind<string>("Fill", "Format", "<color=yellow>{0}/{1}</color>", "Формат строки: {0} — занято ячеек, {1} — всего ячеек, {2} — процент заполнения. Допускаются теги TextMeshPro.");
			_separator = config.Bind<string>("Fill", "Separator", "<br>", "Разделитель перед строкой заполненности (обычно как ChestSignsContentListSeparator в ServersideQoL).");
			if (!_enabled.Value)
			{
				Plugin.Log.LogInfo((object)"Заполненность сундуков на табличках выключена.");
				return;
			}
			List<MethodInfo> list = (from m in processor?.GetNestedTypes(BindingFlags.Public | BindingFlags.NonPublic).SelectMany((Type t) => t.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
				where m.Name.Contains("EvaluateMatch") && m.ReturnType == typeof(string)
				select m).Where(delegate(MethodInfo m)
			{
				ParameterInfo[] parameters = m.GetParameters();
				return parameters.Length == 1 && parameters[0].ParameterType == typeof(Match);
			}).ToList();
			if (list == null || list.Count != 1)
			{
				Plugin.Log.LogWarning((object)"ServersideQoL.ContainerSigns изменился (нет EvaluateMatch); заполненность сундуков не показывается.");
				return;
			}
			_stateField = AccessTools.Field(list[0].DeclaringType, "containerState");
			_getInventory = ((_stateField == null) ? null : AccessTools.Method(_stateField.FieldType, "GetInventory", (Type[])null, (Type[])null));
			_inventoryProperty = ((_getInventory == null) ? null : AccessTools.Property(_getInventory.ReturnType, "Inventory"));
			if (_inventoryProperty == null || _inventoryProperty.PropertyType != typeof(Inventory))
			{
				Plugin.Log.LogWarning((object)"ServersideQoL изменился (нет containerState.GetInventory().Inventory); заполненность сундуков не показывается.");
				return;
			}
			harmony.Patch((MethodBase)list[0], (HarmonyMethod)null, new HarmonyMethod(typeof(ChestFill), "EvaluateMatchPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			Plugin.Log.LogInfo((object)("Заполненность сундуков на табличках: формат «" + _format.Value + "», патч " + list[0].DeclaringType?.Name + "." + list[0].Name + "."));
		}

		private static void EvaluateMatchPostfix(object __instance, ref string __result)
		{
			if (__instance == null || __result == null || !__result.StartsWith("<i ls></i>") || !__result.EndsWith("<i le></i>"))
			{
				return;
			}
			try
			{
				object value = _stateField.GetValue(__instance);
				object obj = ((value == null) ? null : _getInventory.Invoke(value, null));
				if (obj == null)
				{
					return;
				}
				object? value2 = _inventoryProperty.GetValue(obj, null);
				Inventory val = (Inventory)((value2 is Inventory) ? value2 : null);
				if (val != null)
				{
					string text = FormatLine(val.NrOfItems(), val.GetWidth() * val.GetHeight(), _format.Value);
					if (text != null)
					{
						__result = __result.Substring(0, __result.Length - "<i le></i>".Length) + _separator.Value + text + "<i le></i>";
					}
				}
			}
			catch (Exception arg)
			{
				if (!_errorLogged)
				{
					_errorLogged = true;
					Plugin.Log.LogError((object)$"Ошибка строки заполненности (дальше не повторяется): {arg}");
				}
			}
		}

		internal static string FormatLine(int used, int total, string format)
		{
			if (total <= 0)
			{
				return null;
			}
			int num = (int)Math.Round(100.0 * (double)used / (double)total);
			try
			{
				return string.Format(format, used, total, num);
			}
			catch (FormatException)
			{
				if (!_errorLogged)
				{
					_errorLogged = true;
					Plugin.Log.LogError((object)("Неверный формат [Fill] Format: «" + format + "»; используется {0}/{1}."));
				}
				return $"{used}/{total}";
			}
		}
	}
	[BepInPlugin("sansara.LocalizedContainerSigns", "Localized Container Signs", "1.1.1")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string Guid = "sansara.LocalizedContainerSigns";

		public const string Name = "Localized Container Signs";

		public const string Version = "1.1.1";

		private const string ContainerSignsGuid = "ArgusMagnus.ServersideQoL.ContainerSigns";

		internal const string ProcessorType = "ServersideQoL.ContainerSigns.ContainerAndSignProcessor";

		private const string NamesFileName = "sansara.LocalizedContainerSigns.names.txt";

		internal static ManualLogSource Log;

		private static ConfigEntry<string> _language;

		private static string _namesPath;

		private static Dictionary<string, string> _translations;

		private static Dictionary<string, string> _overrides;

		private static readonly Dictionary<string, string> NameCache = new Dictionary<string, string>();

		private static bool _patched;

		private void Awake()
		{
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Expected O, but got Unknown
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			_language = ((BaseUnityPlugin)this).Config.Bind<string>("General", "Language", "Russian", "Язык названий предметов на табличках сундуков: имя столбца из файлов локализации игры (Russian, English, Ukrainian, German …). Применяется после перезапуска.");
			_namesPath = Path.Combine(Paths.ConfigPath, "sansara.LocalizedContainerSigns.names.txt");
			Harmony val = new Harmony("sansara.LocalizedContainerSigns");
			ChestFill.Init(((BaseUnityPlugin)this).Config, val, AccessTools.TypeByName("ServersideQoL.ContainerSigns.ContainerAndSignProcessor"));
			MethodInfo methodInfo = FindNameSelector();
			if (methodInfo == null)
			{
				Log.LogWarning((object)"ServersideQoL.ContainerSigns не найден или изменился (нет выбора имени предмета для списка содержимого); названия не переводятся.");
				return;
			}
			val.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(Plugin), "NameSelectorPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			Log.LogInfo((object)("Названия на табличках сундуков: язык " + _language.Value + ", патч " + methodInfo.DeclaringType?.Name + "." + methodInfo.Name + "."));
			_patched = true;
		}

		private void Start()
		{
			if (!_patched)
			{
				return;
			}
			try
			{
				EnsureLoaded();
				Log.LogInfo((object)("Пример: Wood → " + Translate("Wood", "$item_wood") + ", TrophyDeer → " + Translate("TrophyDeer", "$item_trophy_deer") + "."));
			}
			catch (Exception arg)
			{
				Log.LogError((object)$"Ошибка загрузки названий: {arg}");
			}
		}

		private static MethodInfo FindNameSelector()
		{
			Type type = AccessTools.TypeByName("ServersideQoL.ContainerSigns.ContainerAndSignProcessor");
			if (type == null)
			{
				return null;
			}
			List<MethodInfo> list = (from m in type.GetNestedTypes(BindingFlags.Public | BindingFlags.NonPublic).SelectMany((Type t) => t.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic))
				where m.ReturnType == typeof(string)
				select m).Where(delegate(MethodInfo m)
			{
				ParameterInfo[] parameters = m.GetParameters();
				return parameters.Length == 1 && parameters[0].ParameterType == typeof(ItemData);
			}).ToList();
			if (list.Count != 1)
			{
				if (list.Count > 1)
				{
					Log.LogWarning((object)("Найдено несколько подходящих методов: " + string.Join(", ", list.Select((MethodInfo m) => m.Name)) + "."));
				}
				return null;
			}
			return list[0];
		}

		private static void NameSelectorPostfix(ItemData __0, ref string __result)
		{
			if (__0 == null || string.IsNullOrEmpty(__result))
			{
				return;
			}
			try
			{
				if (NameCache.TryGetValue(__result, out var value))
				{
					__result = value;
					return;
				}
				string text = Translate(((Object)(object)__0.m_dropPrefab != (Object)null) ? ((Object)__0.m_dropPrefab).name : __result, __0.m_shared?.m_name);
				NameCache[__result] = text;
				__result = text;
			}
			catch (Exception arg)
			{
				Log.LogError((object)$"Ошибка перевода названия '{__result}': {arg}");
			}
		}

		private static string Translate(string prefabName, string token)
		{
			EnsureLoaded();
			if (_overrides.TryGetValue(prefabName, out var value))
			{
				return value;
			}
			if (string.IsNullOrEmpty(token))
			{
				return prefabName;
			}
			if (token.StartsWith("$") && _translations.TryGetValue(token.Substring(1), out value) && value.Length > 0)
			{
				return value;
			}
			Localization instance = Localization.instance;
			string text = ((instance != null) ? instance.Localize(token) : null);
			if (!string.IsNullOrEmpty(text) && !text.StartsWith("[") && !text.StartsWith("$"))
			{
				return text;
			}
			return prefabName;
		}

		private static void EnsureLoaded()
		{
			if (_translations != null)
			{
				return;
			}
			_overrides = LoadOverrides();
			_translations = new Dictionary<string, string>();
			try
			{
				foreach (TextAsset item in LocalizationAssets())
				{
					ReadColumn(item, _language.Value, _translations);
				}
			}
			catch (Exception arg)
			{
				Log.LogError((object)$"Не удалось прочитать локализацию игры: {arg}");
			}
			if (_translations.Count == 0)
			{
				Log.LogWarning((object)("Язык '" + _language.Value + "' не найден в файлах локализации игры; используются собственные названия из sansara.LocalizedContainerSigns.names.txt и локализация сервера."));
			}
			else
			{
				Log.LogInfo((object)$"Загружено строк локализации '{_language.Value}': {_translations.Count}, собственных названий: {_overrides.Count}.");
			}
		}

		private static IEnumerable<TextAsset> LocalizationAssets()
		{
			_ = Localization.instance;
			object obj = AccessTools.Field(typeof(Localization), "m_localizationSettings")?.GetValue(null);
			if (obj == null)
			{
				yield break;
			}
			Traverse val = Traverse.Create(obj);
			if (!((val.Field("Localizations").GetValue() ?? val.Property("Localizations", (object[])null).GetValue()) is IEnumerable enumerable))
			{
				yield break;
			}
			foreach (object item in enumerable)
			{
				TextAsset val2 = (TextAsset)((item is TextAsset) ? item : null);
				if (val2 != null && (Object)(object)val2 != (Object)null)
				{
					yield return val2;
				}
			}
		}

		internal static void ReadColumn(TextAsset asset, string language, Dictionary<string, string> into)
		{
			List<List<string>> list = SplitCsv(asset.text);
			if (list.Count == 0)
			{
				return;
			}
			int num = list[0].FindIndex((string h) => StripQuotes(h).Trim() == language);
			if (num < 0)
			{
				return;
			}
			foreach (List<string> item in list.Skip(1))
			{
				if (item.Count != 0 && item[0].Length != 0 && !item[0].StartsWith("//"))
				{
					string text = ((item.Count > num) ? item[num].Trim() : "");
					if (text.Length == 0 && item.Count > 1)
					{
						text = item[1].Trim();
					}
					into[item[0]] = text;
				}
			}
		}

		private static string StripQuotes(string s)
		{
			if (s.Length < 2 || s[0] != '"' || s[s.Length - 1] != '"')
			{
				return s;
			}
			return s.Substring(1, s.Length - 2);
		}

		internal static List<List<string>> SplitCsv(string text)
		{
			List<List<string>> list = new List<List<string>>();
			List<string> list2 = new List<string>();
			StringBuilder stringBuilder = new StringBuilder();
			bool flag = false;
			for (int i = 0; i < text.Length; i++)
			{
				char c = text[i];
				switch (c)
				{
				case '"':
					if (flag && i + 1 < text.Length && text[i + 1] == '"')
					{
						stringBuilder.Append('"');
						i++;
					}
					else
					{
						flag = !flag;
					}
					continue;
				case ',':
					if (!flag)
					{
						list2.Add(stringBuilder.ToString());
						stringBuilder.Length = 0;
						continue;
					}
					break;
				}
				if (c == '\n' && !flag)
				{
					list2.Add(stringBuilder.ToString().TrimEnd(new char[1] { '\r' }));
					stringBuilder.Length = 0;
					list.Add(list2);
					list2 = new List<string>();
				}
				else
				{
					stringBuilder.Append(c);
				}
			}
			if (stringBuilder.Length > 0 || list2.Count > 0)
			{
				list2.Add(stringBuilder.ToString());
				list.Add(list2);
			}
			return list;
		}

		private static Dictionary<string, string> LoadOverrides()
		{
			Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.Ordinal);
			try
			{
				if (!File.Exists(_namesPath))
				{
					File.WriteAllText(_namesPath, "# Собственные названия предметов для табличек сундуков ServersideQoL.\n# Формат: ИмяПрефаба = Название, по одному на строку. Пример:\n# BoneFragments = Кости\n# Применяется после перезапуска сервера.\n", new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
					return dictionary;
				}
				string[] array = File.ReadAllLines(_namesPath, Encoding.UTF8);
				for (int i = 0; i < array.Length; i++)
				{
					string text = array[i].Trim();
					if (text.Length == 0 || text[0] == '#')
					{
						continue;
					}
					int num = text.IndexOf('=');
					if (num <= 0)
					{
						Log.LogWarning((object)("sansara.LocalizedContainerSigns.names.txt: строка без '=' пропущена: " + text));
						continue;
					}
					string text2 = text.Substring(0, num).Trim();
					string text3 = text.Substring(num + 1).Trim();
					if (text2.Length > 0 && text3.Length > 0)
					{
						dictionary[text2] = text3;
					}
				}
			}
			catch (Exception arg)
			{
				Log.LogError((object)string.Format("Не удалось прочитать {0}: {1}", "sansara.LocalizedContainerSigns.names.txt", arg));
			}
			return dictionary;
		}
	}
}