Decompiled source of GermanTranslation v1.0.0

GermanTranslation.dll

Decompiled 11 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using TMPro;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.TextCore;
using UnityEngine.TextCore.LowLevel;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyVersion("0.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace GermanTranslation
{
	internal static class CodeStrings
	{
		private static readonly Dictionary<string, Dictionary<string, string>> map = new Dictionary<string, Dictionary<string, string>>();

		private static readonly Dictionary<MethodBase, Dictionary<string, string>> byMethod = new Dictionary<MethodBase, Dictionary<string, string>>();

		public static int Patched;

		public static void Load(string path)
		{
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Invalid comparison between Unknown and I4
			if (!File.Exists(path))
			{
				Plugin.Log.LogWarning((object)("Missing " + path));
				return;
			}
			foreach (KeyValuePair<string, JToken> item in JObject.Parse(File.ReadAllText(path, Encoding.UTF8)))
			{
				if (item.Key.StartsWith("_"))
				{
					continue;
				}
				JToken value = item.Value;
				JObject val = (JObject)(object)((value is JObject) ? value : null);
				if (val == null)
				{
					continue;
				}
				if (!map.TryGetValue(item.Key, out var value2))
				{
					value2 = (map[item.Key] = new Dictionary<string, string>());
				}
				foreach (KeyValuePair<string, JToken> item2 in val)
				{
					if ((int)item2.Value.Type == 8)
					{
						value2[item2.Key] = (string)item2.Value;
					}
				}
			}
		}

		private static Type FindType(string typeName)
		{
			int num = typeName.IndexOf("d__*", StringComparison.Ordinal);
			if (num < 0)
			{
				return AccessTools.TypeByName(typeName);
			}
			int num2 = typeName.IndexOf('/');
			Type type = AccessTools.TypeByName(typeName.Substring(0, num2).Replace('/', '+'));
			string prefix = typeName.Substring(num2 + 1, num + 3 - num2 - 1);
			return type?.GetNestedTypes(BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((Type t) => t.Name.StartsWith(prefix, StringComparison.Ordinal));
		}

		public static void Patch(Harmony harmony)
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Expected O, but got Unknown
			HarmonyMethod val = new HarmonyMethod(AccessTools.Method(typeof(CodeStrings), "Transpiler", (Type[])null, (Type[])null));
			foreach (KeyValuePair<string, Dictionary<string, string>> item in map)
			{
				int num = item.Key.LastIndexOf("::", StringComparison.Ordinal);
				if (num < 0)
				{
					continue;
				}
				string text = item.Key.Substring(0, num);
				string methodName = item.Key.Substring(num + 2);
				Type type = FindType((text.IndexOf("d__*", StringComparison.Ordinal) >= 0) ? text : text.Replace('/', '+'));
				if (type == null)
				{
					Plugin.Log.LogWarning((object)("Type not found: " + item.Key));
					continue;
				}
				IEnumerable<MethodBase> enumerable = ((methodName == ".ctor") ? AccessTools.GetDeclaredConstructors(type, (bool?)null) : ((!(methodName == ".cctor")) ? (from m in AccessTools.GetDeclaredMethods(type)
					where m.Name == methodName && !m.IsAbstract
					select m).Cast<MethodBase>() : AccessTools.GetDeclaredConstructors(type, (bool?)true)));
				foreach (MethodBase item2 in enumerable)
				{
					byMethod[item2] = item.Value;
					try
					{
						harmony.Patch(item2, (HarmonyMethod)null, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null);
						Patched++;
					}
					catch (Exception ex)
					{
						Plugin.Log.LogError((object)("Code patch " + item.Key + ": " + ex.Message));
					}
				}
			}
		}

		private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> code, MethodBase original)
		{
			byMethod.TryGetValue(original, out var strings);
			foreach (CodeInstruction item in code)
			{
				if (strings != null && item.opcode == OpCodes.Ldstr && item.operand is string key && strings.TryGetValue(key, out var value))
				{
					item.operand = value;
				}
				yield return item;
			}
		}
	}
	internal static class Commands
	{
		private static readonly List<KeyValuePair<string[], string>> phrases = new List<KeyValuePair<string[], string>>();

		private static List<string> englishWords;

		private static bool loggedValidation;

		public static int Count => phrases.Count;

		public static void Load(string path)
		{
			if (!File.Exists(path))
			{
				Plugin.Log.LogWarning((object)("Missing " + path));
				return;
			}
			Dictionary<string, string> dictionary = JsonConvert.DeserializeObject<Dictionary<string, string>>(File.ReadAllText(path, Encoding.UTF8));
			if (dictionary == null)
			{
				return;
			}
			foreach (KeyValuePair<string, string> item in dictionary)
			{
				if (!item.Key.StartsWith("_"))
				{
					Add(item.Key, item.Value);
				}
			}
			Sort();
		}

		internal static void Add(string german, string english)
		{
			string[] array = Normalize(german).Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
			if (array.Length != 0)
			{
				phrases.Add(new KeyValuePair<string[], string>(array, Normalize(english)));
			}
		}

		internal static void Sort()
		{
			phrases.Sort((KeyValuePair<string[], string> a, KeyValuePair<string[], string> b) => b.Key.Length.CompareTo(a.Key.Length));
		}

		private static string Normalize(string s)
		{
			StringBuilder stringBuilder = new StringBuilder();
			foreach (char c in s)
			{
				if (!char.IsPunctuation(c))
				{
					stringBuilder.Append(c);
				}
			}
			return stringBuilder.ToString().ToLowerInvariant();
		}

		public static string Translate(string input)
		{
			if (phrases.Count == 0 || string.IsNullOrEmpty(input))
			{
				return input;
			}
			string[] array = input.Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
			List<string> list = new List<string>();
			int num = 0;
			while (num < array.Length)
			{
				bool flag = false;
				foreach (KeyValuePair<string[], string> phrase in phrases)
				{
					string[] key = phrase.Key;
					if (num + key.Length <= array.Length)
					{
						bool flag2 = true;
						for (int i = 0; i < key.Length && flag2; i++)
						{
							flag2 = string.Equals(array[num + i], key[i], StringComparison.OrdinalIgnoreCase);
						}
						if (flag2)
						{
							list.Add(phrase.Value);
							num += key.Length;
							flag = true;
							break;
						}
					}
				}
				if (!flag)
				{
					list.Add(ByPrefix(array[num]) ?? array[num]);
					num++;
				}
			}
			string text = string.Join(" ", list);
			if (text != input)
			{
				Plugin.Log.LogInfo((object)("terminal: '" + input + "' -> '" + text + "'"));
			}
			return text;
		}

		private static string ByPrefix(string word)
		{
			if (word.Length < 3 || StartsEnglishWord(word))
			{
				return null;
			}
			string text = null;
			foreach (KeyValuePair<string[], string> phrase in phrases)
			{
				if (phrase.Key.Length == 1 && phrase.Key[0].StartsWith(word, StringComparison.OrdinalIgnoreCase))
				{
					if (text != null && text != phrase.Value)
					{
						return null;
					}
					text = phrase.Value;
				}
			}
			return text;
		}

		private static bool StartsEnglishWord(string word)
		{
			if (englishWords == null)
			{
				TerminalKeyword[] array = Object.FindObjectOfType<Terminal>()?.terminalNodes?.allKeywords;
				if (array == null)
				{
					return false;
				}
				englishWords = (from k in array
					where (Object)(object)k != (Object)null && k.word != null
					select k.word.ToLowerInvariant()).ToList();
			}
			return englishWords.Any((string w) => w.StartsWith(word, StringComparison.OrdinalIgnoreCase));
		}

		public static void AllowAnyInput()
		{
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			Terminal[] array = Resources.FindObjectsOfTypeAll<Terminal>();
			for (int i = 0; i < array.Length; i++)
			{
				TMP_InputField screenText = array[i].screenText;
				if (!((Object)(object)screenText == (Object)null) && (int)screenText.characterValidation != 0)
				{
					if (!loggedValidation)
					{
						Plugin.Log.LogInfo((object)$"terminal: input filter {screenText.characterValidation} removed");
						loggedValidation = true;
					}
					screenText.characterValidation = (CharacterValidation)0;
				}
			}
		}
	}
	internal static class Fonts
	{
		private const string Needed = "äöüÄÖÜ߀";

		private static readonly HashSet<int> done = new HashSet<int>();

		private static readonly Dictionary<string, TMP_FontAsset> fallbacks = new Dictionary<string, TMP_FontAsset>();

		public static void Apply()
		{
			Font val = ((IEnumerable<Font>)Resources.FindObjectsOfTypeAll<Font>()).FirstOrDefault((Func<Font, bool>)((Font f) => ((Object)f).name == "3270-Regular"));
			List<string> list = new List<string>();
			TMP_FontAsset[] array = Resources.FindObjectsOfTypeAll<TMP_FontAsset>();
			foreach (TMP_FontAsset asset in array)
			{
				if (fallbacks.ContainsValue(asset) || !done.Add(((Object)asset).GetInstanceID()))
				{
					continue;
				}
				string text = new string("äöüÄÖÜ߀".Where((char c) => !asset.HasCharacter(c, false, false)).ToArray());
				if (text.Length == 0)
				{
					continue;
				}
				if ((Object)(object)val == (Object)null)
				{
					list.Add(((Object)asset).name + " lacks '" + text + "' (no source font)");
					continue;
				}
				TMP_FontAsset val2 = FallbackFor(val, asset);
				if (!((Object)(object)val2 == (Object)null))
				{
					if (asset.fallbackFontAssetTable == null)
					{
						asset.fallbackFontAssetTable = new List<TMP_FontAsset>();
					}
					if (!asset.fallbackFontAssetTable.Contains(val2))
					{
						asset.fallbackFontAssetTable.Insert(0, val2);
					}
					list.Add(((Object)asset).name + " lacks '" + text + "' -> " + ((Object)val2).name);
				}
			}
			if (list.Count > 0)
			{
				Plugin.Log.LogInfo((object)("fonts: " + string.Join("; ", list)));
			}
		}

		private static TMP_FontAsset FallbackFor(Font source, TMP_FontAsset like)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: 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_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_010d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0113: Invalid comparison between Unknown and I4
			//IL_0150: Unknown result type (might be due to invalid IL or missing references)
			//IL_0155: Unknown result type (might be due to invalid IL or missing references)
			//IL_016e: Expected O, but got Unknown
			//IL_0131: Unknown result type (might be due to invalid IL or missing references)
			FaceInfo faceInfo = like.faceInfo;
			int num = Mathf.Max(8, Mathf.RoundToInt((float)((FaceInfo)(ref faceInfo)).pointSize));
			int num2 = ((like.atlasPadding > 0) ? like.atlasPadding : 9);
			GlyphRenderMode atlasRenderMode = like.atlasRenderMode;
			object[] obj = new object[5]
			{
				num,
				num2,
				atlasRenderMode,
				((Object)(object)((TMP_Asset)like).material != (Object)null) ? ((Object)((TMP_Asset)like).material.shader).name : "-",
				null
			};
			Material material = ((TMP_Asset)like).material;
			obj[4] = ((material != null) ? new int?(((Object)material).GetInstanceID()) : ((int?)null));
			string key = string.Format("{0}/{1}/{2}/{3}/{4}", obj);
			if (fallbacks.TryGetValue(key, out var value) && (Object)(object)value != (Object)null)
			{
				return value;
			}
			TMP_FontAsset val = TMP_FontAsset.CreateFontAsset(source, num, num2, (GlyphRenderMode)4165, 1024, 1024, (AtlasPopulationMode)1, true);
			if ((Object)(object)val == (Object)null)
			{
				return null;
			}
			((Object)val).name = "GermanTranslation " + ((Object)like).name;
			if ((int)atlasRenderMode != 4165)
			{
				AccessTools.Field(typeof(TMP_FontAsset), "m_AtlasRenderMode")?.SetValue(val, atlasRenderMode);
			}
			if ((Object)(object)((TMP_Asset)like).material != (Object)null)
			{
				Material val2 = new Material(((TMP_Asset)like).material)
				{
					name = ((Object)val).name + " Material"
				};
				val2.SetTexture(ShaderUtilities.ID_MainTex, (Texture)(object)val.atlasTexture);
				((TMP_Asset)val).material = val2;
			}
			Object.DontDestroyOnLoad((Object)(object)val);
			fallbacks[key] = val;
			return val;
		}
	}
	[HarmonyPatch]
	internal static class TmpTextHook
	{
		private static IEnumerable<MethodBase> TargetMethods()
		{
			yield return AccessTools.PropertySetter(typeof(TMP_Text), "text");
			Type[][] array = new Type[2][]
			{
				new Type[1] { typeof(string) },
				new Type[2]
				{
					typeof(string),
					typeof(bool)
				}
			};
			foreach (Type[] array2 in array)
			{
				MethodInfo methodInfo = AccessTools.Method(typeof(TMP_Text), "SetText", array2, (Type[])null);
				if (methodInfo != null)
				{
					yield return methodInfo;
				}
			}
		}

		private static void Prefix(ref string __0)
		{
			__0 = Translator.Translate(__0);
		}
	}
	[HarmonyPatch]
	internal static class UiTextHook
	{
		private static MethodBase TargetMethod()
		{
			return AccessTools.PropertySetter(typeof(Text), "text");
		}

		private static void Prefix(ref string __0)
		{
			__0 = Translator.Translate(__0);
		}
	}
	[HarmonyPatch(typeof(Terminal), "TextPostProcess")]
	internal static class TerminalOutputHook
	{
		private static void Postfix(ref string __result)
		{
			__result = Words.Apply(StoreNames.Apply(__result));
		}
	}
	[HarmonyPatch(typeof(Terminal), "RemovePunctuation")]
	internal static class TerminalInputHook
	{
		private static void Postfix(ref string __result)
		{
			if (Plugin.GermanCommands.Value)
			{
				__result = Commands.Translate(__result);
			}
		}
	}
	[HarmonyPatch(typeof(HUDManager), "ReadDialogue")]
	internal static class DialogueHook
	{
		private static void Prefix(DialogueSegment[] __0)
		{
			if (__0 == null)
			{
				return;
			}
			foreach (DialogueSegment val in __0)
			{
				if (val != null)
				{
					val.speakerText = Translator.Translate(val.speakerText);
					val.bodyText = Translator.Translate(val.bodyText);
				}
			}
		}
	}
	[HarmonyPatch(typeof(HUDManager), "GetClockTimeFormatted")]
	internal static class ClockHook
	{
		private static void Postfix(ref string __result, float __0, float __1)
		{
			int num = (int)(__0 * (60f * __1)) + 360;
			int num2 = num / 60;
			__result = ((num2 >= 24) ? "00:00" : $"{num2:00}:{num % 60:00}");
		}
	}
	[HarmonyPatch(typeof(StartOfRound), "SetMapScreenInfoToCurrentLevel")]
	internal static class MapScreenHook
	{
		private static void Prefix(StartOfRound __instance)
		{
			SelectableLevel currentLevel = __instance.currentLevel;
			if ((Object)(object)currentLevel != (Object)null)
			{
				currentLevel.LevelDescription = Translator.Translate(currentLevel.LevelDescription);
			}
		}
	}
	[HarmonyPatch(typeof(HUDManager), "SetClock")]
	internal static class HudClockHook
	{
		private static void Postfix(HUDManager __instance, float __0, float __1)
		{
			if (!((Object)(object)__instance.clockNumber == (Object)null))
			{
				int num = (int)(__0 * (60f * __1)) + 360;
				int num2 = num / 60;
				((TMP_Text)__instance.clockNumber).text = ((num2 >= 24) ? "00:00" : $"{num2:00}:{num % 60:00}");
			}
		}
	}
	[BepInPlugin("nergy.germantranslation", "GermanTranslation", "1.0.0")]
	public class Plugin : BaseUnityPlugin
	{
		public const string Guid = "nergy.germantranslation";

		public const string Name = "GermanTranslation";

		public const string Version = "1.0.0";

		internal static ManualLogSource Log;

		internal static string Dir;

		internal static ConfigEntry<bool> LogUntranslated;

		internal static ConfigEntry<bool> GermanCommands;

		internal static ConfigEntry<bool> UseEuro;

		private void Awake()
		{
			//IL_0192: Unknown result type (might be due to invalid IL or missing references)
			//IL_0198: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			Dir = Path.Combine(Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location), "translations");
			LogUntranslated = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "LogUntranslated", false, "Writes every English text without a German translation to BepInEx/GermanTranslation_untranslated.txt (for finding gaps).");
			UseEuro = ((BaseUnityPlugin)this).Config.Bind<bool>("Display", "UseEuro", true, "Show amounts of money with the euro sign (130€) instead of the dollar sign ($130).");
			GermanCommands = ((BaseUnityPlugin)this).Config.Bind<bool>("Terminal", "GermanCommands", true, "Allow German terminal commands (e.g. 'hilfe', 'monde'). English commands always keep working.");
			LoadFile("texts.json", Translator.Load);
			LoadFile("extra.json", Translator.Load);
			LoadFile("code.json", CodeStrings.Load);
			LoadFile("code_extra.json", CodeStrings.Load);
			LoadFile("commands.json", Commands.Load);
			LoadFile("items.json", StoreNames.Load);
			LoadFile("names.json", StoreNames.Load);
			Harmony val = new Harmony("nergy.germantranslation");
			Type[] typesFromAssembly = AccessTools.GetTypesFromAssembly(Assembly.GetExecutingAssembly());
			foreach (Type type in typesFromAssembly)
			{
				if (type.GetCustomAttributes(typeof(HarmonyPatch), inherit: false).Length != 0)
				{
					try
					{
						val.CreateClassProcessor(type).Patch();
					}
					catch (Exception ex)
					{
						Log.LogError((object)("Patch " + type.Name + " failed: " + (ex.InnerException?.Message ?? ex.Message)));
					}
				}
			}
			CodeStrings.Patch(val);
			SweepRunner.Create();
			SceneManager.sceneLoaded += delegate
			{
				SweepRunner.Schedule();
			};
			SweepRunner.Schedule();
			Log.LogInfo((object)string.Format("{0} {1}: {2} texts, {3} patched methods, {4} command aliases, {5} item names", "GermanTranslation", "1.0.0", Translator.Count, CodeStrings.Patched, Commands.Count, StoreNames.Count));
		}

		private static void LoadFile(string name, Action<string> load)
		{
			try
			{
				load(Path.Combine(Dir, name));
			}
			catch (Exception ex)
			{
				Log.LogError((object)("Could not load " + name + ": " + ex.Message));
			}
		}

		internal static void RunSweep()
		{
			Safe("fonts", Fonts.Apply);
			Safe("terminal nodes", Sweeper.TerminalNodes);
			Safe("data", Sweeper.Data);
			Safe("scene text", Sweeper.SceneText);
			Safe("terminal input", Commands.AllowAnyInput);
		}

		private static void Safe(string what, Action a)
		{
			try
			{
				a();
			}
			catch (Exception arg)
			{
				Log.LogError((object)$"{what}: {arg}");
			}
		}
	}
	internal static class StoreNames
	{
		private static readonly List<KeyValuePair<Regex, string>> rules = new List<KeyValuePair<Regex, string>>();

		public static int Count => rules.Count;

		public static void Load(string path)
		{
			if (!File.Exists(path))
			{
				Plugin.Log.LogWarning((object)("Missing " + path));
				return;
			}
			Dictionary<string, string> dictionary = JsonConvert.DeserializeObject<Dictionary<string, string>>(File.ReadAllText(path, Encoding.UTF8));
			if (dictionary == null)
			{
				return;
			}
			foreach (KeyValuePair<string, string> item in dictionary)
			{
				if (!item.Key.StartsWith("_"))
				{
					Regex key = new Regex("(?m)^(\\*\\s*)?" + Regex.Escape(item.Key) + "[ \\t]+//[ \\t]+");
					rules.Add(new KeyValuePair<Regex, string>(key, "$1" + item.Key + " (" + item.Value + ")  //  "));
					Commands.Add(item.Value, item.Key);
					Translator.AddEntry(item.Key, item.Value);
				}
			}
			Commands.Sort();
		}

		public static string Apply(string s)
		{
			if (string.IsNullOrEmpty(s) || s.IndexOf("//", StringComparison.Ordinal) < 0)
			{
				return s;
			}
			foreach (KeyValuePair<Regex, string> rule in rules)
			{
				s = rule.Key.Replace(s, rule.Value);
			}
			return s;
		}
	}
	internal static class Sweeper
	{
		private static readonly (Type type, string[] fields)[] DataFields = new(Type, string[])[5]
		{
			(typeof(SelectableLevel), new string[1] { "LevelDescription" }),
			(typeof(InteractTrigger), new string[3] { "hoverTip", "disabledHoverTip", "holdTip" }),
			(typeof(Item), new string[2] { "toolTips", "itemName" }),
			(typeof(ScanNodeProperties), new string[2] { "headerText", "subText" }),
			(typeof(StunGrenadeItem), new string[1] { "throwString" })
		};

		public static void TerminalNodes()
		{
			TerminalNode[] array = Resources.FindObjectsOfTypeAll<TerminalNode>();
			foreach (TerminalNode val in array)
			{
				string text = Translator.Translate(val.displayText);
				if ((object)text != val.displayText)
				{
					val.displayText = text;
				}
				if (val.creatureName != null)
				{
					string text2 = Translator.Translate(val.creatureName);
					if ((object)text2 != val.creatureName)
					{
						val.creatureName = text2;
					}
				}
			}
		}

		public static void Data()
		{
			(Type, string[])[] dataFields = DataFields;
			for (int i = 0; i < dataFields.Length; i++)
			{
				(Type, string[]) tuple = dataFields[i];
				Type item = tuple.Item1;
				string[] item2 = tuple.Item2;
				List<FieldInfo> list = new List<FieldInfo>();
				string[] array = item2;
				foreach (string text in array)
				{
					FieldInfo fieldInfo = AccessTools.Field(item, text);
					if (fieldInfo != null)
					{
						list.Add(fieldInfo);
					}
				}
				Object[] array2 = Resources.FindObjectsOfTypeAll(item);
				foreach (Object obj in array2)
				{
					foreach (FieldInfo item3 in list)
					{
						TranslateField(obj, item3);
					}
				}
			}
			UnlockablesList[] array3 = Resources.FindObjectsOfTypeAll<UnlockablesList>();
			foreach (UnlockablesList val in array3)
			{
				if (val.unlockables == null)
				{
					continue;
				}
				foreach (UnlockableItem unlockable in val.unlockables)
				{
					string text2 = Translator.Translate(unlockable.unlockableName);
					if ((object)text2 != unlockable.unlockableName)
					{
						unlockable.unlockableName = text2;
					}
				}
			}
			TimeOfDay[] array4 = Resources.FindObjectsOfTypeAll<TimeOfDay>();
			foreach (TimeOfDay obj2 in array4)
			{
				string[] array = new string[2] { "shipLeavingSoonDialogue", "shipLeavingEarlyDialogue" };
				foreach (string text3 in array)
				{
					if (AccessTools.Field(typeof(TimeOfDay), text3)?.GetValue(obj2) is DialogueSegment[] array5)
					{
						DialogueSegment[] array6 = array5;
						foreach (DialogueSegment obj3 in array6)
						{
							obj3.bodyText = Translator.Translate(obj3.bodyText);
							obj3.speakerText = Translator.Translate(obj3.speakerText);
						}
					}
				}
			}
		}

		private static void TranslateField(object obj, FieldInfo field)
		{
			if (field.FieldType == typeof(string))
			{
				string obj2 = (string)field.GetValue(obj);
				string text = Translator.Translate(obj2);
				if ((object)obj2 != text)
				{
					field.SetValue(obj, text);
				}
			}
			else if (field.FieldType == typeof(string[]) && field.GetValue(obj) is string[] array)
			{
				for (int i = 0; i < array.Length; i++)
				{
					array[i] = Translator.Translate(array[i]);
				}
			}
		}

		public static void SceneText()
		{
			TMP_Text[] array = Resources.FindObjectsOfTypeAll<TMP_Text>();
			foreach (TMP_Text val in array)
			{
				string text = Translator.Translate(val.text);
				if ((object)text != val.text)
				{
					val.text = text;
				}
			}
			Text[] array2 = Resources.FindObjectsOfTypeAll<Text>();
			foreach (Text val2 in array2)
			{
				string text2 = Translator.Translate(val2.text);
				if ((object)text2 != val2.text)
				{
					val2.text = text2;
				}
			}
		}
	}
	internal class SweepRunner : MonoBehaviour
	{
		private static SweepRunner instance;

		private readonly List<float> due = new List<float>();

		public static void Create()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Expected O, but got Unknown
			GameObject val = new GameObject("GermanTranslation_Sweeper")
			{
				hideFlags = (HideFlags)61
			};
			Object.DontDestroyOnLoad((Object)val);
			instance = val.AddComponent<SweepRunner>();
		}

		public static void Schedule()
		{
			if (!((Object)(object)instance == (Object)null))
			{
				float unscaledTime = Time.unscaledTime;
				instance.due.Clear();
				instance.due.AddRange(new float[4]
				{
					unscaledTime,
					unscaledTime + 1f,
					unscaledTime + 3f,
					unscaledTime + 10f
				});
			}
		}

		private void Update()
		{
			if (due.Count != 0 && !(Time.unscaledTime < due[0]))
			{
				due.RemoveAt(0);
				Plugin.RunSweep();
			}
		}
	}
	internal static class Translator
	{
		private static readonly Dictionary<string, string> exact = new Dictionary<string, string>();

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

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

		private static readonly Regex Dollars = new Regex("\\$(\\d+(?:[.,]\\d+)?)");

		public static int Count => exact.Count;

		public static void Load(string path)
		{
			if (!File.Exists(path))
			{
				Plugin.Log.LogWarning((object)("Missing " + path));
				return;
			}
			Dictionary<string, string> dictionary = JsonConvert.DeserializeObject<Dictionary<string, string>>(File.ReadAllText(path, Encoding.UTF8));
			if (dictionary == null)
			{
				return;
			}
			foreach (KeyValuePair<string, string> item in dictionary)
			{
				if (!item.Key.StartsWith("_"))
				{
					exact[item.Key] = item.Value;
					germanValues.Add(item.Value);
				}
			}
		}

		internal static void AddEntry(string english, string german)
		{
			if (!exact.ContainsKey(english))
			{
				exact[english] = german;
			}
			germanValues.Add(german);
		}

		public static string Translate(string s)
		{
			string text = TranslateCore(s);
			if (text != null && Plugin.UseEuro != null && Plugin.UseEuro.Value && text.IndexOf('$') >= 0)
			{
				text = Dollars.Replace(text, "$1€");
			}
			return text;
		}

		private static string TranslateCore(string s)
		{
			if (string.IsNullOrEmpty(s) || exact.Count == 0)
			{
				return s;
			}
			int i = 0;
			int num;
			for (num = s.Length; i < num && char.IsWhiteSpace(s[i]); i++)
			{
			}
			while (num > i && char.IsWhiteSpace(s[num - 1]))
			{
				num--;
			}
			if (i == num)
			{
				return s;
			}
			string text = ((i == 0 && num == s.Length) ? s : s.Substring(i, num - i));
			if (exact.TryGetValue(text, out var value))
			{
				if (text.Length != s.Length)
				{
					return s.Substring(0, i) + value + s.Substring(num);
				}
				return value;
			}
			string text2 = Words.Apply(s);
			if ((object)text2 != s)
			{
				return text2;
			}
			ReportMissing(text);
			return s;
		}

		private static void ReportMissing(string core)
		{
			if (!Plugin.LogUntranslated.Value || core.Length < 3 || germanValues.Contains(core) || core.StartsWith("FPS:"))
			{
				return;
			}
			bool flag = false;
			for (int i = 0; i < core.Length; i++)
			{
				if (char.IsLetter(core[i]))
				{
					flag = true;
					break;
				}
			}
			if (!flag || !reported.Add(core))
			{
				return;
			}
			try
			{
				File.AppendAllText(Path.Combine(Paths.BepInExRootPath, "GermanTranslation_untranslated.txt"), JsonConvert.ToString(core) + Environment.NewLine, Encoding.UTF8);
			}
			catch
			{
			}
		}
	}
	internal static class Words
	{
		private static readonly Dictionary<string, string> Weather = new Dictionary<string, string>
		{
			["Rainy"] = "Regen",
			["Stormy"] = "Gewitter",
			["Foggy"] = "Nebel",
			["Flooded"] = "Überflutet",
			["Eclipsed"] = "Sonnenfinsternis",
			["DustClouds"] = "Staubwolken",
			["None"] = "Klar"
		};

		private static readonly Dictionary<string, string> CauseOfDeath = new Dictionary<string, string>
		{
			["Unknown"] = "Unbekannt",
			["Bludgeoning"] = "Stumpfe Gewalt",
			["Gravity"] = "Schwerkraft",
			["Blast"] = "Explosion",
			["Strangulation"] = "Strangulation",
			["Suffocation"] = "Ersticken",
			["Mauling"] = "Zerfleischt",
			["Gunshots"] = "Schussverletzungen",
			["Crushing"] = "Zerquetscht",
			["Drowning"] = "Ertrinken",
			["Abandoned"] = "Zurückgelassen",
			["Electrocution"] = "Stromschlag",
			["Kicking"] = "Tritte",
			["Burning"] = "Verbrennen",
			["Stabbing"] = "Erstochen",
			["Fan"] = "Ventilator",
			["Inertia"] = "Trägheit",
			["Snipping"] = "Abgeschnitten",
			["Scratching"] = "Zerkratzt"
		};

		private static readonly Dictionary<string, string> Days = new Dictionary<string, string>
		{
			["Monday"] = "Montag",
			["Tuesday"] = "Dienstag",
			["Wednesday"] = "Mittwoch",
			["Thursday"] = "Donnerstag",
			["Friday"] = "Freitag",
			["Saturday"] = "Samstag",
			["Sunday"] = "Sonntag"
		};

		private static readonly Regex Greeting = new Regex("(Schönen )(Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday)\\b");

		private static readonly Regex InList = new Regex("\\((Rainy|Stormy|Foggy|Flooded|Eclipsed|DustClouds)\\)");

		private static readonly Regex OnScreen = new Regex("(Wetter: )(Rainy|Stormy|Foggy|Flooded|Eclipsed|DustClouds|None)\\b");

		private static readonly Regex Cause = new Regex("(Todesursache: )(Unknown|Bludgeoning|Gravity|Blast|Strangulation|Suffocation|Mauling|Gunshots|Crushing|Drowning|Abandoned|Electrocution|Kicking|Burning|Stabbing|Fan|Inertia|Snipping|Scratching)\\b");

		private static readonly KeyValuePair<string, string>[] MicPrefixes = new KeyValuePair<string, string>[3]
		{
			new KeyValuePair<string, string>("MIC VOLUME: ", "MIKROFONLAUTSTÄRKE: "),
			new KeyValuePair<string, string>("NOISE GATE: ", "RAUSCHSPERRE: "),
			new KeyValuePair<string, string>("NOISE SUPPRESSION: ", "RAUSCHUNTERDRÜCKUNG: ")
		};

		public static string Apply(string s)
		{
			if (string.IsNullOrEmpty(s))
			{
				return s;
			}
			if (s.IndexOf('(') >= 0)
			{
				s = InList.Replace(s, (Match m) => "(" + Weather[m.Groups[1].Value] + ")");
			}
			if (s.IndexOf("Wetter: ", StringComparison.Ordinal) >= 0)
			{
				s = OnScreen.Replace(s, (Match m) => m.Groups[1].Value + Weather[m.Groups[2].Value]);
			}
			s = RuntimeTexts(s);
			if (s.IndexOf("Schönen ", StringComparison.Ordinal) >= 0)
			{
				s = Greeting.Replace(s, (Match m) => m.Groups[1].Value + Days[m.Groups[2].Value]);
			}
			if (s.EndsWith(" lb", StringComparison.Ordinal))
			{
				s = PoundsToKg(s);
			}
			if (s.StartsWith("Player #", StringComparison.Ordinal))
			{
				s = "Spieler #" + s.Substring("Player #".Length);
			}
			if (s.StartsWith("MIC ", StringComparison.Ordinal) || s.StartsWith("NOISE ", StringComparison.Ordinal))
			{
				s = MicLabels(s);
			}
			if (s.IndexOf("Todesursache: ", StringComparison.Ordinal) >= 0)
			{
				s = Cause.Replace(s, (Match m) => m.Groups[1].Value + CauseOfDeath[m.Groups[2].Value]);
			}
			return s;
		}

		private static string RuntimeTexts(string s)
		{
			if (s.StartsWith("VALUE: ", StringComparison.Ordinal))
			{
				s = "WERT: " + s.Substring(7);
			}
			else if (s.StartsWith("Value: ", StringComparison.Ordinal))
			{
				s = "Wert: " + s.Substring(7);
			}
			if (s.IndexOf("1 Tage", StringComparison.Ordinal) >= 0)
			{
				s = Regex.Replace(s, "(?<!\\d)1 Tage\\b", "1 Tag");
			}
			if (s.IndexOf("Filter quality: ", StringComparison.Ordinal) >= 0)
			{
				s = s.Replace("HEALTH RISK!\nAir filter overwhelmed by particulates;\n\nFilter quality: ", "GESUNDHEITSRISIKO!\nLuftfilter durch Partikel überlastet;\n\nFilterqualität: ");
			}
			if (s.IndexOf("REACHING ", StringComparison.Ordinal) >= 0)
			{
				s = s.Replace("HIGH FEVER DETECTED!\nREACHING ", "HOHES FIEBER ERKANNT!\nSTEIGT AUF ");
				s = Regex.Replace(s, "(\\d+)°F", (Match m) => Math.Round((double)(int.Parse(m.Groups[1].Value) - 32) / 1.8) + "°C");
			}
			return s;
		}

		private static string PoundsToKg(string s)
		{
			Match match = Regex.Match(s, "^(\\d+) lb$");
			if (!match.Success)
			{
				return s;
			}
			return (int)Math.Round((double)int.Parse(match.Groups[1].Value) * 0.45359237) + " kg";
		}

		private static string MicLabels(string s)
		{
			KeyValuePair<string, string>[] micPrefixes = MicPrefixes;
			for (int i = 0; i < micPrefixes.Length; i++)
			{
				KeyValuePair<string, string> keyValuePair = micPrefixes[i];
				if (s.StartsWith(keyValuePair.Key, StringComparison.Ordinal))
				{
					return keyValuePair.Value + s.Substring(keyValuePair.Key.Length);
				}
			}
			return s;
		}
	}
}