Decompiled source of RoleplayNPCs HU v1.0.0

BepInEx/plugins/SeHun-RoleplayNPCsAdapter/SeHun.RoleplayNPCsAdapter.dll

Decompiled a week ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Bootstrap;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using SeHun.Core;
using SeHunTranslationLoader;
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: AssemblyCompany("SeHun.RoleplayNPCsAdapter")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.0.3.0")]
[assembly: AssemblyInformationalVersion("0.0.3")]
[assembly: AssemblyProduct("SeHun.RoleplayNPCsAdapter")]
[assembly: AssemblyTitle("SeHun.RoleplayNPCsAdapter")]
[assembly: AssemblyVersion("0.0.3.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace SeHun.RoleplayNPCsAdapter
{
	public sealed class CatalogTranslationIndex
	{
		private readonly Dictionary<string, string> pairs = new Dictionary<string, string>(StringComparer.Ordinal);

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

		public int MappedRows { get; private set; }

		public int SkippedRows { get; private set; }

		public static string Pair(string en, string ru)
		{
			return en.Length + ":" + en + ru;
		}

		public static JObject Read(Assembly assembly, string resource)
		{
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Expected O, but got Unknown
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Expected O, but got Unknown
			using Stream stream = assembly.GetManifestResourceStream(resource) ?? throw new InvalidDataException("Missing resource: " + resource);
			if (stream.Length > 2097152)
			{
				throw new InvalidDataException("Oversized catalog resource.");
			}
			using StreamReader streamReader = new StreamReader(stream, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true));
			JsonTextReader val = new JsonTextReader((TextReader)streamReader)
			{
				MaxDepth = 32,
				DateParseHandling = (DateParseHandling)0
			};
			try
			{
				return JObject.Load((JsonReader)(object)val, new JsonLoadSettings
				{
					DuplicatePropertyNameHandling = (DuplicatePropertyNameHandling)2
				});
			}
			finally
			{
				((IDisposable)val)?.Dispose();
			}
		}

		public CatalogTranslationIndex(Assembly roleplay, Dictionary<string, string> translations)
		{
			Assembly assembly = typeof(CatalogTranslationIndex).Assembly;
			Dictionary<string, JToken> dictionary = ((IEnumerable<JToken>)Read(assembly, "SeHun.catalog.json")["lines"]).ToDictionary<JToken, string>((JToken x) => (string)x[(object)"id"], StringComparer.Ordinal);
			Dictionary<string, string> dictionary2 = ((IEnumerable<JToken>)Read(assembly, "SeHun.collisions.json")["choices"]).ToDictionary<JToken, string, string>((JToken x) => Pair((string)x[(object)"en"], (string)x[(object)"ru"]), (JToken x) => (string)x[(object)"key"], StringComparer.Ordinal);
			JObject val = Read(roleplay, "RoleplayNPCs.DialogueDefaults.json");
			if ((int?)val["version"] == 1)
			{
				JToken obj = val["lines"];
				JArray val2 = (JArray)(object)((obj is JArray) ? obj : null);
				if (val2 != null && ((JContainer)val2).Count != 0 && ((JContainer)val2).Count <= 4096)
				{
					HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
					HashSet<string> hashSet2 = new HashSet<string>(StringComparer.Ordinal);
					HashSet<string> hashSet3 = new HashSet<string>(StringComparer.Ordinal);
					foreach (JToken row in val2)
					{
						string text = (string)row[(object)"id"];
						string text2 = (string)row[(object)"key"];
						string text3 = (string)row[(object)"en"];
						string text4 = (string)row[(object)"ru"];
						if (string.IsNullOrEmpty(text) || string.IsNullOrEmpty(text2) || text3 == null || text4 == null || text3.Length > 512 || text4.Length > 512 || !hashSet.Add(text) || !hashSet2.Add(text2))
						{
							throw new InvalidDataException("Invalid catalog row.");
						}
						string text5 = Pair(text3, text4);
						if (!dictionary.TryGetValue(text, out var old) || new string[3] { "key", "en", "ru" }.Any((string f) => (string)row[(object)f] != (string)old[(object)f]))
						{
							SkippedRows++;
							continue;
						}
						string value;
						string key = (dictionary2.TryGetValue(text5, out value) ? value : text2);
						if (!translations.TryGetValue(key, out var value2) && !translations.TryGetValue(text3, out value2))
						{
							SkippedRows++;
							continue;
						}
						if (TranslationBridge.Tokens(text3) != TranslationBridge.Tokens(value2))
						{
							throw new InvalidDataException("Catalog placeholder mismatch: " + text);
						}
						if (pairs.TryGetValue(text5, out var value3) && value3 != value2)
						{
							hashSet3.Add(text5);
							SkippedRows++;
							continue;
						}
						pairs[text5] = value2;
						if (text2.StartsWith("@rpnpc|", StringComparison.Ordinal))
						{
							events[text2.Substring(7)] = text5;
						}
						MappedRows++;
					}
					foreach (string item in hashSet3)
					{
						pairs.Remove(item);
					}
					{
						foreach (JToken item2 in (IEnumerable<JToken>)Read(assembly, "SeHun.extra-snapshots.json")["lines"])
						{
							string text6 = (string)item2[(object)"en"];
							string ru = (string)item2[(object)"ru"];
							if (translations.TryGetValue(text6, out var value4))
							{
								pairs[Pair(text6, ru)] = value4;
							}
						}
						return;
					}
				}
			}
			throw new InvalidDataException("Unsupported catalog schema.");
		}

		public bool Snapshot(string en, string ru, out string hu)
		{
			return pairs.TryGetValue(Pair(en ?? "", ru ?? ""), out hu);
		}

		public bool Event(string id, string en, string ru, out string hu)
		{
			hu = null;
			if (events.TryGetValue(id, out var value) && value == Pair(en, ru))
			{
				return pairs.TryGetValue(value, out hu);
			}
			return false;
		}
	}
	public static class Compatibility
	{
		public static void Verify(Assembly roleplay)
		{
			try
			{
				Require(roleplay, "RoleplayNPCs.NpcDialogue", "Localize", typeof(string), typeof(string));
				MethodInfo methodInfo = Require(roleplay, "RoleplayNPCs.NpcSpeechGrammar", "Unpack", typeof(void), typeof(string).MakeByRefType(), typeof(int).MakeByRefType(), typeof(int).MakeByRefType());
				MethodInfo methodInfo2 = Require(roleplay, "RoleplayNPCs.NpcDialogueCatalog", "ReadSnapshot", typeof(bool), typeof(string), typeof(string).MakeByRefType(), typeof(string).MakeByRefType(), typeof(string).MakeByRefType());
				MethodInfo methodInfo3 = Require(roleplay, "RoleplayNPCs.NpcSpeechGrammar", "Resolve", typeof(string), typeof(string), typeof(int), typeof(int));
				int[] array = new int[3] { -1, 0, 1 };
				foreach (int num in array)
				{
					int[] array2 = new int[3] { -1, 0, 1 };
					foreach (int num2 in array2)
					{
						object[] array3 = new object[3]
						{
							"@rpnpcg|" + Code(num) + "|" + Code(num2) + "|probe",
							99,
							99
						};
						methodInfo.Invoke(null, array3);
						Equal(array3[0], "probe", "grammar payload");
						Equal(array3[1], num, "speaker model");
						Equal(array3[2], num2, "listener model");
						Equal(methodInfo3.Invoke(null, new object[3] { "{speaker:male|female|unknown}/{listener:male|female|unknown}/{name}", num, num2 }), Gender(num) + "/" + Gender(num2) + "/{name}", "grammar alternatives");
					}
				}
				object[] array4 = new object[3] { "Talk", 99, 99 };
				methodInfo.Invoke(null, array4);
				Equal(array4[0], "Talk", "plain input");
				Equal(array4[1], -1, "plain speaker");
				Equal(array4[2], -1, "plain listener");
				string[] array5 = new string[2] { "@rpnpcg|x|1|Talk", "@rpnpcg|0|1|@rpnpcg|0|1|Talk" };
				foreach (string text in array5)
				{
					object[] array6 = new object[3] { text, 99, 99 };
					methodInfo.Invoke(null, array6);
					Equal(array6[0], "", "invalid grammar rejection");
				}
				string text2 = "Hello {name}. {speaker:He|She|They} spoke.";
				string text3 = "Привет, {name}.";
				string text4 = "Árvíztűrő";
				object[] array7 = new object[4]
				{
					"@rpnpcd|" + B64(text2) + "|" + B64(text3) + "|" + B64(text4),
					null,
					null,
					null
				};
				Equal(methodInfo2.Invoke(null, array7), true, "snapshot recognition");
				Equal(array7[1], text2, "snapshot English");
				Equal(array7[2], text3, "snapshot Russian");
				Equal(array7[3], text4, "snapshot UTF8 name");
				array5 = new string[3]
				{
					"@rpnpcd|%%%|%%%|",
					"@rpnpcd|YQ==|Yg==",
					"@rpnpcd|" + new string('a', 8193)
				};
				foreach (string text5 in array5)
				{
					object[] array8 = new object[4] { text5, null, null, null };
					Equal(methodInfo2.Invoke(null, array8), true, "invalid snapshot recognition");
					Equal(array8[1], "", "invalid snapshot English rejection");
					Equal(array8[2], "", "invalid snapshot Russian rejection");
					Equal(array8[3], "", "invalid snapshot name rejection");
				}
				object[] parameters = new object[4] { "Talk", null, null, null };
				Equal(methodInfo2.Invoke(null, parameters), false, "plain snapshot fallback");
				Equal(methodInfo3.Invoke(null, new object[3] { "Árvíztűrő {name}", -1, -1 }), "Árvíztűrő {name}", "literal preservation");
				Equal(methodInfo3.Invoke(null, new object[3] { "{speaker:|female|}", 0, 0 }), "", "empty grammar alternative");
			}
			catch (Exception ex)
			{
				Exception ex2 = ((ex is TargetInvocationException && ex.InnerException != null) ? ex.InnerException : ex);
				throw new InvalidOperationException("RoleplayNPCs translation contract incompatible; original text retained. " + ex2.Message, ex2);
			}
		}

		private static MethodInfo Require(Assembly assembly, string type, string name, Type result, params Type[] args)
		{
			MethodInfo methodInfo = TranslationBridge.Method(assembly, type, name, args);
			if (methodInfo.ReturnType != result || methodInfo.ContainsGenericParameters || !methodInfo.IsStatic || methodInfo.GetMethodBody() == null)
			{
				throw new MissingMethodException(type + "." + name + ": unsupported signature or body.");
			}
			return methodInfo;
		}

		private static void Equal(object actual, object expected, string check)
		{
			if (!object.Equals(actual, expected))
			{
				throw new InvalidOperationException("Failed compatibility probe: " + check);
			}
		}

		private static string Code(int model)
		{
			if (model >= 0)
			{
				return model.ToString(CultureInfo.InvariantCulture);
			}
			return "?";
		}

		private static string Gender(int model)
		{
			return model switch
			{
				1 => "female", 
				0 => "male", 
				_ => "unknown", 
			};
		}

		private static string B64(string value)
		{
			return Convert.ToBase64String(Encoding.UTF8.GetBytes(value));
		}
	}
	public sealed class EventTranslation
	{
		private readonly FieldInfo english;

		private readonly FieldInfo russian;

		private readonly FieldInfo store;

		private readonly MethodInfo storeSnapshot;

		private readonly MethodInfo readSnapshot;

		private readonly CatalogTranslationIndex index;

		public EventTranslation(Assembly roleplay, CatalogTranslationIndex index)
		{
			this.index = index;
			Type type = roleplay.GetType("RoleplayNPCs.NpcDialogue", throwOnError: true);
			english = type.GetField("EnglishEvents", BindingFlags.Static | BindingFlags.NonPublic);
			russian = type.GetField("RussianEvents", BindingFlags.Static | BindingFlags.NonPublic);
			FieldInfo[] array = new FieldInfo[2] { english, russian };
			foreach (FieldInfo fieldInfo in array)
			{
				if (fieldInfo == null || fieldInfo.FieldType != typeof(Dictionary<string, string>))
				{
					throw new MissingFieldException("Event dictionary contract changed.");
				}
			}
			store = roleplay.GetType("RoleplayNPCs.NpcDialogueContent", throwOnError: true).GetField("_store", BindingFlags.Static | BindingFlags.NonPublic);
			if (store == null)
			{
				throw new MissingFieldException("Active content store changed.");
			}
			storeSnapshot = store.FieldType.GetMethod("Snapshot", BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[1] { typeof(string) }, null);
			readSnapshot = TranslationBridge.Method(roleplay, "RoleplayNPCs.NpcDialogueCatalog", "ReadSnapshot", typeof(string), typeof(string).MakeByRefType(), typeof(string).MakeByRefType(), typeof(string).MakeByRefType());
			if (storeSnapshot == null || storeSnapshot.ReturnType != typeof(string))
			{
				throw new MissingMethodException("Active snapshot contract changed.");
			}
		}

		public bool Try(string text, out string hu, out string parameter)
		{
			hu = null;
			parameter = null;
			if (!text.StartsWith("@rpnpc|", StringComparison.Ordinal) || text.Length > 1024)
			{
				return false;
			}
			string[] array = text.Substring(7).Split(new char[1] { '|' });
			if (array.Length > 2 || array[0].Length == 0)
			{
				return false;
			}
			Dictionary<string, string> obj = (Dictionary<string, string>)english.GetValue(null);
			Dictionary<string, string> dictionary = (Dictionary<string, string>)russian.GetValue(null);
			if (!obj.TryGetValue(array[0], out var value) || !dictionary.TryGetValue(array[0], out var value2) || !index.Event(array[0], value, value2, out hu))
			{
				return false;
			}
			object value3 = store.GetValue(null);
			if (value3 != null)
			{
				object[] array2 = new object[4]
				{
					storeSnapshot.Invoke(value3, new object[1] { text }),
					null,
					null,
					null
				};
				if (!(bool)readSnapshot.Invoke(null, array2) || (string)array2[1] != value || (string)array2[2] != value2)
				{
					return false;
				}
			}
			parameter = ((array.Length == 2) ? array[1] : "?");
			return true;
		}
	}
	[BepInPlugin("sehun.roleplaynpcs.adapter", "SeHun RoleplayNPCs Adapter TEST", "0.0.3")]
	[BepInDependency("sehun.translationloader", "0.6.0")]
	[BepInDependency("com.roleplaynpcs.valheim", "0.9.32")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string Guid = "sehun.roleplaynpcs.adapter";

		private Adapter adapter;

		private void Awake()
		{
			try
			{
				Assembly assembly = ((object)Chainloader.PluginInfos["com.roleplaynpcs.valheim"].Instance).GetType().Assembly;
				adapter = new Adapter(assembly);
				SeHunPlugin.Adapters.Register((ITranslationAdapter)(object)adapter);
				((BaseUnityPlugin)this).Logger.LogInfo((object)"RoleplayNPCsAdapter 0.0.3: registered; waiting for Loader package dispatch.");
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("RoleplayNPCsAdapter disabled: " + ex.Message));
			}
		}

		private void OnDestroy()
		{
			adapter?.Stop();
		}
	}
	public sealed class Adapter : ITranslationAdapter
	{
		public const string AdapterId = "valheim.roleplaynpcs";

		private readonly Assembly assembly;

		private Harmony harmony;

		private static TranslationBridge bridge;

		private static Action<string> warn;

		private static bool faulted;

		public string Id => "valheim.roleplaynpcs";

		public Adapter(Assembly assembly)
		{
			this.assembly = assembly;
		}

		public void Load(Package package, Action<string> info, Action<string> warning)
		{
			//IL_018b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0191: Expected O, but got Unknown
			//IL_01bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c9: Expected O, but got Unknown
			if (harmony != null)
			{
				throw new InvalidOperationException("Only one RoleplayNPCs test package is supported.");
			}
			if (package.SchemaVersion != 1 || package.Game != "valheim" || package.Adapter != Id || package.Language != "Hungarian")
			{
				throw new InvalidDataException("Expected schema 1, valheim, valheim.roleplaynpcs, Hungarian.");
			}
			RequireHash(typeof(SeHunPlugin).Assembly.Location, "586771DECBAD1CA1A4E1282483A05CDEDBA1DF7A0E4FD48FFAF4B944654AA9C9");
			RequireHash(typeof(ITranslationAdapter).Assembly.Location, "C483573FA7751A2FE6FAEB795F21ACF5B7A4E39B9C53A344DD38968157E545DA");
			Dictionary<string, string> dictionary = Packages.ReadTranslations(package.FilePath);
			TranslationBridge translationBridge = new TranslationBridge(assembly, dictionary);
			info("RoleplayNPCsAdapter catalog: " + translationBridge.Index.MappedRows + " mapped rows; " + translationBridge.Index.SkippedRows + " skipped rows.");
			info("RoleplayNPCsAdapter 0.0.3: translation contract verified for RoleplayNPCs " + assembly.GetName().Version?.ToString() + "; full-mod hash is not required.");
			MethodInfo methodInfo = TranslationBridge.Method(assembly, "RoleplayNPCs.NpcDialogue", "Localize", typeof(string));
			if (methodInfo.ReturnType != typeof(string))
			{
				throw new MissingMethodException("Unexpected Localize return type.");
			}
			Harmony val = new Harmony("sehun.roleplaynpcs.adapter");
			try
			{
				bridge = translationBridge;
				warn = warning;
				faulted = false;
				val.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(Adapter).GetMethod("Postfix", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				RoleplayUiPatches.Install(val, assembly, translationBridge, warning);
				harmony = val;
				info("RoleplayNPCsAdapter 0.0.3: ACTIVE via Loader; " + dictionary.Count + " Hungarian entries; Localize postfix installed.");
			}
			catch
			{
				UnpatchOwned(val);
				bridge = null;
				RoleplayUiPatches.Clear();
				throw;
			}
		}

		public void Stop()
		{
			UnpatchOwned(harmony);
			RoleplayUiPatches.Clear();
			harmony = null;
			bridge = null;
		}

		private static void UnpatchOwned(Harmony patcher)
		{
			if (patcher == null)
			{
				return;
			}
			MethodBase[] array = patcher.GetPatchedMethods().ToArray();
			foreach (MethodBase methodBase in array)
			{
				try
				{
					patcher.Unpatch(methodBase, (HarmonyPatchType)0, "sehun.roleplaynpcs.adapter");
				}
				catch (Exception ex)
				{
					warn?.Invoke("RoleplayNPCsAdapter unpatch failed: " + methodBase.Name + ": " + ex.GetType().Name);
				}
			}
		}

		private static void Postfix(string __0, ref string __result)
		{
			if (bridge == null || faulted)
			{
				return;
			}
			try
			{
				string language = ((Localization.instance == null) ? null : Localization.instance.GetSelectedLanguage());
				__result = bridge.Translate(__0, language, __result);
			}
			catch (Exception ex)
			{
				faulted = true;
				warn?.Invoke("RoleplayNPCsAdapter translation disabled for this session; original text retained: " + ex.Message);
			}
		}

		public static void RequireHash(string path, string expected)
		{
			using FileStream inputStream = File.OpenRead(path);
			using SHA256 sHA = SHA256.Create();
			if (BitConverter.ToString(sHA.ComputeHash(inputStream)).Replace("-", "") != expected)
			{
				throw new InvalidOperationException("Unsupported DLL hash: " + Path.GetFileName(path));
			}
		}
	}
	public sealed class TranslationBridge
	{
		private readonly Dictionary<string, string> translations;

		private readonly MethodInfo unpack;

		private readonly MethodInfo snapshot;

		private readonly MethodInfo resolve;

		public readonly CatalogTranslationIndex Index;

		private readonly EventTranslation events;

		public TranslationBridge(Assembly roleplay, Dictionary<string, string> translations)
		{
			Compatibility.Verify(roleplay);
			this.translations = new Dictionary<string, string>(translations, StringComparer.Ordinal);
			foreach (KeyValuePair<string, string> translation in translations)
			{
				if (string.IsNullOrWhiteSpace(translation.Key) || translation.Key.Length > 512 || string.IsNullOrWhiteSpace(translation.Value) || translation.Value.Length > 512 || translation.Value.Contains("<") || translation.Value.Contains(">"))
				{
					throw new InvalidDataException("Invalid translation value: " + translation.Key);
				}
				if (Tokens(translation.Key) != Tokens(translation.Value))
				{
					throw new InvalidDataException("Placeholder mismatch: " + translation.Key);
				}
			}
			Index = new CatalogTranslationIndex(roleplay, this.translations);
			events = new EventTranslation(roleplay, Index);
			unpack = Method(roleplay, "RoleplayNPCs.NpcSpeechGrammar", "Unpack", typeof(string).MakeByRefType(), typeof(int).MakeByRefType(), typeof(int).MakeByRefType());
			snapshot = Method(roleplay, "RoleplayNPCs.NpcDialogueCatalog", "ReadSnapshot", typeof(string), typeof(string).MakeByRefType(), typeof(string).MakeByRefType(), typeof(string).MakeByRefType());
			resolve = Method(roleplay, "RoleplayNPCs.NpcSpeechGrammar", "Resolve", typeof(string), typeof(int), typeof(int));
		}

		public static string Tokens(string text)
		{
			List<string> list = new List<string>();
			foreach (Match item in Regex.Matches(text, "\\{[^{}]*\\}"))
			{
				if (item.Value == "{name}")
				{
					list.Add("name");
					continue;
				}
				if (Regex.IsMatch(item.Value, "^\\{(speaker|listener):[^{}|]*\\|[^{}|]*\\|[^{}|]*\\}$"))
				{
					list.Add(item.Value.StartsWith("{speaker:") ? "speaker" : "listener");
					continue;
				}
				throw new InvalidDataException("Unknown placeholder: " + item.Value);
			}
			if (Regex.Replace(text, "\\{[^{}]*\\}", "").IndexOfAny(new char[2] { '{', '}' }) >= 0)
			{
				throw new InvalidDataException("Unbalanced placeholder braces.");
			}
			list.Sort(StringComparer.Ordinal);
			return string.Join(",", list);
		}

		public static MethodInfo Method(Assembly assembly, string type, string name, params Type[] args)
		{
			return assembly.GetType(type, throwOnError: true).GetMethod(name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, args, null) ?? throw new MissingMethodException(type, name);
		}

		public string Translate(string input, string language, string original)
		{
			if (language != "Hungarian" || input == null)
			{
				return original;
			}
			object[] array = new object[3] { input, -1, -1 };
			unpack.Invoke(null, array);
			string text = (string)array[0];
			object[] array2 = new object[4] { text, null, null, null };
			bool num = (bool)snapshot.Invoke(null, array2);
			string parameter = null;
			string value;
			if (num)
			{
				if (!Index.Snapshot((string)array2[1], (string)array2[2], out value))
				{
					return original;
				}
				parameter = (string)array2[3];
			}
			else if (text.StartsWith("@rpnpc|", StringComparison.Ordinal))
			{
				if (!events.Try(text, out value, out parameter))
				{
					return original;
				}
			}
			else if (!translations.TryGetValue(text, out value))
			{
				return original;
			}
			value = (string)resolve.Invoke(null, new object[3]
			{
				value,
				array[1],
				array[2]
			});
			if (parameter != null)
			{
				string text2 = (parameter ?? "").Replace("|", "").Replace("<", "").Replace(">", "")
					.Replace("\n", " ")
					.Replace("\r", " ");
				if (text2.Length > 40)
				{
					text2 = text2.Substring(0, 40);
				}
				value = value.Replace("{name}", text2);
			}
			if (value.Length > 512)
			{
				return original;
			}
			return value;
		}
	}
	public static class RoleplayUiPatches
	{
		private static TranslationBridge bridge;

		private static Type wardrobe;

		private static FieldInfo current;

		private static FieldInfo title;

		private static MethodInfo registerSkald;

		private static MethodInfo registerScroll;

		private static Action<string> warning;

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

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

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

		private static readonly Dictionary<Component, Tuple<string, string>> buttonText = new Dictionary<Component, Tuple<string, string>>();

		private static string Language
		{
			get
			{
				Localization instance = Localization.instance;
				if (instance == null)
				{
					return null;
				}
				return instance.GetSelectedLanguage();
			}
		}

		public static string Header(bool isWardrobe, string language, string existing, string translated)
		{
			if (!isWardrobe || !(language == "Hungarian") || !(existing == "NPC wardrobe"))
			{
				return existing;
			}
			return translated;
		}

		private static void Guard(string route, Action action)
		{
			if (failed.Contains(route))
			{
				return;
			}
			try
			{
				action();
			}
			catch (Exception ex)
			{
				failed.Add(route);
				warning?.Invoke("RoleplayNPCsAdapter UI fallback (" + route + "): " + ex.GetType().Name);
			}
		}

		private static void Patch(Harmony harmony, Assembly assembly, string type, string name, string patch, bool prefix, Type result, params Type[] args)
		{
			Guard(type + "." + name, delegate
			{
				//IL_005c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0062: Expected O, but got Unknown
				MethodInfo methodInfo = TranslationBridge.Method(assembly, type, name, args);
				if (methodInfo.ReturnType != result || methodInfo.GetMethodBody() == null)
				{
					throw new MissingMethodException(name);
				}
				HarmonyMethod val = new HarmonyMethod(typeof(RoleplayUiPatches).GetMethod(patch, BindingFlags.Static | BindingFlags.NonPublic));
				harmony.Patch((MethodBase)methodInfo, prefix ? val : null, prefix ? null : val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			});
		}

		public static void Install(Harmony harmony, Assembly assembly, TranslationBridge translator, Action<string> warn)
		{
			bridge = translator;
			warning = warn;
			failed.Clear();
			textPairs.Clear();
			wardrobeMessages.Clear();
			foreach (JToken item in (IEnumerable<JToken>)CatalogTranslationIndex.Read(typeof(RoleplayUiPatches).Assembly, "SeHun.ui-pairs.json")["lines"])
			{
				textPairs.Add(CatalogTranslationIndex.Pair((string)item[(object)"en"], (string)item[(object)"ru"]));
			}
			foreach (JToken item2 in (IEnumerable<JToken>)CatalogTranslationIndex.Read(typeof(RoleplayUiPatches).Assembly, "SeHun.wardrobe-messages.json")["keys"])
			{
				wardrobeMessages.Add((string)item2);
			}
			Patch(harmony, assembly, "RoleplayNPCs.NpcResidentLogbook", "Text", "TextPostfix", false, typeof(string), typeof(string), typeof(string));
			Patch(harmony, assembly, "RoleplayNPCs.NpcInteractionMenu", "ShortLabel", "LabelPostfix", false, typeof(string), typeof(int));
			Patch(harmony, assembly, "RoleplayNPCs.NpcWardrobeRpc", "ShowMessage", "MessagePrefix", true, typeof(void), typeof(string));
			Patch(harmony, assembly, "RoleplayNPCs.NpcSkaldLocalization", "Register", "SkaldPostfix", false, typeof(void), typeof(Localization));
			Patch(harmony, assembly, "RoleplayNPCs.NpcSummoningScroll", "Localize", "ScrollPostfix", false, typeof(void), typeof(Localization));
			Guard("wardrobe-header-install", delegate
			{
				//IL_0131: Unknown result type (might be due to invalid IL or missing references)
				//IL_013e: Expected O, but got Unknown
				wardrobe = assembly.GetType("RoleplayNPCs.NpcWardrobe", throwOnError: true);
				if (!typeof(Component).IsAssignableFrom(wardrobe))
				{
					throw new InvalidOperationException("Wardrobe component changed.");
				}
				Type type = AccessTools.TypeByName("InventoryGui");
				Type type2 = AccessTools.TypeByName("Player");
				current = AccessTools.Field(type, "m_currentContainer");
				title = AccessTools.Field(type, "m_containerName");
				MethodInfo methodInfo = AccessTools.Method(type, "UpdateContainer", new Type[1] { type2 }, (Type[])null);
				if (current == null || title == null || methodInfo == null || methodInfo.ReturnType != typeof(void) || !typeof(Component).IsAssignableFrom(current.FieldType) || title.FieldType.GetProperty("text")?.PropertyType != typeof(string))
				{
					throw new MissingMemberException("Container presentation contract changed.");
				}
				harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(RoleplayUiPatches).GetMethod("ContainerPostfix", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			});
			Guard("initial-words", delegate
			{
				registerSkald = TranslationBridge.Method(assembly, "RoleplayNPCs.NpcSkaldLocalization", "Register", typeof(Localization));
				registerScroll = TranslationBridge.Method(assembly, "RoleplayNPCs.NpcSummoningScroll", "Localize", typeof(Localization));
				if (Localization.instance != null)
				{
					registerSkald.Invoke(null, new object[1] { Localization.instance });
					registerScroll.Invoke(null, new object[1] { Localization.instance });
				}
			});
		}

		private static void TextPostfix(string __0, string __1, ref string __result)
		{
			if (bridge != null && __0 != null && __1 != null && textPairs.Contains(CatalogTranslationIndex.Pair(__0, __1)))
			{
				string original = __result;
				string translated = original;
				Guard("logbook", delegate
				{
					translated = bridge.Translate((__0 == "Close" && __1 == "Закрыть") ? "#ui:logbook:close-button" : __0, Language, original);
				});
				__result = translated;
			}
		}

		private static void LabelPostfix(ref string __result)
		{
			if (!(__result != "Perform") && bridge != null)
			{
				string value = __result;
				Guard("perform", delegate
				{
					value = bridge.Translate("Perform", Language, value);
				});
				__result = value;
			}
		}

		private static void MessagePrefix(ref string __0)
		{
			if (bridge != null && __0 != null && wardrobeMessages.Contains(__0))
			{
				string value = __0;
				Guard("wardrobe-message", delegate
				{
					value = bridge.Translate(value, Language, value);
				});
				__0 = value;
			}
		}

		private static void ContainerPostfix(object __instance)
		{
			if (bridge == null)
			{
				return;
			}
			Guard("wardrobe-header", delegate
			{
				RestoreButtons();
				if (!(Language != "Hungarian"))
				{
					object? value = current.GetValue(__instance);
					Component val = (Component)((value is Component) ? value : null);
					if (!((Object)(object)val == (Object)null) && !((Object)(object)val.GetComponent(wardrobe) == (Object)null))
					{
						object value2 = title.GetValue(__instance);
						if (value2 != null)
						{
							PropertyInfo property = value2.GetType().GetProperty("text");
							string text = (string)property.GetValue(value2, null);
							string text2 = Header(isWardrobe: true, Language, text, bridge.Translate("NPC wardrobe", Language, "NPC wardrobe"));
							if (text2 != text)
							{
								property.SetValue(value2, text2, null);
							}
							SetButton(__instance, "m_takeAllButton", "#ui:wardrobe:take-all");
							SetButton(__instance, "m_stackAllButton", "#ui:wardrobe:stack-all");
						}
					}
				}
			});
		}

		private static void SetButton(object gui, string field, string key)
		{
			object? obj = AccessTools.Field(gui.GetType(), field)?.GetValue(gui);
			Component val = (Component)((obj is Component) ? obj : null);
			Type type = AccessTools.TypeByName("TMPro.TMP_Text");
			if ((Object)(object)val == (Object)null || type == null)
			{
				return;
			}
			Component componentInChildren = val.GetComponentInChildren(type, true);
			if (!((Object)(object)componentInChildren == (Object)null))
			{
				PropertyInfo property = ((object)componentInChildren).GetType().GetProperty("text");
				string text = (string)property.GetValue(componentInChildren, null);
				string text2 = bridge.Translate(key, Language, text);
				if (!(text == text2))
				{
					buttonText[componentInChildren] = Tuple.Create(text, text2);
					property.SetValue(componentInChildren, text2, null);
				}
			}
		}

		private static void RestoreButtons()
		{
			foreach (KeyValuePair<Component, Tuple<string, string>> item in buttonText)
			{
				if (!((Object)(object)item.Key == (Object)null))
				{
					PropertyInfo property = ((object)item.Key).GetType().GetProperty("text");
					if ((string)property.GetValue(item.Key, null) == item.Value.Item2)
					{
						property.SetValue(item.Key, item.Value.Item1, null);
					}
				}
			}
			buttonText.Clear();
		}

		private static void Words(Localization language, string route, params string[] keys)
		{
			if (bridge == null || language == null || language.GetSelectedLanguage() != "Hungarian")
			{
				return;
			}
			Guard(route, delegate
			{
				MethodInfo methodInfo = AccessTools.Method(typeof(Localization), "AddWord", new Type[2]
				{
					typeof(string),
					typeof(string)
				}, (Type[])null);
				for (int i = 0; i < keys.Length; i += 2)
				{
					methodInfo.Invoke(language, new object[2]
					{
						keys[i],
						bridge.Translate(keys[i + 1], "Hungarian", keys[i + 1])
					});
				}
			});
		}

		private static void SkaldPostfix(Localization __0)
		{
			Words(__0, "skald", "rpnpc_inspiration", "Inspiration", "rpnpc_inspiration_tooltip", "A Skald's performance improves skill learning for 20 minutes. Its strength depends on the Skald's mood when the performance begins.");
		}

		private static void ScrollPostfix(Localization __0)
		{
			Words(__0 ?? Localization.instance, "scroll", "rpnpc_scroll_name", "Settlement Charter", "rpnpc_scroll_description", "A reusable scroll for summoning residents and keeping their logbook. Use from inventory or hotbar. Server administrators only.");
		}

		public static void Clear()
		{
			Guard("restore-buttons", RestoreButtons);
			bridge = null;
			Guard("restore-words", delegate
			{
				if (Localization.instance != null)
				{
					registerSkald?.Invoke(null, new object[1] { Localization.instance });
					registerScroll?.Invoke(null, new object[1] { Localization.instance });
				}
			});
		}
	}
}