using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using GlobalEnums; using HarmonyLib; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using TeamCherry.Localization; 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: AssemblyCompany("Silksong.I18N")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("A Hollow Knight: Silksong library that automatically loads localized text for other mods. Note that most consumers do not need to NuGet dependency on this package.")] [assembly: AssemblyFileVersion("1.1.1.0")] [assembly: AssemblyInformationalVersion("1.1.1+0279b17abf96c24b6527d48d0d1ce3d562b22389")] [assembly: AssemblyProduct("Silksong.I18N")] [assembly: AssemblyTitle("I18N")] [assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/silksong-modding/Silksong.I18N")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.1.1.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 BepInEx { [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] [Conditional("CodeGeneration")] internal sealed class BepInAutoPluginAttribute : Attribute { public BepInAutoPluginAttribute(string? id = null, string? name = null, string? version = null) { } } } namespace BepInEx.Preloader.Core.Patching { [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] [Conditional("CodeGeneration")] internal sealed class PatcherAutoPluginAttribute : Attribute { public PatcherAutoPluginAttribute(string? id = null, string? name = null, string? version = null) { } } } namespace Silksong.I18N { [BepInPlugin("org.silksong-modding.i18n", "I18N", "1.1.1")] public sealed class I18NPlugin : BaseUnityPlugin { private static I18NPlugin? _instance; private ConfigEntry? useLanguageOverride; private ConfigEntry? languageOverride; public const string Id = "org.silksong-modding.i18n"; internal ManualLogSource Logger => ((BaseUnityPlugin)this).Logger; internal static I18NPlugin Instance { get { if (!((Object)(object)_instance != (Object)null)) { throw new NullReferenceException("I18N instance is not ready"); } return _instance; } } public LanguageCode? LanguageOverride { get { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) if (useLanguageOverride != null && useLanguageOverride.Value) { return (LanguageCode?)languageOverride?.Value; } return null; } } public bool UseLanguageOverride { get { if (useLanguageOverride == null) { return false; } return useLanguageOverride.Value; } } public static string Name => "I18N"; public static string Version => "1.1.1"; private void Start() { //IL_000b: Unknown result type (might be due to invalid IL or missing references) _instance = this; new Harmony("org.silksong-modding.i18n").PatchAll(); useLanguageOverride = ((BaseUnityPlugin)this).Config.Bind("General", "Use Language Override", false, "Whether to manually specify the language used for all modded text."); languageOverride = ((BaseUnityPlugin)this).Config.Bind("General", "Language Override", (SupportedLanguages)44, "Modded text will use this language if Use Language Override is enabled."); useLanguageOverride.SettingChanged += delegate { LanguagePatches.LoadAllModSheets(); }; languageOverride.SettingChanged += delegate { LanguagePatches.LoadAllModSheets(); }; LanguagePatches.LoadAllModSheets(); } } [HarmonyPatch] internal static class LanguagePatches { [HarmonyPatch(typeof(Language), "DoSwitch")] [HarmonyPostfix] private static void OnLanguageSwitched() { LoadAllModSheets(); } public unsafe static void LoadAllModSheets() { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_01d0: Unknown result type (might be due to invalid IL or missing references) LanguageCode val = (LanguageCode)(((??)I18NPlugin.Instance.LanguageOverride) ?? Language._currentLanguage); if (I18NPlugin.Instance.UseLanguageOverride) { I18NPlugin.Instance.Logger.LogDebug((object)$"using language override {val}"); } foreach (KeyValuePair pluginInfo in Chainloader.PluginInfos) { pluginInfo.Deconstruct(out var key, out var value); string text = key; BaseUnityPlugin instance = value.Instance; if (!Object.op_Implicit((Object)(object)instance)) { continue; } Assembly assembly = ((object)instance).GetType().Assembly; if (Utility.IsNullOrWhiteSpace(assembly.Location)) { I18NPlugin.Instance.Logger.LogDebug((object)("mod " + text + " assembly has no location, if you are using ScriptEngine, please enable DumpedAssemblies of ScriptEngine and place the languages folder in BepInEx\\ScriptEngineDumpedAssemblies")); continue; } string directoryName = Path.GetDirectoryName(assembly.Location); if (!Directory.Exists(directoryName)) { continue; } if (string.Equals(Path.GetFullPath(directoryName).TrimEnd(Path.DirectorySeparatorChar), Path.GetFullPath(Paths.PluginPath).TrimEnd(Path.DirectorySeparatorChar), StringComparison.InvariantCultureIgnoreCase)) { I18NPlugin.Instance.Logger.LogInfo((object)("mod " + text + " installed directly in plugins dir, not loading languages")); continue; } Dictionary dictionary = null; NeutralResourcesLanguageAttribute customAttribute = assembly.GetCustomAttribute(); if (customAttribute != null) { string text2 = customAttribute.CultureName.ToUpper(); dictionary = LoadModSheet(directoryName, text2.ToLower()); if (dictionary != null) { I18NPlugin.Instance.Logger.LogDebug((object)("loaded fallback sheet in language " + text2 + " for mod " + text)); } } Dictionary dictionary2 = LoadModSheet(directoryName, ((object)(*(LanguageCode*)(&val))/*cast due to .constrained prefix*/).ToString().ToLower(), dictionary); if (dictionary2 != null) { Language._currentEntrySheets["Mods." + text] = dictionary2; I18NPlugin.Instance.Logger.LogDebug((object)$"loaded sheet in language {val} for mod {text}"); } } } private static Dictionary? LoadModSheet(string modDir, string lang, Dictionary? fallback = null) { EnumerationOptions opts = new EnumerationOptions(); opts.MatchCasing = MatchCasing.CaseInsensitive; bool flag = false; Dictionary dictionary = fallback ?? new Dictionary(); try { foreach (Dictionary item in (from p in Directory.EnumerateDirectories(modDir, "languages", opts).SelectMany((string dir) => Directory.EnumerateFiles(dir, lang + ".json", opts)) orderby p select ReadSheetFile(p)).OfType>()) { if (flag) { I18NPlugin.Instance.Logger.LogWarning((object)("multiple casings found for language " + lang.ToUpper() + " in: " + modDir)); } flag = true; foreach (KeyValuePair item2 in item) { item2.Deconstruct(out var key, out var value); string key2 = key; string value2 = value; dictionary[key2] = value2; } } } catch (Exception arg) { I18NPlugin.Instance.Logger.LogError((object)$"unable to load mod sheets: {modDir}\n{arg}"); return null; } if (flag || fallback != null) { return dictionary; } return null; } private static Dictionary? ReadSheetFile(string path) { try { using StreamReader streamReader = new StreamReader(File.OpenRead(path), Encoding.UTF8, detectEncodingFromByteOrderMarks: false); return JsonConvert.DeserializeObject>(streamReader.ReadToEnd()); } catch (Exception arg) { I18NPlugin.Instance.Logger.LogError((object)$"unable to read language file: {path}\n{arg}"); return null; } } [HarmonyPatch(typeof(Language), "Get", new Type[] { typeof(string), typeof(string) })] [HarmonyPostfix] private static void OnGetLocalizedText(string? key, string? sheetTitle) { WarnIfModKeyMissing(sheetTitle, key); } [HarmonyPatch(typeof(LocalisedString), "ToString", new Type[] { typeof(bool) })] [HarmonyPostfix] private static void OnGetLocalizedString(LocalisedString __instance, bool allowBlankText) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) WarnIfModKeyMissing(__instance.Sheet, __instance.Key, allowBlankText); } private static void WarnIfModKeyMissing(string? sheet, string? key, bool allowBlankText = true) { //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: 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) if (!string.IsNullOrEmpty(sheet) && !string.IsNullOrEmpty(key) && sheet.StartsWith("Mods.")) { if (!Language.Has(key, sheet)) { LanguageCode val = (LanguageCode)(((??)I18NPlugin.Instance.LanguageOverride) ?? Language._currentLanguage); string arg = sheet.Substring("Mods.".Length); I18NPlugin.Instance.Logger.LogWarning((object)$"language {val} for mod {arg} missing: {key}"); } else if (!allowBlankText && string.IsNullOrWhiteSpace(LocalisedString.ReplaceTags(Language.Get(key, sheet)))) { LanguageCode val2 = (LanguageCode)(((??)I18NPlugin.Instance.LanguageOverride) ?? Language._currentLanguage); string arg2 = sheet.Substring("Mods.".Length); I18NPlugin.Instance.Logger.LogWarning((object)$"language {val2} for mod {arg2} is blank at: {key}"); } } } } }