Decompiled source of Wesleys Enemies Localize v1.0.0

WesleysEnemiesLocalize.dll

Decompiled 20 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using BepInEx;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: IgnoresAccessChecksTo("Assembly-CSharp")]
[assembly: AssemblyCompany("Midorino")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+b24e3fcb591b98a2e17e6d618a7603905068ceed")]
[assembly: AssemblyProduct("WesleysEnemiesLocalize")]
[assembly: AssemblyTitle("WesleysEnemiesLocalize")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[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 WesleysEnemiesLocalize
{
	internal sealed class MiniJson
	{
		private readonly string _text;

		private int _pos;

		private MiniJson(string text)
		{
			_text = text;
		}

		internal static object? Parse(string text)
		{
			MiniJson miniJson = new MiniJson(text);
			if (miniJson._pos < text.Length && text[miniJson._pos] == '\ufeff')
			{
				miniJson._pos++;
			}
			object? result = miniJson.ReadValue();
			miniJson.SkipWhitespace();
			if (miniJson._pos < text.Length)
			{
				throw miniJson.Error("unexpected text after the end");
			}
			return result;
		}

		private object? ReadValue()
		{
			SkipWhitespace();
			if (_pos >= _text.Length)
			{
				throw Error("unexpected end of file");
			}
			char c = _text[_pos];
			switch (c)
			{
			case '{':
				return ReadObject();
			case '[':
				return ReadArray();
			case '"':
				return ReadString();
			case 't':
				ReadWord("true");
				return true;
			case 'f':
				ReadWord("false");
				return false;
			case 'n':
				ReadWord("null");
				return null;
			default:
				switch (c)
				{
				case '-':
				case '0':
				case '1':
				case '2':
				case '3':
				case '4':
				case '5':
				case '6':
				case '7':
				case '8':
				case '9':
					return ReadNumber();
				default:
					throw Error($"unexpected character '{c}'");
				}
			}
		}

		private Dictionary<string, object?> ReadObject()
		{
			Dictionary<string, object> dictionary = new Dictionary<string, object>();
			_pos++;
			SkipWhitespace();
			if (Peek() == '}')
			{
				_pos++;
				return dictionary;
			}
			while (true)
			{
				SkipWhitespace();
				if (Peek() != '"')
				{
					break;
				}
				string key = ReadString();
				SkipWhitespace();
				Expect(':');
				dictionary[key] = ReadValue();
				SkipWhitespace();
				switch (Peek())
				{
				case ',':
					break;
				case '}':
					_pos++;
					return dictionary;
				default:
					throw Error("expected ',' or '}' (a comma may be missing)");
				}
				_pos++;
			}
			throw Error("expected a \"name\"");
		}

		private List<object?> ReadArray()
		{
			List<object> list = new List<object>();
			_pos++;
			SkipWhitespace();
			if (Peek() == ']')
			{
				_pos++;
				return list;
			}
			while (true)
			{
				list.Add(ReadValue());
				SkipWhitespace();
				switch (Peek())
				{
				case ',':
					break;
				case ']':
					_pos++;
					return list;
				default:
					throw Error("expected ',' or ']'");
				}
				_pos++;
			}
		}

		private string ReadString()
		{
			_pos++;
			StringBuilder stringBuilder = new StringBuilder();
			while (_pos < _text.Length)
			{
				char c = _text[_pos++];
				switch (c)
				{
				case '"':
					return stringBuilder.ToString();
				case '\n':
					throw Error("a string is not closed with \" before the end of the line");
				default:
					stringBuilder.Append(c);
					break;
				case '\\':
				{
					if (_pos >= _text.Length)
					{
						throw Error("unexpected end of file");
					}
					char c2 = _text[_pos++];
					switch (c2)
					{
					case '"':
						stringBuilder.Append('"');
						break;
					case '\\':
						stringBuilder.Append('\\');
						break;
					case '/':
						stringBuilder.Append('/');
						break;
					case 'b':
						stringBuilder.Append('\b');
						break;
					case 'f':
						stringBuilder.Append('\f');
						break;
					case 'n':
						stringBuilder.Append('\n');
						break;
					case 'r':
						stringBuilder.Append('\r');
						break;
					case 't':
						stringBuilder.Append('\t');
						break;
					case 'u':
					{
						if (_pos + 4 > _text.Length || !int.TryParse(_text.Substring(_pos, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var result))
						{
							throw Error("bad \\u escape");
						}
						stringBuilder.Append((char)result);
						_pos += 4;
						break;
					}
					default:
						throw Error($"bad escape '\\{c2}'");
					}
					break;
				}
				}
			}
			throw Error("a string is not closed with \"");
		}

		private double ReadNumber()
		{
			int pos = _pos;
			while (_pos < _text.Length && "+-0123456789.eE".IndexOf(_text[_pos]) >= 0)
			{
				_pos++;
			}
			string text = _text.Substring(pos, _pos - pos);
			if (!double.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
			{
				_pos = pos;
				throw Error("bad number '" + text + "'");
			}
			return result;
		}

		private void ReadWord(string word)
		{
			if (string.CompareOrdinal(_text, _pos, word, 0, word.Length) != 0)
			{
				throw Error("unexpected word (did you forget \"quotes\"?)");
			}
			_pos += word.Length;
		}

		private void Expect(char c)
		{
			if (Peek() != c)
			{
				throw Error($"expected '{c}'");
			}
			_pos++;
		}

		private char Peek()
		{
			if (_pos >= _text.Length)
			{
				return '\0';
			}
			return _text[_pos];
		}

		private void SkipWhitespace()
		{
			while (_pos < _text.Length && char.IsWhiteSpace(_text[_pos]))
			{
				_pos++;
			}
		}

		private FormatException Error(string message)
		{
			int num = 1;
			int num2 = 1;
			for (int i = 0; i < _pos && i < _text.Length; i++)
			{
				if (_text[i] == '\n')
				{
					num++;
					num2 = 1;
				}
				else
				{
					num2++;
				}
			}
			return new FormatException($"{message} (line {num}, column {num2})");
		}
	}
	internal sealed class NameTable
	{
		private const float ReloadCheckInterval = 2f;

		private readonly ManualLogSource _log;

		private readonly string _path;

		private readonly string _fileName;

		private readonly string _label;

		private DateTime _loadedWriteTime;

		private float _nextReloadCheck;

		private Dictionary<string, Dictionary<string, string>> _tables = NewTables();

		private string? _cachedLocale;

		private Dictionary<string, string>? _cachedTable;

		private readonly HashSet<string> _reported = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		internal NameTable(ManualLogSource log, string path, string defaultJson, string label, params string[] legacyPaths)
		{
			_log = log;
			_path = path;
			_fileName = Path.GetFileName(path);
			_label = label;
			try
			{
				if (!File.Exists(path))
				{
					string text = Array.Find(legacyPaths, File.Exists);
					if (text != null)
					{
						File.Copy(text, path);
						_log.LogInfo((object)("Copied " + Path.GetFileName(text) + " to " + _fileName));
					}
					else
					{
						File.WriteAllText(path, defaultJson, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
						_log.LogInfo((object)("Created " + path));
					}
				}
			}
			catch (Exception ex)
			{
				_log.LogError((object)("Could not create " + path + ": " + ex.Message));
			}
			Load();
		}

		internal bool TryTranslate(string name, out string translated)
		{
			translated = name;
			try
			{
				ReloadIfChanged();
				Dictionary<string, string> dictionary = CurrentTable();
				if (dictionary != null && dictionary.TryGetValue(name.Trim(), out var value) && !string.IsNullOrEmpty(value))
				{
					translated = value;
					return true;
				}
			}
			catch (Exception arg)
			{
				if (_reported.Add("\0error"))
				{
					_log.LogError((object)$"Translate failed: {arg}");
				}
			}
			return false;
		}

		internal void ReportIfMissing(string name)
		{
			try
			{
				if (!string.IsNullOrEmpty(name))
				{
					Dictionary<string, string> dictionary = CurrentTable();
					if (dictionary != null && !dictionary.ContainsKey(name.Trim()) && _reported.Add(name.Trim()))
					{
						_log.LogDebug((object)("No translation for \"" + name.Trim() + "\" (language " + SemiFunc.LocaleGetCode() + "). Add it to " + _fileName + " if you want."));
					}
				}
			}
			catch (Exception ex)
			{
				_log.LogDebug((object)("ReportIfMissing failed: " + ex.Message));
			}
		}

		private Dictionary<string, string>? CurrentTable()
		{
			string text = SemiFunc.LocaleGetCode() ?? "";
			if (text == _cachedLocale)
			{
				return _cachedTable;
			}
			_cachedLocale = text;
			_cachedTable = null;
			if (_tables.TryGetValue(text, out Dictionary<string, string> value))
			{
				_cachedTable = value;
			}
			else
			{
				int num = text.IndexOf('-');
				if (num > 0 && _tables.TryGetValue(text.Substring(0, num), out Dictionary<string, string> value2))
				{
					_cachedTable = value2;
				}
			}
			return _cachedTable;
		}

		private void ReloadIfChanged()
		{
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			if (!(realtimeSinceStartup < _nextReloadCheck))
			{
				_nextReloadCheck = realtimeSinceStartup + 2f;
				if (File.Exists(_path) && File.GetLastWriteTimeUtc(_path) != _loadedWriteTime)
				{
					Load();
				}
			}
		}

		private void Load()
		{
			try
			{
				if (!File.Exists(_path))
				{
					_log.LogWarning((object)(_path + " not found. No names are translated."));
					return;
				}
				_loadedWriteTime = File.GetLastWriteTimeUtc(_path);
				string text = File.ReadAllText(_path, Encoding.UTF8);
				Dictionary<string, Dictionary<string, string>> dictionary = (_tables = Parse(text));
				_cachedLocale = null;
				_cachedTable = null;
				_reported.Clear();
				List<string> list = new List<string>();
				foreach (KeyValuePair<string, Dictionary<string, string>> item in dictionary)
				{
					list.Add($"{item.Key}={item.Value.Count}");
				}
				_log.LogInfo((object)(_label + ": loaded " + _fileName + " (" + ((list.Count > 0) ? string.Join(", ", list) : "no languages") + ")"));
			}
			catch (Exception ex)
			{
				_log.LogError((object)("Could not read " + _path + ": " + ex.Message + " (keeping the previous names)"));
			}
		}

		private Dictionary<string, Dictionary<string, string>> Parse(string text)
		{
			Dictionary<string, object> obj = (MiniJson.Parse(text) as Dictionary<string, object>) ?? throw new FormatException("the top level must be { ... }");
			Dictionary<string, Dictionary<string, string>> dictionary = NewTables();
			foreach (KeyValuePair<string, object> item in obj)
			{
				if (item.Key.StartsWith("_", StringComparison.Ordinal) || !(item.Value is Dictionary<string, object> dictionary2))
				{
					continue;
				}
				Dictionary<string, string> dictionary3 = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
				foreach (KeyValuePair<string, object> item2 in dictionary2)
				{
					if (!item2.Key.StartsWith("_", StringComparison.Ordinal))
					{
						if (item2.Value is string value)
						{
							dictionary3[item2.Key.Trim()] = value;
						}
						else
						{
							_log.LogWarning((object)(item.Key + ".\"" + item2.Key + "\": the value must be a string in \"quotes\" (ignored)"));
						}
					}
				}
				dictionary[item.Key.Trim()] = dictionary3;
			}
			return dictionary;
		}

		private static Dictionary<string, Dictionary<string, string>> NewTables()
		{
			return new Dictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase);
		}
	}
	[BepInPlugin("Midorino.WesleysEnemiesLocalize", "Wesleys_Enemies_Localize", "1.0.0")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string Guid = "Midorino.WesleysEnemiesLocalize";

		public const string Name = "Wesleys_Enemies_Localize";

		public const string Version = "1.0.0";

		public const string ConfigFileName = "Wesleys_Enemies_Localize.json";

		private const string LegacyConfigFileName = "WesleysEnemiesJP.json";

		private const string DefaultJson = "{\n  \"_readme\": \"Enemy display names per game language (ja = Japanese). Left: English enemy name, right: name to show. Add other languages the same way. Keys starting with _ are ignored. Changes are reloaded while the game is running.\",\n  \"ja\": {\n    \"Gusher\": \"ガッシャー\",\n    \"Roaster\": \"ロースター\",\n    \"Lost droid\": \"迷えるドロイド\",\n    \"Lost Destroyer\": \"喪失の破壊者\",\n    \"Soldier\": \"兵士\",\n    \"Soldier Ghost\": \"幽霊兵士\",\n    \"Voodoo\": \"ブードゥー\"\n  }\n}\n";

		internal static ManualLogSource Log;

		internal static NameTable Names;

		private void Awake()
		{
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			Log = ((BaseUnityPlugin)this).Logger;
			Names = new NameTable(Log, Path.Combine(Paths.ConfigPath, "Wesleys_Enemies_Localize.json"), "{\n  \"_readme\": \"Enemy display names per game language (ja = Japanese). Left: English enemy name, right: name to show. Add other languages the same way. Keys starting with _ are ignored. Changes are reloaded while the game is running.\",\n  \"ja\": {\n    \"Gusher\": \"ガッシャー\",\n    \"Roaster\": \"ロースター\",\n    \"Lost droid\": \"迷えるドロイド\",\n    \"Lost Destroyer\": \"喪失の破壊者\",\n    \"Soldier\": \"兵士\",\n    \"Soldier Ghost\": \"幽霊兵士\",\n    \"Voodoo\": \"ブードゥー\"\n  }\n}\n", "Wesleys_Enemies_Localize 1.0.0", Path.Combine(Paths.ConfigPath, "WesleysEnemiesJP.json"));
			new Harmony("Midorino.WesleysEnemiesLocalize").PatchAll(typeof(Plugin).Assembly);
		}
	}
	[HarmonyPatch(typeof(ItemInfoUI), "ItemInfoText", new Type[]
	{
		typeof(ItemAttributes),
		typeof(string),
		typeof(bool)
	})]
	internal static class ItemInfoTextPatch
	{
		private static void Prefix(ref string message, bool enemy)
		{
			if (enemy && !string.IsNullOrEmpty(message) && Plugin.Names.TryTranslate(message, out string translated))
			{
				message = translated;
			}
		}
	}
	[HarmonyPatch(typeof(EnemyParent), "Awake")]
	internal static class EnemyParentAwakePatch
	{
		private static void Postfix(EnemyParent __instance)
		{
			if (!((Object)(object)__instance.enemyNameLocalized != (Object)null))
			{
				Plugin.Names.ReportIfMissing(__instance.enemyName);
			}
		}
	}
}