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 Tolmach v0.6.1
plugins/Tolmach.dll
Decompiled 7 hours ago
The result has been truncated due to the large size, download it to view full contents!
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