Decompiled source of HowtoFishLocalizationAPI v1.0.0

BepInEx/plugins/HowtoFishLocalizationAPI/HowtoFishLocalizationAPI.dll

Decompiled 8 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using HowtoFishLocalizationAPI.Api;
using HowtoFishLocalizationAPI.Integrations.HTFModManager;
using HowtoFishLocalizationAPI.Integrations.ModMenu;
using Newtonsoft.Json;
using UnityEngine;
using UnityEngine.Localization;
using UnityEngine.Localization.Settings;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("HowtoFishLocalizationAPI")]
[assembly: AssemblyDescription("How to Fish Localization API by Ice Box Studio")]
[assembly: AssemblyCompany("Ice Box Studio")]
[assembly: AssemblyProduct("HowtoFishLocalizationAPI")]
[assembly: AssemblyCopyright("Copyright © 2026 Ice Box Studio All rights reserved.")]
[assembly: ComVisible(false)]
[assembly: Guid("79870812-2aca-43d5-b058-5844d23fb3b4")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace HowtoFishLocalizationAPI
{
	[BepInPlugin("IceBoxStudio.HowToFish.LocalizationAPI", "HowtoFishLocalizationAPI", "1.0.0")]
	public sealed class HowtoFishLocalizationAPI : BaseUnityPlugin
	{
		private Harmony _harmony;

		internal static ManualLogSource Log { get; private set; }

		private void Awake()
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			LocalizationApi.NotifyLanguageChanged();
			_harmony = new Harmony("IceBoxStudio.HowToFish.LocalizationAPI");
			_harmony.PatchAll(Assembly.GetExecutingAssembly());
			ModMenuSupport.Init(_harmony);
			Log.LogInfo((object)"HowtoFishLocalizationAPI 1.0.0 initialized.");
		}
	}
	public static class PluginInfo
	{
		public const string PLUGIN_GUID = "IceBoxStudio.HowToFish.LocalizationAPI";

		public const string PLUGIN_NAME = "HowtoFishLocalizationAPI";

		public const string PLUGIN_VERSION = "1.0.0";
	}
}
namespace HowtoFishLocalizationAPI.Patches
{
	[HarmonyPatch(typeof(LocalizationManager), "SetLanguage")]
	internal static class GameInitializationPatch
	{
		[HarmonyPostfix]
		private static void Postfix(int __0)
		{
			LocalizationApi.NotifyLanguageChanged(__0);
		}
	}
}
namespace HowtoFishLocalizationAPI.Integrations.ModMenu
{
	internal static class ModMenuPatch
	{
		private static void Row(Transform __0, ConfigEntryBase __1)
		{
			ModMenuSupport.LocalizeRow(__0, __1);
		}
	}
	internal static class ModMenuSupport
	{
		private const string ModMenuAssembly = "HowToFish.ModMenu";

		private const string ModMenuType = "HowToFish.ModMenu.NativeModMenu";

		private const string ModMenuGuid = "community.howtofish.modmenu";

		private const string LabelPrefix = "modmenu.config.";

		private static readonly Dictionary<ConfigFile, string> Owners = new Dictionary<ConfigFile, string>();

		private static Harmony _harmony;

		private static bool _patched;

		private static bool _initialized;

		internal static void Init(Harmony harmony)
		{
			if (!_initialized)
			{
				_initialized = true;
				_harmony = harmony;
				LocalizationApi.LanguageChanged += LangChanged;
				AppDomain.CurrentDomain.AssemblyLoad += Loaded;
				Patch();
				HTFModManagerSupport.Init(harmony);
			}
		}

		internal static void LocalizeRow(Transform parent, ConfigEntryBase entry)
		{
			try
			{
				if ((Object)(object)parent == (Object)null || entry == null || !Owner(entry, out var guid))
				{
					return;
				}
				ConfigDefinition definition = entry.Definition;
				string text = "modmenu.config." + definition.Section + "." + definition.Key;
				ModLocalizer modLocalizer = Localizer(guid);
				if (modLocalizer == null)
				{
					return;
				}
				string localizedText = modLocalizer.GetLocalizedText(text);
				if (string.Equals(localizedText, text, StringComparison.Ordinal))
				{
					return;
				}
				string b = "SettingRow_" + definition.Key;
				Transform val = null;
				for (int num = parent.childCount - 1; num >= 0; num--)
				{
					Transform child = parent.GetChild(num);
					if (!((Object)(object)child == (Object)null) && string.Equals(((Object)child).name, b, StringComparison.Ordinal))
					{
						val = child.Find("Label");
						if ((Object)(object)val != (Object)null)
						{
							break;
						}
					}
				}
				if ((Object)(object)val == (Object)null)
				{
					return;
				}
				Component[] components = ((Component)val).GetComponents<Component>();
				foreach (Component val2 in components)
				{
					if (!((Object)(object)val2 == (Object)null))
					{
						PropertyInfo property = ((object)val2).GetType().GetProperty("text", BindingFlags.Instance | BindingFlags.Public);
						if (!(property == null) && property.CanWrite && !(property.PropertyType != typeof(string)))
						{
							property.SetValue(val2, localizedText, null);
							break;
						}
					}
				}
			}
			catch
			{
			}
		}

		private static void Patch()
		{
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Expected O, but got Unknown
			if (_patched || _harmony == null)
			{
				return;
			}
			Type type = Find("HowToFish.ModMenu.NativeModMenu");
			if (type == null)
			{
				return;
			}
			MethodInfo methodInfo = AccessTools.Method(type, "CreateConfigSettingRow", new Type[4]
			{
				typeof(Transform),
				typeof(ConfigEntryBase),
				typeof(string),
				typeof(float)
			}, (Type[])null);
			MethodInfo methodInfo2 = AccessTools.Method(typeof(ModMenuPatch), "Row", (Type[])null, (Type[])null);
			if (methodInfo == null || methodInfo2 == null)
			{
				return;
			}
			try
			{
				_harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				_patched = true;
				AppDomain.CurrentDomain.AssemblyLoad -= Loaded;
			}
			catch
			{
			}
		}

		private static Type Find(string fullName)
		{
			Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
			foreach (Assembly assembly in assemblies)
			{
				if (string.Equals(assembly.GetName().Name, "HowToFish.ModMenu", StringComparison.OrdinalIgnoreCase))
				{
					return assembly.GetType(fullName, throwOnError: false);
				}
			}
			return null;
		}

		private static void Loaded(object sender, AssemblyLoadEventArgs args)
		{
			if (args.LoadedAssembly.GetName().Name.Equals("HowToFish.ModMenu", StringComparison.OrdinalIgnoreCase))
			{
				Patch();
			}
			HTFModManagerSupport.Patch(args.LoadedAssembly);
		}

		private static void LangChanged(string language)
		{
			object obj = Menu();
			if (obj == null)
			{
				return;
			}
			Type type = obj.GetType();
			FieldInfo fieldInfo = AccessTools.Field(type, "_open");
			if (!(fieldInfo == null))
			{
				object value = fieldInfo.GetValue(obj);
				if (value is bool && (bool)value)
				{
					AccessTools.Method(type, "RenderSettings", (Type[])null, (Type[])null)?.Invoke(obj, null);
				}
			}
		}

		private static object Menu()
		{
			if (!Chainloader.PluginInfos.TryGetValue("community.howtofish.modmenu", out var value) || value == null || (Object)(object)value.Instance == (Object)null)
			{
				return null;
			}
			return AccessTools.Field(((object)value.Instance).GetType(), "_menu")?.GetValue(value.Instance);
		}

		private static bool Owner(ConfigEntryBase entry, out string guid)
		{
			guid = null;
			ConfigFile configFile = entry.ConfigFile;
			if (configFile == null)
			{
				return false;
			}
			if (Owners.TryGetValue(configFile, out guid))
			{
				return true;
			}
			foreach (KeyValuePair<string, PluginInfo> pluginInfo in Chainloader.PluginInfos)
			{
				PluginInfo value = pluginInfo.Value;
				if (value != null && !((Object)(object)value.Instance == (Object)null) && value.Instance.Config != null && value.Instance.Config == configFile)
				{
					guid = pluginInfo.Key;
					Owners[configFile] = guid;
					return true;
				}
			}
			return false;
		}

		internal static bool OwnerFor(ConfigFile config, out string guid)
		{
			guid = null;
			if (config == null)
			{
				return false;
			}
			if (Owners.TryGetValue(config, out guid))
			{
				return true;
			}
			foreach (KeyValuePair<string, PluginInfo> pluginInfo in Chainloader.PluginInfos)
			{
				PluginInfo value = pluginInfo.Value;
				if (value != null && !((Object)(object)value.Instance == (Object)null) && value.Instance.Config != null && value.Instance.Config == config)
				{
					guid = pluginInfo.Key;
					Owners[config] = guid;
					return true;
				}
			}
			return false;
		}

		private static ModLocalizer Localizer(string guid)
		{
			object obj = AccessTools.Field(typeof(LocalizationApi), "Localizers")?.GetValue(null);
			if (obj == null)
			{
				return null;
			}
			MethodInfo methodInfo = AccessTools.Method(obj.GetType(), "TryGetValue", (Type[])null, (Type[])null);
			if (methodInfo == null)
			{
				return null;
			}
			object[] array = new object[2] { guid, null };
			object obj2 = methodInfo.Invoke(obj, array);
			if (!(obj2 is bool) || !(bool)obj2)
			{
				return null;
			}
			return array[1] as ModLocalizer;
		}

		internal static ModLocalizer GetLocalizer(string guid)
		{
			return Localizer(guid);
		}
	}
}
namespace HowtoFishLocalizationAPI.Integrations.HTFModManager
{
	internal static class HTFModManagerPatch
	{
		private static void Row(Transform __0, ConfigDefinition __1, ConfigEntryBase __2)
		{
			HTFModManagerSupport.LocalizeRow(__0, __1, __2);
		}
	}
	internal static class HTFModManagerSupport
	{
		private const string AssemblyName = "HTFModManager";

		private const string TypeName = "HTFModManager.Plugin";

		private const string PluginGuid = "com.howtofish.htfmodmanager";

		private const string LabelPrefix = "modmenu.config.";

		private static Harmony _harmony;

		private static bool _patched;

		private static bool _initialized;

		internal static void Init(Harmony harmony)
		{
			if (!_initialized)
			{
				_initialized = true;
				_harmony = harmony;
				LocalizationApi.LanguageChanged += LangChanged;
				Patch();
			}
		}

		internal static void Patch(Assembly loaded = null)
		{
			//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: Expected O, but got Unknown
			if (_patched || _harmony == null || (loaded != null && !string.Equals(loaded.GetName().Name, "HTFModManager", StringComparison.OrdinalIgnoreCase)))
			{
				return;
			}
			Type type = Find();
			if (type == null)
			{
				return;
			}
			MethodInfo methodInfo = AccessTools.Method(type, "AddGenericConfigRow", new Type[4]
			{
				typeof(Transform),
				typeof(ConfigDefinition),
				typeof(ConfigEntryBase),
				typeof(ConfigFile)
			}, (Type[])null);
			MethodInfo methodInfo2 = AccessTools.Method(typeof(HTFModManagerPatch), "Row", (Type[])null, (Type[])null);
			if (methodInfo == null || methodInfo2 == null)
			{
				return;
			}
			try
			{
				_harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				_patched = true;
			}
			catch
			{
			}
		}

		internal static void LocalizeRow(Transform layout, ConfigDefinition definition, ConfigEntryBase entry)
		{
			try
			{
				if ((Object)(object)layout == (Object)null || entry == null || !Owner(entry, out var guid))
				{
					return;
				}
				ModLocalizer modLocalizer = Localizer(guid);
				if (modLocalizer == null)
				{
					return;
				}
				string text = "modmenu.config." + definition.Section + "." + definition.Key;
				string localizedText = modLocalizer.GetLocalizedText(text);
				if (string.Equals(localizedText, text, StringComparison.Ordinal))
				{
					return;
				}
				string[] array = new string[4] { "CfgBool_", "CfgNumber_", "CfgText_", "CfgChoice_" };
				Transform val = null;
				for (int i = 0; i < array.Length; i++)
				{
					string b = array[i] + Sanitize(definition.Key);
					for (int num = layout.childCount - 1; num >= 0; num--)
					{
						Transform child = layout.GetChild(num);
						if ((Object)(object)child != (Object)null && string.Equals(((Object)child).name, b, StringComparison.Ordinal))
						{
							val = child;
							break;
						}
					}
					if ((Object)(object)val != (Object)null)
					{
						break;
					}
				}
				if ((Object)(object)val == (Object)null)
				{
					return;
				}
				Component[] componentsInChildren = ((Component)val).GetComponentsInChildren<Component>(true);
				Component val2 = null;
				foreach (Component val3 in componentsInChildren)
				{
					if ((Object)(object)val3 == (Object)null)
					{
						continue;
					}
					PropertyInfo property = ((object)val3).GetType().GetProperty("text", BindingFlags.Instance | BindingFlags.Public);
					if (!(property == null) && property.CanWrite && !(property.PropertyType != typeof(string)))
					{
						if ((Object)(object)val2 == (Object)null)
						{
							val2 = val3;
						}
						string name = ((Object)val3.gameObject).name;
						if (name.IndexOf("Text", StringComparison.OrdinalIgnoreCase) >= 0 || name.IndexOf("Title", StringComparison.OrdinalIgnoreCase) >= 0)
						{
							property.SetValue(val3, localizedText, null);
							return;
						}
					}
				}
				if ((Object)(object)val2 != (Object)null)
				{
					((object)val2).GetType().GetProperty("text", BindingFlags.Instance | BindingFlags.Public)?.SetValue(val2, localizedText, null);
				}
			}
			catch
			{
			}
		}

		private static Type Find()
		{
			Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
			foreach (Assembly assembly in assemblies)
			{
				if (string.Equals(assembly.GetName().Name, "HTFModManager", StringComparison.OrdinalIgnoreCase))
				{
					return assembly.GetType("HTFModManager.Plugin", throwOnError: false);
				}
			}
			return null;
		}

		private static void LangChanged(string language)
		{
			if (!Chainloader.PluginInfos.TryGetValue("com.howtofish.htfmodmanager", out var value) || value == null || (Object)(object)value.Instance == (Object)null)
			{
				return;
			}
			try
			{
				Type type = ((object)value.Instance).GetType();
				FieldInfo fieldInfo = AccessTools.Field(type, "_configViewOpen");
				if (fieldInfo == null)
				{
					return;
				}
				object value2 = fieldInfo.GetValue(value.Instance);
				if (!(value2 is bool) || !(bool)value2)
				{
					return;
				}
				FieldInfo fieldInfo2 = AccessTools.Field(type, "_selectedPlugin");
				FieldInfo fieldInfo3 = AccessTools.Field(type, "_modsContent");
				object? obj = fieldInfo2?.GetValue(value.Instance);
				PluginInfo val = (PluginInfo)((obj is PluginInfo) ? obj : null);
				object? obj2 = fieldInfo3?.GetValue(value.Instance);
				Transform val2 = (Transform)((obj2 is Transform) ? obj2 : null);
				object obj3;
				if (val == null)
				{
					obj3 = null;
				}
				else
				{
					BaseUnityPlugin instance = val.Instance;
					obj3 = ((instance != null) ? instance.Config : null);
				}
				ConfigFile val3 = (ConfigFile)obj3;
				if ((Object)(object)val2 == (Object)null || val3 == null)
				{
					return;
				}
				foreach (KeyValuePair<ConfigDefinition, ConfigEntryBase> item in val3)
				{
					LocalizeRow(val2, item.Key, item.Value);
				}
			}
			catch
			{
			}
		}

		private static bool Owner(ConfigEntryBase entry, out string guid)
		{
			guid = null;
			ConfigFile configFile = entry.ConfigFile;
			if (configFile == null)
			{
				return false;
			}
			return ModMenuSupport.OwnerFor(configFile, out guid);
		}

		private static ModLocalizer Localizer(string guid)
		{
			return ModMenuSupport.GetLocalizer(guid);
		}

		private static string Sanitize(string value)
		{
			if (string.IsNullOrEmpty(value))
			{
				return "Unknown";
			}
			char[] array = value.ToCharArray();
			for (int i = 0; i < array.Length; i++)
			{
				if (!char.IsLetterOrDigit(array[i]))
				{
					array[i] = '_';
				}
			}
			return new string(array);
		}
	}
}
namespace HowtoFishLocalizationAPI.Api
{
	public static class LocalizationApi
	{
		[Serializable]
		private sealed class LocalSave
		{
			public int SavedLanguage = -1;
		}

		public const string DefaultLanguage = "en";

		private static readonly Dictionary<string, ModLocalizer> Localizers = new Dictionary<string, ModLocalizer>(StringComparer.OrdinalIgnoreCase);

		private static string _currentLanguage = "en";

		public static string CurrentLanguage => _currentLanguage;

		public static event Action<string> LanguageChanged;

		public static ModLocalizer For(string pluginGuid)
		{
			if (string.IsNullOrWhiteSpace(pluginGuid))
			{
				throw new ArgumentException("Plugin GUID cannot be empty.", "pluginGuid");
			}
			if (!Localizers.TryGetValue(pluginGuid, out var value))
			{
				value = new ModLocalizer();
				Localizers.Add(pluginGuid, value);
			}
			return value;
		}

		public static bool TryGetCurrentGameLanguage(out string language)
		{
			language = CurrentLanguage;
			return true;
		}

		internal static void NotifyLanguageChanged(int? languageIndex = null)
		{
			_currentLanguage = (languageIndex.HasValue ? (Lang(languageIndex.Value) ?? "en") : Read());
			Action<string> languageChanged = LocalizationApi.LanguageChanged;
			if (languageChanged == null)
			{
				return;
			}
			Delegate[] invocationList = languageChanged.GetInvocationList();
			foreach (Delegate obj in invocationList)
			{
				try
				{
					((Action<string>)obj)(_currentLanguage);
				}
				catch (Exception ex)
				{
					ManualLogSource log = HowtoFishLocalizationAPI.Log;
					if (log != null)
					{
						log.LogError((object)("Language callback failed: " + ex));
					}
				}
			}
		}

		private static string Read()
		{
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00df: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			string text = ReadSave();
			if (!string.IsNullOrEmpty(text))
			{
				return text;
			}
			switch (LocalizationManager.CurLanguage)
			{
			case 0:
				return "en";
			case 1:
				return "sv";
			case 2:
				return "zh-CN";
			case 3:
				return "zh-TW";
			case 4:
				return "fr";
			case 5:
				return "de";
			case 6:
				return "it";
			case 7:
				return "ja";
			case 8:
				return "ko";
			case 9:
				return "pl";
			case 10:
				return "pt-BR";
			case 11:
				return "ru";
			case 12:
				return "es-MX";
			case 13:
				return "es-ES";
			case 14:
				return "tr";
			case 15:
				return "uk";
			default:
				if (LocalizationSettings.InitializationOperation.IsDone && (Object)(object)LocalizationSettings.SelectedLocale != (Object)null)
				{
					LocaleIdentifier identifier = LocalizationSettings.SelectedLocale.Identifier;
					string code = ((LocaleIdentifier)(ref identifier)).Code;
					if (!string.IsNullOrWhiteSpace(code))
					{
						if (code.StartsWith("zh", StringComparison.OrdinalIgnoreCase))
						{
							if (code.IndexOf("TW", StringComparison.OrdinalIgnoreCase) < 0 && code.IndexOf("HK", StringComparison.OrdinalIgnoreCase) < 0)
							{
								return "zh-CN";
							}
							return "zh-TW";
						}
						return code;
					}
				}
				return "en";
			}
		}

		private static string ReadSave()
		{
			string path = Path.Combine(Application.persistentDataPath, "Saves", "local.txt");
			if (!File.Exists(path))
			{
				return null;
			}
			try
			{
				LocalSave localSave = JsonConvert.DeserializeObject<LocalSave>(File.ReadAllText(path));
				return (localSave == null) ? null : Lang(localSave.SavedLanguage);
			}
			catch
			{
				return null;
			}
		}

		private static string Lang(int index)
		{
			return index switch
			{
				0 => "en", 
				1 => "sv", 
				2 => "zh-CN", 
				3 => "zh-TW", 
				4 => "fr", 
				5 => "de", 
				6 => "it", 
				7 => "ja", 
				8 => "ko", 
				9 => "pl", 
				10 => "pt-BR", 
				11 => "ru", 
				12 => "es-MX", 
				13 => "es-ES", 
				14 => "tr", 
				15 => "uk", 
				_ => null, 
			};
		}
	}
	public sealed class ModLocalizer
	{
		private readonly Dictionary<string, Dictionary<string, string>> _localizations = new Dictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase);

		internal ModLocalizer()
		{
		}

		public void RegisterJson(string jsonPath)
		{
			if (string.IsNullOrWhiteSpace(jsonPath))
			{
				throw new ArgumentException("Localization file path cannot be empty.", "jsonPath");
			}
			if (!File.Exists(jsonPath))
			{
				throw new FileNotFoundException("Localization file not found.", jsonPath);
			}
			Dictionary<string, Dictionary<string, string>> dictionary = JsonConvert.DeserializeObject<Dictionary<string, Dictionary<string, string>>>(File.ReadAllText(jsonPath));
			if (dictionary == null || !dictionary.ContainsKey("en"))
			{
				throw new InvalidDataException("Localization file must contain a en section.");
			}
			_localizations.Clear();
			foreach (KeyValuePair<string, Dictionary<string, string>> item in dictionary)
			{
				if (!string.IsNullOrWhiteSpace(item.Key) && item.Value != null)
				{
					_localizations[item.Key] = item.Value;
				}
			}
		}

		public string GetLocalizedText(string key, params object[] args)
		{
			if (string.IsNullOrEmpty(key))
			{
				return string.Empty;
			}
			if (TryText(LocalizationApi.CurrentLanguage, key, out var text) || TryText("en", key, out text))
			{
				return Fmt(text, args);
			}
			return key;
		}

		private bool TryText(string language, string key, out string text)
		{
			text = null;
			if (_localizations.TryGetValue(language, out var value))
			{
				return value.TryGetValue(key, out text);
			}
			return false;
		}

		private static string Fmt(string text, object[] args)
		{
			if (args == null || args.Length == 0)
			{
				return text;
			}
			try
			{
				return string.Format(text, args);
			}
			catch (FormatException)
			{
				return text;
			}
		}
	}
}