Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of RoleplayNPCs HU v1.0.0
BepInEx/plugins/SeHun-RoleplayNPCsAdapter/SeHun.RoleplayNPCsAdapter.dll
Decompiled a week agousing 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 }); } }); } } }