Decompiled source of WKLocalizationLoader v0.6.3
plugins/WKLocalizationLoader.dll
Decompiled 3 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Serialization; using System.Runtime.Versioning; using System.Security; using System.Security.Cryptography; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using DG.Tweening; using DG.Tweening.Core; using DG.Tweening.Plugins.Options; using Febucci.UI; using Febucci.UI.Core; using HarmonyLib; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using Steamworks; using Steamworks.Data; using TMPro; using UnityEngine; using UnityEngine.Events; using UnityEngine.SceneManagement; using UnityEngine.TextCore; using UnityEngine.TextCore.LowLevel; using UnityEngine.UI; using WKLocalizationLoader.Config; using WKLocalizationLoader.FontFactory; using WKLocalizationLoader.Modules; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: IgnoresAccessChecksTo("Assembly-CSharp")] [assembly: IgnoresAccessChecksTo("Unity.TextMeshPro")] [assembly: IgnoresAccessChecksTo("UnityEngine.UI")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("WKLocalizationLoader")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("0.6.3.0")] [assembly: AssemblyInformationalVersion("0.6.3+69a5d4aa1d94e44ad1972915f95ed94bcad92993")] [assembly: AssemblyProduct("WKLocalizationLoader")] [assembly: AssemblyTitle("WKLocalizationLoader")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.6.3.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [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 WKLocalizationLoader { public static class CacheManager { private static JsonSerializerSettings _jsonSerializerSettings; private static Dictionary<string, Font> _fontCache; private static Dictionary<string, TMP_FontAsset> _fontAssetCache; private static Dictionary<(string, RegexOptions), Regex> _regexCache; private static ValueCollection<Type, Object> _scriptableObjectCache; private static Dictionary<string, TextAsset> _textAssetCache; private static Plugin _plugin; private static ManualLogSource _logger; public static void Initialize(Plugin plugin) { //IL_0001: 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) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Expected O, but got Unknown //IL_001d: Expected O, but got Unknown //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0036: 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_004b: Expected O, but got Unknown _jsonSerializerSettings = new JsonSerializerSettings { ContractResolver = (IContractResolver)new DefaultContractResolver { NamingStrategy = (NamingStrategy)new DefaultNamingStrategy() }, Formatting = (Formatting)1, TypeNameHandling = (TypeNameHandling)0, NullValueHandling = (NullValueHandling)1, MissingMemberHandling = (MissingMemberHandling)0, ReferenceLoopHandling = (ReferenceLoopHandling)1 }; _fontCache = new Dictionary<string, Font>(); _fontAssetCache = new Dictionary<string, TMP_FontAsset>(); if (plugin != null) { _plugin = plugin; string text = ((BaseUnityPlugin)_plugin).Info.Metadata.Name + "/CacheManager"; _logger = Logger.CreateLogSource(text); } } public static void AddFontToMemoryCache(string hash, Font font) { if (_fontCache == null) { _fontCache = new Dictionary<string, Font>(); } if (!string.IsNullOrWhiteSpace(hash) && font != null) { _fontCache[hash] = font; } } public static Font GetFontFromMemoryCache(string hash) { if (!string.IsNullOrWhiteSpace(hash) && _fontCache != null && _fontCache.TryGetValue(hash, out var value)) { return value; } return null; } public static Font CreateFontFromDiskCache(string hash) { if (string.IsNullOrWhiteSpace(hash) || _jsonSerializerSettings == null) { return null; } Font result = null; try { if (FileManager.TryGetFontCachePaths(hash, out var cacheDataPath, out var atlasPath)) { result = FontBuilder.CreateFontFromDiskCache(cacheDataPath, atlasPath, _jsonSerializerSettings, _logger); } } catch (Exception ex) { ManualLogSource logger = _logger; if (logger != null) { logger.LogWarning((object)("An error occurred while creating Font (hash: " + hash + ") from disk cache.")); } ManualLogSource logger2 = _logger; if (logger2 != null) { logger2.LogWarning((object)ex.Message); } result = null; } return result; } public static void WriteFontDiskCache(string hash, Font font) { if (string.IsNullOrWhiteSpace(hash) || font == null || _jsonSerializerSettings == null) { return; } try { string cacheFolder = Path.Combine(FileManager.FontCacheFolder, hash); FontBuilder.WriteFontDiskCache(cacheFolder, font, _jsonSerializerSettings, _logger); } catch (Exception ex) { ManualLogSource logger = _logger; if (logger != null) { logger.LogWarning((object)("An error occurred while writing Font (hash: " + hash + ") cache to disk.")); } ManualLogSource logger2 = _logger; if (logger2 != null) { logger2.LogWarning((object)ex.Message); } } } public static void AddFontAssetToMemoryCache(string hash, TMP_FontAsset fontAsset) { if (_fontAssetCache == null) { _fontAssetCache = new Dictionary<string, TMP_FontAsset>(); } if (!string.IsNullOrWhiteSpace(hash) && fontAsset != null) { _fontAssetCache[hash] = fontAsset; } } public static TMP_FontAsset GetFontAssetFromMemoryCache(string hash) { if (!string.IsNullOrWhiteSpace(hash) && _fontAssetCache != null && _fontAssetCache.TryGetValue(hash, out var value)) { return value; } return null; } public static TMP_FontAsset CreateFontAssetFromDiskCache(string hash) { if (string.IsNullOrWhiteSpace(hash) || _jsonSerializerSettings == null) { return null; } TMP_FontAsset result = null; try { if (FileManager.TryGetFontAssetCachePaths(hash, out var cacheDataPath, out var atlasPathMatches)) { List<string> atlasPaths = (from p in atlasPathMatches orderby int.Parse(p.MatchResult.Groups[1].Value) select p.Path).ToList(); result = FontAssetBuilder.CreateFontAssetFromDiskCache(cacheDataPath, atlasPaths, _jsonSerializerSettings, _logger); } } catch (Exception ex) { ManualLogSource logger = _logger; if (logger != null) { logger.LogWarning((object)("An error occurred while creating FontAsset (hash: " + hash + ") from disk cache.")); } ManualLogSource logger2 = _logger; if (logger2 != null) { logger2.LogWarning((object)ex.Message); } result = null; } return result; } public static void WriteFontAssetDiskCache(string hash, TMP_FontAsset fontAsset) { if (string.IsNullOrWhiteSpace(hash) || fontAsset == null || _jsonSerializerSettings == null) { return; } try { string cacheFolder = Path.Combine(FileManager.FontAssetCacheFolder, hash); FontAssetBuilder.WriteFontAssetDiskCache(cacheFolder, fontAsset, _jsonSerializerSettings, _logger); } catch (Exception ex) { ManualLogSource logger = _logger; if (logger != null) { logger.LogWarning((object)("An error occurred while writing FontAsset (hash: " + hash + ") cache to disk.")); } ManualLogSource logger2 = _logger; if (logger2 != null) { logger2.LogWarning((object)ex.Message); } } } public static Regex GetOrCreateRegex(string pattern, RegexOptions regexOptions = RegexOptions.None) { if (_regexCache == null) { _regexCache = new Dictionary<(string, RegexOptions), Regex>(); } (string, RegexOptions) key = (pattern, regexOptions); Regex value = null; if (_regexCache.TryGetValue(key, out value) && value != null) { return value; } value = new Regex(pattern, regexOptions); _regexCache[key] = value; return value; } public static void ScanScriptableObjects() { if (_scriptableObjectCache == null) { _scriptableObjectCache = new ValueCollection<Type, Object>(); } Object[] array = Resources.FindObjectsOfTypeAll(typeof(ScriptableObject)); foreach (Object val in array) { _scriptableObjectCache.Add(((object)val).GetType(), val); } } public static IEnumerable<TScriptableObject> EnumerateScriptableObjects<TScriptableObject>() where TScriptableObject : ScriptableObject { if (_scriptableObjectCache == null || !_scriptableObjectCache.TryGetValues(typeof(TScriptableObject), out var scriptableObjects)) { yield break; } foreach (Object scriptableObject in scriptableObjects) { TScriptableObject so = (TScriptableObject)(object)((scriptableObject is TScriptableObject) ? scriptableObject : null); if (so != null) { yield return so; } } } public static TextAsset GetOrCreateTextAsset(string text) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown if (_textAssetCache == null) { _textAssetCache = new Dictionary<string, TextAsset>(); } TextAsset value = null; if (_textAssetCache.TryGetValue(text, out value) && (Object)(object)value != (Object)null) { return value; } value = new TextAsset(text); _textAssetCache[text] = value; return value; } } public static class ConfigManager { private static Plugin _plugin; private static ConfigFile _config; private static ManualLogSource _logger; public static void Initialize(Plugin plugin) { if (plugin != null) { _plugin = plugin; _config = ((BaseUnityPlugin)plugin).Config; string text = ((BaseUnityPlugin)_plugin).Info.Metadata.Name + "/ConfigManager"; _logger = Logger.CreateLogSource(text); } } public static bool IsModuleEnabled(string section, string moduleDescription = null) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Expected O, but got Unknown //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Expected O, but got Unknown if (_config == null) { return true; } ConfigDefinition val = new ConfigDefinition(section, "IsEnabled"); string text = "Set this field to \"false\" to disable this module."; ConfigEntry<bool> val2 = default(ConfigEntry<bool>); if (moduleDescription != null) { text = moduleDescription + "\n" + text; } else if (_config.TryGetEntry<bool>(val, ref val2)) { text = ((ConfigEntryBase)val2).Description.Description; } ConfigDescription val3 = new ConfigDescription(text, (AcceptableValueBase)null, Array.Empty<object>()); ConfigEntry<bool> val4 = _config.Bind<bool>(val, true, val3); return val4.Value; } public static bool IsModuleUserOverridesEnabled(string section) { if (_config == null) { return false; } ConfigEntry<bool> val = _config.Bind<bool>(section, "EnableUserOverrides", false, "Set this field to \"true\" to apply the custom values below."); return val.Value; } public static object GetConfigEntryValue(string section, string moduleDescription, string key, object defaultValue, string entryDescription) { if (_config == null) { return defaultValue; } bool flag = IsModuleEnabled(section, moduleDescription); bool flag2 = IsModuleUserOverridesEnabled(section); object obj = BindConfigEntryValue(section, key, defaultValue, entryDescription); return (flag && flag2) ? obj : defaultValue; } public static object BindConfigEntryValue(string section, string key, object defaultValue, string entryDescription) { if (1 == 0) { } ConfigEntryBase val; if (!(defaultValue is bool flag)) { if (!(defaultValue is int num)) { if (!(defaultValue is float num2)) { if (!(defaultValue is string text)) { throw new NotSupportedException("\"" + (defaultValue?.GetType().Name ?? "Null") + "\" entry type isn't currently supported."); } val = (ConfigEntryBase)(object)_config.Bind<string>(section, key, text, entryDescription); } else { val = (ConfigEntryBase)(object)_config.Bind<float>(section, key, num2, entryDescription); } } else { val = (ConfigEntryBase)(object)_config.Bind<int>(section, key, num, entryDescription); } } else { val = (ConfigEntryBase)(object)_config.Bind<bool>(section, key, flag, entryDescription); } if (1 == 0) { } ConfigEntryBase val2 = val; return val2.BoxedValue; } } public static class FileManager { private static Plugin _plugin; private static ManualLogSource _logger; private static string _rootFolder; private static string _languageFolder; public static string RootFolder { get { if (_rootFolder == null) { ManualLogSource logger = _logger; if (logger != null) { logger.LogWarning((object)"RootFolder is null."); } } else if (!Directory.Exists(_rootFolder)) { ManualLogSource logger2 = _logger; if (logger2 != null) { logger2.LogWarning((object)("RootFolder \"" + _rootFolder + "\" does not exist.")); } } return _rootFolder ?? Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); } } public static string LanguageFolder { get { if (_languageFolder == null) { ManualLogSource logger = _logger; if (logger != null) { logger.LogWarning((object)"LanguageFolder is null."); } } else if (!Directory.Exists(_languageFolder)) { ManualLogSource logger2 = _logger; if (logger2 != null) { logger2.LogWarning((object)("LanguageFolder \"" + _languageFolder + "\" does not exist.")); } } return _languageFolder ?? RootFolder; } } public static string FontsFolder => Path.Combine(LanguageFolder, "Fonts"); public static string CacheFolder => Path.Combine(RootFolder, "Cache"); public static string FontCacheFolder => Path.Combine(CacheFolder, "Fonts"); public static string FontAssetCacheFolder => Path.Combine(CacheFolder, "FontAssets"); public static void Initialize(Plugin plugin) { _rootFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); if (plugin != null) { _plugin = plugin; string text = ((BaseUnityPlugin)_plugin).Info.Metadata.Name + "/FileManager"; _logger = Logger.CreateLogSource(text); SetLanguageFolder(_plugin.LanguageFolder); } } public static void SetLanguageFolder(string languageFolder) { _languageFolder = Path.GetFullPath(Path.Combine(Paths.PluginPath, languageFolder)); } public static bool TryGetModuleFilePath(string fileName, out string filePath) { filePath = null; return TryGetExistingFilePath(LanguageFolder, fileName, out filePath); } public static bool TryGetFontFilePath(string fileName, out string filePath) { filePath = null; return TryGetExistingFilePath(FontsFolder, fileName, out filePath); } public static bool TryGetFontCachePaths(string hash, out string cacheDataPath, out string atlasPath) { cacheDataPath = null; atlasPath = null; if (TryGetExistingFolderPath(FontCacheFolder, hash, out var folderPath)) { return TryGetExistingFilePath(folderPath, "CachedFontData.json", out cacheDataPath) && TryGetExistingFilePath(folderPath, "RawAtlasTextureData", out atlasPath); } return false; } public static bool TryGetFontAssetCachePaths(string hash, out string cacheDataPath, out IEnumerable<PathMatchResult> atlasPathMatches) { cacheDataPath = null; atlasPathMatches = null; if (TryGetExistingFolderPath(FontAssetCacheFolder, hash, out var folderPath)) { return TryGetExistingFilePath(folderPath, "CachedFontAssetData.json", out cacheDataPath) && TrySearchFilePaths(folderPath, "^RawAtlasTextureData_(\\d+)", out atlasPathMatches); } return false; } public static bool TryGetExistingFilePath(string folder, string fileName, out string filePath) { try { filePath = Path.Combine(folder, fileName); return File.Exists(filePath); } catch (Exception ex) { ManualLogSource logger = _logger; if (logger != null) { logger.LogError((object)ex.Message); } filePath = null; return false; } } public static bool TryGetExistingFolderPath(string parentFolder, string folder, out string folderPath) { try { folderPath = Path.Combine(parentFolder, folder); return Directory.Exists(folderPath); } catch (Exception ex) { ManualLogSource logger = _logger; if (logger != null) { logger.LogError((object)ex.Message); } folderPath = null; return false; } } public static bool TrySearchFilePaths(string folder, string fileNamePattern, out IEnumerable<PathMatchResult> matchResults) { try { Regex regex = CacheManager.GetOrCreateRegex(fileNamePattern); matchResults = from f in Directory.EnumerateFiles(folder) select new PathMatchResult(f, regex.Match(Path.GetFileName(f))) into p where p.MatchResult.Success select p; return matchResults.Count() > 0; } catch (Exception ex) { ManualLogSource logger = _logger; if (logger != null) { logger.LogError((object)ex.Message); } matchResults = null; return false; } } } public static class HashCalculator { private static JsonSerializerSettings _jsonSerializerSettings; public static void Initialize() { //IL_0001: 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) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Expected O, but got Unknown //IL_001d: Expected O, but got Unknown //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Expected O, but got Unknown _jsonSerializerSettings = new JsonSerializerSettings { ContractResolver = (IContractResolver)new DefaultContractResolver { NamingStrategy = (NamingStrategy)new DefaultNamingStrategy() }, Formatting = (Formatting)0, NullValueHandling = (NullValueHandling)0, DefaultValueHandling = (DefaultValueHandling)2 }; } public static string GetHashString(string characters, FontProperties fontProperties, int hashStringLength = 6) { var targetObject = new { Characters = characters, Properties = fontProperties }; return GetHashString(targetObject, hashStringLength); } public static string GetHashString(string characters, FontAssetProperties fontAssetProperties, int hashStringLength = 6) { var targetObject = new { Characters = characters, Properties = fontAssetProperties }; return GetHashString(targetObject, hashStringLength); } public static string GetHashString(object targetObject, int hashStringLength) { if (_jsonSerializerSettings == null) { Initialize(); } string s = JsonConvert.SerializeObject(targetObject, _jsonSerializerSettings); using SHA256 sHA = SHA256.Create(); byte[] array = sHA.ComputeHash(Encoding.UTF8.GetBytes(s)); string text = BitConverter.ToString(array).Replace("-", ""); return text.Substring(0, hashStringLength).ToLower(); } } public static class LanguageScanner { public static List<string> Scan(int maxRecersionDepth = 5, ManualLogSource logger = null) { List<string> list = new List<string>(); ScanLanguageFolders(Paths.PluginPath, maxRecersionDepth, 0, list, logger); return list; } public static void ScanLanguageFolders(string directory, int maxDepth, int currentDepth, List<string> results, ManualLogSource logger = null) { IEnumerable<string> enumerable = Directory.EnumerateFiles(directory, "*", SearchOption.TopDirectoryOnly); foreach (string item in enumerable) { if (!IsLanguageFolderMarkerFile(item)) { continue; } string text = "." + directory.Substring(Paths.PluginPath.Length); text = text.Replace("\\", "/"); if (!results.Contains(text)) { if (logger != null) { logger.LogInfo((object)("Auto-detected Language Folder \"" + text + "\".")); } results.Add(text); } } if (currentDepth >= maxDepth) { return; } IEnumerable<string> enumerable2 = Directory.EnumerateDirectories(directory, "*", SearchOption.TopDirectoryOnly); foreach (string item2 in enumerable2) { ScanLanguageFolders(item2, maxDepth, currentDepth + 1, results, logger); } } public static bool IsLanguageFolderMarkerFile(string filePath) { string fileName = Path.GetFileName(filePath); if (1 == 0) { } bool result = fileName == ".wklocalization"; if (1 == 0) { } return result; } } public class ModuleInfo { public Type ModuleClass; public ModuleStatus Status; public string Message; public ModuleInfo(Type moduleClass, ModuleStatus status, string message) { ModuleClass = moduleClass; Status = status; Message = message; } } public class ModuleLoadResult { private ManualLogSource _logger; public List<ModuleInfo> ModuleInfos; public ModuleLoadResult(ManualLogSource logger) { _logger = logger; ModuleInfos = new List<ModuleInfo>(); } public void AddOKModule(Type moduleClass) { string message = "Loaded \"" + moduleClass.Name + "\" successfully."; AddModuleInfo(moduleClass, ModuleStatus.OK, message); } public void AddDisabledModule(Type moduleClass) { string message = "\"" + moduleClass.Name + "\" is loaded but manually disabled in config."; AddModuleInfo(moduleClass, ModuleStatus.Disabled, message); } public void AddFileMissingModule(Type moduleClass) { string message = "\"" + moduleClass.Name + "\" is missing its associated .json file and disabled by default."; AddModuleInfo(moduleClass, ModuleStatus.Disabled, message); } public void AddConflictedModule(Type moduleClass, List<string> conflictedModGUIDs) { string text = null; text = ((conflictedModGUIDs != null && conflictedModGUIDs.Count != 0) ? ("\"" + moduleClass.Name + "\" is disabled to avoid conflicts with the following mod(s):\n" + string.Join("\n", conflictedModGUIDs)) : ("\"" + moduleClass.Name + "\" is disabled to avoid conflicts.")); AddModuleInfo(moduleClass, ModuleStatus.Conflicted, text); } public void AddDeserializationFailedModule(Type moduleClass, string filePath, Exception e) { string message = "An error occurred while deserializing \"" + moduleClass.Name + "\" from \"" + filePath + "\".\n" + e.Message; AddModuleInfo(moduleClass, ModuleStatus.Failed, message); } public void AddModuleInfo(Type moduleClass, ModuleStatus status, string message) { ModuleInfo item = new ModuleInfo(moduleClass, status, message); ModuleInfos.Add(item); } public List<Type> FilterModuleClassesByModuleStatus(ModuleStatus status) { if (ModuleInfos == null) { return null; } return (from m in ModuleInfos where m.Status == status select m.ModuleClass).ToList(); } public void PrintModuleInfoMessageBySeverity(ModuleStatus minSeverity) { if (ModuleInfos == null || _logger == null) { return; } List<ModuleInfo> list = ModuleInfos.Where((ModuleInfo m) => m.Status >= minSeverity).ToList(); foreach (ModuleInfo item in list) { PrintModuleInfoMessage(item); } } public void PrintModuleInfoMessage(ModuleInfo moduleInfo) { if (_logger != null) { switch (moduleInfo.Status) { case ModuleStatus.OK: _logger.LogInfo((object)moduleInfo.Message); break; case ModuleStatus.Disabled: _logger.LogInfo((object)moduleInfo.Message); break; case ModuleStatus.Conflicted: _logger.LogInfo((object)moduleInfo.Message); break; case ModuleStatus.Failed: _logger.LogError((object)moduleInfo.Message); break; default: throw new ArgumentOutOfRangeException("How?"); } } } } public static class ModuleManager { private static Plugin _plugin; private static ManualLogSource _logger; private static JsonSerializerSettings _jsonSerializerSettings; private static ModuleLoadResult _moduleLoadResult; private static ValueCollection<Type, string> _conflictedModsInfo = new ValueCollection<Type, string>(); public static void Initialize(Plugin plugin) { //IL_0001: 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) //IL_0013: Expected O, but got Unknown _jsonSerializerSettings = new JsonSerializerSettings { NullValueHandling = (NullValueHandling)1 }; if (plugin != null) { _plugin = plugin; string text = ((BaseUnityPlugin)_plugin).Info.Metadata.Name + "/ModuleManager"; _logger = Logger.CreateLogSource(text); _moduleLoadResult = new ModuleLoadResult(_logger); } } public static void LoadAllModules() { LoadModule<AchievementPatch>("Achievements.json"); LoadModule<AnnouncementSubtitlePatch>("AnnouncementSubtitles.json"); LoadModule<AnnouncementSubtitleTimingPatch>("AnnouncementSubtitleTimings.json"); LoadModule<CosmeticPatch>("Cosmetics.json"); LoadModule<DeathTextPatch>("DeathTexts.json"); LoadModule<DocumentPatch>("Documents.json"); LoadModule<FacilityUpgradePatch>("FacilityUpgrades.json"); LoadModule<FontPatch>("Fonts.json"); LoadModule<FontAssetPatch>("FontAssets.json"); LoadModule<GamemodePatch>("Gamemodes.json"); LoadModule<GameplayTextPatch>("GameplayTexts.json"); LoadModule<ItemDescriptionPatch>("ItemDescriptions.json"); LoadModule<LocationNamePatch>("LocationNames.json"); LoadModule<MainMenuPatch>("MainMenu.json"); LoadModule<MotherSubtitlePatch>("MotherSubtitles.json"); LoadModule<NotePatch>("Notes.json"); LoadModule<ObjectivePatch>("Objectives.json"); LoadModule<PerkPatch>("Perks.json"); LoadModule<ProgressionUnlockPatch>("ProgressionUnlocks.json"); LoadModule<QuietOSPatch>("QuietOS.json"); LoadModule<RecordingSubtitlePatch>("RecordingSubtitles.json"); LoadModule<RecordingSubtitleTimingPatch>("RecordingSubtitleTimings.json"); LoadModule<RoachTraderSubtitlePatch>("RoachTraderSubtitles.json"); LoadModule<ScoreScreenPatch>("ScoreScreen.json"); LoadModule<StaticTextPatch>("StaticTexts.json"); LoadModule<TextScrawlPatch>("TextScrawls.json"); LoadModule<TrinketPatch>("Trinkets.json"); } public static void LoadModule<TModule>(string fileName) where TModule : ModuleBase<TModule> { Type typeFromHandle = typeof(TModule); if (DetectConflictedMods(typeFromHandle, out var conflictedModGUIDs)) { _moduleLoadResult.AddConflictedModule(typeFromHandle, conflictedModGUIDs); return; } if (!FileManager.TryGetModuleFilePath(fileName, out var filePath)) { _moduleLoadResult.AddFileMissingModule(typeFromHandle); return; } try { string text = File.ReadAllText(filePath); if (string.IsNullOrWhiteSpace(text)) { throw new InvalidDataException("File content is empty or whitespace."); } JsonConvert.DeserializeObject<TModule>(text, _jsonSerializerSettings); } catch (Exception e) { _moduleLoadResult.AddDeserializationFailedModule(typeFromHandle, fileName, e); return; } if (ModuleBase<TModule>.IsEnabled) { _moduleLoadResult.AddOKModule(typeFromHandle); } else { _moduleLoadResult.AddDisabledModule(typeFromHandle); } } public static bool DetectConflictedMods(Type moduleClass, out List<string> conflictedModGUIDs) { conflictedModGUIDs = null; if (_conflictedModsInfo != null && _conflictedModsInfo.TryGetValues(moduleClass, out conflictedModGUIDs)) { return conflictedModGUIDs.Any((string g) => Chainloader.PluginInfos.ContainsKey(g)); } return false; } public static List<Type> FilterModuleClassesByModuleStatus(ModuleStatus status) { return _moduleLoadResult?.FilterModuleClassesByModuleStatus(status); } public static void PrintModuleInfoMessageBySeverity(ModuleStatus minSeverity) { _moduleLoadResult?.PrintModuleInfoMessageBySeverity(minSeverity); } } public enum ModuleStatus { OK, Disabled, Conflicted, Failed } public class PathMatchResult { public string Path; public Match MatchResult; public PathMatchResult(string path, Match matchResult) { Path = path; MatchResult = matchResult; } } [BepInPlugin("mimimi-turret.wk-localization-loader", "WKLocalizationLoader", "0.6.3")] [BepInProcess("White Knuckle.exe")] public class Plugin : BaseUnityPlugin { private ConfigEntry<string> _languageFolder; private ConfigEntry<int> _maxScanDepth; public static ManualLogSource Logger; private string _languageFolderDescription => "Specifies the path to a Language Folder.\nA relative path is resolved from \"BepInEx\\plugins\\\".\n\nA Language Folder may contain any of the following files:\n* Texts.json\n* FontAssets.json\n* Fonts\\\n* Licenses\\ (licenses of the fonts, etc.)\n\nLeave this field empty to auto-detect a \nLanguage Folder installed in \"BepInEx\\plugins\\\".\nBy default, the plugin will load the first valid Language Folder it detects.\nFor further info, see \"MaxScanDepth\" below or plugin wiki."; private string _maxScanDepthDescription => "Limits the directory depth when auto-detecting Language Folders.\n\nA Language Folder is detected when it contains the following file:\n* .wklocalization\nNote: This file is for auto-detection purpose only.\nIt does not store any actual information or data.\n\nScanning will start from \"BepInEx\\plugins\\\" where the directory depth is 0."; public string LanguageFolder => _languageFolder?.Value; public int MaxScanDepth => _maxScanDepth?.Value ?? 5; private void Awake() { Initialize(); if (string.IsNullOrWhiteSpace(LanguageFolder)) { Logger.LogFatal((object)"Failed to auto-detect Language Folders."); return; } FileManager.Initialize(this); ConfigManager.Initialize(this); ModuleManager.Initialize(this); ResourceLoader.Initialize(this); CacheManager.Initialize(this); List<Type> moduleClasses = LoadAllModules(); ApplyHarmonyPatches(moduleClasses); ApplyScriptableObjectPatches(moduleClasses); } private void Initialize() { _languageFolder = ((BaseUnityPlugin)this).Config.Bind<string>("General", "LanguageFolder", "", _languageFolderDescription); _maxScanDepth = ((BaseUnityPlugin)this).Config.Bind<int>("General", "MaxScanDepth", 5, _maxScanDepthDescription); Logger = ((BaseUnityPlugin)this).Logger; if (string.IsNullOrWhiteSpace(LanguageFolder)) { List<string> list = LanguageScanner.Scan(MaxScanDepth, Logger); if (list.Count != 0) { Logger.LogInfo((object)"Loading the first valid Language Folder detected by default."); _languageFolder.Value = list.FirstOrDefault(); } } } private List<Type> LoadAllModules() { ModuleManager.LoadAllModules(); List<Type> result = ModuleManager.FilterModuleClassesByModuleStatus(ModuleStatus.OK); ModuleManager.PrintModuleInfoMessageBySeverity(ModuleStatus.OK); return result; } private void ApplyHarmonyPatches(List<Type> moduleClasses) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Expected O, but got Unknown Harmony val = new Harmony(((BaseUnityPlugin)this).Info.Metadata.GUID); foreach (Type moduleClass in moduleClasses) { if (((MemberInfo)moduleClass).GetCustomAttribute<HarmonyPatch>() != null) { PatchClassProcessor val2 = val.CreateClassProcessor(moduleClass); val2.Patch(); } } } private void ApplyScriptableObjectPatches(List<Type> moduleClasses) { ScriptableObjectPatcher.Initialize(moduleClasses); } } public static class ResourceLoader { private static Plugin _plugin; private static ManualLogSource _logger; public static void Initialize(Plugin plugin) { if (plugin != null) { _plugin = plugin; string text = ((BaseUnityPlugin)_plugin).Info.Metadata.Name + "/ResourceLoader"; _logger = Logger.CreateLogSource(text); } } public static bool TryGetOrCreateFont(string characters, FontProperties fontProperties, out Font font, bool isDiskCacheEnabled = false) { string hashString = HashCalculator.GetHashString(characters, fontProperties); font = CacheManager.GetFontFromMemoryCache(hashString); if ((Object)(object)font != (Object)null) { return true; } bool flag = isDiskCacheEnabled; if (isDiskCacheEnabled) { font = CacheManager.CreateFontFromDiskCache(hashString); flag = font == null; } if (font == null) { font = CreateFont(hashString, characters, fontProperties); } flag = flag && (Object)(object)font != (Object)null; if (font == null) { font = FontBuilder.CreateFontFromOSFont(fontProperties); } if (font == null) { return false; } if (flag) { CacheManager.WriteFontDiskCache(hashString, font); } CacheManager.AddFontToMemoryCache(hashString, font); return true; } public static Font CreateFont(string hash, string characters, FontProperties fontProperties) { try { if (FileManager.TryGetFontFilePath(fontProperties.FileName, out var filePath)) { if (string.IsNullOrEmpty(fontProperties.FontName)) { fontProperties.FontName = "SubstituteFont - " + Path.GetFileNameWithoutExtension(filePath); } return FontBuilder.CreateFont(filePath, characters, fontProperties, _logger); } return null; } catch (Exception ex) { ManualLogSource logger = _logger; if (logger != null) { logger.LogError((object)("An error occurred while creating Font (hash: " + hash + ").")); } ManualLogSource logger2 = _logger; if (logger2 != null) { logger2.LogError((object)ex.Message); } return null; } } public static bool TryGetOrCreateFontAsset(string characters, FontAssetProperties fontAssetProperties, out TMP_FontAsset fontAsset, bool isDiskCacheEnabled = false) { string hashString = HashCalculator.GetHashString(characters, fontAssetProperties); fontAsset = CacheManager.GetFontAssetFromMemoryCache(hashString); if ((Object)(object)fontAsset != (Object)null) { return true; } bool flag = isDiskCacheEnabled; if (isDiskCacheEnabled) { fontAsset = CacheManager.CreateFontAssetFromDiskCache(hashString); flag = fontAsset == null; } if (fontAsset == null) { fontAsset = CreateFontAsset(hashString, characters, fontAssetProperties); } if (fontAsset == null) { return false; } if (flag) { CacheManager.WriteFontAssetDiskCache(hashString, fontAsset); } CacheManager.AddFontAssetToMemoryCache(hashString, fontAsset); return true; } public static TMP_FontAsset CreateFontAsset(string hash, string characters, FontAssetProperties fontAssetProperties) { try { if (FileManager.TryGetFontFilePath(fontAssetProperties.FileName, out var filePath)) { if (string.IsNullOrEmpty(fontAssetProperties.FontName)) { fontAssetProperties.FontName = "FallbackFontAsset - " + Path.GetFileNameWithoutExtension(filePath); } return FontAssetBuilder.CreateFontAsset(filePath, characters, fontAssetProperties, _logger); } return null; } catch (Exception ex) { ManualLogSource logger = _logger; if (logger != null) { logger.LogError((object)("An error occurred while creating FontAsset (hash: " + hash + ").")); } _logger.LogError((object)ex.Message); return null; } } } public class ScriptableObjectPatcher { public static List<Type> ModuleClasses; public static void Initialize(List<Type> moduleClasses) { ModuleClasses = moduleClasses; FilterScriptableObjectPatchClasses(); if (ModuleClasses != null && ModuleClasses.Count != 0) { SceneManager.sceneLoaded += OnSceneLoaded; } } public static void OnSceneLoaded(Scene scene, LoadSceneMode loadSceneMode) { if (((Scene)(ref scene)).name == "Main-Menu") { CacheManager.ScanScriptableObjects(); ApplyScriptableObjectPatches(); SceneManager.sceneLoaded -= OnSceneLoaded; } } public static void ApplyScriptableObjectPatches() { foreach (Type moduleClass in ModuleClasses) { moduleClass.GetMethod("PatchScriptableObjects", BindingFlags.Static | BindingFlags.Public)?.Invoke(null, null); } } public static void FilterScriptableObjectPatchClasses() { if (ModuleClasses != null && ModuleClasses.Count != 0) { ModuleClasses = ModuleClasses.Where((Type m) => typeof(IScriptableObjectPatch).IsAssignableFrom(m) && m.GetMethod("PatchScriptableObjects", BindingFlags.Static | BindingFlags.Public) != null).ToList(); } } } public class TemplateTranslations { private Dictionary<string, string> _textTranslations; private Dictionary<Regex, string> _templateMappings; private readonly Regex _templateGroupRegex; private readonly Regex _escapedTemplateGroupRegex; public TemplateTranslations(Dictionary<string, string> textTranslations) { _templateGroupRegex = CacheManager.GetOrCreateRegex("\\{(\\d+)\\}", RegexOptions.Compiled); _escapedTemplateGroupRegex = CacheManager.GetOrCreateRegex("\\\\\\{\\d+\\}", RegexOptions.Compiled); AddTemplateTranslations(textTranslations); } public void AddTemplateTranslations(Dictionary<string, string> textTranslations) { foreach (KeyValuePair<string, string> textTranslation in textTranslations) { AddTemplateTranslation(textTranslation.Key, textTranslation.Value); } } public void AddTemplateTranslation(string originalTemplateString, string translatedTemplateString) { if (_textTranslations == null) { _textTranslations = new Dictionary<string, string>(); } if (_templateMappings == null) { _templateMappings = new Dictionary<Regex, string>(); } if (originalTemplateString != null && translatedTemplateString != null) { _textTranslations[originalTemplateString] = translatedTemplateString; if (_templateGroupRegex.IsMatch(originalTemplateString)) { Regex key = CreateTemplateRegex(originalTemplateString); _templateMappings[key] = translatedTemplateString; } } } public string GetTemplateTranslation(string originalText) { if (string.IsNullOrWhiteSpace(originalText)) { return originalText; } if (_textTranslations != null && _textTranslations.TryGetValue(originalText, out var value) && value != null) { return value; } if (_templateMappings != null) { foreach (KeyValuePair<Regex, string> templateMapping in _templateMappings) { Regex key = templateMapping.Key; if (key != null) { Match match = key.Match(originalText); if (match.Success) { string value2 = templateMapping.Value; return (value2 == null) ? originalText : BuildStringFromTemplate(value2, match); } } } } return originalText; } public string BuildStringFromTemplate(string templateString, Match templateMatch) { return _templateGroupRegex.Replace(templateString, delegate(Match m) { int num = Convert.ToInt32(m.Groups[1].Value) + 1; return (num > templateMatch.Groups.Count) ? "" : templateMatch.Groups[num].Value; }); } public Regex CreateTemplateRegex(string templateString) { string input = Regex.Escape(templateString); string text = _escapedTemplateGroupRegex.Replace(input, "(.*)"); text = "^" + text + "$"; return CacheManager.GetOrCreateRegex(text, RegexOptions.Singleline); } } public class ValueCollection<TKey, TValue> { private Dictionary<TKey, List<TValue>> _dictionary; public void Add(TKey key, TValue value) { if (_dictionary == null) { _dictionary = new Dictionary<TKey, List<TValue>>(); } if (key == null || value == null) { return; } if (TryGetValues(key, out var values)) { if (!values.Contains(value)) { values.Add(value); } } else { _dictionary[key] = new List<TValue> { value }; } } public bool TryGetValues(TKey key, out List<TValue> values) { if (key == null || _dictionary == null || !_dictionary.TryGetValue(key, out values) || values == null) { values = null; return false; } return true; } } public static class MyPluginInfo { public const string PLUGIN_GUID = "WKLocalizationLoader"; public const string PLUGIN_NAME = "WKLocalizationLoader"; public const string PLUGIN_VERSION = "0.6.3"; } } namespace WKLocalizationLoader.Modules { [HarmonyPatch] public class AchievementPatch : TextTranslator<AchievementPatch> { [JsonProperty] public static Dictionary<string, string> AchievementTitles; [JsonProperty] public static Dictionary<string, string> AchievementDescriptions; [JsonIgnore] public static AchievementPatchSettings ModuleSettings; [HarmonyPostfix] [HarmonyPatch(typeof(CL_AchievementManager), "Awake")] public static void Postfix_AchievementManager_Awake(CL_AchievementManager __instance) { if (!ModuleBase<AchievementPatch>.IsEnabled) { return; } foreach (GameAchievement achievement in __instance.achievements) { if (achievement.announce) { achievement.name = TextTranslator<AchievementPatch>.GetTextTranslation(AchievementTitles, achievement.name); achievement.announceText = TextTranslator<AchievementPatch>.GetTextTranslation(AchievementDescriptions, achievement.announceText); } } } } [ConfigSection("Modules.AchievementPatch", "This module replaces texts for achievements.")] public class AchievementPatchSettings : ModuleSettingsBase { } [HarmonyPatch] public class AnnouncementSubtitlePatch : ModuleBase<AnnouncementSubtitlePatch> { [JsonProperty] public static Dictionary<string, string> AnnouncementSubtitles; [JsonIgnore] public static AnnouncementSubtitlePatchSettings ModuleSettings; [HarmonyPostfix] [HarmonyPatch(typeof(Localization), "GetLine")] public static string Postfix_Localization_GetLine(string __result, string group, string key) { if (!ModuleBase<AnnouncementSubtitlePatch>.IsEnabled || group != "announcements" || AnnouncementSubtitles == null || !AnnouncementSubtitles.ContainsKey(key)) { return __result; } return AnnouncementSubtitles[key] ?? __result; } } [ConfigSection("Modules.AnnouncementSubtitlePatch", "This module replaces announcer subtitle texts.")] public class AnnouncementSubtitlePatchSettings : ModuleSettingsBase { } [HarmonyPriority(0)] [HarmonyPatch] public class AnnouncementSubtitleTimingPatch : ModuleBase<AnnouncementSubtitleTimingPatch> { [JsonProperty] public static AnnouncementSubtitleTimingPatchSettings ModuleSettings; [JsonProperty] public static Dictionary<string, List<float>> AnnouncementSubtitleTimings; [JsonIgnore] public static readonly string[] LinebreakPattern = new string[1] { "<br>" }; [JsonIgnore] public static readonly Regex DelayRegex = CacheManager.GetOrCreateRegex("<delay\\s*=\\s*([+-]?\\d*\\.?\\d+)>", RegexOptions.IgnoreCase | RegexOptions.Compiled); [HarmonyPostfix] [HarmonyPatch(typeof(Localization), "GetLine")] public static string Postfix_Localization_GetLine(string __result, string group, string key) { if (!ModuleBase<AnnouncementSubtitleTimingPatch>.IsEnabled || group != "announcements" || AnnouncementSubtitleTimings == null || !AnnouncementSubtitleTimings.ContainsKey(key) || (ModuleSettings.UseOriginalDelay && DelayRegex.IsMatch(__result))) { return __result; } return RebuildSubtitleTextWithTimings(__result, AnnouncementSubtitleTimings[key]); } public static string RebuildSubtitleTextWithTimings(string subtitleText, List<float> subtitleTimings) { if (subtitleTimings == null || subtitleTimings.Count == 0) { return subtitleText; } string[] array = subtitleText.Split(LinebreakPattern, StringSplitOptions.None); int num = Math.Min(array.Length, subtitleTimings.Count); for (int i = 0; i < num; i++) { string subtitleLine = array[i]; subtitleLine = RemoveDelayTag(subtitleLine); float num2 = (float)subtitleLine.Length * ModuleSettings.CharacterInterval + ModuleSettings.BaseDuration; float num3 = subtitleTimings[i]; if (i > 0) { num3 -= subtitleTimings[i - 1]; } if (i == num - 1) { num3 += ModuleSettings.EndDelay; } float num4 = num3 - num2; string delayTag = ((num4 < 0f) ? $"<delay={num4:F3}>" : $"<delay={num4:F4}>"); subtitleLine = InsertDelayTag(subtitleLine, delayTag); array[i] = subtitleLine; } return string.Join(LinebreakPattern[0], array); } public static string RemoveDelayTag(string subtitleLine) { Match match = DelayRegex.Match(subtitleLine); return match.Success ? subtitleLine.Remove(match.Index, match.Length) : subtitleLine; } public static string InsertDelayTag(string subtitleLine, string delayTag) { if (string.IsNullOrEmpty(delayTag)) { return subtitleLine; } Match match = DelayRegex.Match(subtitleLine); return match.Success ? subtitleLine.Insert(match.Index, delayTag) : (subtitleLine + delayTag); } } [ConfigSection("Modules.AnnouncementSubtitleTimingPatch", "This module adjusts display timings of announcer subtitles.")] public class AnnouncementSubtitleTimingPatchSettings : ModuleSettingsBase { [ConfigEntry("BaseDuration", 2.2f, "Base duration (in seconds) for displaying a subtitle.")] public float BaseDuration; [ConfigEntry("CharacterInterval", 0.1f, "Additional duration (in seconds) added per character in the subtitle text.")] public float CharacterInterval; [ConfigEntry("EndDelay", 0.5f, "Extra duration (in seconds) added at the end of a subtitle.")] public float EndDelay; [ConfigEntry("UseOriginalDelay", false, "Set this field to \"true\" to retain original timings of\nsubtitles that contain \"<delay>\" tag(s).")] public bool UseOriginalDelay; } [HarmonyPatch] public class CosmeticPatch : TextTranslator<CosmeticPatch> { [JsonProperty] public static Dictionary<string, string> CosmeticDescriptions; [JsonProperty] public static Dictionary<string, string> PaletteNames; [JsonProperty] public static string PaletteTextTemplate; [JsonIgnore] public static CosmeticPatchSettings ModuleSettings; [HarmonyPostfix] [HarmonyPatch(typeof(UI_CosmeticInfoPanel), "Open")] public static void Postfix_CosmeticInfoPanel_Open(UI_CosmeticInfoPanel __instance) { if (ModuleBase<CosmeticPatch>.IsEnabled) { __instance.descText.text = TextTranslator<CosmeticPatch>.GetTextTranslation(CosmeticDescriptions, __instance.descText.text); } } [HarmonyPostfix] [HarmonyPatch(typeof(UI_CosmeticInfoPanel), "UpdateSprite")] public static void Postfix_CosmeticInfoPanel_UpdateSprite(UI_CosmeticInfoPanel __instance) { if (ModuleBase<CosmeticPatch>.IsEnabled) { TranslatePaletteText(__instance); } } [HarmonyPostfix] [HarmonyPatch(typeof(UI_CosmeticInfoPanel), "ChangePalette")] public static void Postfix_CosmeticInfoPanel_ChangePalette(UI_CosmeticInfoPanel __instance) { if (ModuleBase<CosmeticPatch>.IsEnabled) { TranslatePaletteText(__instance); } } public static void TranslatePaletteText(UI_CosmeticInfoPanel infoPanel) { Cosmetic_Base selectedCosmetic = infoPanel.selectedCosmetic; if (!(selectedCosmetic.cosmeticInfo.tag != "hand")) { Cosmetic_HandItem val = (Cosmetic_HandItem)(object)((selectedCosmetic is Cosmetic_HandItem) ? selectedCosmetic : null); List<ColorPalette> palettes = val.cosmeticData.palettes; if (palettes != null && palettes.Count != 0) { ColorPalette val2 = palettes[val.currentPaletteId]; string textTranslation = TextTranslator<CosmeticPatch>.GetTextTranslation(PaletteNames, val2.title); int num = val.currentPaletteId + 1; int count = palettes.Count; string text = PaletteTextTemplate ?? "{name} ({current}/{count})"; infoPanel.debugText.text = text.Replace("{name}", textTranslation).Replace("{current}", num.ToString()).Replace("{count}", count.ToString()); } } } } [ConfigSection("Modules.CosmeticPatch", "This module replaces texts for cosmetics.")] public class CosmeticPatchSettings : ModuleSettingsBase { } [HarmonyPatch] public class DeathTextPatch : TextTranslator<DeathTextPatch> { [JsonProperty] public static Dictionary<string, string> DeathMessages; [JsonProperty] public static Dictionary<string, string> DeathTips; [JsonIgnore] public static DeathTextPatchSettings ModuleSettings; [HarmonyPostfix] [HarmonyPatch(typeof(Localization), "GetLine")] public static string Postfix_Localization_GetLine(string __result, string group, string key) { if (!ModuleBase<DeathTextPatch>.IsEnabled || group != "deathmessages" || DeathMessages == null || !DeathMessages.ContainsKey(key)) { return __result; } return DeathMessages[key] ?? __result; } [HarmonyPostfix] [HarmonyPatch(typeof(UI_ScoreScreen), "SetTip")] public static void Postfix_ScoreScreen_SetTip(UI_ScoreScreen __instance) { if (ModuleBase<DeathTextPatch>.IsEnabled && __instance.useDeathText && __instance.tipText != null) { __instance.tipText.text = TextTranslator<DeathTextPatch>.GetTextTranslation(DeathTips, __instance.tipText.text); } } } [ConfigSection("Modules.DeathTextPatch", "This module replaces death messages and death tips.")] public class DeathTextPatchSettings : ModuleSettingsBase { } [HarmonyPatch] public class DocumentPatch : TextTranslator<DocumentPatch> { [JsonProperty] public static Dictionary<string, string> DocumentTexts; [JsonIgnore] public static DocumentPatchSettings ModuleSettings; [HarmonyPrefix] [HarmonyPatch(typeof(App_DocumentReader), "Start")] public static void Prefix_DocumentReader_Start(App_DocumentReader __instance) { if (ModuleBase<DocumentPatch>.IsEnabled) { string text = ""; OS_Window component = ((Component)__instance).GetComponent<OS_Window>(); FileInfo fileInfo = component.file.fileInfo; if (fileInfo.textAssetData == null) { string data = fileInfo.data; string text2 = DarkMachineFunctions.ProcessText(data, true); text = TextTranslator<DocumentPatch>.GetTextTranslation(DocumentTexts, text2); fileInfo.data = data.Replace(text2, text); } else { text = TextTranslator<DocumentPatch>.GetTextTranslation(DocumentTexts, fileInfo.textAssetData.text); fileInfo.textAssetData = CacheManager.GetOrCreateTextAsset(text); } } } } [ConfigSection("Modules.DocumentPatch", "This module replaces texts for QuietOS document files.")] public class DocumentPatchSettings : ModuleSettingsBase { } [HarmonyPatch] public class FacilityUpgradePatch : TextTranslator<FacilityUpgradePatch> { [JsonProperty] public static Dictionary<string, string> UpgradePageTitles; [JsonProperty] public static Dictionary<string, string> UpgradeTitles; [JsonProperty] public static Dictionary<string, string> UpgradeDescriptions; [JsonProperty] public static Dictionary<string, string> UpgradeUnlockDescriptions; [JsonProperty] public static string UpgradePageCounterTemplate; [JsonProperty] public static string UpgradeLockedHoverTextTemplate; [JsonProperty] public static string UpgradeCantAffordHoverTextTemplate; [JsonIgnore] public static FacilityUpgradePatchSettings ModuleSettings; [HarmonyPrefix] [HarmonyPatch(typeof(UI_FacilityMenu_Button), "Initialize")] public static void Prefix_FacilityMenuButton_Initialize(ref FacilityUpgrade upg) { if (ModuleBase<FacilityUpgradePatch>.IsEnabled) { PatchFacilityUpgrade(upg); } } [HarmonyPostfix] [HarmonyPatch(typeof(UI_FacilityMenu_Button), "Refresh")] public static void Postfix_FacilityMenuButton_Refresh(UI_FacilityMenu_Button __instance) { if (!ModuleBase<FacilityUpgradePatch>.IsEnabled) { return; } FacilityUpgrade upgrade = __instance.upgrade; Facility facility = __instance.facility; if (upgrade == null || facility == null) { return; } if (upgrade.IsLocked(facility.id)) { if (UpgradeLockedHoverTextTemplate != null) { __instance.tooltip.tip = UpgradeLockedHoverTextTemplate.Replace("{unlockDescription}", upgrade.unlockDesc).Replace("{description}", upgrade.description); } return; } int value = StatManager.saveData.GetRoachBankByID("campaign").value; if (!upgrade.IsOwned(facility.id) && upgrade.cost >= value && UpgradeCantAffordHoverTextTemplate != null) { __instance.tooltip.tip = UpgradeCantAffordHoverTextTemplate.Replace("{description}", upgrade.description); } } [HarmonyPrefix] [HarmonyPatch(typeof(App_Facility_Card), "Initialize")] public static void Prefix_FacilityApp_Initialize(ref FacilityUpgrade up) { if (ModuleBase<FacilityUpgradePatch>.IsEnabled) { PatchFacilityUpgrade(up); } } [HarmonyPostfix] [HarmonyPatch(typeof(App_Facility_Card), "CheckLock")] public static void Postfix_FacilityAppCard_CheckLock(App_Facility_Card __instance) { if (ModuleBase<FacilityUpgradePatch>.IsEnabled) { FacilityUpgrade upgrade = __instance.upgrade; if (!string.IsNullOrEmpty(upgrade.unlockFlag) && !CL_GameManager.HasActiveFlag(upgrade.unlockFlag, true) && upgrade.prerequisiteUpgrade != null && !__instance.facility.HasUpgrade(upgrade.prerequisiteUpgrade.id) && UpgradeLockedHoverTextTemplate != null) { __instance.tooltip.tip = UpgradeLockedHoverTextTemplate.Replace("{unlockDescription}", upgrade.unlockDesc).Replace("{description}", upgrade.description); } } } [HarmonyPostfix] [HarmonyPatch(typeof(App_FacilitySlotHolder), "SetPage")] public static void Postfix_FacilitySlotHolder_SetPage(List<UpgradePage> pageList, ref int pageNumber, TMP_Text titleObject, string title) { if (ModuleBase<FacilityUpgradePatch>.IsEnabled) { string textTranslation = TextTranslator<FacilityUpgradePatch>.GetTextTranslation(UpgradePageTitles, title); int count = pageList.Count; if (count < 2) { titleObject.text = textTranslation; return; } int num = pageNumber + 1; string text = UpgradePageCounterTemplate ?? "{title} ({current}/{total})"; titleObject.text = text.Replace("{title}", textTranslation).Replace("{current}", num.ToString()).Replace("{total}", count.ToString()); } } public static void PatchFacilityUpgrade(FacilityUpgrade upgrade) { upgrade.cardName = TextTranslator<FacilityUpgradePatch>.GetTextTranslation(UpgradeTitles, upgrade.cardName); upgrade.description = TextTranslator<FacilityUpgradePatch>.GetTextTranslation(UpgradeDescriptions, upgrade.description); upgrade.unlockDesc = TextTranslator<FacilityUpgradePatch>.GetTextTranslation(UpgradeUnlockDescriptions, upgrade.unlockDesc); } } [ConfigSection("Modules.FacilityUpgradePatch", "This module replaces texts of facility upgrades.")] public class FacilityUpgradePatchSettings : ModuleSettingsBase { } [HarmonyPatch] public class FontAssetPatch : ModuleBase<FontAssetPatch> { [JsonProperty] public static FontAssetPatchSettings ModuleSettings; [JsonProperty] public static Dictionary<string, List<FontAssetProperties>> CustomFontAssets; [JsonProperty] public static string CharactersToRender; [JsonIgnore] public static ValueCollection<string, TMP_FontAsset> FallbackFontAssets = new ValueCollection<string, TMP_FontAsset>(); [OnDeserialized] private void OnDeserialized(StreamingContext _) { if (!ModuleBase<FontAssetPatch>.IsEnabled) { return; } foreach (KeyValuePair<string, List<FontAssetProperties>> customFontAsset in CustomFontAssets) { string key = customFontAsset.Key; List<FontAssetProperties> value = customFontAsset.Value; foreach (FontAssetProperties item in value) { CreateAndRegisterFallbackFontAsset(key, item); } } } [HarmonyPostfix] [HarmonyPatch(typeof(TMP_FontAsset), "Awake")] public static void Postfix_FontAsset_Awake(TMP_FontAsset __instance) { if (ModuleBase<FontAssetPatch>.IsEnabled) { AddFallbackFontAssets(__instance); } } public static void AddFallbackFontAssets(TMP_FontAsset __instance) { if (TryGetFallbackFontAssets(((Object)__instance).name, out var fallbackFontAssets)) { if (ModuleSettings.HighFallbackPriority) { __instance.fallbackFontAssetTable = fallbackFontAssets.Union(__instance.fallbackFontAssetTable).ToList(); } else { __instance.fallbackFontAssetTable = __instance.fallbackFontAssetTable.Union(fallbackFontAssets).ToList(); } } } public static void CreateAndRegisterFallbackFontAsset(string targetFontName, FontAssetProperties fallbackFontAssetProperties) { if (ResourceLoader.TryGetOrCreateFontAsset(CharactersToRender, fallbackFontAssetProperties, out var fontAsset, ModuleSettings.SaveFontAssetCacheOnDisk)) { RegisterFallbackFontAsset(targetFontName, fontAsset); } } public static void RegisterFallbackFontAsset(string targetFontName, TMP_FontAsset fallbackFontAsset) { FallbackFontAssets?.Add(targetFontName, fallbackFontAsset); } public static bool TryGetFallbackFontAssets(string targetFontName, out List<TMP_FontAsset> fallbackFontAssets) { if (FallbackFontAssets != null && FallbackFontAssets.TryGetValues(targetFontName, out fallbackFontAssets)) { return true; } fallbackFontAssets = null; return false; } } [ConfigSection("Modules.FontAssetPatch", "This module adds fallback font assets.")] public class FontAssetPatchSettings : ModuleSettingsBase { [ConfigEntry("HighFallbackPriority", true, "Set this field to \"false\" to\nlower fallback priority of custom fallback font assets.")] public bool HighFallbackPriority; [ConfigEntry("SaveFontAssetCacheOnDisk", false, "Set this field to \"true\" to cache generated TMP_FontAsset\non disk to reduce load times on subsequent game launches.\nWarning: Cache size may grow significantly --\nA 4096×4096 atlas alone is about 16MB.\nEnable this only if you have spare disk space.\nCache files are stored in the same directory as plugin .dll.")] public bool SaveFontAssetCacheOnDisk; } [HarmonyPriority(0)] [HarmonyPatch] public class FontPatch : ModuleBase<FontPatch> { [JsonProperty] public static FontPatchSettings ModuleSettings; [JsonProperty] public static Dictionary<string, FontProperties> CustomFonts; [JsonProperty] public static string CharactersToRender; [JsonIgnore] public static Dictionary<string, Font> SubstituteFonts = new Dictionary<string, Font>(); [OnDeserialized] private void OnDeserialized(StreamingContext _) { if (!ModuleBase<FontPatch>.IsEnabled) { return; } foreach (KeyValuePair<string, FontProperties> customFont in CustomFonts) { string key = customFont.Key; FontProperties value = customFont.Value; CreateAndRegisterSubstituteFont(key, value); } } [HarmonyPostfix] [HarmonyPatch(typeof(Text), "OnEnable")] public static void Postfix_Text_OnEnable(Text __instance) { if (ModuleBase<FontPatch>.IsEnabled) { ReplaceFont(__instance); } } public static void ReplaceFont(Text __instance) { Font font = __instance.font; string targetFontName = ((font != null) ? ((Object)font).name : null); if (TryGetSubstituteFont(targetFontName, out var substituteFont)) { __instance.font = substituteFont; } } public static void CreateAndRegisterSubstituteFont(string targetFontName, FontProperties substituteFontProperties) { if (ResourceLoader.TryGetOrCreateFont(CharactersToRender, substituteFontProperties, out var font, ModuleSettings.SaveFontCacheOnDisk)) { RegisterSubstituteFont(targetFontName, font); } } public static void RegisterSubstituteFont(string targetFontName, Font substituteFont) { if (SubstituteFonts == null) { SubstituteFonts = new Dictionary<string, Font>(); } if (targetFontName != null && substituteFont != null) { SubstituteFonts[targetFontName] = substituteFont; } } public static bool TryGetSubstituteFont(string targetFontName, out Font substituteFont) { if (targetFontName == null || SubstituteFonts == null || !SubstituteFonts.TryGetValue(targetFontName, out substituteFont) || substituteFont == null) { substituteFont = null; return false; } return true; } } [ConfigSection("Modules.FontPatch", "This module replaces font of Text class instances.")] public class FontPatchSettings : ModuleSettingsBase { [ConfigEntry("SaveFontCacheOnDisk", false, "Set this field to \"true\" to cache generated Font\non disk to reduce load times on subsequent game launches.\nWarning: Cache size may grow significantly --\nA 4096×4096 atlas alone is about 16MB.\nEnable this only if you have spare disk space.\nCache files are stored in the same directory as plugin .dll.")] public bool SaveFontCacheOnDisk; } [HarmonyPatch] public class GamemodePatch : TextTranslator<GamemodePatch>, IScriptableObjectPatch { [JsonProperty] public static Dictionary<string, string> CapsuleNames; [JsonProperty] public static Dictionary<string, string> GamemodeUnlockHints; [JsonProperty] public static Dictionary<string, string> GamemodeDescriptions; [JsonProperty] public static Dictionary<string, string> NewGameTexts; [JsonProperty] public static Dictionary<string, string> GamemodeIntroTexts; [JsonProperty] public static Dictionary<string, string> GamemodeTextPrefixes; [JsonProperty] public static Dictionary<string, string> ModifierTitles; [JsonProperty] public static Dictionary<string, string> ModifierDescriptions; [JsonProperty] public static Dictionary<string, string> ModifierAppends; [JsonProperty] public static bool KeepWhiteSpaceInGamemodeName; [JsonProperty] public static string GamemodeTextTemplate; [JsonProperty] public static string ModifierConflictedDescription; [JsonProperty] public static string ModifierLockedDescriptionTemplate; [JsonProperty] public static string ModifierUnlockProgressTemplate; [JsonIgnore] public static Regex WhiteSpaceRegex = CacheManager.GetOrCreateRegex("\\s+|<br\\s*>", RegexOptions.IgnoreCase); [JsonIgnore] public static GamemodePatchSettings ModuleSettings; public static void PatchScriptableObjects() { if (ModuleBase<GamemodePatch>.IsEnabled) { PatchGamemodes(); PatchGamemodeSettings(); } } public static void PatchGamemodes() { IEnumerable<M_Gamemode> enumerable = CacheManager.EnumerateScriptableObjects<M_Gamemode>(); foreach (M_Gamemode item in enumerable) { item.unlockHint = TextTranslator<GamemodePatch>.GetTextTranslation(GamemodeUnlockHints, item.unlockHint); item.modeDescription = TextTranslator<GamemodePatch>.GetTextTranslation(GamemodeDescriptions, item.modeDescription); item.newGameText = TextTranslator<GamemodePatch>.GetTextTranslation(NewGameTexts, item.newGameText); item.introText = TextTranslator<GamemodePatch>.GetTextTranslation(GamemodeIntroTexts, item.introText); } } public static void PatchGamemodeSettings() { IEnumerable<GamemodeSetting> enumerable = CacheManager.EnumerateScriptableObjects<GamemodeSetting>(); foreach (GamemodeSetting item in enumerable) { item.title = TextTranslator<GamemodePatch>.GetTextTranslation(ModifierTitles, item.title); item.description = TextTranslator<GamemodePatch>.GetTextTranslation(ModifierDescriptions, item.description); } } [HarmonyPostfix] [HarmonyPatch(typeof(UI_Gamemode_Button), "Initialize")] public static void Postfix_GamemodeButton_Initialize(UI_Gamemode_Button __instance) { if (ModuleBase<GamemodePatch>.IsEnabled && __instance.gamemode != null && CapsuleNames != null && CapsuleNames.TryGetValue(__instance.gamemode.gamemodeName, out var value) && value != null) { __instance.title.text = value; } } [HarmonyPostfix] [HarmonyPatch(typeof(UI_GamemodeText), "Refresh")] public static void Postfix_GamemodeText_Refresh(UI_GamemodeText __instance) { if (ModuleBase<GamemodePatch>.IsEnabled && CL_GameManager.gamemode != null) { __instance.text.text = GetTranslatedGamemodeText(CL_GameManager.gamemode, __instance.text.text); } } [HarmonyPostfix] [HarmonyPatch(typeof(UI_LeaderboardEntryDetailWindow), "ShowDetails")] public static void Postfix_LeaderboardEntryWindow_ShowDetails(UI_LeaderboardEntryDetailWindow __instance) { if (ModuleBase<GamemodePatch>.IsEnabled) { __instance.gamemodeText.text = GetTranslatedGamemodeText(CL_GameManager.GetBaseGamemode(), __instance.gamemodeText.text); } } [HarmonyPostfix] [HarmonyPatch(typeof(UI_LeaderboardEntryDetailWindow), "ShowNoEntryDetails")] public static void Postfix_LeaderboardEntryWindow_ShowNoEntryDetails(UI_LeaderboardEntryDetailWindow __instance) { if (ModuleBase<GamemodePatch>.IsEnabled) { __instance.gamemodeText.text = GetTranslatedGamemodeText(CL_GameManager.GetBaseGamemode(), __instance.gamemodeText.text); } } [HarmonyPostfix] [HarmonyPatch(typeof(UI_GamemodeSetting), "UpdateColor")] public static void Postfix_GamemodeSettingUI_UpdateColor(UI_GamemodeSetting __instance) { if (!ModuleBase<GamemodePatch>.IsEnabled) { return; } string text = __instance.descriptionText.text; if (text == "LOCKED") { __instance.descriptionText.text = ModifierConflictedDescription ?? "LOCKED"; return; } ProgressionUnlock unlock = __instance.gamemodeSetting.unlock; if (!unlock.CheckUnlock()) { string newValue = (unlock.showProgression ? GetTranslatedProgress(unlock) : ""); string text2 = ModifierLockedDescriptionTemplate ?? "{unlockHint}{progress}"; __instance.descriptionText.text = text2.Replace("{unlockHint}", unlock.unlockHint).Replace("{progress}", newValue); } } public static string GetTranslatedGamemodeText(M_Gamemode gamemode, string gamemodeText) { string text = gamemode.gamemodeName; string[] array = gamemodeText.Split(new string[1] { text }, 2, StringSplitOptions.None); if (array.Length != 2) { return gamemodeText; } string originalText = array[0]; string text2 = array[1]; originalText = TextTranslator<GamemodePatch>.GetTextTranslation(GamemodeTextPrefixes, originalText); if (CapsuleNames != null && CapsuleNames.TryGetValue(text, out var value) && value != null) { text = WhiteSpaceRegex.Replace(value, KeepWhiteSpaceInGamemodeName ? " " : ""); } if (text2 != null && ModifierAppends != null) { foreach (KeyValuePair<string, string> modifierAppend in ModifierAppends) { string key = modifierAppend.Key; string value2 = modifierAppend.Value; if (key != null && value2 != null) { text2 = text2.Replace(key, value2); } } } string text3 = GamemodeTextTemplate ?? "{prefix}{gamemodeName}{modifierAppends}"; return text3.Replace("{prefix}", originalText).Replace("{gamemodeName}", text).Replace("{modifierAppends}", text2); } public static string GetTranslatedProgress(ProgressionUnlock unlock) { string text = unlock.GetProgressString(); if (text != "N/A" && ModifierUnlockProgressTemplate != null) { string[] array = text.Split(new char[1] { '/' }, 2); if (array.Length == 2) { string newValue = array[0]; string newValue2 = array[1]; text = ModifierUnlockProgressTemplate.Replace("{current}", newValue).Replace("{required}", newValue2); } } return text; } } [ConfigSection("Modules.GamemodePatch", "This module replaces texts for gamemodes and gamemode settings.")] public class GamemodePatchSettings : ModuleSettingsBase { } [HarmonyPatch] public class GameplayTextPatch : TextTranslator<GameplayTextPatch> { [JsonProperty] public static Dictionary<string, string> RoachCounterTemplates; [JsonProperty] public static Dictionary<string, string> BadgeTitles; [JsonProperty] public static string ScoreTrackerTemplate; [JsonProperty] public static string DistanceTrackerTemplate; [JsonProperty] public static string SpeedTrackerTemplate; [JsonProperty] public static string HighScoreTrackerTemplate; [JsonProperty] public static string ForlornGatewayDoorPoweredText; [JsonProperty] public static string VendorUnavailableText; [JsonProperty] public static string VendorCostTemplate; [JsonProperty] public static string VendorPurchasedText; [JsonIgnore] public static GameplayTextPatchSettings ModuleSettings; [HarmonyPostfix] [HarmonyPatch(typeof(CL_GameManager), "Update")] public static void Postfix_GameManager_Update(CL_GameManager __instance) { if (!ModuleBase<GameplayTextPatch>.IsEnabled || CL_GameManager.runHasEnded) { return; } CL_UIManager uiMan = __instance.uiMan; if (uiMan != null && uiMan.scoreTracker != null) { string text = uiMan.scoreTracker.text; if (text.StartsWith("Score: ") && ScoreTrackerTemplate != null) { string newValue = text.Substring(7); uiMan.scoreTracker.text = ScoreTrackerTemplate.Replace("{score}", newValue); } string text2 = uiMan.ascentTracker.text; if (text2.StartsWith("Climb Distance: ") && DistanceTrackerTemplate != null) { string newValue2 = text2.Substring(16); uiMan.ascentTracker.text = DistanceTrackerTemplate.Replace("{distance}", newValue2); } string text3 = uiMan.ascentRateTracker.text; if (text3.StartsWith("Climb Speed: ") && SpeedTrackerTemplate != null) { string newValue3 = text3.Substring(13); uiMan.ascentRateTracker.text = SpeedTrackerTemplate.Replace("{speed}", newValue3); } string text4 = uiMan.highScoreTracker.text; if (text4.StartsWith("High Score: ") && HighScoreTrackerTemplate != null) { string newValue4 = text4.Substring(12); uiMan.highScoreTracker.text = HighScoreTrackerTemplate.Replace("{highScore}", newValue4); } } } [HarmonyPostfix] [HarmonyPatch(typeof(UT_CheckFlag), "CheckFlag")] public static void Postfix_CheckFlag_CheckFlag(UT_CheckFlag __instance) { if (!ModuleBase<GameplayTextPatch>.IsEnabled || ForlornGatewayDoorPoweredText == null) { return; } string flagName = __instance.flagName; if (flagName != "habentrywaypowered" && flagName != "habentryunlocked") { return; } SessionFlag gameFlag = CL_GameManager.GetGameFlag(flagName); if (gameFlag == null || !gameFlag.state) { return; } TMP_Text[] componentsInChildren = ((Component)((Component)__instance).transform.parent).GetComponentsInChildren<TMP_Text>(); if (componentsInChildren == null || componentsInChildren.Length == 0) { return; } foreach (TMP_Text val in componentsInChildren) { if (val.text == "POWERED") { val.text = ForlornGatewayDoorPoweredText; break; } } } [HarmonyPrefix] [HarmonyPatch(typeof(UT_RoachTextCounter), "UpdateText")] public static void Prefix_RoachTextCounter_UpdateText(UT_RoachTextCounter __instance) { if (ModuleBase<GameplayTextPatch>.IsEnabled) { __instance.textFormat = TextTranslator<GameplayTextPatch>.GetTextTranslation(RoachCounterTemplates, __instance.textFormat); } } [HarmonyPrefix] [HarmonyPatch(typeof(UI_Badge), "ShowBadge")] public static void Prefix_Badge_ShowBadge(Sprite sprite, ref string title) { if (ModuleBase<GameplayTextPatch>.IsEnabled) { title = TextTranslator<GameplayTextPatch>.GetTextTranslation(BadgeTitles, title); } } [HarmonyPrefix] [HarmonyPatch(typeof(ENV_Vendor_Disk), "CheckBlock")] public static void Prefix_DiskVendor_CheckBlock(ENV_Vendor_Disk __instance) { if (ModuleBase<GameplayTextPatch>.IsEnabled && VendorUnavailableText != null && !__instance.isBlocked && !__instance.hasBeenBought && CL_GameManager.HasActiveFlag("blockshops", false)) { __instance.isBlocked = true; __instance.purchaseButton.SetInteractable(false); __instance.costText.text = VendorUnavailableText; } } [HarmonyPrefix] [HarmonyPatch(typeof(ENV_Vendor_Disk), "CheckRoaches")] public static void Prefix_DiskVendor_CheckRoaches(ENV_Vendor_Disk __instance) { if (ModuleBase<GameplayTextPatch>.IsEnabled && VendorPurchasedText != null && __instance.allowPurchases && !__instance.isBlocked && !__instance.hasBeenBought && !(__instance.id == "")) { SessionFlag gameFlag = CL_GameManager.GetGameFlag("boughtdisk-" + __instance.id + "-station"); if (gameFlag != null && gameFlag.state) { __instance.hasBeenBought = true; __instance.costText.text = VendorPurchasedText; ((Component)__instance.purchaseSprite).gameObject.SetActive(false); } } } [HarmonyPostfix] [HarmonyPatch(typeof(ENV_Vendor_Disk), "CheckRoaches")] public static void Postfix_DiskVendor_CheckRoaches(ENV_Vendor_Disk __instance) { if (ModuleBase<GameplayTextPatch>.IsEnabled && VendorCostTemplate != null && __instance.allowPurchases && !__instance.isBlocked && !__instance.hasBeenBought) { int cost = __instance.cost; int roaches = CL_GameManager.GetRoaches(false); __instance.costText.text = VendorCostTemplate.Replace("{cost}", cost.ToString()).Replace("{balance}", roaches.ToString()); } } [HarmonyPostfix] [HarmonyPatch(typeof(ENV_Vendor_Disk), "Purchase")] public static void Postfix_DiskVendor_Purchase(ENV_Vendor_Disk __instance) { if (ModuleBase<GameplayTextPatch>.IsEnabled && !__instance.isBlocked && !((Component)__instance.purchaseSprite).gameObject.activeInHierarchy && VendorPurchasedText != null) { __instance.costText.text = VendorPurchasedText; } } [HarmonyPrefix] [HarmonyPatch(typeof(ENV_Vendor_Event), "CheckRoaches")] public static void Prefix_EventVendor_CheckRoaches(ENV_Vendor_Event __instance) { if (ModuleBase<GameplayTextPatch>.IsEnabled && VendorPurchasedText != null && __instance.allowPurchases && !__instance.hasBeenBought && !(__instance.id == "")) { SessionFlag gameFlag = CL_GameManager.GetGameFlag("boughtdisk-" + __instance.id + "-station"); if (gameFlag != null && gameFlag.state) { __instance.hasBeenBought = true; __instance.costText.text = VendorPurchasedText; ((Component)__instance.purchaseSprite).gameObject.SetActive(false); } } } [HarmonyPostfix] [HarmonyPatch(typeof(ENV_Vendor_Event), "CheckRoaches")] public static void Postfix_EventVendor_CheckRoaches(ENV_Vendor_Event __instance) { if (ModuleBase<GameplayTextPatch>.IsEnabled && VendorCostTemplate != null && __instance.allowPurchases && !__instance.hasBeenBought) { int cost = __instance.cost; int roaches = CL_GameManager.GetRoaches(false); __instance.costText.text = VendorCostTemplate.Replace("{cost}", cost.ToString()).Replace("{balance}", roaches.ToString()); } } [HarmonyPostfix] [HarmonyPatch(typeof(ENV_Vendor_Event), "Purchase")] public static void Postfix_EventVendor_Purchase(ENV_Vendor_Event __instance) { if (ModuleBase<GameplayTextPatch>.IsEnabled && !((Component)__instance.purchaseSprite).gameObject.activeInHierarchy && VendorPurchasedText != null) { __instance.costText.text = VendorPurchasedText; } } } [ConfigSection("Modules.GameplayTextPatch", "This module replaces texts of\nroach counters, vendors and stat trackers.")] public class GameplayTextPatchSettings : ModuleSettingsBase { } public interface IScriptableObjectPatch { } [HarmonyPatch] public class ItemDescriptionPatch : ModuleBase<ItemDescriptionPatch> { [JsonProperty] public static Dictionary<string, string> ItemDescriptions; [JsonIgnore] public static ItemDescriptionPatchSettings ModuleSettings; [HarmonyPostfix] [HarmonyPatch(typeof(Localization), "GetLine")] public static string Postfix_Localization_GetLine(string __result, string group, string key) { if (!ModuleBase<ItemDescriptionPatch>.IsEnabled || group != "items" || ItemDescriptions == null || !ItemDescriptions.ContainsKey(key)) { return __result; } return ItemDescriptions[key] ?? __result; } } [ConfigSection("Modules.ItemDescriptionPatch", "This module replaces item description texts.")] public class ItemDescriptionPatchSettings : ModuleSettingsBase { } [HarmonyPatch] public class LocationNamePatch : TextTranslator<LocationNamePatch>, IScriptableObjectPatch { [JsonProperty] public static Dictionary<string, string> RegionIntroTexts; [JsonProperty] public static Dictionary<string, string> SubregionIntroTexts; [JsonProperty] public static Dictionary<string, string> LevelIntroTexts; [JsonProperty] public static Dictionary<string, string> LevelSaveNames; [JsonProperty] public static string ContinueTextTemplate; [JsonIgnore] public static LocationNamePatchSettings ModuleSettings; public static void PatchScriptableObjects() { if (ModuleBase<LocationNamePatch>.IsEnabled) { PatchRegions(); PatchSubregions(); } } public static void PatchRegions() { IEnumerable<M_Region> enumerable = CacheManager.EnumerateScriptableObjects<M_Region>(); foreach (M_Region item in enumerable) { item.introText = TextTranslator<LocationNamePatch>.GetTextTranslation(RegionIntroTexts, item.introText); } } public static void PatchSubregions() { IEnumerable<M_Subregion> enumerable = CacheManager.EnumerateScriptableObjects<M_Subregion>(); foreach (M_Subregion item in enumerable) { item.introText = TextTranslator<LocationNamePatch>.GetTextTranslation(SubregionIntroTexts, item.introText); } } [HarmonyPostfix] [HarmonyPatch(typeof(UT_ZoneTitler), "Start")] public static void Postfix_ZoneTitler_Start(UT_ZoneTitler __instance) { if (ModuleBase<LocationNamePatch>.IsEnabled) { __instance.region = TextTranslator<LocationNamePatch>.GetTextTranslation(RegionIntroTexts, __instance.region); __instance.subRegion = TextTranslator<LocationNamePatch>.GetTextTranslation(SubregionIntroTexts, __instance.subRegion); } } [HarmonyPostfix] [HarmonyPatch(typeof(M_Level), "Awake")] public static void Postfix_Level_Awake(M_Level __instance) { if (ModuleBase<LocationNamePatch>.IsEnabled) { __instance.introText = TextTranslator<LocationNamePatch>.GetTextTranslation(LevelIntroTexts, __instance.introText); } } [HarmonyPostfix] [HarmonyPatch(typeof(UI_GamemodeScreen), "RefreshCurrentGamemode")] public static void Postfix_GamemodeScreen_RefreshCurrentGamemode(UI_GamemodeScreen __instance) { if (ModuleBase<LocationNamePatch>.IsEnabled) { string text = __instance.currentPanel.continueButtonText.text; if (CL_SaveManager.SessionFileExists(__instance.baseGamemode.gamemodeName, CL_GameManager.IsHardmode()) && !CL_GameManager.gamemode.IsCompetitive() && !CL_GameManager.GetBaseGamemode().IsCompetitive() && text.StartsWith("Continue: ")) { string originalText = text.Substring(10); originalText = TextTranslator<LocationNamePatch>.GetTextTranslation(LevelSaveNames, originalText); string text2 = ContinueTextTemplate ?? "Continue: {saveName}"; __instance.currentPanel.continueButtonText.text = text2.Replace("{saveName}", originalText); } } } } [ConfigSection("Modules.LocationNamePatch", "This module replaces location intro texts and level save names.")] public class LocationNamePatchSettings : ModuleSettingsBase { } [HarmonyPatch] public class MainMenuPatch : TextTranslator<MainMenuPatch> { [JsonProperty] public static Dictionary<string, string> PageTitles; [JsonProperty] public static string LoadingProgressTemplate; [JsonProperty] public static string PageCounterTemplate; [JsonIgnore] public static MainMenuPatchSettings ModuleSettings; [HarmonyPrefix] [HarmonyPatch(typeof(UT_Intro), "EndIntro")] public static bool Prefix_Intro_EndIntro(UT_Intro __instance) { if (!ModuleBase<MainMenuPatch>.IsEnabled || LoadingProgressTemplate == null) { return true; } __instance.video.Stop(); __instance.hasSkipped = true; TMP_Text loadPercentageText = __instance.loadPercentageText; ((Component)loadPercentageText.transform.parent).gameObject.SetActive(true); loadPercentageText.text = LoadingProgressTemplate.Replace("{progress}", "0"); ((MonoBehaviour)__instance).StartCoroutine(CustomLoadingIntro(__instance)); return false; } [HarmonyPostfix] [HarmonyPatch(typeof(UI_PageHolder), "UpdatePage")] public static void Postfix_PageHolder_UpdatePage(UI_PageHolder __instance) { if (!ModuleBase<MainMenuPatch>.IsEnabled) { return; } TMP_Text pageTitle = __instance.pageTitle; if (pageTitle != null) { List<UI_Page> pages = __instance.pages; int count = pages.Count; if (count != 0) { string textTranslation = TextTranslator<MainMenuPatch>.GetTextTranslation(PageTitles, pages[__instance.currentPage].title); int num = __instance.currentPage + 1; string text = PageCounterTemplate ?? "{title} ({current}/{total})"; pageTitle.text = text.Replace("{title}", textTranslation).Replace("{current}", num.ToString()).Replace("{total}", count.ToString()); } } } public static IEnumerator CustomLoadingIntro(UT_Intro intro) { yield return (object)new WaitForSeconds(0.1f); UnityEvent onEnd = intro.onEnd; if (onEnd != null) { onEnd.Invoke(); } yield return (object)new WaitForSeconds(0.1f); while (true) { UpdateLoadingProgressText(); yield return null; yield return null; if (intro.loadMenuOperation != null && !((double)intro.loadMenuOperation.progress < 0.89)) { intro.loadMenuOperation.allowSceneActivation = true; if (intro.loadMenuOperation.isDone) { break; } } } UpdateLoadingProgressText(); intro.loadMenuOperation.allowSceneActivation = true; void UpdateLoadingProgressText() { if (intro.loadMenuOperation != null) { string newValue = Mathf.RoundToInt(intro.loadMenuOperation.progress * 100f).ToString(); intro.loadPercentageText.text = LoadingProgressTemplate.Replace("{progress}", newValue); } } } } [ConfigSection("Modules.MainMenuPatch", "This module replaces texts of loading intro and menu pages.")] public class MainMenuPatchSettings : ModuleSettingsBase { } public abstract class ModuleBase<TModule> { [JsonIgnore] public static bool IsEnabled; public ModuleBase() { FieldInfo field = GetType().GetField("ModuleSettings", BindingFlags.Static | BindingFlags.Public); if ((object)field != null) { ConfigSectionAttribute customAttribute = field.FieldType.GetCustomAttribute<ConfigSectionAttribute>(); if (customAttribute != null) { customAttribute.Deconstruct(out var section, out var moduleDescription); string section2 = section; string moduleDescription2 = moduleDescription; IsEnabled = ConfigManager.IsModuleEnabled(section2, moduleDescription2); } if (IsEnabled && field.GetValue(this) == null) { object value = Activator.CreateInstance(field.FieldType); field.SetValue(this, value); } } } } public class ModuleSettingsBase { public ModuleSettingsBase() { Type type = GetType(); ConfigSectionAttribute customAttribute = type.GetCustomAttribute<ConfigSectionAttribute>(); FieldInfo[] fields = type.GetFields(); foreach (FieldInfo fieldInfo in fields) { ConfigEntryAttribute customAttribute2 = fieldInfo.GetCustomAttribute<ConfigEntryAttribute>(); PopulateModuleSettingsField(this, fieldInfo, customAttribute, customAttribute2); } } private void PopulateModuleSettingsField(ModuleSettingsBase moduleSettings, FieldInfo field, ConfigSectionAttribute configSectionAttribute, ConfigEntryAttribute configEntryAttribute) { if (field.GetValue(moduleSettings) == null || configEntryAttribute != null) { field.SetValue(moduleSettings, configEntryAttribute.DefaultValue); if (configSectionAttribute != null) { configSectionAttribute.Deconstruct(out var section, out var moduleDescription); string section2 = section; string moduleDescription2 = moduleDescription; configEntryAttribute.Deconstruct(out moduleDescription, out var defaultValue, out section); string key = moduleDescription; object defaultValue2 = defaultValue; string entryDescription = section; object configEntryValue = ConfigManager.GetConfigEntryValue(section2, moduleDescription2, key, defaultValue2, entryDescription); field.SetValue(moduleSettings, configEntryValue); } } } } [HarmonyPatch] public class MotherSubtitlePatch : ModuleBase<MotherSubtitlePatch> { [JsonProperty] public static string RandomCharacters; [JsonProperty] public static string NonRandomCharacters; [JsonProperty] public static Dictionary<string, string> MotherSubtitles; [JsonIgnore] public static MotherSubtitlePatchSettings ModuleSettings; [HarmonyPostfix] [HarmonyPatch(typeof(Localization), "GetLine")] public static string Postfix_GetLine(string __result, string group, string key) { if (!ModuleBase<MotherSubtitlePatch>.IsEnabled || group != "mother" || MotherSubtitles == null || !MotherSubtitles.ContainsKey(key)) { return __result; } return MotherSubtitles[key] ?? __result; } [HarmonyTranspiler] [HarmonyPatch(typeof(HUD_CustomElement_PsychicCommunication), "PlaySubtitle")] public static IEnumerable<CodeInstruction> Transpiler_PlaySubtitle(IEnumerable<CodeInstruction> codeInstructions) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Expected O, but got Unknown //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Expected O, but got Unknown //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Expected O, but got Unknown //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Expected O, but got Unknown //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Expected O, but got Unknown //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Expected O, but got Unknown CodeMatcher val = new CodeMatcher(codeInstructions, (ILGenerator)null); val.MatchForward(false, (CodeMatch[])(object)new CodeMatch[1] { new CodeMatch((Func<CodeInstruction, bool>)((CodeInstruction i) => i.opcode == OpCodes.Stfld && i.operand is FieldInfo fieldInfo2 && fieldInfo2.Name == "rand"), (string)null) }); if (!val.IsValid) { return codeInstructions; } FieldInfo fieldInfo = (FieldInfo)val.Instruction.operand; FieldInfo field = fieldInfo.DeclaringType.GetField("startString"); val.MatchBack(false, (CodeMatch[])(object)new CodeMatch[1] { new CodeMatch((OpCode?)OpCodes.Ldstr, (object)"abcdefghijklmnopqrstuvwxyz", (string)null) }); if (!val.IsValid) { return codeInstructions; } MethodInfo method = typeof(MotherSubtitlePatch).GetMethod("GetRandomCharacters"); val.RemoveInstruction(); val.Insert((CodeInstruction[])(object)new CodeInstruction[3] { new CodeInstruction(OpCodes.Dup, (object)null), new CodeInstruction(OpCodes.Ldfld, (object)field), new CodeInstruction(OpCodes.Call, (object)method) }); return val.InstructionEnumeration(); } public static string GetRandomCharacters(string startString) { if (!ModuleBase<MotherSubtitlePatch>.IsEnabled || string.IsNullOrWhiteSpace(startString)) { return "abcdefghijklmnopqrstuvwxyz"; } string text = (string.IsNullOrEmpty(RandomCharacters) ? new string(startString.Distinct().ToArray()) : RandomCharacters); text = text.ToLower(); if (!string.IsNullOrEmpty(NonRandomCharacters)) { NonRandomCharacters = NonRandomCharacters.ToLower(); text = new string(text.Where((char c) => !Enumerable.Contains(NonRandomCharacters, c)).ToArray()); } return text; } } [ConfigSection("Modules.MotherSubtitlePatch", "This module replaces Mother subtitle texts.")] public class MotherSubtitlePatchSettings : ModuleSettingsBase { } [HarmonyPatch] public class NotePatch : TextTranslator<NotePatch> { [JsonProperty] public static Dictionary<string, string> NoteTexts; [JsonIgnore] public static Dictionary<string, string> TrimmedNoteTexts; [JsonIgnore] public static NotePatchSettings ModuleSettings; [OnDeserialized] private void OnDeserialized(StreamingContext _) { TrimmedNoteTexts = NoteTexts.ToDictionary((KeyValuePair<string, string> t) => t.Key.TrimStart(Array.Empty<char>()), (KeyValuePair<string, string> t) => t.Value); } [HarmonyPostfix] [HarmonyPatch(typeof(HandItem_Note), "Initialize")] public static void Postfix_HandItemNote_Initialize(HandItem_Note __instance) { if (ModuleBase<NotePatch>.IsEnabled) { __instance.text.text = TextTranslator<NotePatch>.GetTextTranslation(TrimmedNoteTexts, __instance.text.text); } } } [ConfigSection("Modules.NotePatch", "This module replaces texts for paper notes.")] public class NotePatchSettings : ModuleSettingsBase { } [HarmonyPatch] public class ObjectivePatch : TemplateTranslator<ObjectivePatch> { [JsonProperty] public static Dictionary<string, string> ObjectiveViewerTitleTemplates; [JsonProperty] public static Dictionary<string, string> ObjectiveTitleTemplates; [JsonProperty] public static Dictionary<string, string> ObjectiveDescriptionTemplates; [JsonProperty] public static Dictionary<string, string> ObjectiveProgressHeaderTemplates; [JsonProperty] public static Dictionary<string, string> ObjectiveSuccessHeaders; [JsonIgnore] public static TemplateTranslations ViewerTitleTemplates; [JsonIgnore] public static TemplateTranslations TitleTemplates; [JsonIgnore] public static TemplateTranslations DescriptionTemplates; [JsonIgnore] public static ObjectivePatchSettings ModuleSettings; [OnDeserialized] private void OnDeserialized(StreamingContext _) { if (ModuleBase<ObjectivePatch>.IsEnabled) { ViewerTitleTemplates = new TemplateTranslations(ObjectiveViewerTitleTemplates); TitleTemplates = new TemplateTranslations(ObjectiveTitleTemplates); DescriptionTemplates = new TemplateTranslations(ObjectiveDescriptionTemplates); } } [HarmonyPostfix] [HarmonyPatch(typeof(UI_ObjectiveViewer), "Awake")] public static void Postfix_ObjectiveViewer_Awake(UI_ObjectiveViewer __instance) { if (ModuleBase<ObjectivePatch>.IsEnabled) { __instance.objectiveViewerTitle.text = TemplateTranslator<ObjectivePatch>.GetTemplateTranslation(ViewerTitleTemplates, __instance.objectiveViewerTitle.text); } } [HarmonyPrefix] [HarmonyPatch(typeof(UI_ObjectiveViewer), "SetTitle")] public static void Prefix_ObjectiveViewer_SetTitle(ref string s) { if (ModuleBase<ObjectivePatch>.IsEnabled) { s = TemplateTranslator<ObjectivePatch>.GetTemplateTranslation(ViewerTitleTemplates, s); } } [HarmonyPrefix] [HarmonyPatch(typeof(UI_ObjectiveViewer), "CreateOrUpdateObjective")] public static void Prefix_ObjectiveViewer_CreateOrUpdateObjective(string id, ref string title, ref string desc) { if (ModuleBase<ObjectivePatch>.IsEnabled) { title = TemplateTranslator<ObjectivePatch>.GetTemplateTranslation(TitleTemplates, title); desc = TemplateTranslator<ObjectivePatch>.GetTemplateTranslation(DescriptionTemplates, desc); } } [HarmonyPrefix] [HarmonyPatch(typeof(CH_ChallengeCounter), "Start")] public static void Prefix_ChallengeCounter_Start(CH_ChallengeCounter __instance) { if (!ModuleBase<ObjectivePatch>.IsEnabled) { return; } foreach (ObjectiveCounter objective in __instance.objectives) { objective.objectiveTitle = TemplateTranslator<ObjectivePatch>.GetTemplateTranslation(TitleTemplates, objective.objectiveTitle); objective.objectiveDesc = TemplateTranslator<ObjectivePatch>.GetTemplateTranslation(DescriptionTemplates, objective.objectiveDesc); objective.progressHeaderDesc = TextTranslator<ObjectivePatch>.GetTextTranslation(ObjectiveProgressHeaderTemplates, objective.progressHeaderDesc); objective.finishedHeaderDesc = TextTranslator<ObjectivePatch>.GetTextTranslation(ObjectiveSuccessHeaders, objective.finishedHeaderDesc); } } [HarmonyPostfix] [HarmonyPatch(typeof(CH_RoachCollector), "Start")] public static void Postfix_RoachCollector_Start(CH_RoachCollector __instance) { if (ModuleBase<ObjectivePatch>.IsEnabled) { __instance.successText = TextTranslator<ObjectivePatch>.GetTextTranslation(ObjectiveSuccessHeaders, __instance.successText); } } } [ConfigSection("Modules.ObjectivePatch", "This module replaces text information of objectives.")] public class ObjectivePatchSettings : ModuleSettingsBase { } [HarmonyPatch] public class PerkPatch : TextTranslator<PerkPatch>, IScriptableObjectPatch { [JsonProperty] public static Dictionary<string, string> PerkTitles; [JsonProperty] public static Dictionary<string, string> PerkDescriptions; [JsonProperty] public static Dictionary<string, string> PerkFlavorTexts; [JsonProperty] public static Dictionary<string, string> DurationRoughTexts; [JsonProperty] public static string DurationSecondTemplate; [JsonProperty] public static string DurationSecondsTemplate; [JsonProperty] public static string AppPerkHoverTextTemplate; [JsonProperty] public static string AppPerkAmountTemplate; [JsonProperty] public static string AppRefreshPurchasedText; [JsonIgnore] public static readonly Regex SecondsFormatRegex = CacheManager.GetOrCreateRegex("\\{.*?\\^s.*?\\}", RegexOptions.Compiled); [JsonIgnore] public static readonly Regex SecondsRegex = CacheManager.GetOrCreateRegex("([+-]?\\d+(?:\\.\\d+)?) Seconds?", RegexOptions.Compiled); [JsonIgnore] public static PerkPatchSettings ModuleSettings; public static void PatchScriptableObjects() { if (ModuleBase<PerkPatch>.IsEnabled) { PatchPerks(); } } public static void PatchPerks() { IEnumerable<Perk> enumerable = CacheManager.EnumerateScriptableObjects<Perk>(); foreach (Perk item in enumerable) { item.title = TextTranslator<PerkPatch>.GetTextTranslation(PerkTitles, item.title); item.description = TextTranslator<PerkPatch>.GetTextTranslation(PerkDescriptions, item.description); item.flavorText = TextTranslator<PerkPatch>.GetTextTranslation(PerkFlavorTexts, item.flavorText); } } [HarmonyPostfix] [HarmonyPatch(typeof(Perk), "GetTitle")] public static string Postfix_Perk_GetTitle(string __result, Perk __instance) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Invalid comparison between Unknown and I4 //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Invalid comparison between Unknown and I4 if (!ModuleBase<PerkPatch>.IsEnabled || ((int)__instance.perkType != 8 && (int)__instance.perkType != 9)) { return __result; } string translatedTrinketType = GetTranslatedTrinketType(__instance); string trinketPerkTitle = GetTrinketPerkTitle(__instance); return translatedTrinketType + "\n<shimmer s=0.1>" + trinketPerkTitle + "</shimmer></color>"; } [HarmonyPostfix] [HarmonyPatch(typeof(Perk), "GetDescription")] public static string Postfix_Perk_GetDescription(string __result, Perk __instance) { if (!ModuleBase<PerkPatch>.IsEnabled || !SecondsFormatRegex.IsMatch(__instance.description)) { return __result; } return SecondsRegex.Replace(__result, delegate(Match m) { string value = m.Groups[1].Value; string text = ((value == "1") ? (DurationSecondTemplate ?? "{time} Second") : (DurationSecondsTemplate ?? "{time} Seconds")); return text.Replace("{time}", value); }); } [HarmonyPostfix] [HarmonyPatch(typeof(PerkModule_RemovalTimer), "GetCounterString")] public static string Postfix_RemovalTimerModule_GetCounterString(string __result, PerkModule_RemovalTimer __instance) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Invalid comparison between Unknown and I4 if (!ModuleBase<PerkPatch>.IsEnabled || (int)__instance.removalTimerDisplayType != 3) { return __result; } string removalTimerPrefix = __instance.removalTimerPrefix; string originalText = __result.Substring(removalTimerPrefix.Length); originalText = TextTranslator<PerkPatch>.GetTextTranslation(DurationRoughTexts, originalText); return removalTimerPrefix + originalText; } [HarmonyPostfix] [HarmonyPatch(typeof(App_PerkPage), "GenerateIcons")] public static void Postfix_PerkPage_GenerateIcons(App_PerkPage __instance) { if (!ModuleBase<PerkPatch>.IsEnabled) { return; } Image[] componentsInChildren = ((Component)__instance.iconParent).GetComponentsInChildren<Image>(); if (componentsInChildren == null || componentsInChildren.Length == 0) { return; } List<Perk> perks = CL_GameManager.gMan.localPlayer.perks; if (perks == null || perks.Count == 0) { return; } Dictionary<Sprite, Perk> dictionary = perks.ToDictionary((Perk p) => p.icon, (Perk p) => p); foreach (Image val in componentsInChildren) { OS_Tooltip component = ((Component)val).GetComponent<OS_Tooltip>(); if ((Object)(object)component != (Object)null && dictionary.TryGetValue(val.sprite, out var value)) { string title = value.GetTitle(true); string newValue = "<color=#C7C7C7>" + GetTranslatedAppPerkAmount(value, isPreview: false); string newValue2 = "<color=\"grey\">" + value.GetDescription(true, true, true, true) + "<color>"; string text = AppPerkHoverTextTemplate ?? "{title}{amount}\n{description}"; component.tip = text.Replace("{title}", title).Replace("{amount}", newValue).Replace("{description}", newValue2); } } } [HarmonyPostfix] [HarmonyPatch(typeof(App_PerkPage_Card), "Initialize")] public static void Postfix_PerkPageCard_Initialize(App_PerkPage_Card __instance, App_PerkPage page, Perk p) { if (ModuleBase<PerkPatch>.IsEnabled) { string title = p.GetTitle(true); string newValue = "<color=\"grey\">" + GetTranslatedAppPerkAmount(p, isPreview: true); string newValue2 = "<color=#C7C7C7>" + p.GetDescription(true, false, true, true) + "</color>"; string text = AppPerkHoverTextTemplate ?? "{title}{amount}\n{description}"; __instance.tooltip.tip = text.Replace("{title}", title).Replace("{amount}", newValue).Replace("{description}", newValue2); } } [HarmonyPostfix] [HarmonyPatch(typeof(App_PerkPage), "PurchaseRefresh")] public static void Postfix_PerkPage_PurchaseRefresh(App_PerkPage __instance) { if (!ModuleBase<PerkPatch>.IsEnabled || AppRefreshPurchasedText == null) { return; } GameObject reloadSettingsRoot = __instance.reloadSettingsRoot; TMP_Text[] componentsInChildren = reloadSettingsRoot.GetComponentsInChildren<TMP_Text>(); if (componentsInChildren == null || componentsInChildren.Length == 0) { return; } foreach (TMP_Text val in componentsInChildren) { if (val.text == "<color=\"red>PURCHASED</color>") { val.text = AppRefreshPurchasedText; break; } } } public static string GetTranslatedTrinketType(Perk perk) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0009: I