Decompiled source of Tolmach v0.6.1

plugins/Tolmach.dll

Decompiled 7 hours ago
using System;
using System.Collections;
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.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
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: InternalsVisibleTo("Tolmach.Tests")]
[assembly: ComVisible(false)]
[assembly: AssemblyCompany("Tolmach")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Client-side Russian translations for Valheim mods and the game's closed captions.")]
[assembly: AssemblyFileVersion("0.6.1.0")]
[assembly: AssemblyInformationalVersion("0.6.1")]
[assembly: AssemblyProduct("Tolmach")]
[assembly: AssemblyTitle("Tolmach")]
[assembly: AssemblyVersion("0.6.1.0")]
namespace Tolmach;

internal static class CallSites
{
	private sealed class Site
	{
		public Module Module;

		public MethodBase Caller;
	}

	private static readonly Dictionary<IntPtr, List<Site>> Sinks = new Dictionary<IntPtr, List<Site>>();

	private static readonly HashSet<IntPtr> Patched = new HashSet<IntPtr>();

	private static readonly string[] DerivedSinkTypes = new string[3] { "Player", "TMPro.TextMeshProUGUI", "TMPro.TextMeshPro" };

	private const int LastArgument = 7;

	internal static void Cover(Harmony harmony, Module module, string owner, MethodBase method, List<CodeInstruction> body, string reason)
	{
		PatchEvidence patchEvidence = new PatchEvidence
		{
			method = owner,
			notPatched = reason
		};
		module.Patches[RuntimeAccess.MethodKey(method)] = patchEvidence;
		HashSet<IntPtr> hashSet = new HashSet<IntPtr>();
		foreach (CodeInstruction item in body)
		{
			FieldInfo fieldInfo = item.operand as FieldInfo;
			if (fieldInfo != null && item.opcode == OpCodes.Stfld && DisplayPatches.DisplayField(fieldInfo, module))
			{
				Add(patchEvidence.leftAsIs, fieldInfo.DeclaringType.Name + "." + fieldInfo.Name);
				continue;
			}
			MethodBase methodBase = item.operand as MethodBase;
			if (methodBase == null || (!(item.opcode == OpCodes.Call) && !(item.opcode == OpCodes.Callvirt) && !(item.opcode == OpCodes.Newobj)))
			{
				continue;
			}
			bool[] array = DisplayPatches.StringSink(methodBase);
			if (!array.Any((bool b) => b) || !hashSet.Add(methodBase.MethodHandle.Value))
			{
				continue;
			}
			if (DisplayPatches.LocalizedReturn(methodBase))
			{
				Add(patchEvidence.leftAsIs, Name(methodBase));
				continue;
			}
			foreach (MethodBase item2 in WithOverrides(methodBase))
			{
				if (!Install(harmony, item2, array))
				{
					Add(patchEvidence.leftAsIs, Name(item2));
					continue;
				}
				if (!Sinks.TryGetValue(item2.MethodHandle.Value, out var value))
				{
					value = (Sinks[item2.MethodHandle.Value] = new List<Site>());
				}
				value.Add(new Site
				{
					Module = module,
					Caller = method
				});
				Add(patchEvidence.callSites, Name(item2));
			}
		}
		if (patchEvidence.leftAsIs.Count != 0 && HoldsCatalogText(body, module))
		{
			module.Warn("UI patch skipped " + owner + ": " + reason + "; left untranslated: " + string.Join(", ", patchEvidence.leftAsIs.ToArray()));
		}
	}

	internal static void Reset()
	{
		Sinks.Clear();
		Patched.Clear();
	}

	private static void Add(List<string> list, string name)
	{
		if (!list.Contains(name))
		{
			list.Add(name);
		}
	}

	private static string Name(MethodBase method)
	{
		return method.DeclaringType.Name + "." + method.Name;
	}

	private static bool HoldsCatalogText(List<CodeInstruction> body, Module module)
	{
		if (module.Table != null)
		{
			return body.Any(delegate(CodeInstruction i)
			{
				string text = ((i.opcode == OpCodes.Ldstr) ? (i.operand as string) : null);
				return text != null && module.Table.Translate(text) != text;
			});
		}
		return false;
	}

	private static IEnumerable<MethodBase> WithOverrides(MethodBase sink)
	{
		yield return sink;
		MethodInfo method = sink as MethodInfo;
		if (method == null || !method.IsVirtual || method.IsFinal)
		{
			yield break;
		}
		Type[] parameters = (from p in method.GetParameters()
			select p.ParameterType).ToArray();
		string[] derivedSinkTypes = DerivedSinkTypes;
		for (int num = 0; num < derivedSinkTypes.Length; num++)
		{
			Type type = RuntimeAccess.ExactType(derivedSinkTypes[num]);
			if (!(type == null) && !(type == method.DeclaringType) && method.DeclaringType.IsAssignableFrom(type))
			{
				MethodInfo methodInfo = AccessTools.DeclaredMethod(type, method.Name, parameters, (Type[])null);
				if (methodInfo != null && methodInfo.GetBaseDefinition() == method.GetBaseDefinition())
				{
					yield return methodInfo;
				}
			}
		}
	}

	private static bool Install(Harmony harmony, MethodBase target, bool[] selected)
	{
		//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d6: Expected O, but got Unknown
		if (Patched.Contains(target.MethodHandle.Value))
		{
			return true;
		}
		if (!RuntimeAccess.ManagedBody(target) || DisplayPatches.UnsupportedHandler(target) != null)
		{
			return false;
		}
		ParameterInfo[] parameters = target.GetParameters();
		if (parameters.Length != selected.Length)
		{
			return false;
		}
		List<MethodInfo> list = new List<MethodInfo>();
		for (int i = 0; i < selected.Length; i++)
		{
			if (selected[i])
			{
				if (i > 7)
				{
					return false;
				}
				list.Add(typeof(CallSites).GetMethod(((parameters[i].ParameterType == typeof(string[])) ? "Texts" : "Text") + i, AccessTools.all));
			}
		}
		try
		{
			foreach (MethodInfo item in list)
			{
				harmony.Patch(target, new HarmonyMethod(item)
				{
					priority = 0
				}, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
		}
		catch (Exception)
		{
			return false;
		}
		Patched.Add(target.MethodHandle.Value);
		return true;
	}

	private static void Text0(MethodBase __originalMethod, ref string __0)
	{
		__0 = Translate(__originalMethod, __0);
	}

	private static void Text1(MethodBase __originalMethod, ref string __1)
	{
		__1 = Translate(__originalMethod, __1);
	}

	private static void Text2(MethodBase __originalMethod, ref string __2)
	{
		__2 = Translate(__originalMethod, __2);
	}

	private static void Text3(MethodBase __originalMethod, ref string __3)
	{
		__3 = Translate(__originalMethod, __3);
	}

	private static void Text4(MethodBase __originalMethod, ref string __4)
	{
		__4 = Translate(__originalMethod, __4);
	}

	private static void Text5(MethodBase __originalMethod, ref string __5)
	{
		__5 = Translate(__originalMethod, __5);
	}

	private static void Text6(MethodBase __originalMethod, ref string __6)
	{
		__6 = Translate(__originalMethod, __6);
	}

	private static void Text7(MethodBase __originalMethod, ref string __7)
	{
		__7 = Translate(__originalMethod, __7);
	}

	private static void Texts0(MethodBase __originalMethod, ref string[] __0)
	{
		__0 = TranslateAll(__originalMethod, __0);
	}

	private static void Texts1(MethodBase __originalMethod, ref string[] __1)
	{
		__1 = TranslateAll(__originalMethod, __1);
	}

	private static void Texts2(MethodBase __originalMethod, ref string[] __2)
	{
		__2 = TranslateAll(__originalMethod, __2);
	}

	private static void Texts3(MethodBase __originalMethod, ref string[] __3)
	{
		__3 = TranslateAll(__originalMethod, __3);
	}

	private static void Texts4(MethodBase __originalMethod, ref string[] __4)
	{
		__4 = TranslateAll(__originalMethod, __4);
	}

	private static void Texts5(MethodBase __originalMethod, ref string[] __5)
	{
		__5 = TranslateAll(__originalMethod, __5);
	}

	private static void Texts6(MethodBase __originalMethod, ref string[] __6)
	{
		__6 = TranslateAll(__originalMethod, __6);
	}

	private static void Texts7(MethodBase __originalMethod, ref string[] __7)
	{
		__7 = TranslateAll(__originalMethod, __7);
	}

	private static string Translate(MethodBase sink, string value)
	{
		if (!TextEngine.IsRussian || string.IsNullOrEmpty(value) || !Sinks.TryGetValue(sink.MethodHandle.Value, out var value2))
		{
			return value;
		}
		MethodBase methodBase = null;
		foreach (Site item in value2)
		{
			string text = TextEngine.Display(value, item.Module.id);
			if (!(text == value))
			{
				if (methodBase == null)
				{
					methodBase = Caller(sink);
				}
				if (methodBase != null && Same(methodBase, item.Caller))
				{
					return text;
				}
			}
		}
		return value;
	}

	private static string[] TranslateAll(MethodBase sink, string[] values)
	{
		if (!TextEngine.IsRussian || values == null || !Sinks.ContainsKey(sink.MethodHandle.Value))
		{
			return values;
		}
		string[] array = null;
		for (int i = 0; i < values.Length; i++)
		{
			string text = Translate(sink, values[i]);
			if (!(text == values[i]))
			{
				if (array == null)
				{
					array = (string[])values.Clone();
				}
				array[i] = text;
			}
		}
		return array ?? values;
	}

	private static MethodBase Caller(MethodBase sink)
	{
		StackTrace stackTrace = new StackTrace(fNeedFileInfo: false);
		for (int i = 0; i < stackTrace.FrameCount; i++)
		{
			MethodBase method = stackTrace.GetFrame(i).GetMethod();
			if (!(method == null) && !(method.DeclaringType == null) && !(method.DeclaringType == typeof(CallSites)) && !method.DeclaringType.Assembly.IsDynamic && !Same(method, sink))
			{
				return method;
			}
		}
		return null;
	}

	private static bool Same(MethodBase a, MethodBase b)
	{
		try
		{
			return a.MethodHandle.Value == b.MethodHandle.Value;
		}
		catch (Exception)
		{
			return false;
		}
	}
}
internal sealed class PluginIdentity
{
	internal readonly string Guid;

	internal readonly Assembly Assembly;

	internal readonly Version Version;

	internal PluginIdentity(string guid, Assembly assembly, Version version)
	{
		Guid = guid;
		Assembly = assembly;
		Version = version;
	}
}
internal static class CatalogLoader
{
	private static readonly Regex Hole = new Regex("\\{(\\d+)\\}", RegexOptions.CultureInvariant);

	private static readonly JsonSerializer Serializer = JsonSerializer.Create(new JsonSerializerSettings
	{
		MissingMemberHandling = (MissingMemberHandling)1
	});

	internal static readonly Regex CllcNameplate = new Regex("\\A(?:[^\\[\\]{}]|\\[[^\\[\\]{}]*\\{(?:effect|infusion|affix)\\}[^\\[\\]{}]*\\])*\\{name\\}(?:[^\\[\\]{}]|\\[[^\\[\\]{}]*\\{(?:effect|infusion|affix)\\}[^\\[\\]{}]*\\])*\\z", RegexOptions.CultureInvariant);

	internal const string GameGuid = "valheim";

	internal const string FilePrefix = "tolmach-";

	internal static Module Read(string path)
	{
		//IL_002c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0031: Unknown result type (might be due to invalid IL or missing references)
		//IL_003e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0046: Expected O, but got Unknown
		//IL_0047: Unknown result type (might be due to invalid IL or missing references)
		//IL_004c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0058: Expected O, but got Unknown
		if (new FileInfo(path).Length > 2097152)
		{
			throw new InvalidDataException("Catalog is larger than 2 MiB.");
		}
		using StreamReader streamReader = new StreamReader(path, Encoding.UTF8, detectEncodingFromByteOrderMarks: true);
		JsonTextReader val = new JsonTextReader((TextReader)streamReader)
		{
			MaxDepth = 32,
			DateParseHandling = (DateParseHandling)0
		};
		try
		{
			JObject obj = JObject.Load((JsonReader)(object)val, new JsonLoadSettings
			{
				DuplicatePropertyNameHandling = (DuplicatePropertyNameHandling)2
			});
			if (((JsonReader)val).Read())
			{
				throw new InvalidDataException("Unexpected data after catalog object.");
			}
			Module module = ((JToken)obj).ToObject<Module>(Serializer);
			Validate(module);
			return module;
		}
		finally
		{
			((IDisposable)val)?.Dispose();
		}
	}

	internal static void Validate(Module m)
	{
		if (m == null || string.IsNullOrEmpty(m.id) || !Regex.IsMatch(m.id, "\\A[A-Za-z0-9_.]+\\z") || string.IsNullOrEmpty(m.assembly) || m.guids.Count == 0 || m.guids.Any(string.IsNullOrWhiteSpace) || m.namespaces.Any(string.IsNullOrWhiteSpace))
		{
			throw new InvalidDataException("Missing or invalid module identity.");
		}
		if (!Version.TryParse(m.version, out Version result) || !Version.TryParse(m.pluginVersion, out result))
		{
			throw new InvalidDataException("Package version and BepInPlugin version must both be declared.");
		}
		if (m.namespaces.Count == 0 && m.NeedsIlAdapters)
		{
			throw new InvalidDataException("Scoped adapters need plugin namespaces.");
		}
		if (!new HashSet<string>(m.words.Keys).SetEquals(m.englishWords.Keys))
		{
			throw new InvalidDataException("English/Russian localization keys differ.");
		}
		if (m.replaceNative.Any((string key) => key == null || !m.words.ContainsKey(key)) || m.replaceNative.Distinct().Count() != m.replaceNative.Count)
		{
			throw new InvalidDataException("A replaceNative key is not one of the module's words.");
		}
		if (m.terms.Any((KeyValuePair<string, Dictionary<string, string>> t) => !Regex.IsMatch(t.Key, "\\A[A-Za-z0-9_]+\\z") || t.Value == null || t.Value.Count == 0))
		{
			throw new InvalidDataException("Invalid term vocabulary.");
		}
		foreach (Dictionary<string, string> item in new Dictionary<string, string>[5] { m.words, m.englishWords, m.texts, m.mapLabels, m.rawTexts }.Concat(m.terms.Values))
		{
			if (item.Any((KeyValuePair<string, string> p) => string.IsNullOrEmpty(p.Key) || string.IsNullOrEmpty(p.Value)))
			{
				throw new InvalidDataException("Empty translation key/value.");
			}
		}
		foreach (PatternSpec pattern in m.patterns)
		{
			ValidatePattern(m, pattern, "text", "message", "mapLabel", "item", "itemSet");
		}
		foreach (PatternSpec rawPattern in m.rawPatterns)
		{
			ValidatePattern(m, rawPattern, "text");
			if (Hole.Replace(rawPattern.source, "").Trim().Length == 0)
			{
				throw new InvalidDataException("Raw pattern has no fixed text.");
			}
		}
		foreach (MethodSpec @return in m.returns)
		{
			if (@return == null || string.IsNullOrEmpty(@return.type) || string.IsNullOrEmpty(@return.method))
			{
				throw new InvalidDataException("Invalid return adapter.");
			}
		}
		foreach (LiteralSpec item2 in m.literals.Concat(m.configTexts))
		{
			if (item2 == null || string.IsNullOrEmpty(item2.type) || string.IsNullOrEmpty(item2.method) || item2.values.Count == 0 || item2.values.Any((KeyValuePair<string, string> p) => string.IsNullOrEmpty(p.Key) || string.IsNullOrEmpty(p.Value)))
			{
				throw new InvalidDataException("Invalid literal or config text adapter.");
			}
		}
		foreach (PrefabSpec prefab in m.prefabs)
		{
			if (prefab == null || prefab.fields.Values.Any(string.IsNullOrEmpty))
			{
				throw new InvalidDataException("Invalid display fallback.");
			}
		}
		if (m.cllc != null)
		{
			ValidateCllc(m.cllc);
		}
	}

	private static void ValidateCllc(CllcLanguage t)
	{
		if (!t.creatureGender.ContainsKey("default") || t.creatureGender.Values.Any((string g) => g == null || !t.genderedCreatureTranslations.ContainsKey(g)))
		{
			throw new InvalidDataException("Every CLLC gender, including the default one, needs a nameplate template.");
		}
		foreach (string value in t.genderedCreatureTranslations.Values)
		{
			if (value == null || !CllcNameplate.IsMatch(value))
			{
				throw new InvalidDataException("Invalid CLLC nameplate template: " + value);
			}
		}
		if (t.genderedTranslations.Any((KeyValuePair<string, Dictionary<string, string>> g) => g.Value == null || !t.genderedCreatureTranslations.ContainsKey(g.Key)))
		{
			throw new InvalidDataException("CLLC gendered words for a gender without a template.");
		}
		foreach (Dictionary<string, string> item in new Dictionary<string, string>[4] { t.creatureGender, t.genderedCreatureTranslations, t.translations, t.settingGroups }.Concat(t.genderedTranslations.Values).Concat(t.enumTranslations.Values))
		{
			if (item == null || item.Any((KeyValuePair<string, string> p) => string.IsNullOrEmpty(p.Key) || string.IsNullOrEmpty(p.Value)))
			{
				throw new InvalidDataException("Empty CLLC key/value.");
			}
		}
		if (t.settings.Any((KeyValuePair<string, CllcSetting> p) => string.IsNullOrEmpty(p.Key) || p.Value == null || string.IsNullOrEmpty(p.Value.display) || string.IsNullOrEmpty(p.Value.desc)))
		{
			throw new InvalidDataException("A CLLC setting needs a name and a description.");
		}
	}

	private static void ValidatePattern(Module m, PatternSpec p, params string[] semantics)
	{
		if (p == null || string.IsNullOrEmpty(p.source) || string.IsNullOrEmpty(p.target))
		{
			throw new InvalidDataException("Invalid pattern.");
		}
		HashSet<string> holes = new HashSet<string>(from Match h in Hole.Matches(p.source)
			select h.Groups[1].Value);
		if (!holes.SetEquals(from Match h in Hole.Matches(p.target)
			select h.Groups[1].Value))
		{
			throw new InvalidDataException("Pattern placeholders differ.");
		}
		if (p.numeric.Any((int n) => !holes.Contains(n.ToString(CultureInfo.InvariantCulture))))
		{
			throw new InvalidDataException("Numeric argument has no source placeholder.");
		}
		foreach (KeyValuePair<string, string> argument in p.arguments)
		{
			bool flag = argument.Value != null && (Array.IndexOf(semantics, argument.Value) >= 0 || (argument.Value.StartsWith("term:", StringComparison.Ordinal) && m.terms.ContainsKey(argument.Value.Substring(5))));
			if (!holes.Contains(argument.Key) || !flag || p.numeric.Contains(int.Parse(argument.Key, CultureInfo.InvariantCulture)))
			{
				throw new InvalidDataException("Unknown or conflicting semantic argument.");
			}
		}
	}

	internal static PluginIdentity GameIdentity(Type versionType)
	{
		object instanceOrType = ((versionType == null) ? null : RuntimeAccess.Read(versionType, "CurrentVersion"));
		object obj = RuntimeAccess.Read(instanceOrType, "m_major");
		object obj2 = RuntimeAccess.Read(instanceOrType, "m_minor");
		object obj3 = RuntimeAccess.Read(instanceOrType, "m_patch");
		if (!(obj is int) || !(obj2 is int) || !(obj3 is int) || (int)obj < 0 || (int)obj2 < 0 || (int)obj3 < 0)
		{
			return null;
		}
		return new PluginIdentity("valheim", versionType.Assembly, new Version((int)obj, (int)obj2, (int)obj3));
	}

	internal static bool SameVersion(Version expected, Version actual)
	{
		if (expected == null || actual == null)
		{
			return false;
		}
		if (expected.Major == actual.Major && expected.Minor == actual.Minor && Math.Max(0, expected.Build) == Math.Max(0, actual.Build))
		{
			return Math.Max(0, expected.Revision) == Math.Max(0, actual.Revision);
		}
		return false;
	}

	internal static bool TryBind(Module module, Func<string, PluginIdentity> lookup, bool onlyAuditedVersions, out string reason)
	{
		module.RuntimeAssembly = null;
		module.Table = null;
		module.ExactVersion = false;
		module.UiAllowed = false;
		PluginIdentity pluginIdentity = null;
		foreach (string guid in module.guids)
		{
			pluginIdentity = lookup(guid);
			if (pluginIdentity != null)
			{
				break;
			}
		}
		if (pluginIdentity == null || pluginIdentity.Assembly == null)
		{
			reason = "not installed/loaded";
			return false;
		}
		if (!module.guids.Contains(pluginIdentity.Guid) || pluginIdentity.Assembly.GetName().Name != module.assembly)
		{
			reason = "GUID/assembly identity mismatch; skipped";
			return false;
		}
		module.RuntimeAssembly = pluginIdentity.Assembly;
		module.ExactVersion = SameVersion(new Version(module.pluginVersion), pluginIdentity.Version);
		module.UiAllowed = module.ExactVersion || !onlyAuditedVersions;
		if (!module.ExactVersion)
		{
			module.Warn("Expected plugin " + module.pluginVersion + " (package " + module.version + "), loaded " + pluginIdentity.Version?.ToString() + ".");
		}
		module.Table = new TextTable(module);
		reason = (module.ExactVersion ? "loaded" : (module.UiAllowed ? "loaded; different plugin version, adapters apply where the catalog strings are found" : "native dictionaries only; different plugin version"));
		return true;
	}
}
internal static class CllcAdapter
{
	private const string WrapperType = "CreatureLevelControl.LocalizationWrapper";

	private const string AttributesType = "CreatureLevelControl.ConfigurationManagerAttributes";

	private static readonly string[] Tables = new string[7] { "creatureGender", "genderedCreatureTranslations", "genderedTranslations", "translations", "enumTranslations", "settingGroups", "settings" };

	private static readonly Regex Placeholder = new Regex("\\{[^{}]+\\}", RegexOptions.CultureInvariant);

	private static readonly Regex GroupNumber = new Regex("\\A\\d+ - ", RegexOptions.CultureInvariant);

	internal static void Refresh(Module m)
	{
		if (m.cllc == null)
		{
			return;
		}
		ConfigFile config = null;
		foreach (string guid in m.guids)
		{
			if (Chainloader.PluginInfos.TryGetValue(guid, out var value) && (Object)(object)value.Instance != (Object)null)
			{
				config = value.Instance.Config;
				break;
			}
		}
		Apply(m, config);
	}

	internal static void Apply(Module m, ConfigFile config)
	{
		if (m.cllc == null || m.RuntimeAssembly == null || !TextEngine.IsRussian)
		{
			return;
		}
		Type wrapper = m.RuntimeAssembly.GetType("CreatureLevelControl.LocalizationWrapper");
		FieldInfo fieldInfo = ((wrapper == null) ? null : AccessTools.Field(wrapper, "instance"));
		FieldInfo fieldInfo2 = ((wrapper == null) ? null : AccessTools.Field(wrapper, "language"));
		object obj = ((wrapper == null) ? null : RuntimeAccess.Read(wrapper, "English"));
		object obj2 = ((fieldInfo == null || !fieldInfo.IsStatic) ? null : fieldInfo.GetValue(null));
		if (obj2 == null || obj == null || fieldInfo2 == null || fieldInfo2.IsStatic || Tables.Any(delegate(string name)
		{
			FieldInfo fieldInfo3 = AccessTools.Field(wrapper, name);
			return fieldInfo3 == null || fieldInfo3.IsStatic || !typeof(IDictionary).IsAssignableFrom(fieldInfo3.FieldType);
		}))
		{
			m.Warn("CLLC LocalizationWrapper API not found; the mod keeps its own texts.");
			return;
		}
		object obj3 = obj2;
		if (fieldInfo2.GetValue(obj2) as string != "Russian")
		{
			obj3 = Activator.CreateInstance(wrapper, nonPublic: true);
			fieldInfo2.SetValue(obj3, "Russian");
		}
		m.NativeWords += Fill(obj3, obj, m);
		if (obj3 != obj2)
		{
			fieldInfo.SetValue(null, obj3);
		}
		if (config != null)
		{
			Relabel(m, wrapper, obj, config);
		}
	}

	private static int Fill(object table, object english, Module m)
	{
		CllcLanguage cllc = m.cllc;
		int num = 0;
		int unknown = 0;
		IDictionary dictionary = Dictionary(table, "genderedCreatureTranslations", create: true);
		if (dictionary.Count == 0)
		{
			IDictionary dictionary2 = Dictionary(table, "creatureGender", create: true);
			IDictionary dictionary3 = Dictionary(table, "genderedTranslations", create: true);
			foreach (KeyValuePair<string, string> item in cllc.creatureGender)
			{
				dictionary2[item.Key] = item.Value;
			}
			foreach (KeyValuePair<string, string> genderedCreatureTranslation in cllc.genderedCreatureTranslations)
			{
				dictionary[genderedCreatureTranslation.Key] = genderedCreatureTranslation.Value;
			}
			foreach (KeyValuePair<string, Dictionary<string, string>> genderedTranslation in cllc.genderedTranslations)
			{
				dictionary3[genderedTranslation.Key] = new Dictionary<string, string>(genderedTranslation.Value);
			}
			num += cllc.creatureGender.Count + cllc.genderedCreatureTranslations.Count + cllc.genderedTranslations.Values.Sum((Dictionary<string, string> f) => f.Count);
		}
		num += FillWords(Dictionary(table, "translations", create: true), Dictionary(english, "translations", create: false), cllc.translations, placeholders: true, m, ref unknown);
		num += FillWords(Dictionary(table, "settingGroups", create: true), Dictionary(english, "settingGroups", create: false), cllc.settingGroups, placeholders: false, m, ref unknown);
		IDictionary dictionary4 = Dictionary(table, "enumTranslations", create: true);
		IDictionary dictionary5 = Dictionary(english, "enumTranslations", create: false);
		foreach (KeyValuePair<string, Dictionary<string, string>> enumTranslation in cllc.enumTranslations)
		{
			if (!(dictionary5[enumTranslation.Key] is IDictionary dictionary6))
			{
				unknown += enumTranslation.Value.Count;
				continue;
			}
			IDictionary dictionary7 = dictionary4[enumTranslation.Key] as IDictionary;
			if (dictionary7 == null)
			{
				dictionary7 = new Dictionary<string, string>();
				dictionary4[enumTranslation.Key] = dictionary7;
			}
			foreach (KeyValuePair<string, string> item2 in enumTranslation.Value)
			{
				string english2 = (dictionary6[item2.Key] as string) ?? item2.Key;
				if (LocalizationBridge.ShouldFill(dictionary7[item2.Key] as string, english2, item2.Key))
				{
					dictionary7[item2.Key] = item2.Value;
					num++;
				}
			}
		}
		num += FillSettings(Dictionary(table, "settings", create: true), Dictionary(english, "settings", create: false), cllc.settings, m, ref unknown);
		if (unknown != 0)
		{
			m.Warn(unknown + " CLLC catalog entries are unknown to the installed version and were skipped.");
		}
		return num;
	}

	private static int FillWords(IDictionary target, IDictionary english, Dictionary<string, string> ours, bool placeholders, Module m, ref int unknown)
	{
		int num = 0;
		foreach (KeyValuePair<string, string> our in ours)
		{
			if (!(english[our.Key] is string english2))
			{
				unknown++;
			}
			else if (placeholders && !SamePlaceholders(english2, our.Value))
			{
				m.Warn("CLLC entry skipped, placeholders differ from English: " + our.Key);
			}
			else if (LocalizationBridge.ShouldFill(target[our.Key] as string, english2, our.Key))
			{
				target[our.Key] = our.Value;
				num++;
			}
		}
		return num;
	}

	private static int FillSettings(IDictionary target, IDictionary english, Dictionary<string, CllcSetting> ours, Module m, ref int unknown)
	{
		Type type = target.GetType().GetGenericArguments()[1];
		FieldInfo fieldInfo = AccessTools.Field(type, "display");
		FieldInfo fieldInfo2 = AccessTools.Field(type, "desc");
		if (fieldInfo == null || fieldInfo2 == null)
		{
			m.Warn("CLLC SettingTexts fields not found; setting texts are not filled.");
			return 0;
		}
		int num = 0;
		foreach (KeyValuePair<string, CllcSetting> our in ours)
		{
			object obj = english[our.Key];
			if (obj == null)
			{
				unknown++;
				continue;
			}
			string english2 = (fieldInfo.GetValue(obj) as string) ?? our.Key;
			string english3 = (fieldInfo2.GetValue(obj) as string) ?? "";
			if (!SamePlaceholders(english2, our.Value.display) || !SamePlaceholders(english3, our.Value.desc))
			{
				m.Warn("CLLC setting skipped, placeholders differ from English: " + our.Key);
				continue;
			}
			object obj2 = target[our.Key];
			bool flag = LocalizationBridge.ShouldFill((obj2 == null) ? null : (fieldInfo.GetValue(obj2) as string), english2, our.Key);
			bool flag2 = LocalizationBridge.ShouldFill((obj2 == null) ? null : (fieldInfo2.GetValue(obj2) as string), english3, our.Key);
			if (flag || flag2)
			{
				object obj3 = obj2 ?? Activator.CreateInstance(type);
				if (obj3 == obj)
				{
					obj3 = AccessTools.Method(typeof(object), "MemberwiseClone", (Type[])null, (Type[])null).Invoke(obj3, null);
				}
				if (flag)
				{
					fieldInfo.SetValue(obj3, our.Value.display);
				}
				if (flag2)
				{
					fieldInfo2.SetValue(obj3, our.Value.desc);
				}
				target[our.Key] = obj3;
				num++;
			}
		}
		return num;
	}

	private static void Relabel(Module m, Type wrapper, object english, ConfigFile config)
	{
		MethodInfo methodInfo = AccessTools.Method(wrapper, "getSettingGroupTranslation", new Type[1] { typeof(string) }, (Type[])null);
		MethodInfo methodInfo2 = AccessTools.Method(wrapper, "getSettingTranslation", new Type[1] { typeof(string) }, (Type[])null);
		MethodInfo methodInfo3 = AccessTools.Method(wrapper, "getTranslation", new Type[1] { typeof(string) }, (Type[])null);
		Type attributes = m.RuntimeAssembly.GetType("CreatureLevelControl.ConfigurationManagerAttributes");
		FieldInfo fieldInfo = ((methodInfo2 == null) ? null : AccessTools.Field(methodInfo2.ReturnType, "display"));
		FieldInfo fieldInfo2 = ((methodInfo2 == null) ? null : AccessTools.Field(methodInfo2.ReturnType, "desc"));
		FieldInfo fieldInfo3 = ((attributes == null) ? null : AccessTools.Field(attributes, "Category"));
		FieldInfo fieldInfo4 = ((attributes == null) ? null : AccessTools.Field(attributes, "DispName"));
		FieldInfo fieldInfo5 = ((attributes == null) ? null : AccessTools.Field(attributes, "Description"));
		if (methodInfo == null || methodInfo2 == null || methodInfo3 == null || fieldInfo == null || fieldInfo2 == null || fieldInfo3 == null || fieldInfo4 == null || fieldInfo5 == null)
		{
			m.Warn("CLLC setting lookups not found; the config window keeps the texts the mod bound.");
			return;
		}
		IDictionary dictionary = Dictionary(english, "settings", create: false);
		List<KeyValuePair<string, Regex>> list = new List<KeyValuePair<string, Regex>>();
		foreach (object key in dictionary.Keys)
		{
			string text = key as string;
			Match match = ((text == null) ? Match.Empty : Placeholder.Match(text));
			if (match.Success)
			{
				list.Add(new KeyValuePair<string, Regex>(text, new Regex("\\A" + Regex.Escape(text.Substring(0, match.Index)) + "(.+?)" + Regex.Escape(text.Substring(match.Index + match.Length)) + "\\z", RegexOptions.CultureInvariant)));
			}
		}
		int num = 0;
		foreach (KeyValuePair<ConfigDefinition, ConfigEntryBase> item in config)
		{
			object[] array = ((item.Value == null || item.Value.Description == null) ? null : item.Value.Description.Tags);
			if (array == null || !array.Any((object tag) => tag != null && tag.GetType() == attributes))
			{
				continue;
			}
			string text2 = null;
			string text3 = null;
			if (dictionary.Contains(item.Key.Key))
			{
				text2 = item.Key.Key;
			}
			else
			{
				foreach (KeyValuePair<string, Regex> item2 in list)
				{
					Match match2 = item2.Value.Match(item.Key.Key);
					if (match2.Success)
					{
						text2 = item2.Key;
						text3 = match2.Groups[1].Value;
						break;
					}
				}
			}
			if (text2 == null)
			{
				continue;
			}
			try
			{
				string value = GroupNumber.Match(item.Key.Section).Value;
				string value2 = value + (string)methodInfo.Invoke(null, new object[1] { item.Key.Section.Substring(value.Length) });
				object obj = methodInfo2.Invoke(null, new object[1] { text2 });
				string text4 = (fieldInfo.GetValue(obj) as string) ?? text2;
				string text5 = fieldInfo2.GetValue(obj) as string;
				if (text3 != null)
				{
					string value3 = Placeholder.Match(text2).Value;
					string newValue = (string)methodInfo3.Invoke(null, new object[1] { text3 });
					text4 = text4.Replace(value3, newValue);
					if (text5 != null)
					{
						text5 = text5.Replace(value3, newValue);
					}
				}
				object[] array2 = array;
				foreach (object obj2 in array2)
				{
					if (obj2 != null && !(obj2.GetType() != attributes))
					{
						if (fieldInfo3.GetValue(obj2) != null)
						{
							fieldInfo3.SetValue(obj2, value2);
						}
						if (fieldInfo4.GetValue(obj2) != null)
						{
							fieldInfo4.SetValue(obj2, text4);
						}
						if (fieldInfo5.GetValue(obj2) != null && text5 != null)
						{
							fieldInfo5.SetValue(obj2, text5);
						}
					}
				}
			}
			catch (TargetInvocationException)
			{
				num++;
			}
		}
		if (num != 0)
		{
			m.Warn(num + " CLLC config entries kept their texts: the mod's lookup failed.");
		}
	}

	private static IDictionary Dictionary(object owner, string name, bool create)
	{
		FieldInfo fieldInfo = AccessTools.Field(owner.GetType(), name);
		IDictionary dictionary = fieldInfo.GetValue(owner) as IDictionary;
		if (dictionary == null && create)
		{
			dictionary = (IDictionary)Activator.CreateInstance(fieldInfo.FieldType);
			fieldInfo.SetValue(owner, dictionary);
		}
		return dictionary ?? new Hashtable();
	}

	private static bool SamePlaceholders(string english, string russian)
	{
		return new HashSet<string>(from Match p in Placeholder.Matches(english)
			select p.Value).SetEquals(from Match p in Placeholder.Matches(russian ?? "")
			select p.Value);
	}
}
public static class CreatureNameDisplay
{
	internal const string ModuleId = "HumanoidRandomizer";

	private static Module owner;

	private static TextTable table;

	private static CreatureNameOwner provenance;

	private static FieldInfo nameField;

	private static FieldInfo tameableCharacter;

	internal static void Reset()
	{
		owner = null;
		table = null;
		provenance = null;
		nameField = null;
		tameableCharacter = null;
	}

	internal static void Install(Harmony harmony, Type characterType, Type tameableType = null, Type sceneType = null)
	{
		Reset();
		if (!TextEngine.Modules.TryGetValue("HumanoidRandomizer", out var value) || !value.UiAllowed || value.rawPatterns.Count == 0)
		{
			return;
		}
		owner = value;
		try
		{
			nameField = ((characterType == null) ? null : AccessTools.Field(characterType, "m_name"));
			if (nameField != null)
			{
				nameField = AccessTools.DeclaredField(nameField.DeclaringType, nameField.Name);
			}
			provenance = new CreatureNameOwner(value, characterType);
			if (nameField == null || nameField.IsStatic || nameField.FieldType != typeof(string) || !provenance.Available)
			{
				value.Warn("Creature-name display API unavailable; variant names are left unchanged. No global name-template fallback is used.");
				return;
			}
			provenance.BindClones(value, characterType, sceneType);
			table = new TextTable(value.rawTexts, value.rawPatterns, new Dictionary<string, string>(), value.terms);
		}
		catch (Exception ex)
		{
			value.Warn("Creature-name display initialization: " + ex.GetType().Name + "; original names retained, other adapters remain available.");
			Reset();
			return;
		}
		Patch(harmony, characterType, "GetHoverName");
		tameableCharacter = ((tameableType == null) ? null : AccessTools.Field(tameableType, "m_character"));
		if (tameableCharacter == null || tameableCharacter.IsStatic || !nameField.DeclaringType.IsAssignableFrom(tameableCharacter.FieldType))
		{
			value.Warn("Creature-name Tameable API unavailable; tameable default names are left unchanged.");
		}
		else
		{
			Patch(harmony, tameableType, "GetName");
		}
	}

	private static void Patch(Harmony harmony, Type type, string methodName)
	{
		//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c2: Expected O, but got Unknown
		try
		{
			MethodInfo methodInfo = AccessTools.Method(type, methodName, Type.EmptyTypes, (Type[])null);
			if (methodInfo == null || methodInfo.IsStatic || methodInfo.ReturnType != typeof(string) || !RuntimeAccess.ManagedBody(methodInfo))
			{
				owner.Warn("Creature-name display target unavailable: " + type.FullName + "." + methodName);
				return;
			}
			string text = DisplayPatches.UnsupportedHandler(methodInfo);
			if (text != null)
			{
				Record(methodInfo, 0, text + "; no global fallback");
				owner.Warn("Creature-name display target skipped " + methodName + ": " + text);
			}
			else
			{
				harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(CreatureNameDisplay), "Transpile", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null);
			}
		}
		catch (Exception ex)
		{
			owner.Warn("Creature-name display target " + methodName + ": " + ex.GetType().Name);
		}
	}

	private static IEnumerable<CodeInstruction> Transpile(IEnumerable<CodeInstruction> instructions, MethodBase __originalMethod)
	{
		List<CodeInstruction> code = new List<CodeInstruction>(instructions);
		int fields = 0;
		for (int i = 0; i < code.Count; i++)
		{
			CodeInstruction instruction = code[i];
			yield return instruction;
			if (i == 0 || instruction.opcode != OpCodes.Ldfld || !object.Equals(instruction.operand, nameField) || instruction.labels.Count != 0 || instruction.blocks.Count != 0 || code[i - 1].blocks.Count != 0)
			{
				continue;
			}
			bool num = code[i - 1].opcode == OpCodes.Ldarg_0;
			bool tameable = i >= 2 && code[i - 2].opcode == OpCodes.Ldarg_0 && code[i - 1].opcode == OpCodes.Ldfld && object.Equals(code[i - 1].operand, tameableCharacter) && code[i - 1].labels.Count == 0 && code[i - 2].blocks.Count == 0;
			if (num || tameable)
			{
				yield return new CodeInstruction(OpCodes.Ldarg_0, (object)null);
				if (tameable)
				{
					yield return new CodeInstruction(OpCodes.Ldfld, (object)tameableCharacter);
				}
				yield return new CodeInstruction(OpCodes.Call, (object)AccessTools.Method(typeof(CreatureNameDisplay), "Display", (Type[])null, (Type[])null));
				fields++;
			}
		}
		Record(__originalMethod, fields, (fields == 0) ? "no safe creature-name read; no global fallback" : null);
		if (owner != null && fields == 0)
		{
			owner.Warn("No safe creature-name field read in " + __originalMethod.Name + "; that method was left unchanged.");
		}
	}

	private static void Record(MethodBase method, int fields, string reason)
	{
		if (owner != null)
		{
			string text = method.DeclaringType.FullName + "." + method.Name + " [creature name]";
			if (!owner.Patches.TryGetValue(text, out var value))
			{
				owner.ScannedMethods++;
			}
			if (value != null && value.displayFields > 0)
			{
				owner.PatchedMethods--;
			}
			if (fields > 0)
			{
				owner.PatchedMethods++;
			}
			owner.Patches[text] = new PatchEvidence
			{
				method = text,
				displayFields = fields,
				notPatched = reason
			};
		}
	}

	public static string Display(string value, object character)
	{
		if (!TextEngine.IsRussian || value == null || character == null || owner == null || table == null || !owner.UiAllowed)
		{
			return value;
		}
		if (!TextEngine.Modules.TryGetValue("HumanoidRandomizer", out var value2) || owner != value2)
		{
			return value;
		}
		try
		{
			string text = table.TranslateWhole(value);
			return (text != value && provenance.Owns(character)) ? text : value;
		}
		catch (Exception ex)
		{
			owner.Warn("Creature-name ownership check failed: " + ex.GetType().Name + "; original text retained.");
			return value;
		}
	}
}
internal sealed class CreatureNameOwner
{
	private readonly Type markerType;

	private readonly MethodInfo getComponent;

	private readonly MethodInfo isPlayer;

	private FieldInfo viewField;

	private FieldInfo builderField;

	private FieldInfo prefabsField;

	private MethodInfo getZdo;

	private MethodInfo getPrefabId;

	private MethodInfo sceneInstance;

	private MethodInfo getPrefab;

	private bool clonesAvailable;

	internal bool Available
	{
		get
		{
			if (markerType != null && getComponent != null && !getComponent.IsStatic && getComponent.ReturnType.IsAssignableFrom(markerType) && isPlayer != null && !isPlayer.IsStatic)
			{
				return isPlayer.ReturnType == typeof(bool);
			}
			return false;
		}
	}

	internal CreatureNameOwner(Module module, Type characterType)
	{
		markerType = ((module.RuntimeAssembly == null) ? null : module.RuntimeAssembly.GetType("BalrondHumanoidRandomizer.HumanoidRandomizer"));
		getComponent = ((characterType == null) ? null : AccessTools.Method(characterType, "GetComponent", new Type[1] { typeof(Type) }, (Type[])null));
		isPlayer = ((characterType == null) ? null : AccessTools.Method(characterType, "IsPlayer", Type.EmptyTypes, (Type[])null));
	}

	internal void BindClones(Module module, Type characterType, Type sceneType)
	{
		Type type = module.RuntimeAssembly.GetType("BalrondHumanoidRandomizer.Launch");
		builderField = ((type == null) ? null : AccessTools.Field(type, "itemSetBuilder"));
		prefabsField = ((builderField == null) ? null : AccessTools.Field(builderField.FieldType, "prefabs"));
		viewField = AccessTools.Field(characterType, "m_nview");
		getZdo = ((viewField == null) ? null : AccessTools.Method(viewField.FieldType, "GetZDO", Type.EmptyTypes, (Type[])null));
		getPrefabId = ((getZdo == null) ? null : AccessTools.Method(getZdo.ReturnType, "GetPrefab", Type.EmptyTypes, (Type[])null));
		sceneInstance = ((sceneType == null) ? null : AccessTools.PropertyGetter(sceneType, "instance"));
		getPrefab = ((sceneType == null) ? null : AccessTools.Method(sceneType, "GetPrefab", new Type[1] { typeof(int) }, (Type[])null));
		clonesAvailable = builderField != null && builderField.IsStatic && prefabsField != null && !prefabsField.IsStatic && typeof(IList).IsAssignableFrom(prefabsField.FieldType) && viewField != null && !viewField.IsStatic && getZdo != null && !getZdo.IsStatic && getPrefabId != null && !getPrefabId.IsStatic && getPrefabId.ReturnType == typeof(int) && sceneInstance != null && sceneInstance.IsStatic && sceneType.IsAssignableFrom(sceneInstance.ReturnType) && getPrefab != null && !getPrefab.IsStatic && !getPrefab.ReturnType.IsValueType;
		if (!clonesAvailable)
		{
			module.Warn("Creature-name prefab ownership API unavailable; unmarked variants are left unchanged.");
		}
	}

	internal bool Owns(object character)
	{
		if ((bool)isPlayer.Invoke(character, null))
		{
			return false;
		}
		object obj = getComponent.Invoke(character, new object[1] { markerType });
		if (obj != null && markerType.IsInstanceOfType(obj))
		{
			return true;
		}
		if (!clonesAvailable)
		{
			return false;
		}
		object value = viewField.GetValue(character);
		object obj2 = ((value == null) ? null : getZdo.Invoke(value, null));
		object obj3 = sceneInstance.Invoke(null, null);
		if (obj2 == null || obj3 == null)
		{
			return false;
		}
		object obj4 = getPrefab.Invoke(obj3, new object[1] { getPrefabId.Invoke(obj2, null) });
		object value2 = builderField.GetValue(null);
		IList list = ((value2 == null) ? null : (prefabsField.GetValue(value2) as IList));
		if (obj4 == null || list == null)
		{
			return false;
		}
		foreach (object item in list)
		{
			if (obj4 == item)
			{
				return true;
			}
		}
		return false;
	}
}
internal static class DisplayPatches
{
	private static readonly Dictionary<string, Module> Owners = new Dictionary<string, Module>();

	private static readonly MethodInfo Display = typeof(TextEngine).GetMethod("Display");

	private static readonly MethodInfo DisplayArray = typeof(TextEngine).GetMethod("DisplayArray");

	private static readonly MethodInfo Literal = typeof(TextEngine).GetMethod("Literal");

	private static readonly MethodInfo ConfigText = typeof(TextEngine).GetMethod("ConfigText");

	private static readonly HashSet<string> GuiMethods = new HashSet<string>(new string[9] { "Label", "Button", "RepeatButton", "Toggle", "Box", "Window", "ModalWindow", "Toolbar", "SelectionGrid" });

	internal static void Install(Harmony harmony, Module module)
	{
		if (!module.NeedsIlAdapters)
		{
			return;
		}
		if (!module.UiAllowed)
		{
			module.Warn("Version differs from snapshot and OnlyAuditedVersions is on; scoped IL/return adapters skipped. Native key adapters remain enabled.");
			return;
		}
		if (!module.ExactVersion)
		{
			module.Warn("Version differs from snapshot; adapters apply where the catalog strings are found. Strings not found are listed below and stay in English.");
		}
		HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
		Type[] typesFromAssembly = AccessTools.GetTypesFromAssembly(module.RuntimeAssembly);
		foreach (Type type in typesFromAssembly)
		{
			List<MethodBase> list;
			try
			{
				if (!module.OwnType(type) || type.ContainsGenericParameters)
				{
					continue;
				}
				list = AccessTools.GetDeclaredMethods(type).Cast<MethodBase>().ToList();
				list.AddRange(AccessTools.GetDeclaredConstructors(type, (bool?)false).Cast<MethodBase>());
				goto IL_00c2;
			}
			catch (Exception ex)
			{
				module.Warn("Type skipped " + type.Name + ": " + ex.GetType().Name);
			}
			continue;
			IL_00c2:
			foreach (MethodBase item in list)
			{
				try
				{
					InstallMethod(harmony, module, type, item, hashSet);
				}
				catch (Exception ex2)
				{
					module.Warn("UI adapter skipped " + type.FullName + "." + item.Name + ": " + ex2.GetType().Name);
				}
			}
		}
		foreach (MethodSpec @return in module.returns)
		{
			if (!Declares(module, @return.type, @return.method))
			{
				module.Warn("Expected return adapter not found: " + @return.type + "." + @return.method);
			}
		}
		foreach (LiteralSpec literal in module.literals)
		{
			if (!Declares(module, literal.type, literal.method))
			{
				module.Warn("Expected literal adapter not found: " + literal.type + "." + literal.method);
				continue;
			}
			foreach (string key in literal.values.Keys)
			{
				if (!hashSet.Contains(LiteralKey(literal, key)))
				{
					module.Warn("Literal not found in IL: " + literal.type + "." + literal.method + " :: " + Shown(key));
				}
			}
		}
		foreach (LiteralSpec configText in module.configTexts)
		{
			if (!Declares(module, configText.type, configText.method))
			{
				module.Warn("Expected config text adapter not found: " + configText.type + "." + configText.method);
			}
			else if (!hashSet.Contains(ConfigKey(configText)))
			{
				module.Warn("Config value read not found in IL: " + configText.type + "." + configText.method);
			}
		}
	}

	private static string LiteralKey(LiteralSpec spec, string value)
	{
		return spec.type + "\n" + spec.method + "\n" + value;
	}

	private static string ConfigKey(LiteralSpec spec)
	{
		return "config\n" + spec.type + "\n" + spec.method;
	}

	private static bool ConfigRead(CodeInstruction instruction)
	{
		MethodInfo methodInfo = instruction.operand as MethodInfo;
		if (methodInfo != null && (instruction.opcode == OpCodes.Call || instruction.opcode == OpCodes.Callvirt) && methodInfo.Name == "get_Value" && methodInfo.ReturnType == typeof(string) && methodInfo.DeclaringType != null && methodInfo.DeclaringType.IsGenericType)
		{
			return methodInfo.DeclaringType.GetGenericTypeDefinition() == typeof(ConfigEntry<>);
		}
		return false;
	}

	private static string Shown(string value)
	{
		return value.Replace("\r", "\\r").Replace("\n", "\\n").Replace("\t", "\\t");
	}

	private static void InstallMethod(Harmony harmony, Module module, Type type, MethodBase method, HashSet<string> seen)
	{
		//IL_03d7: Unknown result type (might be due to invalid IL or missing references)
		//IL_03fe: Unknown result type (might be due to invalid IL or missing references)
		if (!RuntimeAccess.ManagedBody(method))
		{
			return;
		}
		module.ScannedMethods++;
		LiteralSpec[] array = module.literals.Where((LiteralSpec p) => p.type == type.FullName && p.method == method.Name).ToArray();
		LiteralSpec[] array2 = module.configTexts.Where((LiteralSpec p) => p.type == type.FullName && p.method == method.Name).ToArray();
		MethodInfo methodInfo = method as MethodInfo;
		bool flag = methodInfo != null && methodInfo.ReturnType == typeof(string) && module.returns.Any((MethodSpec p) => p.type == type.FullName && p.method == method.Name);
		if (array.Length == 0 && array2.Length == 0 && !flag && !module.HasDisplayText)
		{
			return;
		}
		bool flag2 = false;
		bool flag3 = false;
		bool flag4 = false;
		List<CodeInstruction> list = null;
		try
		{
			list = PatchProcessor.GetOriginalInstructions(method, (ILGenerator)null);
			foreach (CodeInstruction item in list)
			{
				string text = ((item.opcode == OpCodes.Ldstr) ? (item.operand as string) : null);
				if (text == null)
				{
					continue;
				}
				LiteralSpec[] array3 = array;
				foreach (LiteralSpec literalSpec in array3)
				{
					if (literalSpec.values.ContainsKey(text))
					{
						flag2 = true;
						seen.Add(LiteralKey(literalSpec, text));
					}
				}
			}
			flag3 = module.HasDisplayText && list.Any((CodeInstruction instruction) => Relevant(instruction, module));
			flag4 = array2.Length != 0 && list.Any(ConfigRead);
			if (flag4)
			{
				LiteralSpec[] array3 = array2;
				foreach (LiteralSpec spec in array3)
				{
					seen.Add(ConfigKey(spec));
				}
			}
		}
		catch (Exception ex)
		{
			module.Warn("IL inspection skipped " + type.Name + "." + method.Name + ": " + ex.GetType().Name);
			flag2 = array.Length != 0;
			LiteralSpec[] array3 = array;
			foreach (LiteralSpec literalSpec2 in array3)
			{
				foreach (string key2 in literalSpec2.values.Keys)
				{
					seen.Add(LiteralKey(literalSpec2, key2));
				}
			}
			flag4 = array2.Length != 0;
			array3 = array2;
			foreach (LiteralSpec spec2 in array3)
			{
				seen.Add(ConfigKey(spec2));
			}
		}
		if (!flag2 && !flag && !flag3 && !flag4)
		{
			return;
		}
		string text2 = UnsupportedHandler(method);
		if (text2 != null)
		{
			string text3 = type.FullName + "." + method.Name;
			if (flag3)
			{
				CallSites.Cover(harmony, module, text3, method, list, text2);
			}
			if (flag2 || flag4 || flag)
			{
				module.Warn("UI patch skipped " + text3 + ": " + text2);
			}
			return;
		}
		string key = RuntimeAccess.MethodKey(method);
		Owners[key] = module;
		try
		{
			HarmonyMethod val = ((!(flag2 || flag3 || flag4)) ? ((HarmonyMethod)null) : new HarmonyMethod(typeof(DisplayPatches).GetMethod("Transpile", AccessTools.all)));
			HarmonyMethod val2 = ((!flag) ? ((HarmonyMethod)null) : new HarmonyMethod(typeof(DisplayPatches).GetMethod("TranslateReturn", AccessTools.all)));
			if (val != null)
			{
				val.priority = 0;
			}
			if (val2 != null)
			{
				val2.priority = 0;
			}
			harmony.Patch(method, (HarmonyMethod)null, val2, val, (HarmonyMethod)null, (HarmonyMethod)null);
			module.PatchedMethods++;
			if (!module.Patches.TryGetValue(key, out var value))
			{
				value = new PatchEvidence
				{
					method = type.FullName + "." + method.Name
				};
				module.Patches[key] = value;
			}
			value.returnAdapter = flag;
		}
		catch (Exception ex2)
		{
			module.Warn("UI patch skipped " + type.FullName + "." + method.Name + ": " + ex2.GetType().Name);
		}
	}

	private static bool Declares(Module module, string typeName, string methodName)
	{
		try
		{
			Type type = module.RuntimeAssembly.GetType(typeName, throwOnError: false);
			if (type != null && methodName == ".ctor")
			{
				return AccessTools.GetDeclaredConstructors(type, (bool?)false).Count != 0;
			}
			return type != null && type.GetMethods(AccessTools.allDeclared).Any((MethodInfo m) => m.Name == methodName);
		}
		catch (Exception)
		{
			return false;
		}
	}

	internal static void Reset()
	{
		Owners.Clear();
		CallSites.Reset();
	}

	private static bool Relevant(CodeInstruction instruction, Module module)
	{
		MethodBase methodBase = instruction.operand as MethodBase;
		if (methodBase != null && (instruction.opcode == OpCodes.Call || instruction.opcode == OpCodes.Callvirt || instruction.opcode == OpCodes.Newobj))
		{
			return StringSink(methodBase).Any((bool b) => b);
		}
		FieldInfo fieldInfo = instruction.operand as FieldInfo;
		if (fieldInfo != null && instruction.opcode == OpCodes.Stfld)
		{
			return DisplayField(fieldInfo, module);
		}
		return false;
	}

	internal static bool DisplayField(FieldInfo f, Module m)
	{
		if (f.FieldType != typeof(string) || f.DeclaringType == null)
		{
			return false;
		}
		string fullName = f.DeclaringType.FullName;
		if (!(m.id == "ExpertExplorer") || !(fullName == "MessageHud+UnlockMsg") || (!(f.Name == "m_topic") && !(f.Name == "m_description")))
		{
			if (m.id == "SpeedyPaths" && fullName == "StatusEffect")
			{
				return f.Name == "m_name";
			}
			return false;
		}
		return true;
	}

	internal static bool[] StringSink(MethodBase method)
	{
		ParameterInfo[] parameters = method.GetParameters();
		bool[] array = new bool[parameters.Length];
		if (method.DeclaringType == null || method.ContainsGenericParameters)
		{
			return array;
		}
		if (parameters.Any((ParameterInfo p) => p.ParameterType.IsByRef || p.ParameterType.IsPointer))
		{
			return array;
		}
		string fullName = method.DeclaringType.FullName;
		string name = method.Name;
		bool flag = false;
		if ((fullName == "UnityEngine.GUI" || fullName == "UnityEngine.GUILayout") && GuiMethods.Contains(name))
		{
			flag = true;
		}
		if (fullName == "UnityEngine.GUIContent" && (method.IsConstructor || name == "set_text" || name == "set_tooltip"))
		{
			flag = true;
		}
		switch (fullName)
		{
		case "UnityEngine.UI.Text":
		case "TMPro.TMP_Text":
		case "TMPro.TextMeshProUGUI":
		case "TMPro.TextMeshPro":
			if (name == "set_text" || name == "SetText")
			{
				flag = true;
			}
			break;
		}
		switch (fullName)
		{
		case "Character":
		case "Player":
		case "MessageHud":
		case "Hud":
			if (name == "Message" || name == "ShowMessage")
			{
				flag = true;
			}
			break;
		}
		if (fullName == "Chat" && (name == "SetNpcText" || name == "AddInworldText"))
		{
			flag = true;
		}
		if ((fullName == "Localization" && name == "Localize") || (fullName == "Jotunn.Entities.CustomLocalization" && name == "TryTranslate"))
		{
			flag = true;
		}
		if (!flag)
		{
			return array;
		}
		for (int num = 0; num < parameters.Length; num++)
		{
			Type parameterType = parameters[num].ParameterType;
			array[num] = parameterType == typeof(string) || parameterType == typeof(string[]);
			if ((fullName == "Localization" && name == "Localize") || (fullName == "Jotunn.Entities.CustomLocalization" && name == "TryTranslate"))
			{
				array[num] = num == 0 && parameterType == typeof(string);
			}
		}
		return array;
	}

	internal static bool LocalizedReturn(MethodBase method)
	{
		MethodInfo methodInfo = method as MethodInfo;
		if (methodInfo == null || methodInfo.ReturnType != typeof(string) || methodInfo.DeclaringType == null)
		{
			return false;
		}
		if (!(methodInfo.DeclaringType.FullName == "Localization") || !(methodInfo.Name == "Localize"))
		{
			if (methodInfo.DeclaringType.FullName == "Jotunn.Entities.CustomLocalization")
			{
				return methodInfo.Name == "TryTranslate";
			}
			return false;
		}
		return true;
	}

	private static void TranslateReturn(MethodBase __originalMethod, ref string __result)
	{
		if (Owners.TryGetValue(RuntimeAccess.MethodKey(__originalMethod), out var value))
		{
			__result = TextEngine.Display(__result, value.id);
		}
	}

	private static IEnumerable<CodeInstruction> Transpile(IEnumerable<CodeInstruction> instructions, ILGenerator generator, MethodBase __originalMethod)
	{
		//IL_01f2: Unknown result type (might be due to invalid IL or missing references)
		//IL_01fc: Expected O, but got Unknown
		//IL_0218: Unknown result type (might be due to invalid IL or missing references)
		//IL_0222: Expected O, but got Unknown
		//IL_0239: Unknown result type (might be due to invalid IL or missing references)
		//IL_0243: Expected O, but got Unknown
		//IL_024e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0258: Expected O, but got Unknown
		//IL_0290: Unknown result type (might be due to invalid IL or missing references)
		//IL_029a: Expected O, but got Unknown
		//IL_02a6: Unknown result type (might be due to invalid IL or missing references)
		//IL_02b0: Expected O, but got Unknown
		//IL_02c1: Unknown result type (might be due to invalid IL or missing references)
		//IL_02cb: Expected O, but got Unknown
		//IL_02d6: Unknown result type (might be due to invalid IL or missing references)
		//IL_02e0: Expected O, but got Unknown
		//IL_033c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0343: Expected O, but got Unknown
		//IL_0360: Unknown result type (might be due to invalid IL or missing references)
		//IL_036a: Expected O, but got Unknown
		//IL_0477: Unknown result type (might be due to invalid IL or missing references)
		//IL_0481: Expected O, but got Unknown
		//IL_049d: Unknown result type (might be due to invalid IL or missing references)
		//IL_04a7: Expected O, but got Unknown
		//IL_04bb: Unknown result type (might be due to invalid IL or missing references)
		//IL_04c5: Expected O, but got Unknown
		//IL_053a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0544: Expected O, but got Unknown
		//IL_0550: Unknown result type (might be due to invalid IL or missing references)
		//IL_055a: Expected O, but got Unknown
		//IL_04f3: Unknown result type (might be due to invalid IL or missing references)
		//IL_04fd: Expected O, but got Unknown
		List<CodeInstruction> list = instructions.ToList();
		if (!Owners.TryGetValue(RuntimeAccess.MethodKey(__originalMethod), out var value))
		{
			return list;
		}
		List<CodeInstruction> list2 = new List<CodeInstruction>();
		string key = RuntimeAccess.MethodKey(__originalMethod);
		if (!value.Patches.TryGetValue(key, out var value2))
		{
			value2 = new PatchEvidence
			{
				method = __originalMethod.DeclaringType.FullName + "." + __originalMethod.Name
			};
			value.Patches[key] = value2;
		}
		value2.displayCalls = (value2.displayFields = (value2.literals = (value2.configTexts = (value2.skippedBoundaries = 0))));
		value2.matchedLiterals.Clear();
		string declaring = __originalMethod.DeclaringType.FullName;
		bool flag = value.configTexts.Any((LiteralSpec p) => p.type == declaring && p.method == __originalMethod.Name);
		for (int num = 0; num < list.Count; num++)
		{
			CodeInstruction val = list[num];
			if (val.blocks.Count != 0 || (num > 0 && list[num - 1].opcode.OpCodeType == OpCodeType.Prefix))
			{
				value2.skippedBoundaries++;
				list2.Add(val);
				continue;
			}
			if (val.opcode == OpCodes.Ldstr)
			{
				string value3 = val.operand as string;
				if (value.literals.Any((LiteralSpec p) => p.type == __originalMethod.DeclaringType.FullName && p.method == __originalMethod.Name && value3 != null && p.values.ContainsKey(value3)))
				{
					value2.literals++;
					value2.matchedLiterals.Add(value3);
					list2.Add(val);
					list2.Add(new CodeInstruction(OpCodes.Ldstr, (object)value.id));
					list2.Add(new CodeInstruction(OpCodes.Ldstr, (object)__originalMethod.DeclaringType.FullName));
					list2.Add(new CodeInstruction(OpCodes.Ldstr, (object)__originalMethod.Name));
					list2.Add(new CodeInstruction(OpCodes.Call, (object)Literal));
					continue;
				}
			}
			if (flag && ConfigRead(val))
			{
				value2.configTexts++;
				list2.Add(val);
				list2.Add(new CodeInstruction(OpCodes.Ldstr, (object)value.id));
				list2.Add(new CodeInstruction(OpCodes.Ldstr, (object)declaring));
				list2.Add(new CodeInstruction(OpCodes.Ldstr, (object)__originalMethod.Name));
				list2.Add(new CodeInstruction(OpCodes.Call, (object)ConfigText));
				continue;
			}
			FieldInfo fieldInfo = val.operand as FieldInfo;
			if (value.HasDisplayText && val.opcode == OpCodes.Stfld && fieldInfo != null && DisplayField(fieldInfo, value))
			{
				value2.displayFields++;
				CodeInstruction val2 = new CodeInstruction(OpCodes.Ldstr, (object)value.id);
				CodeInstructionExtensions.MoveLabelsFrom(val2, val);
				list2.Add(val2);
				list2.Add(new CodeInstruction(OpCodes.Call, (object)Display));
				list2.Add(val);
				continue;
			}
			MethodBase methodBase = val.operand as MethodBase;
			bool flag2 = val.opcode == OpCodes.Call || val.opcode == OpCodes.Callvirt || val.opcode == OpCodes.Newobj;
			if (!value.HasDisplayText || methodBase == null || !flag2)
			{
				list2.Add(val);
				continue;
			}
			bool[] array = StringSink(methodBase);
			if (!array.Any((bool b) => b))
			{
				list2.Add(val);
				continue;
			}
			ParameterInfo[] parameters = methodBase.GetParameters();
			LocalBuilder[] array2 = parameters.Select((ParameterInfo p) => generator.DeclareLocal(p.ParameterType)).ToArray();
			List<CodeInstruction> list3 = new List<CodeInstruction>();
			for (int num2 = array2.Length - 1; num2 >= 0; num2--)
			{
				list3.Add(new CodeInstruction(OpCodes.Stloc, (object)array2[num2]));
			}
			for (int num3 = 0; num3 < array2.Length; num3++)
			{
				list3.Add(new CodeInstruction(OpCodes.Ldloc, (object)array2[num3]));
				if (array[num3])
				{
					list3.Add(new CodeInstruction(OpCodes.Ldstr, (object)value.id));
					list3.Add(new CodeInstruction(OpCodes.Call, (object)((parameters[num3].ParameterType == typeof(string[])) ? DisplayArray : Display)));
				}
			}
			CodeInstructionExtensions.MoveLabelsFrom(list3[0], val);
			list3.Add(val);
			if (LocalizedReturn(methodBase))
			{
				list3.Add(new CodeInstruction(OpCodes.Ldstr, (object)value.id));
				list3.Add(new CodeInstruction(OpCodes.Call, (object)Display));
			}
			value2.displayCalls++;
			list2.AddRange(list3);
		}
		string text = __originalMethod.DeclaringType.FullName + "." + __originalMethod.Name;
		HashSet<string> hashSet = new HashSet<string>(from i in list
			where i.opcode == OpCodes.Ldstr && i.operand is string
			select (string)i.operand, StringComparer.Ordinal);
		foreach (LiteralSpec item in value.literals.Where((LiteralSpec p) => p.type == __originalMethod.DeclaringType.FullName && p.method == __originalMethod.Name))
		{
			foreach (string key2 in item.values.Keys)
			{
				if (hashSet.Contains(key2) && !value2.matchedLiterals.Contains(key2))
				{
					value.Warn("Literal not replaced: " + text + " :: " + Shown(key2));
				}
			}
		}
		if (flag && value2.configTexts == 0 && list.Any(ConfigRead))
		{
			value.Warn("Config value read not replaced: " + text);
		}
		if (value2.displayCalls + value2.displayFields + value2.literals + value2.configTexts == 0)
		{
			value.Warn("Transpiler installed but no display site changed: " + text);
		}
		return list2;
	}

	internal static string UnsupportedHandler(MethodBase method)
	{
		MethodBody methodBody = method.GetMethodBody();
		if (methodBody == null)
		{
			return null;
		}
		foreach (ExceptionHandlingClause exceptionHandlingClause in methodBody.ExceptionHandlingClauses)
		{
			if (exceptionHandlingClause.Flags == ExceptionHandlingClauseOptions.Filter)
			{
				return "exception filter";
			}
			if (exceptionHandlingClause.Flags == ExceptionHandlingClauseOptions.Fault)
			{
				return "fault block";
			}
		}
		return null;
	}
}
internal static class ExtraHooks
{
	internal static void Install(Harmony harmony)
	{
		//IL_0075: Unknown result type (might be due to invalid IL or missing references)
		//IL_007a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0089: Expected O, but got Unknown
		//IL_0135: Unknown result type (might be due to invalid IL or missing references)
		//IL_013a: Unknown result type (might be due to invalid IL or missing references)
		//IL_014a: Expected O, but got Unknown
		Type type = RuntimeAccess.ExactType("FejdStartup");
		if (type != null)
		{
			foreach (MethodInfo item in from x in type.GetMethods(AccessTools.allDeclared)
				where x.Name == "SetupGui"
				select x)
			{
				harmony.Patch((MethodBase)item, (HarmonyMethod)null, new HarmonyMethod(typeof(ExtraHooks).GetMethod("AfterMenu", AccessTools.all))
				{
					priority = 0
				}, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
		}
		if (!TextEngine.Modules.TryGetValue("OreMines", out var value) || !value.UiAllowed)
		{
			return;
		}
		Type type2 = RuntimeAccess.ExactType("Chat");
		MethodInfo methodInfo = ((type2 == null) ? null : type2.GetMethods(AccessTools.allDeclared).FirstOrDefault((MethodInfo x) => x.Name == "OnNewChatMessage" && x.GetParameters().Any((ParameterInfo p) => p.Name == "text" && p.ParameterType == typeof(string))));
		if (methodInfo == null)
		{
			value.Warn("Chat.OnNewChatMessage(text) was not found; mine-reset chat message is not patched.");
			return;
		}
		try
		{
			harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(ExtraHooks).GetMethod("ReceivedChat", AccessTools.all))
			{
				priority = 0
			}, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		}
		catch (Exception ex)
		{
			value.Warn("Chat message adapter: " + ex.GetType().Name);
		}
	}

	private static void AfterMenu()
	{
		NativeAdapters.Refresh();
		Plugin.WriteReport();
	}

	private static void ReceivedChat(ref string text)
	{
		if (TextEngine.IsRussian && text == "Mines have been reset!")
		{
			text = TextEngine.Display(text, "OreMines");
		}
	}
}
internal static class GameItems
{
	internal static Func<string, string> ItemToken = FindItemToken;

	internal static Func<string, string> SetToken = FindSetToken;

	private static object setsOf;

	private static Dictionary<string, string> sets;

	private static MethodInfo localize;

	internal static string ItemName(string prefab)
	{
		return Shown(Lookup(ItemToken, prefab));
	}

	internal static string SetName(string shown)
	{
		return Shown(Lookup(SetToken, shown));
	}

	internal static void Reset()
	{
		ItemToken = FindItemToken;
		SetToken = FindSetToken;
		setsOf = null;
		sets = null;
		localize = null;
	}

	private static string Lookup(Func<string, string> lookup, string value)
	{
		if (string.IsNullOrEmpty(value))
		{
			return null;
		}
		try
		{
			return lookup(value);
		}
		catch (Exception ex)
		{
			Plugin.Warn("Game name lookup failed for " + value + ": " + ex.GetType().Name);
			return null;
		}
	}

	private static string Shown(string token)
	{
		if (string.IsNullOrEmpty(token))
		{
			return null;
		}
		object main = LocalizationBridge.Main;
		if (main == null)
		{
			return token;
		}
		if (localize == null || localize.DeclaringType != main.GetType())
		{
			localize = AccessTools.Method(main.GetType(), "Localize", new Type[1] { typeof(string) }, (Type[])null);
		}
		object obj;
		if (!(localize == null))
		{
			obj = localize.Invoke(main, new object[1] { token }) as string;
			if (obj == null)
			{
				return token;
			}
		}
		else
		{
			obj = token;
		}
		return (string)obj;
	}

	private static object Database()
	{
		Type type = RuntimeAccess.ExactType("ObjectDB");
		if (!(type == null))
		{
			return RuntimeAccess.Read(type, "instance");
		}
		return null;
	}

	private static object Shared(object prefab)
	{
		GameObject val = (GameObject)((prefab is GameObject) ? prefab : null);
		Type type = RuntimeAccess.ExactType("ItemDrop");
		return RuntimeAccess.Read(RuntimeAccess.Read(((Object)(object)val == (Object)null || type == null) ? null : val.GetComponent(type), "m_itemData"), "m_shared");
	}

	private static string FindItemToken(string prefab)
	{
		object obj = Database();
		MethodInfo methodInfo = ((obj == null) ? null : AccessTools.Method(obj.GetType(), "GetItemPrefab", new Type[1] { typeof(string) }, (Type[])null));
		if (!(methodInfo == null))
		{
			return RuntimeAccess.Read(Shared(methodInfo.Invoke(obj, new object[1] { prefab })), "m_name") as string;
		}
		return null;
	}

	private static string FindSetToken(string shown)
	{
		object obj = Database();
		if (obj == null)
		{
			return null;
		}
		if (obj != setsOf)
		{
			setsOf = obj;
			sets = null;
		}
		if (sets == null)
		{
			Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
			if (RuntimeAccess.Read(obj, "m_items") is IEnumerable enumerable)
			{
				foreach (object item in enumerable)
				{
					object instanceOrType = Shared(item);
					string text = RuntimeAccess.Read(instanceOrType, "m_setName") as string;
					if (!string.IsNullOrEmpty(text) && !dictionary.ContainsKey(text))
					{
						string text2 = RuntimeAccess.Read(RuntimeAccess.Read(instanceOrType, "m_setStatusEffect"), "m_name") as string;
						dictionary[text] = (text.StartsWith("$", StringComparison.Ordinal) ? text : text2);
					}
				}
			}
			sets = dictionary;
		}
		if (!sets.TryGetValue(shown, out var value))
		{
			return null;
		}
		return value;
	}
}
internal static class LocalizationBridge
{
	private sealed class LanguageState
	{
		internal string Language;
	}

	private static ConditionalWeakTable<object, LanguageState> Languages = new ConditionalWeakTable<object, LanguageState>();

	private static readonly Dictionary<string, string> Russian = new Dictionary<string, string>(StringComparer.Ordinal);

	private static readonly Dictionary<string, string> English = new Dictionary<string, string>(StringComparer.Ordinal);

	private static readonly HashSet<string> Replacing = new HashSet<string>(StringComparer.Ordinal);

	private static Type gameType;

	private static MethodInfo translate;

	private static MethodInfo addWord;

	private static MethodInfo selectedLanguage;

	private static Func<string> readPreference;

	private static bool injecting;

	private static bool warnedCache;

	internal static object Main
	{
		get
		{
			if (!(gameType == null))
			{
				return RuntimeAccess.Read(gameType, "m_instance");
			}
			return null;
		}
	}

	internal static void Install(Harmony harmony, Type localizationType, Func<string> languagePreference)
	{
		if (localizationType == null)
		{
			throw new InvalidOperationException("Game Localization class was not found.");
		}
		if (languagePreference == null)
		{
			throw new ArgumentNullException("languagePreference");
		}
		gameType = localizationType;
		readPreference = languagePreference;
		translate = AccessTools.Method(gameType, "Translate", new Type[1] { typeof(string) }, (Type[])null);
		addWord = AccessTools.Method(gameType, "AddWord", new Type[2]
		{
			typeof(string),
			typeof(string)
		}, (Type[])null);
		if (translate == null || translate.IsStatic || translate.ReturnType != typeof(string) || addWord == null || addWord.IsStatic)
		{
			throw new MissingMethodException("Localization.Translate(string)/AddWord(string,string) API unavailable.");
		}
		selectedLanguage = AccessTools.Method(gameType, "GetSelectedLanguage", Type.EmptyTypes, (Type[])null);
		if (selectedLanguage != null && (selectedLanguage.IsStatic || selectedLanguage.ReturnType != typeof(string)))
		{
			selectedLanguage = null;
		}
		Rebuild();
		object main = Main;
		string text = ((main == null) ? readPreference() : SelectedLanguage(main));
		TextEngine.IsRussian = text == "Russian";
		if (main != null)
		{
			SetLanguage(main, text);
		}
		bool flag = false;
		foreach (MethodInfo declaredMethod in AccessTools.GetDeclaredMethods(gameType))
		{
			ParameterInfo[] parameters = declaredMethod.GetParameters();
			if (declaredMethod.Name == "SetupLanguage" && !declaredMethod.IsStatic && parameters.Length != 0 && parameters[0].ParameterType == typeof(string))
			{
				harmony.Patch((MethodBase)declaredMethod, HM("BeforeSetup", 800), HM("AfterSetup", 0), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				flag = true;
			}
			else if (RawDisplay.HasEntries && declaredMethod.Name == "Localize" && !declaredMethod.IsStatic && declaredMethod.ReturnType == typeof(string) && parameters.Length == 1 && parameters[0].ParameterType == typeof(string))
			{
				harmony.Patch((MethodBase)declaredMethod, HM("BeforeLocalize", 0), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
		}
		if (!flag)
		{
			throw new MissingMethodException("Localization.SetupLanguage API unavailable.");
		}
		harmony.Patch((MethodBase)addWord, HM("BeforeAddWord", 0), HM("AfterAddWord", 0), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
	}

	private static HarmonyMethod HM(string name, int priority)
	{
		//IL_0012: Unknown result type (might be due to invalid IL or missing references)
		//IL_0017: Unknown result type (might be due to invalid IL or missing references)
		//IL_001f: Expected O, but got Unknown
		return new HarmonyMethod(AccessTools.Method(typeof(LocalizationBridge), name, (Type[])null, (Type[])null))
		{
			priority = priority
		};
	}

	internal static void Rebuild()
	{
		Russian.Clear();
		English.Clear();
		Replacing.Clear();
		foreach (Module value2 in TextEngine.Modules.Values)
		{
			foreach (KeyValuePair<string, string> word in value2.words)
			{
				if (!value2.englishWords.TryGetValue(word.Key, out var value))
				{
					continue;
				}
				if (Russian.ContainsKey(word.Key) && (Russian[word.Key] != word.Value || English[word.Key] != value))
				{
					value2.Warn("Conflicting localization key skipped: " + word.Key);
					continue;
				}
				Russian[word.Key] = word.Value;
				English[word.Key] = value;
				if (value2.replaceNative.Contains(word.Key))
				{
					Replacing.Add(word.Key);
				}
			}
		}
	}

	private static bool Fills(string current, string key)
	{
		if (!Replacing.Contains(key))
		{
			return ShouldFill(current, English[key], key);
		}
		return current != Russian[key];
	}

	private static void SetLanguage(object instance, string language)
	{
		Languages.GetValue(instance, (object ignored) => new LanguageState()).Language = language;
	}

	private static string Language(object instance)
	{
		if (instance != null && Languages.TryGetValue(instance, out var value))
		{
			return value.Language;
		}
		if (instance != Main)
		{
			return "";
		}
		return SelectedLanguage(instance);
	}

	private static string SelectedLanguage(object main)
	{
		try
		{
			if (selectedLanguage != null)
			{
				return ((string)selectedLanguage.Invoke(main, null)) ?? "";
			}
		}
		catch (Exception ex)
		{
			Plugin.Warn("Localization.GetSelectedLanguage failed: " + ex.GetType().Name);
		}
		if (readPreference != null)
		{
			return readPreference();
		}
		return "";
	}

	private static void BeforeSetup(object __instance, string __0)
	{
		SetLanguage(__instance, __0);
		if (__instance == Main)
		{
			TextEngine.IsRussian = __0 == "Russian";
		}
	}

	private static void AfterSetup(object __instance, string __0)
	{
		if (__instance != Main)
		{
			return;
		}
		TextEngine.IsRussian = __0 == "Russian";
		foreach (Module value in TextEngine.Modules.Values)
		{
			if (value.Table != null)
			{
				value.Table.Clear();
			}
		}
		Inject(__instance, __0);
		PersistentUi.Refresh();
	}

	internal static bool ShouldFill(string current, string english, string key)
	{
		if (!string.IsNullOrEmpty(current) && !(current == english) && !(current == key) && !(current == "$" + key) && !(current == "[" + key + "]"))
		{
			return current == "[$" + key + "]";
		}
		return true;
	}

	private static string CurrentWord(object instance, string key)
	{
		return (string)translate.Invoke(instance, new object[1] { key });
	}

	private static void BeforeAddWord(object __instance, string __0, ref string __1, out bool __state)
	{
		__state = false;
		if (!(Language(__instance) != "Russian") && !string.IsNullOrEmpty(__0) && Russian.TryGetValue(__0, out var value) && English.TryGetValue(__0, out var value2))
		{
			string text = CurrentWord(__instance, __0);
			if (Replacing.Contains(__0))
			{
				__1 = value;
			}
			else if (__1 == value || ShouldFill(__1, value2, __0))
			{
				__1 = (ShouldFill(text, value2, __0) ? value : text);
			}
			__state = !injecting && text != __1;
		}
	}

	private static void AfterAddWord(object __instance, bool __state)
	{
		if (__state)
		{
			InvalidateCache(__instance);
		}
	}

	private static void BeforeLocalize(object __instance, ref string __0)
	{
		if (Language(__instance) == "Russian")
		{
			__0 = RawDisplay.Translate(__0);
		}
	}

	internal static void InjectMain()
	{
		object main = Main;
		if (main != null)
		{
			string text = Language(main);
			bool flag = text == "Russian";
			if (TextEngine.IsRussian != flag)
			{
				TextEngine.IsRussian = flag;
				PersistentUi.Refresh();
			}
			Inject(main, text);
			InvalidateCache(main);
		}
	}

	private static void Inject(object instance, string language)
	{
		if (injecting || instance == null || language != "Russian")
		{
			return;
		}
		injecting = true;
		bool flag = false;
		try
		{
			foreach (KeyValuePair<string, string> item in English)
			{
				if (Fills(CurrentWord(instance, item.Key), item.Key))
				{
					addWord.Invoke(instance, new object[2]
					{
						item.Key,
						Russian[item.Key]
					});
					flag = true;
				}
			}
		}
		catch (Exception ex)
		{
			Plugin.Warn("Localization registration: " + ex.GetType().Name);
		}
		finally
		{
			injecting = false;
			if (flag)
			{
				InvalidateCache(instance);
			}
		}
	}

	private static void InvalidateCache(object instance)
	{
		try
		{
			object obj = RuntimeAccess.Read(instance, "m_cache");
			if (obj != null)
			{
				MethodInfo methodInfo = AccessTools.Method(obj.GetType(), "EvictAll", Type.EmptyTypes, (Type[])null);
				if (methodInfo == null)
				{
					throw new MissingMethodException("EvictAll");
				}
				methodInfo.Invoke(obj, null);
			}
		}
		catch (Exception ex)
		{
			if (!warnedCache)
			{
				Plugin.Warn("Localization cache invalidation unavailable: " + ex.GetType().Name);
			}
			warnedCache = true;
		}
	}

	internal static void RegisterNativeKey(Module module, string key, string en, string ru)
	{
		key = key.TrimStart(new char[1] { '$' });
		module.englishWords[key] = en;
		module.words[key] = ru;
		if (!Russian.ContainsKey(key))
		{
			Russian[key] = ru;
			English[key] = en;
		}
	}

	internal static void Reset()
	{
		Russian.Clear();
		English.Clear();
		Replacing.Clear();
		Languages = new ConditionalWeakTable<object, LanguageState>();
		gameType = null;
		translate = null;
		addWord = null;
		selectedLanguage = null;
		readPreference = null;
		injecting = (warnedCache = false);
		TextEngine.IsRussian = false;
	}
}
[JsonObject(/*Could not decode attribute arguments.*/)]
public sealed class PatternSpec
{
	public string source = "";

	public string target = "";

	public List<int> numeric = new List<int>();

	public Dictionary<string, string> arguments = new Dictionary<string, string>();
}
[JsonObject(/*Could not decode attribute arguments.*/)]
public sealed class MethodSpec
{
	public string type = "";

	public string method = "";
}
[JsonObject(/*Could not decode attribute arguments.*/)]
public sealed class LiteralSpec
{
	public string type = "";

	public string method = "";

	public Dictionary<string, string> values = new Dictionary<string, string>();
}
[JsonObject(/*Could not decode attribute arguments.*/)]
public sealed class PrefabSpec
{
	public string name = "";

	public string kind = "";

	public Dictionary<string, string> fields = new Dictionary<string, string>();
}
[JsonObject(/*Could not decode attribute arguments.*/)]
public sealed class CllcSetting
{
	public string display = "";

	public string desc = "";
}
[JsonObject(/*Could not decode attribute arguments.*/)]
public sealed class CllcLanguage
{
	public Dictionary<string, string> creatureGender = new Dictionary<string, string>();

	public Dictionary<string, string> genderedCreatureTranslations = new Dictionary<string, string>();

	public Dictionary<string, Dictionary<string, string>> genderedTranslations = new Dictionary<string, Dictionary<string, string>>();

	public Dictionary<string, string> translations = new Dictionary<string, string>();

	public Dictionary<string, Dictionary<string, string>> enumTranslations = new Dictionary<string, Dictionary<string, string>>();

	public Dictionary<string, string> settingGroups = new Dictionary<string, string>();

	public Dictionary<string, CllcSetting> settings = new Dictionary<string, CllcSetting>();
}
public sealed class PatchEvidence
{
	public string method;

	public int displayCalls;

	public int displayFields;

	public int literals;

	public int configTexts;

	public int skippedBoundaries;

	public bool returnAdapter;

	public readonly HashSet<string> matchedLiterals = new HashSet<string>(StringComparer.Ordinal);

	public string notPatched;

	public readonly List<string> callSites = new List<string>();

	public readonly List<string> leftAsIs = new List<string>();
}
[JsonObject(/*Could not decode attribute arguments.*/)]
public sealed class Module
{
	public string id = "";

	public string package = "";

	public string version = "";

	public string pluginVersion = "";

	public string assembly = "";

	public List<string> guids = new List<string>();

	public List<string> namespaces = new List<string>();

	public Dictionary<string, string> words = new Dictionary<string, string>();

	public Dictionary<string, string> englishWords = new Dictionary<string, string>();

	public List<string> replaceNative = new List<string>();

	public Dictionary<string, string> texts = new Dictionary<string, string>();

	public Dictionary<string, string> mapLabels = new Dictionary<string, string>();

	public List<PatternSpec> patterns = new List<PatternSpec>();

	public List<MethodSpec> returns = new List<MethodSpec>();

	public List<LiteralSpec> literals = new List<LiteralSpec>();

	public List<LiteralSpec> configTexts = new List<LiteralSpec>();

	public List<PrefabSpec> prefabs = new List<PrefabSpec>();

	public Dictionary<string, string> rawTexts = new Dictionary<string, string>();

	public List<PatternSpec> rawPatterns = new List<PatternSpec>();

	public Dictionary<string, Dictionary<string, string>> terms = new Dictionary<string, Dictionary<string, string>>();

	public CllcLanguage cllc;

	public List<string> sourceFiles = new List<string>();

	public List<string> notes = new List<string>();

	[JsonIgnore]
	public Assembly RuntimeAssembly;

	[JsonIgnore]
	public TextTable Table;

	[JsonIgnore]
	public TextTable RawTable;

	[JsonIgnore]
	public bool ExactVersion;

	[JsonIgnore]
	public int PatchedMethods;

	[JsonIgnore]
	public int ScannedMethods;

	[JsonIgnore]
	public int NativeWords;

	[JsonIgnore]
	public bool UiAllowed;

	[JsonIgnore]
	public readonly Dictionary<string, PatchEvidence> Patches = new Dictionary<string, PatchEvidence>();

	[JsonIgnore]
	public readonly List<string> Warnings = new List<string>();

	public const string GlobalNamespace = "<global>";

	[JsonIgnore]
	public bool HasDisplayText
	{
		get
		{
			if (texts.Count == 0)
			{
				return patterns.Count != 0;
			}
			return true;
		}
	}

	[JsonIgnore]
	public bool NeedsIlAdapters
	{
		get
		{
			if (!HasDisplayText && literals.Count == 0 && configTexts.Count == 0)
			{
				return returns.Count != 0;
			}
			return true;
		}
	}

	public void Warn(string message)
	{
		if (!Warnings.Contains(message))
		{
			Warnings.Add(message);
			Plugin.Warn(id + ": " + message);
		}
	}

	public bool OwnType(Type type)
	{
		string text = type.Namespace ?? "";
		foreach (string @namespace in namespaces)
		{
			if (@namespace == "<global>")
			{
				if (text.Length == 0)
				{
					return true;
				}
			}
			else if (text == @namespace || text.StartsWith(@namespace + ".", StringComparison.Ordinal))
			{
				return true;
			}
		}
		return false;
	}
}
internal static class NativeAdapters
{
	private static bool busy;

	private static EventInfo jotunnEvent;

	private static Action jotunnHandler;

	internal static void Subscribe()
	{
		Unsubscribe();
		Type type = RuntimeAccess.ExactType("Jotunn.Managers.LocalizationManager");
		if (!(type == null))
		{
			jotunnEvent = type.GetEvent("OnLocalizationAdded", AccessTools.all);
			if (jotunnEvent != null && jotunnEvent.EventHandlerType == typeof(Action))
			{
				jotunnHandler = Refresh;
				jotunnEvent.AddEventHandler(null, jotunnHandler);
			}
		}
	}

	internal static void Unsubscribe()
	{
		try
		{
			if (jotunnEvent != null && jotunnHandler != null)
			{
				jotunnEvent.RemoveEventHandler(null, jotunnHandler);
			}
		}
		catch (Exception ex)
		{
			Plugin.Warn("Jotunn unsubscribe: " + ex.GetType().Name);
		}
		finally
		{
			jotunnEvent = null;
			jotunnHandler = null;
		}
	}

	internal static void Refresh()
	{
		if (busy)
		{
			return;
		}
		busy = true;
		try
		{
			foreach (Module value in TextEngine.Modules.Values)
			{
				try
				{
					AddManagerKeys(value);
				}
				catch (Exception ex)
				{
					value.Warn("LocalizeKey adapter: " + ex.GetType().Name);
				}
				try
				{
					AddJotunnScope(value);
				}
				catch (Exception ex2)
				{
					value.Warn("Jotunn adapter: " + ex2.GetType().Name);
				}
				try
				{
					CllcAdapter.Refresh(value);
				}
				catch (Exception ex3)
				{
					value.Warn("CLLC language adapter: " + ex3.GetType().Name);
				}
			}
			LocalizationBridge.Rebuild();
			LocalizationBridge.InjectMain();
		}
		finally
		{
			busy = false;
		}
	}

	private static void AddManagerKeys(Module m)
	{
		if (m.texts.Count == 0)
		{
			return;
		}
		Type[] typesFromAssembly = AccessTools.GetTypesFromAssembly(m.RuntimeAssembly);
		foreach (Type type in typesFromAssembly)
		{
			try
			{
				if (type.Name == "LocalizeKey" && !type.ContainsGenericParameters)
				{
					AddManagerKeys(m, type);
				}
			}
			catch (Exception ex)
			{
				m.Warn("LocalizeKey adapter skipped " + type.Name + ": " + ex.GetType().Name);
			}
		}
	}

	private static void AddManagerKeys(Module m, Type type)
	{
		FieldInfo field = type.GetField("keys", AccessTools.all);
		if (field == null || !field.IsStatic || !(field.GetValue(null) is IEnumerable source))
		{
			return;
		}
		MethodInfo method = type.GetMethod("Russian", AccessTools.all, null, new Type[1] { typeof(string) }, null);
		object[] array = source.Cast<object>().ToArray();
		foreach (object obj in array)
		{
			IDictionary dictionary = RuntimeAccess.Read(obj, "Localizations") as IDictionary;
			string text = RuntimeAccess.Read(obj, "Key") as string;
			if (dictionary != null && text != null && dictionary["English"] is string text2 && m.texts.TryGetValue(text2, out var value) && LocalizationBridge.ShouldFill(dictionary["Russian"] as string, text2, text))
			{
				bool flag = !m.words.ContainsKey(text.TrimStart(new char[1] { '$' }));
				LocalizationBridge.RegisterNativeKey(m, text, text2, value);
				if (method != null)
				{
					method.Invoke(obj, new object[1] { value });
				}
				else
				{
					m.Warn("LocalizeKey.Russian API missing for " + type.FullName + "; global key fallback only.");
				}
				if (flag)
				{
					m.NativeWords++;
				}
			}
		}
	}

	private static void AddJotunnScope(Module m)
	{
		Type type = RuntimeAccess.ExactType("Jotunn.Managers.LocalizationManager");
		if (type == null || m.words.Count == 0 || !(RuntimeAccess.Read(RuntimeAccess.Read(type, "Instance"), "Localizations") is IDictionary dictionary))
		{
			return;
		}
		foreach (string guid in m.guids)
		{
			object obj = dictionary[guid];
			if (obj != null)
			{
				FillJotunnScope(obj, m);
			}
		}
	}

	internal static void FillJotunnScope(object scope, Module m)
	{
		Type type = scope.GetType();
		MethodInfo methodInfo = AccessTools.Method(type, "GetTranslations", new Type[1] { typeof(string).MakeByRefType() }, (Type[])null) ?? AccessTools.Method(type, "GetTranslations", new Type[1] { typeof(string) }, (Type[])null);
		MethodInfo methodInfo2 = AccessTools.Method(type, "AddTranslation", new Type[2]
		{
			typeof(string).MakeByRefType(),
			typeof(Dictionary<string, string>)
		}, (Type[])null) ?? AccessTools.Method(type, "AddTranslation", new Type[2]
		{
			typeof(string),
			typeof(Dictionary<string, string>)
		}, (Type[])null);
		if (methodInfo == null || methodInfo2 == null || !methodInfo.IsPublic || !methodInfo2.IsPublic || methodInfo.IsStatic || methodInfo2.IsStatic)
		{
			m.Warn("Jotunn public GetTranslations/AddTranslation API unavailable; scope left untouched.");
			return;
		}
		if (!(methodInfo.Invoke(scope, new object[1] { "Russian" }) is IReadOnlyDictionary<string, string> readOnlyDictionary))
		{
			m.Warn("Jotunn returned no readable translation table; scope left untouched.");
			return;
		}
		Dictionary<string, string> dictionary = new Dictionary<string, string>();
		foreach (KeyValuePair<string, string> word in m.words)
		{
			readOnlyDictionary.TryGetValue(word.Key, out var value);
			bool flag = m.replaceNative.Contains(word.Key) && value != word.Value;
			if (m.englishWords.TryGetValue(word.Key, out var value2) && (flag || LocalizationBridge.ShouldFill(value, value2, word.Key)))
			{
				dictionary[word.Key] = word.Value;
			}
		}
		if (dictionary.Count != 0)
		{
			methodInfo2.Invoke(scope, new object[2] { "Russian", dictionary });
		}
	}
}
internal static class PersistentUi
{
	private static Type minimap;

	private static readonly List<WeakReference> PortalLabels = new List<WeakReference>();

	internal static bool StandardMapLabels = true;

	internal static void Install(Harmony harmony)
	{
		//IL_01d5: Unknown result type (might be due to invalid IL or missing references)
		//IL_01da: Unknown result type (might be due to invalid IL or missing references)
		//IL_01e9: Expected O, but got Unknown
		//IL_00db: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ef: Expected O, but got Unknown
		minimap = RuntimeAccess.ExactType("Minimap");
		if (StandardMapLabels && TextEngine.Modules.Values.Any((Module m) => m.UiAllowed && m.mapLabels.Count != 0))
		{
			bool flag = false;
			if (minimap != null)
			{
				MethodInfo[] methods = minimap.GetMethods(AccessTools.allDeclared);
				foreach (MethodInfo methodInfo in methods)
				{
					if (!(methodInfo.Name != "CreateMapNamePin") && methodInfo.GetParameters().Any((ParameterInfo p) => p.ParameterType.FullName == "Minimap+PinData"))
					{
						try
						{
							harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(PersistentUi).GetMethod("AfterPinCreated", AccessTools.all))
							{
								priority = 0
							}, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
							flag = true;
						}
						catch (Exception ex)
						{
							Plugin.Warn("Map label adapter: " + ex.GetType().Name);
						}
					}
				}
			}
			if (!flag)
			{
				Plugin.Warn("Minimap.CreateMapNamePin(PinData, ...) unavailable: map labels remain original; no data-changing fallback.");
			}
		}
		if (TextEngine.Modules.TryGetValue("PortalLines", out var value) && value.UiAllowed)
		{
			Type type = value.RuntimeAssembly.GetType("PortalLines.UI.MapToggle", throwOnError: false);
			MethodInfo methodInfo2 = ((type == null) ? null : type.GetMethods(AccessTools.allDeclared).FirstOrDefault((MethodInfo m) => m.Name == "Create" && m.GetParameters().Length == 1));
			if (methodInfo2 == null)
			{
				value.Warn("Persistent MapToggle.Create adapter unavailable.");
			}
			else
			{
				try
				{
					harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(typeof(PersistentUi).GetMethod("AfterToggleCreated", AccessTools.all))
					{
						priority = 0
					}, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				}
				catch (Exception ex2)
				{
					value.Warn("Persistent toggle adapter: " + ex2.GetType().Name);
				}
			}
		}
		Refresh();
	}

	private static void AfterPinCreated(MethodBase __originalMethod, object[] __args)
	{
		ParameterInfo[] parameters = __originalMethod.GetParameters();
		for (int i = 0; i < parameters.Length; i++)
		{
			if (parameters[i].ParameterType.FullName == "Minimap+PinData")
			{
				ApplyPin(__args[i]);
			}
		}
	}

	internal static void ApplyPin(object pin)
	{
		if (!StandardMapLabels || pin == null)
		{
			return;
		}
		try
		{
			string text = RuntimeAccess.Read(pin, "m_name") as string;
			object obj = RuntimeAccess.Read(RuntimeAccess.Read(pin, "m_NamePinData"), "PinNameText");
			if (string.IsNullOrEmpty(text) || obj == null)
			{
				return;
			}
			foreach (Module value2 in TextEngine.Modules.Values)
			{
				if (value2.UiAllowed && value2.mapLabels.TryGetValue(text, out var value))
				{
					UpdateLabel(obj, text, value);
					break;
				}
			}
		}
		catch (Exception ex)
		{
			Plugin.Warn("Map display refresh skipped: " + ex.GetType().Name);
		}
	}

	private static void AfterToggleCreated(object __instance)
	{
		try
		{
			object obj = RuntimeAccess.Read(RuntimeAccess.Read(__instance, "_toggle"), "gameObject");
			Type type = RuntimeAccess.ExactType("TMPro.TMP_Text");
			if (obj == null || type == null)
			{
				return;
			}
			MethodInfo method = obj.GetType().GetMethod("GetComponentInChildren", AccessTools.all, null, new Type[2]
			{
				typeof(Type),
				typeof(bool)
			}, null);
			object label = ((method == null) ? null : method.Invoke(obj, new object[2] { type, true }));
			if (label != null)
			{
				PortalLabels.RemoveAll((WeakReference w) => !w.IsAlive);
				if (!PortalLabels.Any((WeakReference w) => w.Target == label))
				{
					PortalLabels.Add(new WeakReference(label));
				}
				ApplyToggle(label);
			}
		}
		catch (Exception ex)
		{
			WarnPortal("Toggle label refresh skipped: " + ex.GetType().Name);
		}
	}

	private static bool ApplyToggle(object label)
	{
		if (label == null || !TextEngine.Modules.TryGetValue("PortalLines", out var value) || !value.UiAllowed || !value.texts.TryGetValue("Portal lines", out var value2))
		{
			return false;
		}
		try
		{
			UpdateLabel(label, "Portal lines", value2);
			return true;
		}
		catch
		{
			return false;
		}
	}

	internal static void UpdateLabel(object label, string original, string translated)
	{
		PropertyInfo property = label.GetType().GetProperty("text", AccessTools.all);
		if (property == null || !property.CanRead || !property.CanWrite || property.PropertyType != typeof(string))
		{
			return;
		}
		string text = property.GetValue(label, null) as string;
		if (!(text != original) || !(text != translated))
		{
			string text2 = (TextEngine.IsRussian ? translated : original);
			if (text != text2)
			{
				property.SetValue(label, text2, null);
			}
		}
	}

	internal static void Refresh()
	{
		try
		{
			if (RuntimeAccess.Read((minimap == null) ? null : (RuntimeAccess.Read(minimap, "s_instance") ?? RuntimeAccess.Read(minimap, "m_instance")), "m_pins") is IEnumerable enumerable && StandardMapLabels)
			{
				foreach (object item in enumerable)
				{
					ApplyPin(item);
				}
			}
		}
		catch (Exception ex)
		{
			Plugin.Warn("Map language refresh: " + ex.GetType().Name);
		}
		PortalLabels.RemoveAll((WeakReference w) => !ApplyToggle(w.Target));
	}

	private static void WarnPortal(string message)
	{
		if (TextEngine.Modules.TryGetValue("PortalLines", out var value))
		{
			value.Warn(message);
		}
	}

	internal static void Reset()
	{
		PortalLabels.Clear();
		minimap = null;
	}
}
[BepInPlugin("muratovnik.tolmach", "Tolmach", "0.6.1")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
public sealed class Plugin : BaseUnityPlugin
{
	public const string PluginId = "muratovnik.tolmach";

	public const string PluginVersion = "0.6.1";

	private static ManualLogSource Log;

	private static readonly List<string> Notes = new List<string>();

	private static RuntimeReport report;

	private Harmony harmony;

	private bool started;

	private void Awake()
	{
		Log = ((BaseUnityPlugin)this).Logger;
		Notes.Clear();
		report = new RuntimeReport();
		DisplayPatches.Reset();
		CreatureNameDisplay.Reset();
		TextEngine.IsRussian = false;
		((BaseUnityPlugin)this).Logger.LogInfo((object)("Runtime report session: " + report.SessionId));
		WriteReport();
		try
		{
			Initialize();
		}
		catch (Exception ex)
		{
			report.Status = RuntimeStatus.Failed;
			Notes.Add("Initialization failed: " + ex.GetType().Name + ". See the current BepInEx log.");
			((BaseUnityPlugin)this).Logger.LogError((object)("Initialization failed: " + ex));
			WriteReport();
			Cleanup();
		}
	}

	private void Initialize()
	{
		//IL_0230: Unknown result type (might be due to invalid IL or missing references)
		//IL_023a: Expected O, but got Unknown
		if (!((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Enable Russian translations. Restart the game after changing this setting.").Value)
		{
			report.Status = RuntimeStatus.Disabled;
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Disabled in configuration.");
			WriteReport();
			return;
		}
		bool value = ((BaseUnityPlugin)this).Config.Bind<bool>("Compatibility", "OnlyAuditedVersions", false, "Apply code and screen adapters only to the catalog's checked mod versions. Off: try known strings on other versions. Warnings describe missing known adapter targets, not every untranslated string. Native dictionaries work either way.").Value;
		PersistentUi.StandardMapLabels = ((BaseUnityPlugin)this).Config.Bind<bool>("Display", "TranslateStandardMapLabels", true, "Translate exact standard map labels on screen only; stored names remain unchanged. A custom name identical to a standard label is displayed translated too. Disable to keep all map names as entered. Restart required.").Value;
		string path = Path.Combine(Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location), "catalog");
		if (!Directory.Exists(path))
		{
			report.Status = RuntimeStatus.MissingCatalog;
			Notes.Add("Missing catalog directory next to plugin DLL. Reinstall the complete package.");
			((BaseUnityPlugin)this).Logger.LogError((object)"Missing catalog directory next to plugin DLL.");
			WriteReport();
			return;
		}
		foreach (string item in Directory.GetFiles(path, "tolmach-*.json").OrderBy<string, string>((string s) => s, StringComparer.Ordinal))
		{
			try
			{
				Module module = CatalogLoader.Read(item);
				if (!((BaseUnityPlugin)this).Config.Bind<bool>("Modules", module.id, true, "E