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;
}
}
}
}