using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Reptile; using TMPro; using UnityEngine; using UnityEngine.TextCore; using UnityEngine.TextCore.LowLevel; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")] [assembly: AssemblyCompany("BombRushRUSFontFix")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("BombRushRUSFontFix")] [assembly: AssemblyTitle("BombRushRUSFontFix")] [assembly: AssemblyVersion("1.0.0.0")] [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 BombRushRUSFontFix { [BepInPlugin("ru.bombrush.rusfontfix", "Bomb Rush RUS Font Fix", "1.0.0")] [BepInProcess("Bomb Rush Cyberfunk.exe")] public sealed class Plugin : BaseUnityPlugin { public const string PluginGuid = "ru.bombrush.rusfontfix"; public const string PluginName = "Bomb Rush RUS Font Fix"; public const string PluginVersion = "1.0.0"; private ConfigEntry _enabled; private ConfigEntry _matchOriginalRendering; private ConfigEntry _outlineWidthMultiplier; private ConfigEntry _mainButtonUnderlayDilateMultiplier; private ConfigEntry _mainButtonOutlineWidth; private Harmony _harmony; internal static Plugin Instance { get; private set; } internal RuntimeFontManager Fonts { get; private set; } internal bool IsEnabled { get { if (_enabled != null) { return _enabled.Value; } return true; } } internal float OutlineWidthMultiplier { get { if (!MatchOriginalRendering) { return _outlineWidthMultiplier?.Value ?? 2f; } return 1f; } } internal float MainButtonUnderlayDilateMultiplier { get { if (!MatchOriginalRendering) { return _mainButtonUnderlayDilateMultiplier?.Value ?? 1.5f; } return 1f; } } internal float MainButtonOutlineWidth { get { if (!MatchOriginalRendering) { return _mainButtonOutlineWidth?.Value ?? 0.12f; } return 0f; } } private bool MatchOriginalRendering { get { if (_matchOriginalRendering != null) { return _matchOriginalRendering.Value; } return true; } } private void Awake() { //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Expected O, but got Unknown Instance = this; _enabled = ((BaseUnityPlugin)this).Config.Bind("General", "Enabled", true, "Replace the Russian UI font profiles for which a matching Cyrillic font is available."); _matchOriginalRendering = ((BaseUnityPlugin)this).Config.Bind("Visual", "MatchOriginalRendering", true, "Use the original English TMP material values. Disable to enable the manual outline and underlay adjustments below."); _outlineWidthMultiplier = ((BaseUnityPlugin)this).Config.Bind("Visual", "OutlineWidthMultiplier", 2f, "Multiplier for authored English outline widths. Restart the game after changing it."); _mainButtonUnderlayDilateMultiplier = ((BaseUnityPlugin)this).Config.Bind("Visual", "MainButtonUnderlayDilateMultiplier", 1.5f, "Multiplier for the Induction backdrop/pressed underlay thickness. Restart the game after changing it."); _mainButtonOutlineWidth = ((BaseUnityPlugin)this).Config.Bind("Visual", "MainButtonOutlineWidth", 0.12f, "Symmetric outline added to Induction backdrop/pressed states. Set to 0 to disable. Restart the game after changing it."); Fonts = new RuntimeFontManager(((BaseUnityPlugin)this).Logger, ((BaseUnityPlugin)this).Info.Location); Fonts.Initialize(); _harmony = new Harmony("ru.bombrush.rusfontfix"); _harmony.PatchAll(); ((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("{0} {1} loaded; {2} runtime fonts ready.", "Bomb Rush RUS Font Fix", "1.0.0", Fonts.LoadedFontCount)); } private void OnDestroy() { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } Fonts?.Dispose(); Instance = null; } } internal sealed class RuntimeFontManager : IDisposable { private sealed class FontDefinition { public string Key { get; } public string FileName { get; } public int PointSize { get; } public int Padding { get; } public int AtlasWidth { get; } public int AtlasHeight { get; } public int RenderMode { get; } public float LineHeight { get; } public float AscentLine { get; } public float CapLine { get; } public float MeanLine { get; } public float DescentLine { get; } public float TabWidth { get; } public FontDefinition(string key, string fileName, int pointSize, int padding, int atlasWidth, int atlasHeight, int renderMode, float lineHeight, float ascentLine, float capLine, float meanLine, float descentLine, float tabWidth) { Key = key; FileName = fileName; PointSize = pointSize; Padding = padding; AtlasWidth = atlasWidth; AtlasHeight = atlasHeight; RenderMode = renderMode; LineHeight = lineHeight; AscentLine = ascentLine; CapLine = capLine; MeanLine = meanLine; DescentLine = descentLine; TabWidth = tabWidth; } } private const string PrewarmCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдеёжзийклмнопрстуфхцчшщъыьэюя .,!?;:-–—…'\"«»()[]{}+/%°"; private static readonly FontDefinition[] Definitions = new FontDefinition[12] { new FontDefinition("ForgottenRegular", "ForgottenFuturist-Regular.ttf", 55, 10, 1024, 1024, 4165, 66f, 50.93f, 38f, 28f, -15.07f, 11f), new FontDefinition("ForgottenBold", "ForgottenFuturist-Bold.ttf", 55, 10, 1024, 1024, 4165, 66f, 50.93f, 38f, 28f, -15.07f, 11f), new FontDefinition("Induction", "induction.ttf", 109, 10, 2048, 2048, 4165, 109.218f, 87.2f, 72f, 70f, -22.018f, 61f), new FontDefinition("InductionBackdrop", "induction.ttf", 35, 10, 1024, 1024, 4165, 35.07f, 28f, 22f, 22f, -7.07f, 20f), new FontDefinition("Polyester2070", "2070POLYESTER-Regular.ttf", 57, 8, 1024, 1024, 4165, 52.44f, 42.75f, 35f, 25f, -9.69f, 20f), new FontDefinition("Coolvetica", "coolvetica rg.otf", 76, 8, 1024, 1024, 4165, 91.2f, 73.492f, 50f, 37f, -17.708f, 18f), new FontDefinition("FerriteBlack", "FerriteCoreDX-Black.ttf", 31, 8, 1024, 512, 4165, 37.2f, 31f, 21f, 16f, -6.2f, 14f), new FontDefinition("ForcedSquare", "FORCED SQUARE.ttf", 36, 16, 1024, 1024, 4165, 29.196f, 21.708f, 19f, 15f, -6.192f, 12f), new FontDefinition("Karnivore", "Karnivore-Bold.ttf", 90, 9, 1024, 1024, 4165, 111.96f, 98.91f, 68f, 68f, -13.05f, 41f), new FontDefinition("ModernDos", "ModernDOS8x16-Regular.ttf", 74, 8, 512, 512, 8230, 74f, 55.5f, 47f, 33f, -18.5f, 37f), new FontDefinition("Pitviper", "Pitviper-Regular.ttf", 30, 8, 512, 512, 4165, 28.53f, 22.53f, 21f, 21f, -6f, 17f), new FontDefinition("ProphitDot", "V5PRD_ProphitDot.ttf", 70, 8, 1024, 1024, 4165, 70f, 55.72f, 42f, 35f, -14.28f, 21f) }; private static readonly Dictionary ProfileToFontKey = new Dictionary(StringComparer.OrdinalIgnoreCase) { ["CharacterNameTextCyrillic"] = "InductionBackdrop", ["DefaultTextCyrillic"] = "ForgottenRegular", ["DefaultTextBoldCyrillic"] = "ForgottenBold", ["DefaultTextHighlightedCyrillic"] = "ForgottenRegular", ["DefaultTextThinCyrillic"] = "ForgottenRegular", ["FlavorTextCyrillic"] = "ForgottenRegular", ["GameplayHUDGraffitiTextCyrillic"] = "FerriteBlack", ["GameplayHUDHeaderTextCyrillic"] = "Coolvetica", ["GameplayHudCrewTextCyrillic"] = "Polyester2070", ["GameplayHUDStatusTextCyrillic"] = "Pitviper", ["LoadingTextCyrilic"] = "ProphitDot", ["MainButtonTextCyrillic"] = "Induction", ["MainButtonTextBackdropCyrillic"] = "InductionBackdrop", ["MainButtonTextHighlightedCyrillic"] = "InductionBackdrop", ["MainButtonTextPressedCyrillic"] = "InductionBackdrop", ["MenuTitleTextCyrillic"] = "Coolvetica", ["MenuTimeLineTextCyrillic"] = "FerriteBlack", ["MenuTimeLineSelectedTextCyrillic"] = "FerriteBlack", ["MicroBoyTextOutlineCyrillic"] = "Karnivore", ["MicroBoyTextWithoutOutlineCyrillic"] = "Karnivore", ["DottedTextCyrillic"] = "ProphitDot", ["PhoneClockTextCyrillic"] = "FerriteBlack", ["PhoneFortuneTextCyrillic"] = "ModernDos", ["PhoneMainTextCyrillic"] = "Polyester2070", ["PhoneOnScreenNotificationTextCyrillic"] = "Polyester2070", ["PhoneNotificationTextCyrillic"] = "ForcedSquare", ["SplashScreenTextCyrillic"] = "Induction", ["YesNoBackdropCyrillic"] = "FerriteBlack", ["YesNoCyrillic"] = "FerriteBlack" }; private static readonly Dictionary ProfileToLatinProfile = new Dictionary(StringComparer.OrdinalIgnoreCase) { ["MicroBoyTextOutlineCyrillic"] = "MicroBoyTextLatin", ["MicroBoyTextWithoutOutlineCyrillic"] = "MicroBoyTextWithoutOultineLatin" }; private readonly ManualLogSource _log; private readonly string _fontDirectory; private readonly Dictionary _fonts = new Dictionary(StringComparer.OrdinalIgnoreCase); private readonly Dictionary _materials = new Dictionary(StringComparer.OrdinalIgnoreCase); private readonly Dictionary _styleSources = new Dictionary(StringComparer.OrdinalIgnoreCase); private readonly List _unityFonts = new List(); public int LoadedFontCount => _fonts.Count; public RuntimeFontManager(ManualLogSource log, string pluginAssemblyPath) { _log = log; _fontDirectory = Path.Combine(Path.GetDirectoryName(pluginAssemblyPath) ?? string.Empty, "Fonts"); } public void Initialize() { FontDefinition[] definitions = Definitions; foreach (FontDefinition definition in definitions) { TryLoad(definition); } } public bool TryGetFont(string profileName, out TMP_FontAsset fontAsset) { fontAsset = null; if (!ProfileToFontKey.TryGetValue(profileName, out var value)) { return false; } if (_fonts.TryGetValue(value, out fontAsset)) { return (Object)(object)fontAsset != (Object)null; } return false; } public void AddFallback(TMP_FontAsset fontAsset, TMP_FontAsset fallback) { if (!((Object)(object)fontAsset == (Object)null) && !((Object)(object)fallback == (Object)null) && !((Object)(object)fontAsset == (Object)(object)fallback)) { if (fontAsset.fallbackFontAssetTable == null) { fontAsset.fallbackFontAssetTable = new List(); } if (!fontAsset.fallbackFontAssetTable.Contains(fallback)) { fontAsset.fallbackFontAssetTable.Add(fallback); } } } public Material GetStyledMaterial(string profileName, TMP_FontAsset fontAsset, Material original) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Expected O, but got Unknown if (_materials.TryGetValue(profileName, out var value) && (Object)(object)value != (Object)null) { return value; } Material material = ((TMP_Asset)fontAsset).material; if ((Object)(object)material == (Object)null) { return original; } Material val = GetLatinStyleSource(profileName) ?? original ?? material; Material val2 = new Material(val) { name = profileName + " (Bomb Rush RUS Font Fix)", hideFlags = (HideFlags)61 }; BindRuntimeAtlas(material, val2); float num = (val2.HasProperty("_OutlineWidth") ? val2.GetFloat("_OutlineWidth") : 0f); bool num2 = IsMicroBoyProfile(profileName); float num3 = Mathf.Max(0f, Plugin.Instance?.OutlineWidthMultiplier ?? 2f); float num4 = ((!num2 && num > 0.0001f) ? Mathf.Clamp(num * num3, 0f, 1f) : num); float num5 = (IsMainButtonUnderlayProfile(profileName) ? Mathf.Clamp(Plugin.Instance?.MainButtonOutlineWidth ?? 0.12f, 0f, 1f) : 0f); if (num5 > 0.0001f) { num4 = Mathf.Max(num4, num5); CopyColor(val2, "_UnderlayColor", "_OutlineColor"); } if (val2.HasProperty("_OutlineWidth")) { val2.SetFloat("_OutlineWidth", num4); } float num6 = GetFloat(val2, "_UnderlayDilate"); int num7; float num8; if (IsMainButtonUnderlayProfile(profileName)) { num7 = (val2.IsKeywordEnabled(ShaderUtilities.Keyword_Underlay) ? 1 : 0); if (num7 != 0) { num8 = Mathf.Max(0f, Plugin.Instance?.MainButtonUnderlayDilateMultiplier ?? 1.5f); goto IL_0193; } } else { num7 = 0; } num8 = 1f; goto IL_0193; IL_0193: float num9 = num8; float num10 = ((num7 != 0) ? Mathf.Clamp(num6 * num9, -1f, 1f) : num6); if (val2.HasProperty("_UnderlayDilate")) { val2.SetFloat("_UnderlayDilate", num10); } ShaderUtilities.UpdateShaderRatios(val2); if (!num2 && num4 > 0.0001f) { val2.EnableKeyword(ShaderUtilities.Keyword_Outline); } else { val2.DisableKeyword(ShaderUtilities.Keyword_Outline); } float num11 = GetFloat(val2, "_UnderlayOffsetX"); float num12 = GetFloat(val2, "_UnderlayOffsetY"); _log.LogInfo((object)("Styled material: " + profileName + "; source=" + ((Object)val).name + "; " + $"shader={((Object)val2.shader).name}; authoredOutline={num:0.###}; " + $"outlineMultiplier={num3:0.###}; " + $"mainButtonOutline={num5:0.###}; outline={num4:0.###}; " + $"authoredUnderlayDilate={num6:0.###}; " + $"underlayMultiplier={num9:0.###}; " + $"underlayDilate={num10:0.###}; " + $"underlayOffset=({num11:0.###},{num12:0.###}); " + "keywords=[" + string.Join(",", val2.shaderKeywords) + "]")); _materials[profileName] = val2; return val2; } public void Dispose() { foreach (Material item in _materials.Values.Where((Material value) => (Object)(object)value != (Object)null)) { Object.Destroy((Object)(object)item); } foreach (TMP_FontAsset item2 in _fonts.Values.Where((TMP_FontAsset value) => (Object)(object)value != (Object)null)) { Object.Destroy((Object)(object)item2); } foreach (Font item3 in _unityFonts.Where((Font value) => (Object)(object)value != (Object)null)) { Object.Destroy((Object)(object)item3); } _materials.Clear(); _styleSources.Clear(); _fonts.Clear(); _unityFonts.Clear(); } private void TryLoad(FontDefinition definition) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Expected O, but got Unknown string text = Path.Combine(_fontDirectory, definition.FileName); if (!File.Exists(text)) { _log.LogWarning((object)("Font file is missing: " + text)); return; } Font val = new Font(text); if ((Object)(object)val == (Object)null) { _log.LogError((object)("Unity could not load font file: " + text)); return; } ((Object)val).hideFlags = (HideFlags)61; _unityFonts.Add(val); TMP_FontAsset val2 = TMP_FontAsset.CreateFontAsset(val, definition.PointSize, definition.Padding, (GlyphRenderMode)definition.RenderMode, definition.AtlasWidth, definition.AtlasHeight, (AtlasPopulationMode)1, true); if ((Object)(object)val2 == (Object)null) { _log.LogError((object)("TextMeshPro could not create a font asset for " + definition.FileName + ".")); return; } ((Object)val).name = "BombRushRUSFontFix_" + definition.Key; ((Object)val2).name = "BombRushRUSFontFix_" + definition.Key + "_SDF"; ((Object)val2).hideFlags = (HideFlags)61; ApplyOriginalMetrics(val2, definition); string text2 = default(string); val2.TryAddCharacters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдеёжзийклмнопрстуфхцчшщъыьэюя .,!?;:-–—…'\"«»()[]{}+/%°", ref text2, true); if (!string.IsNullOrEmpty(text2)) { _log.LogWarning((object)(definition.FileName + " is missing characters: " + text2)); } _fonts[definition.Key] = val2; _log.LogInfo((object)("Loaded runtime TMP font: " + definition.FileName + " -> " + ((Object)val2).name)); } private Material GetLatinStyleSource(string profileName) { if (_styleSources.TryGetValue(profileName, out var value)) { return value; } string latinProfileName = GetLatinProfileName(profileName); Material val = null; if (latinProfileName != null) { TextMeshProFont val2 = ((IEnumerable)Resources.FindObjectsOfTypeAll()).FirstOrDefault((Func)((TextMeshProFont candidate) => (Object)(object)candidate != (Object)null && string.Equals(((Object)candidate).name, latinProfileName, StringComparison.OrdinalIgnoreCase))); if ((Object)(object)val2 != (Object)null) { val = val2.FontMaterial; if ((Object)(object)val != (Object)null) { _log.LogInfo((object)("Material style: " + profileName + " <- " + latinProfileName)); } } } _styleSources[profileName] = val; return val; } private static string GetLatinProfileName(string profileName) { if (ProfileToLatinProfile.TryGetValue(profileName, out var value)) { return value; } if (profileName.EndsWith("Cyrillic", StringComparison.OrdinalIgnoreCase)) { return profileName.Substring(0, profileName.Length - "Cyrillic".Length) + "Latin"; } if (profileName.EndsWith("Cyrilic", StringComparison.OrdinalIgnoreCase)) { return profileName.Substring(0, profileName.Length - "Cyrilic".Length) + "Latin"; } return null; } private static bool IsMainButtonUnderlayProfile(string profileName) { if (!string.Equals(profileName, "MainButtonTextBackdropCyrillic", StringComparison.OrdinalIgnoreCase) && !string.Equals(profileName, "MainButtonTextHighlightedCyrillic", StringComparison.OrdinalIgnoreCase)) { return string.Equals(profileName, "MainButtonTextPressedCyrillic", StringComparison.OrdinalIgnoreCase); } return true; } private static bool IsMicroBoyProfile(string profileName) { if (!string.Equals(profileName, "MicroBoyTextOutlineCyrillic", StringComparison.OrdinalIgnoreCase)) { return string.Equals(profileName, "MicroBoyTextWithoutOutlineCyrillic", StringComparison.OrdinalIgnoreCase); } return true; } private static float GetFloat(Material material, string property) { if (!material.HasProperty(property)) { return 0f; } return material.GetFloat(property); } private static void CopyColor(Material material, string sourceProperty, string destinationProperty) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) if (material.HasProperty(sourceProperty) && material.HasProperty(destinationProperty)) { material.SetColor(destinationProperty, material.GetColor(sourceProperty)); } } private static void ApplyOriginalMetrics(TMP_FontAsset fontAsset, FontDefinition definition) { //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_007e: Unknown result type (might be due to invalid IL or missing references) FaceInfo faceInfo = fontAsset.faceInfo; ((FaceInfo)(ref faceInfo)).lineHeight = definition.LineHeight; ((FaceInfo)(ref faceInfo)).ascentLine = definition.AscentLine; ((FaceInfo)(ref faceInfo)).capLine = definition.CapLine; ((FaceInfo)(ref faceInfo)).meanLine = definition.MeanLine; ((FaceInfo)(ref faceInfo)).baseline = 0f; ((FaceInfo)(ref faceInfo)).descentLine = definition.DescentLine; ((FaceInfo)(ref faceInfo)).superscriptOffset = ((FaceInfo)(ref faceInfo)).ascentLine; ((FaceInfo)(ref faceInfo)).subscriptOffset = ((FaceInfo)(ref faceInfo)).descentLine; ((FaceInfo)(ref faceInfo)).tabWidth = definition.TabWidth; fontAsset.faceInfo = faceInfo; } private static void BindRuntimeAtlas(Material source, Material destination) { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) if (source.HasProperty("_MainTex") && destination.HasProperty("_MainTex")) { destination.SetTexture("_MainTex", source.GetTexture("_MainTex")); destination.SetTextureScale("_MainTex", source.GetTextureScale("_MainTex")); destination.SetTextureOffset("_MainTex", source.GetTextureOffset("_MainTex")); } string[] array = new string[3] { "_GradientScale", "_TextureWidth", "_TextureHeight" }; foreach (string text in array) { if (source.HasProperty(text) && destination.HasProperty(text)) { destination.SetFloat(text, source.GetFloat(text)); } } } } [HarmonyPatch] internal static class TextMeshProFontPatches { [HarmonyPostfix] [HarmonyPatch(/*Could not decode attribute arguments.*/)] private static void GetFontAssetPostfix(TextMeshProFont __instance, ref TMP_FontAsset __result) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Invalid comparison between Unknown and I4 Plugin instance = Plugin.Instance; if (!((Object)(object)instance == (Object)null) && instance.IsEnabled && instance.Fonts != null && !((Object)(object)__instance == (Object)null) && (int)__instance.Language == 30 && instance.Fonts.TryGetFont(((Object)__instance).name, out var fontAsset)) { instance.Fonts.AddFallback(fontAsset, __result); __result = fontAsset; } } [HarmonyPostfix] [HarmonyPatch(/*Could not decode attribute arguments.*/)] private static void GetFontMaterialPostfix(TextMeshProFont __instance, ref Material __result) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Invalid comparison between Unknown and I4 Plugin instance = Plugin.Instance; if (!((Object)(object)instance == (Object)null) && instance.IsEnabled && instance.Fonts != null && !((Object)(object)__instance == (Object)null) && (int)__instance.Language == 30 && instance.Fonts.TryGetFont(((Object)__instance).name, out var fontAsset)) { __result = instance.Fonts.GetStyledMaterial(((Object)__instance).name, fontAsset, __result); } } } }