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 Valheim Traditional Chinese v0.1.1
plugins/ValheimTraditionalChinese/ValheimTraditionalChinese.dll
Decompiled a month agousing System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using HarmonyLib; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: AssemblyVersion("0.0.0.0")] namespace ValheimTraditionalChinese; public static class TranslationData { public static readonly Dictionary<string, string> Tokens = new Dictionary<string, string>(StringComparer.Ordinal); public static readonly Dictionary<string, string> Words = new Dictionary<string, string>(StringComparer.Ordinal); public static readonly Dictionary<string, string> Descriptions = new Dictionary<string, string>(StringComparer.Ordinal); public static readonly Dictionary<string, string> UI = new Dictionary<string, string>(StringComparer.Ordinal); public static string Normalize(string value) { return Regex.Replace(value ?? "", "\\s+", " ").Trim(); } public static string Decode(string value) { return Encoding.UTF8.GetString(Convert.FromBase64String(value)); } public static void Load(string root) { Read(Path.Combine(root, "tokens.tsv"), Tokens); Read(Path.Combine(root, "words.tsv"), Words); Read(Path.Combine(root, "config.tsv"), Descriptions); Read(Path.Combine(root, "ui.tsv"), UI); } private static void Read(string path, Dictionary<string, string> target) { target.Clear(); string[] array = File.ReadAllLines(path, Encoding.UTF8); foreach (string text in array) { if (!string.IsNullOrWhiteSpace(text)) { string[] array2 = text.Split(new char[1] { '\t' }); if (array2.Length != 2) { throw new InvalidDataException("Invalid translation row: " + Path.GetFileName(path)); } string text2 = Decode(array2[0]); if (object.ReferenceEquals(target, Descriptions)) { text2 = Normalize(text2); } target.Add(text2, Decode(array2[1])); } } } public static bool TryToken(string key, out string value) { return Tokens.TryGetValue((key ?? "").TrimStart(new char[1] { '$' }), out value); } public static string Word(string text) { if (text == null || !Words.TryGetValue(text, out var value)) { return text; } return value; } public static string Literal(string text) { if (text == null || !UI.TryGetValue(text, out var value)) { return text; } return value; } public static string Description(string text) { if (Descriptions.TryGetValue(Normalize(text), out var value)) { return value; } string[] array = new string[3] { " [Synced with Server]", " [Not Synced with Server]", " [The value will be enforced on a server.]" }; string[] array2 = new string[3] { " [與伺服器同步]", " [不與伺服器同步]", " [由伺服器強制套用]" }; for (int i = 0; i < array.Length; i++) { if (text != null && text.EndsWith(array[i], StringComparison.Ordinal) && Descriptions.TryGetValue(Normalize(text.Substring(0, text.Length - array[i].Length)), out value)) { return value + array2[i]; } } return text; } } [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.*/)] [BepInPlugin("local.valheim.traditionalchinese", "Valheim Traditional Chinese", "0.1.1")] public sealed class Plugin : BaseUnityPlugin { private static Plugin Instance; private static bool UseTraditional; private static Localization MainLocalization; private Harmony harmony; private static readonly Dictionary<MethodBase, HashSet<string>> MethodStrings = new Dictionary<MethodBase, HashSet<string>>(); private static readonly Dictionary<ConfigDescription, string> OriginalDescriptions = new Dictionary<ConfigDescription, string>(); private static readonly FieldInfo DescriptionField = AccessTools.Field(typeof(ConfigDescription), "<Description>k__BackingField"); private string dataDirectory; private void Awake() { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Expected O, but got Unknown //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Expected O, but got Unknown //IL_0099: Expected O, but got Unknown //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Expected O, but got Unknown //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Expected O, but got Unknown //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_0193: Expected O, but got Unknown Instance = this; try { dataDirectory = Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location); TranslationData.Load(dataDirectory); harmony = new Harmony("local.valheim.traditionalchinese"); harmony.Patch((MethodBase)AccessTools.Method(typeof(Localization), "Translate", new Type[1] { typeof(string) }, (Type[])null), new HarmonyMethod(typeof(Plugin), "TranslatePrefix", (Type[])null), new HarmonyMethod(typeof(Plugin), "TranslatePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)AccessTools.Method(typeof(Localization), "SetupLanguage", new Type[1] { typeof(string) }, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(Plugin), "LanguagePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)AccessTools.PropertyGetter(typeof(ConfigDescription), "Description"), (HarmonyMethod)null, new HarmonyMethod(typeof(Plugin), "DescriptionPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); MainLocalization = Localization.instance; UseTraditional = MainLocalization.GetSelectedLanguage() == "Chinese_Trad"; harmony.Patch((MethodBase)AccessTools.Method(typeof(Localization), "Localize", new Type[1] { typeof(string) }, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(Plugin), "TranslatePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Traditional Chinese data loaded; original mod DLLs and gameplay values are unchanged."); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("Translation initialization failed: " + ex)); if (harmony != null) { harmony.UnpatchSelf(); } } } private void Start() { //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Expected O, but got Unknown //IL_0076: Expected O, but got Unknown if (harmony == null) { return; } try { Type type = AccessTools.TypeByName("Jotunn.Entities.CustomLocalization"); if (type != null) { harmony.Patch((MethodBase)AccessTools.Method(type, "TryTranslate", new Type[1] { typeof(string) }, (Type[])null), new HarmonyMethod(typeof(Plugin), "TranslatePrefix", (Type[])null), new HarmonyMethod(typeof(Plugin), "TranslatePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } InstallLiteralPatches(); RefreshDescriptions(); InstallSpeedyPathsDisplayPatch(); ((MonoBehaviour)this).Invoke("LogTranslationSelfCheck", 3f); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Traditional Chinese ready: " + TranslationData.Tokens.Count + " tokens, " + TranslationData.Descriptions.Count + " setting descriptions; " + MethodStrings.Count + " UI methods.")); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("Some translation hooks could not be installed: " + ex)); } } private static bool TranslatePrefix(object __instance, string __0, ref string __result) { if (CanTranslate(__instance) && TranslationData.TryToken(__0, out var value)) { __result = value; return false; } return true; } private static void TranslatePostfix(object __instance, ref string __result) { if (CanTranslate(__instance)) { __result = TranslationData.Word(__result); } } private static void LanguagePostfix(Localization __instance, bool __result) { if (__result) { UseTraditional = __instance.GetSelectedLanguage() == "Chinese_Trad"; RefreshDescriptions(); } } private static bool CanTranslate(object target) { if (UseTraditional) { if (target is Localization) { return object.ReferenceEquals(target, MainLocalization); } return true; } return false; } private void LogTranslationSelfCheck() { string selectedLanguage = MainLocalization.GetSelectedLanguage(); string text = MainLocalization.Localize("$custom_piece_apiary"); string text2 = MainLocalization.Localize("$custom_item_honeycookeddeer"); string text3 = MainLocalization.Localize("$custom_item_honeycookeddeer_description"); if (selectedLanguage == "Chinese_Trad" && (text != "養蜂工作台" || text2 != "蜜汁鹿里肌" || text3 != "以蜂蜜醃製後烹調的鹿肉。")) { ((BaseUnityPlugin)this).Logger.LogError((object)("HoneyPlus translation self-check FAILED: selected=" + selectedLanguage + "; active=" + UseTraditional + "; station=" + text + "; food=" + text2)); } else { ((BaseUnityPlugin)this).Logger.LogInfo((object)("HoneyPlus translation self-check: selected=" + selectedLanguage + "; active=" + UseTraditional + "; station=" + text + "; food=" + text2 + "; description=" + text3)); } } private static void DescriptionPostfix(ref string __result) { if (UseTraditional) { __result = TranslationData.Description(__result); } } private static void RefreshDescriptions() { if (DescriptionField == null || (Object)(object)Instance == (Object)null) { return; } foreach (PluginInfo value2 in Chainloader.PluginInfos.Values) { if ((Object)(object)value2.Instance == (Object)null) { continue; } foreach (KeyValuePair<ConfigDefinition, ConfigEntryBase> item in value2.Instance.Config) { ConfigDescription description = item.Value.Description; if (description == null) { continue; } if (!OriginalDescriptions.TryGetValue(description, out var value)) { value = (string)DescriptionField.GetValue(description); if (TranslationData.Description(value) == value) { continue; } OriginalDescriptions.Add(description, value); } DescriptionField.SetValue(description, UseTraditional ? TranslationData.Description(value) : value); } } } public static string TranslateLiteral(string text) { if (!UseTraditional) { return text; } return TranslationData.Literal(text); } private void InstallLiteralPatches() { //IL_0208: Unknown result type (might be due to invalid IL or missing references) //IL_0214: Expected O, but got Unknown string[] source = File.ReadAllLines(Path.Combine(dataDirectory, "patches.tsv")); foreach (IGrouping<string, string[]> group in from x in source where !string.IsNullOrWhiteSpace(x) select x.Split(new char[1] { '\t' }) into x group x by x[0]) { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); Func<Assembly, bool> predicate = (Assembly a) => !a.IsDynamic && Path.GetFileName(a.Location).Equals(group.Key, StringComparison.OrdinalIgnoreCase); Assembly assembly = assemblies.FirstOrDefault(predicate); if (assembly == null) { continue; } string text; using (SHA256 sHA = SHA256.Create()) { using FileStream inputStream = File.OpenRead(assembly.Location); text = BitConverter.ToString(sHA.ComputeHash(inputStream)).Replace("-", "").ToLowerInvariant(); } if (text != group.First()[1]) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("UI translation skipped for changed version: " + group.Key + ". Token and setting translations remain available.")); continue; } foreach (IGrouping<int, string[]> item in from x in @group group x by int.Parse(x[2])) { try { MethodBase methodBase = assembly.ManifestModule.ResolveMethod(item.Key); MethodStrings[methodBase] = new HashSet<string>(item.Select((string[] x) => TranslationData.Decode(x[3])), StringComparer.Ordinal); harmony.Patch(methodBase, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(Plugin), "TranslateInstructions", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("UI method skipped: " + group.Key + " " + item.Key + ": " + ex.Message)); } } } } private static IEnumerable<CodeInstruction> TranslateInstructions(IEnumerable<CodeInstruction> instructions, MethodBase __originalMethod) { //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Expected O, but got Unknown if (!MethodStrings.TryGetValue(__originalMethod, out var value)) { return instructions; } List<CodeInstruction> list = new List<CodeInstruction>(); MethodInfo methodInfo = AccessTools.Method(typeof(Plugin), "TranslateLiteral", (Type[])null, (Type[])null); foreach (CodeInstruction instruction in instructions) { list.Add(instruction); if (instruction.opcode == OpCodes.Ldstr && value.Contains((string)instruction.operand)) { list.Add(new CodeInstruction(OpCodes.Call, (object)methodInfo)); } } return list; } private void InstallSpeedyPathsDisplayPatch() { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Expected O, but got Unknown Type type = AccessTools.TypeByName("SpeedyPaths.SpeedyPathsClientMod"); if (!(type == null)) { MethodInfo methodInfo = AccessTools.Method(type, "SpeedyPathsStatusEffect_GetIconText", (Type[])null, (Type[])null); if (methodInfo != null) { harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(Plugin), "SpeedyTextPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } } private static void SpeedyTextPostfix(ref string __1) { if (!UseTraditional || __1 == null) { return; } string[] array = new string[10] { "Dirt Path", "Stone Path", "Cultivated", "Hardwood", "Wood", "Stone", "Iron", "Marble", "Grausten", "Speedy Path" }; string[] array2 = new string[10] { "泥土路", "石板路", "耕地", "圓木", "木材", "石材", "鐵木", "黑大理石", "灰石", "道路加速" }; for (int i = 0; i < array.Length; i++) { if (__1 == array[i] || __1.StartsWith(array[i] + " ", StringComparison.Ordinal) || __1.StartsWith(array[i] + "\n", StringComparison.Ordinal)) { __1 = array2[i] + __1.Substring(array[i].Length); break; } } } private void OnDestroy() { if (harmony != null) { harmony.UnpatchSelf(); } if (DescriptionField != null) { foreach (KeyValuePair<ConfigDescription, string> originalDescription in OriginalDescriptions) { DescriptionField.SetValue(originalDescription.Key, originalDescription.Value); } } OriginalDescriptions.Clear(); MethodStrings.Clear(); Instance = null; MainLocalization = null; } }