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.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using SunkenlandLocalizationAPI.Api;
using UnityEngine;
using UnityEngine.Localization;
using UnityEngine.Localization.Settings;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("SunkenlandLocalizationAPI")]
[assembly: AssemblyDescription("Sunkenland Localization API mod for Sunkenland by Ice Box Studio")]
[assembly: AssemblyCompany("Ice Box Studio")]
[assembly: AssemblyProduct("SunkenlandLocalizationAPI")]
[assembly: AssemblyCopyright("Copyright © 2026 Ice Box Studio All rights reserved.")]
[assembly: ComVisible(false)]
[assembly: Guid("79870812-2aca-43d5-b058-5844d23fb3b4")]
[assembly: AssemblyFileVersion("1.2.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("1.2.0.0")]
namespace SunkenlandLocalizationAPI
{
public static class PluginInfo
{
public const string PLUGIN_GUID = "IceBoxStudio.Sunkenland.LocalizationAPI";
public const string PLUGIN_NAME = "SunkenlandLocalizationAPI";
public const string PLUGIN_VERSION = "1.2.0";
}
[BepInPlugin("IceBoxStudio.Sunkenland.LocalizationAPI", "SunkenlandLocalizationAPI", "1.2.0")]
public sealed class SunkenlandLocalizationAPI : BaseUnityPlugin
{
private Harmony _harmony;
internal static ManualLogSource Log { get; private set; }
private void Awake()
{
//IL_0027: Unknown result type (might be due to invalid IL or missing references)
//IL_0031: Expected O, but got Unknown
Log = ((BaseUnityPlugin)this).Logger;
LocalizationApi.LoadSavedLanguage();
LocalizationSettings.SelectedLocaleChanged += OnLanguageChanged;
_harmony = new Harmony("IceBoxStudio.Sunkenland.LocalizationAPI");
_harmony.PatchAll(Assembly.GetExecutingAssembly());
Log.LogInfo((object)"SunkenlandLocalizationAPI 1.2.0 initialized.");
}
private static void OnLanguageChanged(Locale locale)
{
LocalizationApi.NotifyLanguageChanged(locale);
}
}
}
namespace SunkenlandLocalizationAPI.Patches
{
[HarmonyPatch(typeof(GameInitialization), "OnBeforeSplashScreen")]
internal static class GameInitializationPatch
{
[HarmonyPostfix]
private static void Postfix()
{
LocalizationApi.NotifyLanguageChanged(LocalizationSettings.SelectedLocale);
}
}
}
namespace SunkenlandLocalizationAPI.Api
{
internal sealed class ConfigSectionTag
{
private readonly ModLocalizer _localizer;
private readonly string _key;
public string Section { get; }
public string DisplayName => _localizer.GetLocalizedText(_key);
public int? Order { get; }
internal ConfigSectionTag(ModLocalizer localizer, string section, string key, int order)
{
_localizer = localizer;
_key = key;
Section = section;
Order = order;
}
internal bool TryGetDisplayName(out string displayName)
{
displayName = _localizer.GetLocalizedText(_key);
return !string.Equals(displayName, _key, StringComparison.Ordinal);
}
}
internal sealed class ConfigEntryTag
{
private readonly ModLocalizer _localizer;
private readonly string _key;
public string DisplayName => _localizer.GetLocalizedText(_key + ".name");
public string Description => _localizer.GetLocalizedText(_key + ".description");
public int? Order { get; }
public double? SliderStep { get; }
internal ConfigEntryTag(ModLocalizer localizer, string key, int order, double? sliderStep = null)
{
_localizer = localizer;
_key = key;
Order = order;
SliderStep = sliderStep;
}
internal bool TryGetText(string propertyName, out string text)
{
string text2 = (string.Equals(propertyName, "DisplayName", StringComparison.Ordinal) ? ".name" : ".description");
string text3 = _key + text2;
text = _localizer.GetLocalizedText(text3);
return !string.Equals(text, text3, StringComparison.Ordinal);
}
internal bool TryGetDisplayValue(object value, out string displayValue)
{
displayValue = null;
string text = NormalizeValue(value);
if (text.Length == 0)
{
return false;
}
string text2 = _key + "." + text;
string localizedText = _localizer.GetLocalizedText(text2);
if (string.Equals(localizedText, text2, StringComparison.Ordinal))
{
return false;
}
displayValue = localizedText;
return true;
}
private static string NormalizeValue(object value)
{
string text = Convert.ToString(value, CultureInfo.InvariantCulture);
if (string.IsNullOrWhiteSpace(text))
{
return string.Empty;
}
StringBuilder stringBuilder = new StringBuilder(text.Length + 4);
for (int i = 0; i < text.Length; i++)
{
char c = text[i];
if (!char.IsLetterOrDigit(c))
{
if (stringBuilder.Length > 0 && stringBuilder[stringBuilder.Length - 1] != '_')
{
stringBuilder.Append('_');
}
continue;
}
if (char.IsUpper(c) && i > 0 && char.IsLower(text[i - 1]) && stringBuilder.Length > 0 && stringBuilder[stringBuilder.Length - 1] != '_')
{
stringBuilder.Append('_');
}
stringBuilder.Append(char.ToLowerInvariant(c));
}
return stringBuilder.ToString().Trim(new char[1] { '_' });
}
}
public static class LocalizationApi
{
public const string DefaultLanguage = "en";
private static readonly Dictionary<string, ModLocalizer> Localizers = new Dictionary<string, ModLocalizer>(StringComparer.OrdinalIgnoreCase);
private static readonly HashSet<string> GameLanguages = new HashSet<string>(new string[9] { "en", "zh-Hans", "fr", "de", "ja", "ko", "ru", "es", "tr" }, 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 LoadSavedLanguage()
{
try
{
string path = Path.Combine(Application.persistentDataPath, "Setting.json");
if (File.Exists(path))
{
JToken obj = JObject.Parse(File.ReadAllText(path))["SettingProfile"];
object obj2;
if (obj == null)
{
obj2 = null;
}
else
{
JToken obj3 = obj[(object)"value"];
obj2 = ((obj3 != null) ? obj3[(object)"localeCode"] : null);
}
string text = (string)(JToken)obj2;
if (!string.IsNullOrWhiteSpace(text) && GameLanguages.Contains(text))
{
_currentLanguage = text;
}
}
}
catch
{
}
}
public static string GetConfigDescription(ConfigEntryBase entry)
{
string entryTagText = GetEntryTagText(entry, "Description");
if (!string.IsNullOrWhiteSpace(entryTagText))
{
return entryTagText;
}
if (entry != null)
{
return entry.Description.Description;
}
return null;
}
public static string GetConfigDisplayName(ConfigEntryBase entry)
{
string entryTagText = GetEntryTagText(entry, "DisplayName");
if (!string.IsNullOrWhiteSpace(entryTagText))
{
return entryTagText;
}
if (entry != null)
{
return entry.Definition.Key;
}
return null;
}
public static string GetConfigSection(string section, ConfigFile configFile)
{
ConfigEntryBase[] obj = ((configFile != null) ? configFile.GetConfigEntries() : null) ?? Array.Empty<ConfigEntryBase>();
string text = null;
ConfigEntryBase[] array = obj;
foreach (ConfigEntryBase val in array)
{
if (!string.Equals(val.Definition.Section, section, StringComparison.Ordinal))
{
continue;
}
ConfigDescription description = val.Description;
object[] array2 = ((description != null) ? description.Tags : null) ?? Array.Empty<object>();
foreach (object obj2 in array2)
{
if (!string.Equals(ReadString(obj2, "Section"), section, StringComparison.Ordinal))
{
continue;
}
if (obj2 is ConfigSectionTag configSectionTag)
{
if (configSectionTag.TryGetDisplayName(out var displayName))
{
text = displayName;
}
continue;
}
string text2 = ReadString(obj2, "DisplayName");
if (!string.IsNullOrWhiteSpace(text2))
{
text = text2;
}
}
}
if (!string.IsNullOrWhiteSpace(text))
{
return text;
}
return section;
}
public static bool TryGetConfigDisplayValue(ConfigEntryBase entry, object value, out string displayValue)
{
displayValue = null;
if (entry == null || value == null)
{
return false;
}
ConfigDescription description = entry.Description;
object[] array = ((description != null) ? description.Tags : null) ?? Array.Empty<object>();
for (int i = 0; i < array.Length; i++)
{
if (array[i] is ConfigEntryTag configEntryTag && configEntryTag.TryGetDisplayValue(value, out displayValue))
{
return true;
}
}
return false;
}
internal static void NotifyLanguageChanged(Locale locale)
{
string obj = (_currentLanguage = GetLanguage(locale));
Action<string> languageChanged = LocalizationApi.LanguageChanged;
if (languageChanged == null)
{
return;
}
Delegate[] invocationList = languageChanged.GetInvocationList();
foreach (Delegate obj2 in invocationList)
{
try
{
((Action<string>)obj2)(obj);
}
catch (Exception ex)
{
ManualLogSource log = SunkenlandLocalizationAPI.Log;
if (log != null)
{
log.LogError((object)("Language change callback failed: " + ex));
}
}
}
}
private static string GetLanguage(Locale locale)
{
//IL_000a: Unknown result type (might be due to invalid IL or missing references)
//IL_000f: Unknown result type (might be due to invalid IL or missing references)
object obj;
if (!((Object)(object)locale == (Object)null))
{
LocaleIdentifier identifier = locale.Identifier;
obj = ((LocaleIdentifier)(ref identifier)).Code;
}
else
{
obj = null;
}
string text = (string)obj;
if (string.IsNullOrEmpty(text) || !GameLanguages.Contains(text))
{
return "en";
}
return text;
}
private static string GetEntryTagText(ConfigEntryBase entry, string propertyName)
{
if (entry == null)
{
return null;
}
string result = null;
ConfigDescription description = entry.Description;
object[] array = ((description != null) ? description.Tags : null) ?? Array.Empty<object>();
foreach (object obj in array)
{
if (ReadProperty(obj, "Section") != null)
{
continue;
}
if (obj is ConfigEntryTag configEntryTag)
{
if (configEntryTag.TryGetText(propertyName, out var text))
{
result = text;
}
continue;
}
string text2 = ReadString(obj, propertyName);
if (!string.IsNullOrWhiteSpace(text2))
{
result = text2;
}
}
return result;
}
private static string ReadString(object value, string propertyName)
{
return ReadProperty(value, propertyName) as string;
}
private static object ReadProperty(object value, string propertyName)
{
try
{
return (value == null) ? null : AccessTools.Property(value.GetType(), propertyName)?.GetValue(value, null);
}
catch
{
return 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;
}
string currentLanguage = LocalizationApi.CurrentLanguage;
if (TryGetText(currentLanguage, key, out var text))
{
return Format(text, args);
}
if (!string.Equals(currentLanguage, "en", StringComparison.OrdinalIgnoreCase) && TryGetText("en", key, out text))
{
return Format(text, args);
}
return key;
}
public ConfigDescription Config(string key, int entryOrder, string section, string sectionKey, int sectionOrder, AcceptableValueBase acceptableValues = null)
{
return CreateConfig(key, entryOrder, section, sectionKey, sectionOrder, acceptableValues, null);
}
public ConfigDescription Config(string key, int entryOrder, string section, string sectionKey, int sectionOrder, double sliderStep)
{
return CreateConfig(key, entryOrder, section, sectionKey, sectionOrder, null, sliderStep);
}
public ConfigDescription Config(string key, int entryOrder, string section, string sectionKey, int sectionOrder, AcceptableValueBase acceptableValues, double sliderStep)
{
return CreateConfig(key, entryOrder, section, sectionKey, sectionOrder, acceptableValues, sliderStep);
}
private ConfigDescription CreateConfig(string key, int entryOrder, string section, string sectionKey, int sectionOrder, AcceptableValueBase acceptableValues, double? sliderStep)
{
//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
//IL_00de: Expected O, but got Unknown
if (string.IsNullOrWhiteSpace(key))
{
throw new ArgumentException("Localization key cannot be empty.", "key");
}
if (string.IsNullOrWhiteSpace(section))
{
throw new ArgumentException("Config section cannot be empty.", "section");
}
if (string.IsNullOrWhiteSpace(sectionKey))
{
throw new ArgumentException("Section localization key cannot be empty.", "sectionKey");
}
if (sliderStep.HasValue && (sliderStep.Value <= 0.0 || double.IsNaN(sliderStep.Value) || double.IsInfinity(sliderStep.Value)))
{
throw new ArgumentOutOfRangeException("sliderStep");
}
string text = key + ".description";
string text2 = GetLocalizedText(text);
if (string.Equals(text2, text, StringComparison.Ordinal))
{
text2 = string.Empty;
}
return new ConfigDescription(text2, acceptableValues, new object[2]
{
new ConfigSectionTag(this, section, sectionKey, sectionOrder),
new ConfigEntryTag(this, key, entryOrder, sliderStep)
});
}
private bool TryGetText(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 Format(string text, object[] args)
{
if (args == null || args.Length == 0)
{
return text;
}
try
{
return string.Format(text, args);
}
catch (FormatException)
{
return text;
}
}
}
}