Decompiled source of Sweet Russian Translate v0.2.3

SweetRussianTranslate.dll

Decompiled 3 weeks ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using TMPro;
using UnityEngine;
using UnityEngine.Localization.Settings;
using UnityEngine.Localization.Tables;
using UnityEngine.TextCore;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyVersion("0.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 SweetRussianTranslate
{
	internal static class ChatPhrases
	{
		private static readonly List<string> Empty = new List<string>();

		private static Dictionary<string, string> replacements;

		private static Dictionary<string, List<string>> lists;

		internal static Dictionary<string, string> Replacements
		{
			get
			{
				Load();
				return replacements;
			}
		}

		internal static List<string> List(string key)
		{
			Load();
			if (lists.TryGetValue(key, out var value))
			{
				return value;
			}
			return Empty;
		}

		internal static string Pick(string key)
		{
			List<string> list = List(key);
			if (list.Count == 0)
			{
				return string.Empty;
			}
			return list[Random.Range(0, list.Count)];
		}

		private static void Load()
		{
			if (replacements != null)
			{
				return;
			}
			replacements = new Dictionary<string, string>();
			lists = new Dictionary<string, List<string>>();
			foreach (KeyValuePair<string, string> item in Tsv.Read("Chat.tsv"))
			{
				if (item.Key.StartsWith("[") && item.Key.EndsWith("]"))
				{
					if (!lists.TryGetValue(item.Key, out var value))
					{
						value = new List<string>();
						lists[item.Key] = value;
					}
					value.Add(item.Value);
				}
				else
				{
					replacements[item.Key] = item.Value;
				}
			}
			int num = 0;
			foreach (KeyValuePair<string, List<string>> list in lists)
			{
				num += list.Value.Count;
			}
			Plugin.Log.LogInfo((object)("Loaded " + replacements.Count + " chat phrases and " + num + " listed lines in " + lists.Count + " lists."));
		}
	}
	[HarmonyPatch(typeof(ChatManager), "PossessChat")]
	internal static class ChatPossessPatch
	{
		[HarmonyPrefix]
		private static void Prefix(ref string message)
		{
			if (message != null && ChatPhrases.Replacements.TryGetValue(message, out var value))
			{
				message = value;
			}
		}
	}
	[HarmonyPatch(typeof(SemiFunc), "MessageGeneratedGetLeftBehind")]
	internal static class LeftBehindMessagePatch
	{
		[HarmonyPostfix]
		private static void Postfix(ref string __result)
		{
			string text = ChatPhrases.Pick("[LEFT_BEHIND]");
			if (text.Length > 0)
			{
				__result = text;
			}
		}
	}
	[HarmonyPatch(typeof(ValuableLovePotion), "GenerateAffectionateSentence")]
	internal static class LovePotionPatch
	{
		private const string NoPlayerNearby = "this potion";

		[HarmonyPostfix]
		private static void Postfix(ref string __result, string ___playerName)
		{
			string text = ChatPhrases.Pick("[LOVE_TEMPLATE]");
			if (text.Length != 0)
			{
				string newValue = ((___playerName == "this potion") ? "это зелье" : ___playerName);
				string text2 = text.Replace("{playerName}", newValue).Replace("{adjective}", ChatPhrases.Pick("[LOVE_ADJECTIVE]")).Replace("{intensifier}", ChatPhrases.Pick("[LOVE_INTENSIFIER]"))
					.Replace("{adverb}", ChatPhrases.Pick("[LOVE_ADVERB]"))
					.Replace("{noun}", ChatPhrases.Pick("[LOVE_NOUN]"))
					.Replace("{transitiveVerb}", ChatPhrases.Pick("[LOVE_TRANSITIVE_VERB]"))
					.Replace("{intransitiveVerb}", ChatPhrases.Pick("[LOVE_INTRANSITIVE_VERB]"));
				__result = char.ToUpper(text2[0]) + text2.Substring(1);
			}
		}
	}
	internal static class HardcodedLabels
	{
		private static Dictionary<string, string> labels;

		internal static Dictionary<string, string> All
		{
			get
			{
				if (labels == null)
				{
					labels = new Dictionary<string, string>();
					foreach (KeyValuePair<string, string> item in Tsv.Read("Hardcoded.tsv"))
					{
						labels[item.Key] = item.Value;
					}
					Plugin.Log.LogInfo((object)("Loaded " + labels.Count + " hardcoded label replacements."));
				}
				return labels;
			}
		}

		internal static bool Translate(ref string text)
		{
			if (text != null && All.TryGetValue(text, out var value))
			{
				text = value;
				return true;
			}
			return false;
		}
	}
	[HarmonyPatch(typeof(SemiUI), "Start")]
	internal static class HardcodedLabelPatch
	{
		[HarmonyPostfix]
		private static void Postfix(SemiUI __instance)
		{
			if (HardcodedLabels.All.Count == 0)
			{
				return;
			}
			TMP_Text[] componentsInChildren = ((Component)__instance).GetComponentsInChildren<TMP_Text>(true);
			foreach (TMP_Text val in componentsInChildren)
			{
				string text = val.text;
				if (HardcodedLabels.Translate(ref text))
				{
					val.text = text;
				}
			}
		}
	}
	[HarmonyPatch(typeof(BigMessageUI), "BigMessage")]
	internal static class BigMessagePatch
	{
		[HarmonyPrefix]
		private static void Prefix(ref string message)
		{
			HardcodedLabels.Translate(ref message);
		}
	}
	[HarmonyPatch(typeof(ItemTracker), "DisplayColorOverride")]
	internal static class ItemTrackerDisplayPatch
	{
		[HarmonyPrefix]
		private static void Prefix(ref string _text)
		{
			HardcodedLabels.Translate(ref _text);
		}
	}
	[HarmonyPatch(typeof(MenuBigButton), "StateEdit")]
	internal static class MenuBigButtonEditPatch
	{
		[HarmonyPostfix]
		private static void Postfix(MenuBigButton __instance)
		{
			TMP_Text value = Traverse.Create((object)__instance.menuButton).Field("buttonText").GetValue<TMP_Text>();
			if (!((Object)(object)value == (Object)null))
			{
				string text = value.text;
				if (HardcodedLabels.Translate(ref text))
				{
					value.text = text;
				}
			}
		}
	}
	[HarmonyPatch(typeof(LocalizationManager), "Awake")]
	internal static class LocalizationOverridePatch
	{
		private static readonly string[] TableNames = new string[3] { "HUD", "Menu", "Game" };

		private static bool fontLoaded;

		[HarmonyPostfix]
		private static void Postfix()
		{
			LoadCyrillicFallbackFont();
			string[] tableNames = TableNames;
			foreach (string text in tableNames)
			{
				try
				{
					ApplyTable(text);
				}
				catch (Exception ex)
				{
					Plugin.Log.LogError((object)("Failed to apply " + text + ".tsv: " + ex.Message));
				}
			}
		}

		private static void ApplyTable(string tableName)
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			List<KeyValuePair<string, string>> list = Tsv.Read(tableName + ".tsv");
			if (list.Count == 0)
			{
				return;
			}
			StringTable table = ((LocalizedDatabase<StringTable, StringTableEntry>)(object)LocalizationSettings.StringDatabase).GetTable(TableReference.op_Implicit(tableName), LocalizationSettings.ProjectLocale);
			if ((Object)(object)table == (Object)null)
			{
				Plugin.Log.LogError((object)("String table not found: " + tableName));
				return;
			}
			foreach (KeyValuePair<string, string> item in list)
			{
				StringTableEntry entry = ((DetailedLocalizationTable<StringTableEntry>)(object)table).GetEntry(item.Key);
				if (entry != null)
				{
					entry.Value = item.Value;
				}
				else
				{
					((DetailedLocalizationTable<StringTableEntry>)(object)table).AddEntry(item.Key, item.Value);
				}
			}
			Plugin.Log.LogInfo((object)("Applied " + list.Count + " strings to table " + tableName + "."));
		}

		private static void LoadCyrillicFallbackFont()
		{
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Expected O, but got Unknown
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Expected O, but got Unknown
			if (fontLoaded)
			{
				return;
			}
			fontLoaded = true;
			string text = Path.Combine(Plugin.ModFolder, "Teko-Cyrillic.ttf");
			if (!File.Exists(text))
			{
				Plugin.Log.LogError((object)("Font not found: " + text));
				return;
			}
			try
			{
				TMP_FontAsset val = FindGameTekoAsset();
				TMP_FontAsset val3;
				if ((Object)(object)val != (Object)null)
				{
					Font val2 = new Font(text);
					FaceInfo faceInfo = val.faceInfo;
					val3 = TMP_FontAsset.CreateFontAsset(val2, ((FaceInfo)(ref faceInfo)).pointSize, val.atlasPadding, val.atlasRenderMode, val.atlasWidth, val.atlasHeight, (AtlasPopulationMode)1, true);
				}
				else
				{
					val3 = TMP_FontAsset.CreateFontAsset(new Font(text));
				}
				((Object)val3).name = "Teko-Cyrillic SDF";
				((Object)val3).hideFlags = (HideFlags)32;
				if ((Object)(object)val != (Object)null)
				{
					CopyVerticalMetrics(val, val3);
				}
				TMP_Settings.fallbackFontAssets?.Insert(0, val3);
				TMP_FontAsset[] array = Resources.FindObjectsOfTypeAll<TMP_FontAsset>();
				foreach (TMP_FontAsset val4 in array)
				{
					if ((Object)(object)val4 != (Object)(object)val3 && ((Object)val4).name.Contains("Teko"))
					{
						if (val4.fallbackFontAssetTable == null)
						{
							val4.fallbackFontAssetTable = new List<TMP_FontAsset>();
						}
						val4.fallbackFontAssetTable.Insert(0, val3);
					}
				}
				Plugin.Log.LogInfo((object)("Cyrillic fallback font registered" + (((Object)(object)val != (Object)null) ? (" (matched to " + ((Object)val).name + ").") : " (no Teko reference found).")));
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Failed to load Cyrillic font: " + ex));
			}
		}

		private static void CopyVerticalMetrics(TMP_FontAsset from, TMP_FontAsset to)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
			FaceInfo faceInfo = from.faceInfo;
			FaceInfo faceInfo2 = to.faceInfo;
			((FaceInfo)(ref faceInfo2)).scale = ((FaceInfo)(ref faceInfo)).scale;
			((FaceInfo)(ref faceInfo2)).lineHeight = ((FaceInfo)(ref faceInfo)).lineHeight;
			((FaceInfo)(ref faceInfo2)).ascentLine = ((FaceInfo)(ref faceInfo)).ascentLine;
			((FaceInfo)(ref faceInfo2)).capLine = ((FaceInfo)(ref faceInfo)).capLine;
			((FaceInfo)(ref faceInfo2)).meanLine = ((FaceInfo)(ref faceInfo)).meanLine;
			((FaceInfo)(ref faceInfo2)).baseline = ((FaceInfo)(ref faceInfo)).baseline;
			((FaceInfo)(ref faceInfo2)).descentLine = ((FaceInfo)(ref faceInfo)).descentLine;
			((FaceInfo)(ref faceInfo2)).superscriptOffset = ((FaceInfo)(ref faceInfo)).superscriptOffset;
			((FaceInfo)(ref faceInfo2)).superscriptSize = ((FaceInfo)(ref faceInfo)).superscriptSize;
			((FaceInfo)(ref faceInfo2)).subscriptOffset = ((FaceInfo)(ref faceInfo)).subscriptOffset;
			((FaceInfo)(ref faceInfo2)).subscriptSize = ((FaceInfo)(ref faceInfo)).subscriptSize;
			((FaceInfo)(ref faceInfo2)).underlineOffset = ((FaceInfo)(ref faceInfo)).underlineOffset;
			((FaceInfo)(ref faceInfo2)).underlineThickness = ((FaceInfo)(ref faceInfo)).underlineThickness;
			((FaceInfo)(ref faceInfo2)).strikethroughOffset = ((FaceInfo)(ref faceInfo)).strikethroughOffset;
			((FaceInfo)(ref faceInfo2)).strikethroughThickness = ((FaceInfo)(ref faceInfo)).strikethroughThickness;
			((FaceInfo)(ref faceInfo2)).tabWidth = ((FaceInfo)(ref faceInfo)).tabWidth;
			to.faceInfo = faceInfo2;
			Plugin.Log.LogInfo((object)$"Metrics aligned to {((Object)from).name}: pointSize {((FaceInfo)(ref faceInfo)).pointSize}, lineHeight {((FaceInfo)(ref faceInfo)).lineHeight}, baseline {((FaceInfo)(ref faceInfo)).baseline}, ascent {((FaceInfo)(ref faceInfo)).ascentLine}, descent {((FaceInfo)(ref faceInfo)).descentLine}.");
		}

		private static TMP_FontAsset FindGameTekoAsset()
		{
			TMP_FontAsset[] array = Resources.FindObjectsOfTypeAll<TMP_FontAsset>();
			foreach (TMP_FontAsset val in array)
			{
				if (((Object)val).name.Contains("Teko"))
				{
					return val;
				}
			}
			return null;
		}
	}
	[BepInPlugin("sweet.russiantranslate", "Sweet Russian Translate", "0.2.3")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class Plugin : BaseUnityPlugin
	{
		public const string Guid = "sweet.russiantranslate";

		private const string EspeakGuid = "Lavighju.espeakTTS";

		private const string EspeakDefaultLanguage = "en";

		private const int EspeakDefaultSpeed = 175;

		private const int RussianSpeed = 100;

		internal static ManualLogSource Log;

		internal static string ModFolder;

		internal static Plugin Instance;

		private void Awake()
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			ModFolder = Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location);
			Harmony val = new Harmony("sweet.russiantranslate");
			val.PatchAll(Assembly.GetExecutingAssembly());
			int num = 0;
			foreach (MethodBase patchedMethod in val.GetPatchedMethods())
			{
				_ = patchedMethod;
				num++;
			}
			Log.LogInfo((object)("Sweet Russian Translate loaded from " + ModFolder + ", patched methods: " + num));
			TuneEspeak();
			TuneTextures();
		}

		private void TuneEspeak()
		{
			if (((BaseUnityPlugin)this).Config.Bind<bool>("Speech", "Tune espeakTTS for Russian speech", true, "Настроить espeakTTS под русский, чтобы реплики персонажа читались вслух внятно: язык en -> ru, скорость 175 -> 100 слов в минуту. Трогаются только значения по умолчанию: то, что ты выбрал сам, мод не перебивает. Выключи, если хочешь настраивать espeakTTS сам.").Value)
			{
				if (!Chainloader.PluginInfos.TryGetValue("Lavighju.espeakTTS", out var value) || (Object)(object)value.Instance == (Object)null)
				{
					Log.LogInfo((object)"espeakTTS is not installed, Russian speech will stay silent.");
					return;
				}
				ConfigFile config = value.Instance.Config;
				Retune(config, "Language", "en", "ru");
				Retune(config, "Speed", 175, 100);
			}
		}

		private void TuneTextures()
		{
			if (!((BaseUnityPlugin)this).Config.Bind<bool>("Textures", "Translate textures", true, "Переводить надписи, нарисованные на текстурах: вывески комнат, плакаты, экраны. Выключи, если хочешь видеть их по-английски или подозреваешь их в тормозах. Меняется только при перезапуске игры.").Value)
			{
				Log.LogInfo((object)"Textures translation is off by config, signs stay English.");
			}
			else
			{
				TexturePatch.Init(ModFolder);
			}
		}

		private static void Retune<T>(ConfigFile config, string key, T untouched, T wanted)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Expected O, but got Unknown
			ConfigEntry<T> val = default(ConfigEntry<T>);
			if (!config.TryGetEntry<T>(new ConfigDefinition("General", key), ref val))
			{
				Log.LogWarning((object)("espeakTTS has no General/" + key + " setting, leaving it alone."));
				return;
			}
			if (!object.Equals(val.Value, untouched))
			{
				Log.LogInfo((object)("espeakTTS " + key + " is already " + val.Value?.ToString() + ", leaving it alone."));
				return;
			}
			val.Value = wanted;
			ManualLogSource log = Log;
			string[] obj = new string[7] { "espeakTTS ", key, " switched from ", null, null, null, null };
			T val2 = untouched;
			obj[3] = val2?.ToString();
			obj[4] = " to ";
			val2 = wanted;
			obj[5] = val2?.ToString();
			obj[6] = ".";
			log.LogInfo((object)string.Concat(obj));
		}
	}
	internal static class TexturePatch
	{
		private static readonly float[] ScanAt = new float[3] { 0f, 3f, 10f };

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

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

		private static readonly Dictionary<Shader, string[]> Properties = new Dictionary<Shader, string[]>();

		private static readonly HashSet<string> Done = new HashSet<string>();

		private static int replaced;

		internal static void Init(string modFolder)
		{
			string[] files = Directory.GetFiles(modFolder, "resources_*.png", SearchOption.AllDirectories);
			if (files.Length == 0)
			{
				Plugin.Log.LogWarning((object)("Картинок resources_*.png нет, надписи на текстурах останутся английскими: " + modFolder));
				return;
			}
			string[] array = files;
			foreach (string text in array)
			{
				string text2 = Key(Path.GetFileNameWithoutExtension(text));
				if (text2.Length != 0)
				{
					if (Files.ContainsKey(text2))
					{
						Plugin.Log.LogWarning((object)("Два файла на одно имя текстуры, беру первый: " + text));
					}
					else
					{
						Files[text2] = text;
					}
				}
			}
			Plugin.Log.LogInfo((object)("Текстур на подмену: " + Files.Count));
		}

		internal static IEnumerator AfterLevel()
		{
			if (Files.Count != 0)
			{
				float num = 0f;
				float[] scanAt = ScanAt;
				foreach (float at in scanAt)
				{
					yield return (object)new WaitForSeconds(at - num);
					num = at;
					Scan();
				}
			}
		}

		private static void Scan()
		{
			Material[] array = Resources.FindObjectsOfTypeAll<Material>();
			int num = replaced;
			Material[] array2 = array;
			foreach (Material val in array2)
			{
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				Shader shader = val.shader;
				if ((Object)(object)shader == (Object)null)
				{
					continue;
				}
				string[] array3 = TextureProperties(shader);
				foreach (string text in array3)
				{
					Texture texture = val.GetTexture(text);
					if (!((Object)(object)texture == (Object)null))
					{
						Texture2D val2 = Ours(texture);
						if (!((Object)(object)val2 == (Object)null))
						{
							val.SetTexture(text, (Texture)(object)val2);
							replaced++;
							Done.Add(((Object)texture).name);
							Plugin.Log.LogInfo((object)("Текстура заменена: " + ((Object)texture).name + " (материал " + ((Object)val).name + ", " + text + ")"));
						}
					}
				}
			}
			if (replaced != num)
			{
				Plugin.Log.LogInfo((object)("Всего замен: " + replaced + ", разных картинок: " + Done.Count + " из " + Files.Count));
			}
		}

		private static string[] TextureProperties(Shader shader)
		{
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Invalid comparison between Unknown and I4
			if (Properties.TryGetValue(shader, out var value))
			{
				return value;
			}
			List<string> list = new List<string>();
			int propertyCount = shader.GetPropertyCount();
			for (int i = 0; i < propertyCount; i++)
			{
				if ((int)shader.GetPropertyType(i) == 4)
				{
					list.Add(shader.GetPropertyName(i));
				}
			}
			string[] array = list.ToArray();
			Properties[shader] = array;
			return array;
		}

		private static Texture2D Ours(Texture original)
		{
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Expected O, but got Unknown
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			string text = Key(((Object)original).name);
			if (text.Length == 0 || !Files.TryGetValue(text, out var value))
			{
				return null;
			}
			if (Loaded.TryGetValue(text, out var value2))
			{
				return value2;
			}
			Texture2D val = new Texture2D(2, 2, (TextureFormat)4, true, false);
			if (!ImageConversion.LoadImage(val, File.ReadAllBytes(value), true))
			{
				Plugin.Log.LogWarning((object)("PNG не читается, пропускаю: " + value));
				Object.Destroy((Object)(object)val);
				Loaded[text] = null;
				return null;
			}
			((Object)val).name = ((Object)original).name + " RU";
			((Texture)val).wrapMode = original.wrapMode;
			((Texture)val).filterMode = original.filterMode;
			((Texture)val).anisoLevel = original.anisoLevel;
			((Object)val).hideFlags = (HideFlags)32;
			Loaded[text] = val;
			return val;
		}

		private static string Key(string name)
		{
			if (string.IsNullOrEmpty(name))
			{
				return string.Empty;
			}
			StringBuilder stringBuilder = new StringBuilder(name.Length);
			foreach (char c in name)
			{
				if (char.IsLetterOrDigit(c))
				{
					stringBuilder.Append(char.ToLowerInvariant(c));
				}
			}
			string text = stringBuilder.ToString();
			if (!text.StartsWith("resources", StringComparison.Ordinal))
			{
				return text;
			}
			int j;
			for (j = "resources".Length; j < text.Length && char.IsDigit(text[j]); j++)
			{
			}
			if (j <= "resources".Length)
			{
				return text;
			}
			return text.Substring(j);
		}
	}
	[HarmonyPatch(typeof(LevelGenerator), "GenerateDone")]
	internal static class LevelDonePatch
	{
		[HarmonyPostfix]
		private static void Postfix(LevelGenerator __instance)
		{
			if (__instance.Generated)
			{
				((MonoBehaviour)Plugin.Instance).StartCoroutine(TexturePatch.AfterLevel());
			}
		}
	}
	internal static class Tsv
	{
		internal static List<KeyValuePair<string, string>> Read(string fileName)
		{
			List<KeyValuePair<string, string>> list = new List<KeyValuePair<string, string>>();
			string text = Path.Combine(Plugin.ModFolder, fileName);
			if (!File.Exists(text))
			{
				Plugin.Log.LogWarning((object)("Missing file: " + text));
				return list;
			}
			try
			{
				string[] array = File.ReadAllLines(text, Encoding.UTF8);
				foreach (string text2 in array)
				{
					if (!string.IsNullOrWhiteSpace(text2) && !text2.StartsWith("#"))
					{
						string[] array2 = text2.Split('\t');
						if (array2.Length == 2 && !string.IsNullOrWhiteSpace(array2[1]))
						{
							list.Add(new KeyValuePair<string, string>(array2[0], array2[1].Replace("\\n", "\n")));
						}
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Failed to read " + fileName + ": " + ex.Message));
			}
			return list;
		}
	}
}