Decompiled source of Valheim Traditional Chinese v0.1.1

plugins/ValheimTraditionalChinese/ValheimTraditionalChinese.dll

Decompiled a month ago
using 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;
	}
}