Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of MixedCaseMapMarkers v2.0.0
plugins\MixedCaseMapMarkers\MixedCaseMapMarkers.dll
Decompiled 2 months agousing System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using MixedCaseMapMarkers.Fonts; using TMPro; using UnityEngine; using UnityEngine.TextCore.LowLevel; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("LexPy")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("2.0.0.0")] [assembly: AssemblyInformationalVersion("2.0.0")] [assembly: AssemblyProduct("MixedCaseMapMarkers")] [assembly: AssemblyTitle("MixedCaseMapMarkers")] [assembly: AssemblyVersion("2.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace MixedCaseMapMarkers { [BepInPlugin("com.lexpy.mixedcasemapmarkers", "MixedCaseMapMarkers", "2.0.0")] public sealed class Plugin : BaseUnityPlugin { public const string PluginGuid = "com.lexpy.mixedcasemapmarkers"; public const string PluginName = "MixedCaseMapMarkers"; public const string PluginVersion = "2.0.0"; private Harmony? _harmony; internal static ManualLogSource? Log { get; private set; } private void Awake() { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; _harmony = new Harmony("com.lexpy.mixedcasemapmarkers"); _harmony.PatchAll(typeof(Plugin).Assembly); ((BaseUnityPlugin)this).Logger.LogInfo((object)"MixedCaseMapMarkers 2.0.0 loaded."); } private void OnDestroy() { Harmony? harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } MixedCaseFont.Dispose(); _harmony = null; Log = null; } } } namespace MixedCaseMapMarkers.Patches { internal static class MapMarkerController { private static readonly FieldInfo? NameInputField = AccessTools.Field(typeof(Minimap), "m_nameInput"); private static readonly FieldInfo? PinNamePrefabField = AccessTools.Field(typeof(Minimap), "m_pinNamePrefab"); private static TMP_FontAsset? _observedMarkerFont; private static FontStyles _observedMarkerStyle; private static FontWeight _observedMarkerWeight = (FontWeight)400; private static bool _observedMarkerStyleCaptured; private static bool _inputLogged; private static bool _inputRoutingLogged; private static bool _markerLogged; private static bool _markerCaseLogged; private static bool _markerRenderLogged; private static bool _missingInputLogged; private static bool _missingMarkerFontLogged; private static bool _transientSkipLogged; public static int CaptureChildCount(RectTransform? root) { if (root == null) { return -1; } return ((Transform)root).childCount; } public static void ConfigureInput(Minimap minimap) { if ((Object)(object)minimap == (Object)null) { return; } GameObject root = GetRoot(NameInputField?.GetValue(minimap)); if ((Object)(object)root == (Object)null) { LogOnce(ref _missingInputLogged, "Valheim's m_nameInput object could not be found.", warning: true); return; } TMP_FontAsset val = _observedMarkerFont ?? FindFont(PinNamePrefabField?.GetValue(minimap)); if ((Object)(object)val == (Object)null) { LogOnce(ref _missingMarkerFontLogged, "Valheim's map-marker font could not be found.", warning: true); return; } TMP_InputField[] componentsInChildren = root.GetComponentsInChildren<TMP_InputField>(true); foreach (TMP_InputField val2 in componentsInChildren) { RemoveInputCapitalization(val2); if ((Object)(object)val2.textComponent != (Object)null && MixedCaseFont.Apply(val2.textComponent, val)) { ApplyObservedMarkerTypefaceStyle(val2.textComponent); val2.fontAsset = val2.textComponent.font; ((Graphic)val2.textComponent).SetAllDirty(); val2.textComponent.ForceMeshUpdate(true, true); LogInputRoutingOnce(val2.textComponent); } Graphic placeholder = val2.placeholder; TMP_Text val3 = (TMP_Text)(object)((placeholder is TMP_Text) ? placeholder : null); if (val3 != null && MixedCaseFont.Apply(val3, val)) { ApplyObservedMarkerTypefaceStyle(val3); ((Graphic)val3).SetAllDirty(); } } if (!_inputLogged) { string text = (_observedMarkerStyleCaptured ? (", copied marker style=" + ((object)Unsafe.As<FontStyles, FontStyles>(ref _observedMarkerStyle)/*cast due to .constrained prefix*/).ToString() + ", weight=" + ((object)Unsafe.As<FontWeight, FontWeight>(ref _observedMarkerWeight)/*cast due to .constrained prefix*/).ToString()) : ", marker style not yet captured"); ManualLogSource? log = Plugin.Log; if (log != null) { log.LogDebug((object)("Configured the map-marker input field for mixed case using marker font '" + ((Object)val).name + "'" + text + ".")); } _inputLogged = true; } } public static void ConfigureCreatedMapLabel(object? pinData, RectTransform? createRoot, int previousChildCount) { //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) TMP_Text val = TryGetPinNameText(pinData); if (IsTransientSystemMapText(pinData, val)) { if (!_transientSkipLogged) { ManualLogSource? log = Plugin.Log; if (log != null) { log.LogDebug((object)"Leaving transient map PING / I HAVE ARRIVED text completely untouched."); } _transientSkipLogged = true; } return; } if ((Object)(object)val != (Object)null && (Object)(object)val.font != (Object)null) { if ((Object)(object)_observedMarkerFont == (Object)null) { _observedMarkerFont = val.font; ManualLogSource? log2 = Plugin.Log; if (log2 != null) { log2.LogDebug((object)("Captured the actual map-label font: '" + ((Object)_observedMarkerFont).name + "'.")); } } if (!_observedMarkerStyleCaptured) { _observedMarkerStyle = StripCaseStyles(val.fontStyle); _observedMarkerWeight = val.fontWeight; _observedMarkerStyleCaptured = true; ManualLogSource? log3 = Plugin.Log; if (log3 != null) { log3.LogDebug((object)("Captured the actual map-label typeface style: fontStyle=" + ((object)Unsafe.As<FontStyles, FontStyles>(ref _observedMarkerStyle)/*cast due to .constrained prefix*/).ToString() + ", fontWeight=" + ((object)Unsafe.As<FontWeight, FontWeight>(ref _observedMarkerWeight)/*cast due to .constrained prefix*/).ToString() + ".")); } } } bool flag = (Object)(object)val != (Object)null && ContainsCustomLowercase(val.text); if (!_markerCaseLogged && (Object)(object)val != (Object)null) { ManualLogSource? log4 = Plugin.Log; if (log4 != null) { log4.LogDebug((object)("Map-label source text contains lowercase characters before font replacement: " + flag + ".")); } _markerCaseLogged = true; } GameObject val2 = TryGetPinNameGameObject(pinData); if ((Object)(object)val2 == (Object)null && (Object)(object)createRoot != (Object)null && previousChildCount >= 0) { int num = Mathf.Clamp(previousChildCount, 0, ((Transform)createRoot).childCount); if (num < ((Transform)createRoot).childCount) { val2 = ((Component)((Transform)createRoot).GetChild(num)).gameObject; } } int num2 = 0; if ((Object)(object)val2 != (Object)null) { TMP_Text[] componentsInChildren = val2.GetComponentsInChildren<TMP_Text>(true); foreach (TMP_Text val3 in componentsInChildren) { if (!((Object)(object)val3.font == (Object)null) && ContainsCustomLowercase(val3.text)) { TMP_FontAsset originalFont = _observedMarkerFont ?? val3.font; if (MixedCaseFont.Apply(val3, originalFont)) { AttachGuard(val3, originalFont); LogRenderedLowercaseOnce(val3); num2++; } } } } if (num2 == 0 && (Object)(object)val != (Object)null && (Object)(object)val.font != (Object)null && ContainsCustomLowercase(val.text)) { TMP_FontAsset originalFont2 = _observedMarkerFont ?? val.font; if (MixedCaseFont.Apply(val, originalFont2)) { AttachGuard(val, originalFont2); LogRenderedLowercaseOnce(val); num2 = 1; } } if (num2 > 0 && !_markerLogged) { ManualLogSource? log5 = Plugin.Log; if (log5 != null) { log5.LogDebug((object)("Applied the mixed-case font to " + num2 + " lowercase-containing TMP component(s) in the exact map-label object.")); } _markerLogged = true; } } internal static void LogRenderedLowercaseOnce(TMP_Text text) { //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_0050: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_0071: 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) //IL_016f: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Unknown result type (might be due to invalid IL or missing references) //IL_0190: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Unknown result type (might be due to invalid IL or missing references) if (_markerRenderLogged || (Object)(object)text == (Object)null) { return; } text.ForceMeshUpdate(true, true); TMP_TextInfo textInfo = text.textInfo; TMP_CharacterInfo? character = null; TMP_CharacterInfo? character2 = null; for (int i = 0; i < textInfo.characterCount; i++) { TMP_CharacterInfo val = textInfo.characterInfo[i]; if (!character.HasValue && ((val.character >= 'A' && val.character <= 'Z') || (val.character >= '0' && val.character <= '9'))) { character = val; } if (!character2.HasValue && IsCustomLowercase(val.character)) { character2 = val; } if (character.HasValue && character2.HasValue) { break; } } if (character2.HasValue) { string text2 = (((Object)(object)text.font != (Object)null) ? ((Object)text.font).name : "<null>"); string text3 = (((Object)(object)text.fontSharedMaterial != (Object)null) ? ((Object)text.fontSharedMaterial).name : "<null>"); string text4 = DescribeRenderedCharacter(textInfo, character); string text5 = DescribeRenderedCharacter(textInfo, character2); ManualLogSource? log = Plugin.Log; if (log != null) { log.LogDebug((object)("Rendered mixed-case check: upper/digit=" + text4 + "; lowercase=" + text5 + "; assignedFont='" + text2 + "', fontStyle=" + ((object)text.fontStyle/*cast due to .constrained prefix*/).ToString() + ", fontWeight=" + ((object)text.fontWeight/*cast due to .constrained prefix*/).ToString() + ", sharedMaterial='" + text3 + "'.")); } _markerRenderLogged = true; } } private static string DescribeRenderedCharacter(TMP_TextInfo info, TMP_CharacterInfo? character) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_002c: 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) if (!character.HasValue) { return "<none>"; } TMP_CharacterInfo value = character.Value; string text = (((Object)(object)value.fontAsset != (Object)null) ? ((Object)value.fontAsset).name : "<null>"); string text2 = "<none>"; int materialReferenceIndex = value.materialReferenceIndex; if (info.meshInfo != null && materialReferenceIndex >= 0 && materialReferenceIndex < info.meshInfo.Length && (Object)(object)info.meshInfo[materialReferenceIndex].material != (Object)null) { text2 = ((Object)info.meshInfo[materialReferenceIndex].material).name; } return "'" + value.character + "' fontAsset='" + text + "', materialIndex=" + materialReferenceIndex + ", material='" + text2 + "'"; } private unsafe static void LogInputRoutingOnce(TMP_Text text) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) if (!_inputRoutingLogged && !((Object)(object)text == (Object)null) && !((Object)(object)text.font == (Object)null)) { FontStyles val = StripCaseStyles(text.fontStyle); FontWeight fontWeight = text.fontWeight; bool flag = default(bool); TMP_Character characterFromFontAsset = TMP_FontAssetUtilities.GetCharacterFromFontAsset(97u, text.font, true, val, fontWeight, ref flag); bool flag2 = default(bool); TMP_Character characterFromFontAsset2 = TMP_FontAssetUtilities.GetCharacterFromFontAsset(65u, text.font, true, val, fontWeight, ref flag2); string text2 = (((Object)(object)((characterFromFontAsset != null) ? ((TMP_TextElement)characterFromFontAsset).textAsset : null) != (Object)null) ? ((Object)((TMP_TextElement)characterFromFontAsset).textAsset).name : "<none>"); string text3 = (((Object)(object)((characterFromFontAsset2 != null) ? ((TMP_TextElement)characterFromFontAsset2).textAsset : null) != (Object)null) ? ((Object)((TMP_TextElement)characterFromFontAsset2).textAsset).name : "<none>"); ManualLogSource? log = Plugin.Log; if (log != null) { log.LogDebug((object)("Input routing check: fontStyle=" + ((object)(*(FontStyles*)(&val))/*cast due to .constrained prefix*/).ToString() + ", fontWeight=" + ((object)(*(FontWeight*)(&fontWeight))/*cast due to .constrained prefix*/).ToString() + "; 'A' -> '" + text3 + "'; 'a' -> '" + text2 + "'.")); } _inputRoutingLogged = true; } } private static void ApplyObservedMarkerTypefaceStyle(TMP_Text text) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0025: 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_002d: Unknown result type (might be due to invalid IL or missing references) if (_observedMarkerStyleCaptured && !((Object)(object)text == (Object)null)) { FontStyles val = (FontStyles)(text.fontStyle & -60); FontStyles val2 = (FontStyles)(_observedMarkerStyle & 3); text.fontStyle = (FontStyles)(val | val2); text.fontWeight = _observedMarkerWeight; } } private static FontStyles StripCaseStyles(FontStyles style) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Unknown result type (might be due to invalid IL or missing references) return (FontStyles)(style & -57); } private static void AttachGuard(TMP_Text text, TMP_FontAsset originalFont) { MarkerFontGuard markerFontGuard = ((Component)text).gameObject.GetComponent<MarkerFontGuard>(); if ((Object)(object)markerFontGuard == (Object)null) { markerFontGuard = ((Component)text).gameObject.AddComponent<MarkerFontGuard>(); } markerFontGuard.Initialize(text, originalFont); } private static void RemoveInputCapitalization(TMP_InputField inputField) { inputField.contentType = (ContentType)0; inputField.characterValidation = (CharacterValidation)0; inputField.inputValidator = null; inputField.onValidateInput = null; } private static bool IsTransientSystemMapText(object? pinData, TMP_Text? text) { if (pinData != null) { string text2 = (AccessTools.Field(pinData.GetType(), "m_type")?.GetValue(pinData))?.ToString(); if (!string.IsNullOrEmpty(text2) && text2.IndexOf("Ping", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } } string text3 = ((text != null) ? text.text : null); if (string.IsNullOrEmpty(text3)) { return false; } if (!text3.Equals("PING", StringComparison.OrdinalIgnoreCase) && !text3.EndsWith(": PING", StringComparison.OrdinalIgnoreCase) && !text3.Equals("I HAVE ARRIVED!", StringComparison.OrdinalIgnoreCase)) { return text3.EndsWith(": I HAVE ARRIVED!", StringComparison.OrdinalIgnoreCase); } return true; } private static object? TryGetNamePinData(object? pinData) { if (pinData == null) { return null; } return AccessTools.Field(pinData.GetType(), "m_NamePinData")?.GetValue(pinData); } private static TMP_Text? TryGetPinNameText(object? pinData) { object obj = TryGetNamePinData(pinData); if (obj == null) { return null; } Type type = obj.GetType(); object? obj2 = AccessTools.Property(type, "PinNameText")?.GetValue(obj); TMP_Text val = (TMP_Text)((obj2 is TMP_Text) ? obj2 : null); if (val != null) { return val; } object? obj3 = AccessTools.Field(type, "<PinNameText>k__BackingField")?.GetValue(obj); return (TMP_Text?)((obj3 is TMP_Text) ? obj3 : null); } private static GameObject? TryGetPinNameGameObject(object? pinData) { object obj = TryGetNamePinData(pinData); if (obj == null) { return null; } Type type = obj.GetType(); object? obj2 = AccessTools.Property(type, "PinNameGameObject")?.GetValue(obj); GameObject val = (GameObject)((obj2 is GameObject) ? obj2 : null); if (val != null) { return val; } object? obj3 = AccessTools.Field(type, "<PinNameGameObject>k__BackingField")?.GetValue(obj); return (GameObject?)((obj3 is GameObject) ? obj3 : null); } private static TMP_FontAsset? FindFont(object? value) { TMP_Text val = (TMP_Text)((value is TMP_Text) ? value : null); if (val != null) { return val.font; } TMP_InputField val2 = (TMP_InputField)((value is TMP_InputField) ? value : null); if (val2 != null) { TMP_Text textComponent = val2.textComponent; object obj = ((textComponent != null) ? textComponent.font : null); if (obj == null) { Graphic placeholder = val2.placeholder; Graphic obj2 = ((placeholder is TMP_Text) ? placeholder : null); if (obj2 == null) { return null; } obj = ((TMP_Text)obj2).font; } return (TMP_FontAsset?)obj; } GameObject root = GetRoot(value); if ((Object)(object)root == (Object)null) { return null; } TMP_Text[] componentsInChildren = root.GetComponentsInChildren<TMP_Text>(true); for (int i = 0; i < componentsInChildren.Length; i++) { if ((Object)(object)componentsInChildren[i].font != (Object)null && ((Object)componentsInChildren[i].font).name.IndexOf("Valheim-Norse", StringComparison.OrdinalIgnoreCase) >= 0) { return componentsInChildren[i].font; } } for (int j = 0; j < componentsInChildren.Length; j++) { if ((Object)(object)componentsInChildren[j].font != (Object)null) { return componentsInChildren[j].font; } } return null; } private static GameObject? GetRoot(object? value) { GameObject val = (GameObject)((value is GameObject) ? value : null); if (val == null) { Component val2 = (Component)((value is Component) ? value : null); if (val2 != null) { return val2.gameObject; } return null; } return val; } private static bool ContainsCustomLowercase(string? value) { if (string.IsNullOrEmpty(value)) { return false; } for (int i = 0; i < value.Length; i++) { if (IsCustomLowercase(value[i])) { return true; } } return false; } private static bool IsCustomLowercase(char c) { if ((c < 'a' || c > 'z') && c != 'ä' && c != 'ö' && c != 'ü' && c != 'ñ') { return c == 'ç'; } return true; } private static void LogOnce(ref bool flag, string message, bool warning) { if (flag) { return; } if (warning) { ManualLogSource? log = Plugin.Log; if (log != null) { log.LogWarning((object)message); } } else { ManualLogSource? log2 = Plugin.Log; if (log2 != null) { log2.LogDebug((object)message); } } flag = true; } } internal sealed class MarkerFontGuard : MonoBehaviour { private TMP_Text? _text; private TMP_FontAsset? _originalFont; private bool _overwriteLogged; public void Initialize(TMP_Text text, TMP_FontAsset originalFont) { _text = text; _originalFont = originalFont; } private void LateUpdate() { if ((Object)(object)_text == (Object)null || (Object)(object)_originalFont == (Object)null || MixedCaseFont.IsApplied(_text)) { return; } if (!_overwriteLogged) { ManualLogSource? log = Plugin.Log; if (log != null) { log.LogDebug((object)"Valheim changed the map-label font/style after creation; reapplying the mixed-case font on the exact label component."); } _overwriteLogged = true; } if (MixedCaseFont.Apply(_text, _originalFont)) { MapMarkerController.LogRenderedLowercaseOnce(_text); } } } [HarmonyPatch(typeof(Minimap), "ShowPinNameInput")] internal static class ShowPinNameInputPatch { [HarmonyPostfix] private static void Postfix(Minimap __instance) { MapMarkerController.ConfigureInput(__instance); } } [HarmonyPatch(typeof(Minimap), "CreateMapNamePin")] internal static class CreateMapNamePinPatch { [HarmonyPrefix] private static void Prefix(RectTransform __1, out int __state) { __state = MapMarkerController.CaptureChildCount(__1); } [HarmonyPostfix] private static void Postfix(object __0, RectTransform __1, int __state) { MapMarkerController.ConfigureCreatedMapLabel(__0, __1, __state); } } } namespace MixedCaseMapMarkers.Fonts { internal static class MixedCaseFont { private readonly struct FontStyleKey : IEquatable<FontStyleKey> { private readonly int _fontId; private readonly int _styleId; public FontStyleKey(int fontId, int styleId) { _fontId = fontId; _styleId = styleId; } public bool Equals(FontStyleKey other) { if (_fontId == other._fontId) { return _styleId == other._styleId; } return false; } public override bool Equals(object? obj) { if (obj is FontStyleKey other) { return Equals(other); } return false; } public override int GetHashCode() { return (_fontId * 397) ^ _styleId; } } private sealed class CompositeFont { public TMP_FontAsset PrimaryFont { get; } public TMP_FontAsset LowercaseFont { get; } public Material? StyledPrimaryMaterial { get; } public IReadOnlyList<TMP_FontAsset> AlternativeTypefaces { get; } public CompositeFont(TMP_FontAsset primaryFont, TMP_FontAsset lowercaseFont, Material? styledPrimaryMaterial, IReadOnlyList<TMP_FontAsset> alternativeTypefaces) { PrimaryFont = primaryFont; LowercaseFont = lowercaseFont; StyledPrimaryMaterial = styledPrimaryMaterial; AlternativeTypefaces = alternativeTypefaces; } } private static readonly Dictionary<FontStyleKey, CompositeFont> CompositeFonts = new Dictionary<FontStyleKey, CompositeFont>(); private static bool _validationLogged; public static bool Apply(TMP_Text text, TMP_FontAsset originalFont) { if ((Object)(object)text == (Object)null || (Object)(object)originalFont == (Object)null) { return false; } object obj = text.fontSharedMaterial; if (obj == null) { TMP_FontAsset font = text.font; obj = ((font != null) ? ((TMP_Asset)font).material : null) ?? ((TMP_Asset)originalFont).material; } Material styleSource = (Material)obj; CompositeFont orCreateComposite = GetOrCreateComposite(originalFont, styleSource); if (orCreateComposite == null) { return false; } RemoveCaseStyle(text); text.font = orCreateComposite.PrimaryFont; Material primaryMaterial = GetPrimaryMaterial(orCreateComposite, styleSource); if ((Object)(object)primaryMaterial != (Object)null) { text.fontSharedMaterial = primaryMaterial; } ((Graphic)text).SetAllDirty(); text.ForceMeshUpdate(true, true); return true; } public static bool IsApplied(TMP_Text text) { if ((Object)(object)text != (Object)null && (Object)(object)text.font != (Object)null && ((Object)text.font).name.IndexOf("MixedCaseMapMarkers Primary", StringComparison.Ordinal) >= 0) { return !HasCaseStyle(text); } return false; } public static void Dispose() { CompositeFonts.Clear(); RuntimeLowercaseFont.Dispose(); _validationLogged = false; } private static CompositeFont? GetOrCreateComposite(TMP_FontAsset originalFont, Material? styleSource) { //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_01aa: Unknown result type (might be due to invalid IL or missing references) int styleId = (((Object)(object)styleSource != (Object)null) ? ((Object)styleSource).GetInstanceID() : 0); FontStyleKey key = new FontStyleKey(((Object)originalFont).GetInstanceID(), styleId); if (CompositeFonts.TryGetValue(key, out CompositeFont value)) { return value; } TMP_FontAsset val = RuntimeLowercaseFont.Get(); if ((Object)(object)val == (Object)null) { return null; } originalFont.ReadFontAssetDefinition(); TMP_FontAsset val2 = Object.Instantiate<TMP_FontAsset>(originalFont); ((Object)val2).name = ((Object)originalFont).name + " (MixedCaseMapMarkers Primary)"; AtlasPopulationMode atlasPopulationMode = val2.atlasPopulationMode; val2.atlasPopulationMode = (AtlasPopulationMode)0; RemoveLowercaseMappings(val2); TMP_FontAsset val3 = Object.Instantiate<TMP_FontAsset>(val); ((Object)val3).name = "MixedCaseNorseLowercase (MixedCaseMapMarkers Fallback)"; val3.atlasPopulationMode = (AtlasPopulationMode)0; Material val4 = CreateMatchedStyleMaterial(((TMP_Asset)val3).material, styleSource, "MixedCaseMapMarkers-Lowercase-Material"); if ((Object)(object)val4 != (Object)null) { ((TMP_Asset)val3).material = val4; } List<TMP_FontAsset> list = CloneAndSanitizeAlternativeTypefaces(originalFont, val2); RouteAllWeightsToSelf(val3); List<TMP_FontAsset> fallbackFontAssetTable = BuildFallbackChain(val2.fallbackFontAssetTable, val3, originalFont); val2.fallbackFontAssetTable = fallbackFontAssetTable; for (int i = 0; i < list.Count; i++) { TMP_FontAsset obj = list[i]; obj.fallbackFontAssetTable = BuildFallbackChain(obj.fallbackFontAssetTable, val3, originalFont); obj.ReadFontAssetDefinition(); } val3.ReadFontAssetDefinition(); Material val5 = CreateStyledAtlasMaterial(((TMP_Asset)val2).material, styleSource, "MixedCaseMapMarkers-Primary-Material"); Object.DontDestroyOnLoad((Object)(object)val2); Object.DontDestroyOnLoad((Object)(object)val3); for (int j = 0; j < list.Count; j++) { if ((Object)(object)list[j] != (Object)null) { Object.DontDestroyOnLoad((Object)(object)list[j]); } } if ((Object)(object)val4 != (Object)null) { Object.DontDestroyOnLoad((Object)(object)val4); } if ((Object)(object)val5 != (Object)null) { Object.DontDestroyOnLoad((Object)(object)val5); } ValidateCompositeOnce(val2, val3, atlasPopulationMode); CompositeFont compositeFont = new CompositeFont(val2, val3, val5, list); CompositeFonts.Add(key, compositeFont); return compositeFont; } private static List<TMP_FontAsset> CloneAndSanitizeAlternativeTypefaces(TMP_FontAsset originalFont, TMP_FontAsset primaryFont) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0027: 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_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) List<TMP_FontAsset> list = new List<TMP_FontAsset>(); Dictionary<int, TMP_FontAsset> clonesBySourceId = new Dictionary<int, TMP_FontAsset>(); TMP_FontWeightPair[] fontWeightTable = primaryFont.fontWeightTable; if (fontWeightTable == null) { return list; } for (int i = 0; i < fontWeightTable.Length; i++) { TMP_FontWeightPair val = fontWeightTable[i]; val.regularTypeface = CloneAlternativeTypeface(val.regularTypeface, originalFont, primaryFont, clonesBySourceId, list); val.italicTypeface = CloneAlternativeTypeface(val.italicTypeface, originalFont, primaryFont, clonesBySourceId, list); fontWeightTable[i] = val; } return list; } private static TMP_FontAsset? CloneAlternativeTypeface(TMP_FontAsset? source, TMP_FontAsset originalFont, TMP_FontAsset primaryFont, Dictionary<int, TMP_FontAsset> clonesBySourceId, List<TMP_FontAsset> owned) { if ((Object)(object)source == (Object)null) { return null; } if (((Object)source).GetInstanceID() == ((Object)originalFont).GetInstanceID()) { return primaryFont; } int instanceID = ((Object)source).GetInstanceID(); if (clonesBySourceId.TryGetValue(instanceID, out TMP_FontAsset value)) { return value; } source.ReadFontAssetDefinition(); TMP_FontAsset val = Object.Instantiate<TMP_FontAsset>(source); ((Object)val).name = ((Object)source).name + " (MixedCaseMapMarkers Typeface)"; val.atlasPopulationMode = (AtlasPopulationMode)0; RemoveLowercaseMappings(val); clonesBySourceId.Add(instanceID, val); owned.Add(val); return val; } private static List<TMP_FontAsset> BuildFallbackChain(List<TMP_FontAsset>? existing, TMP_FontAsset lowercaseFont, TMP_FontAsset originalValheimFont) { List<TMP_FontAsset> list = new List<TMP_FontAsset>(); list.Add(lowercaseFont); list.Add(originalValheimFont); if (existing != null) { for (int i = 0; i < existing.Count; i++) { TMP_FontAsset val = existing[i]; if (!((Object)(object)val == (Object)null) && val != lowercaseFont && val != originalValheimFont) { list.Add(val); } } } return list; } private static void RouteAllWeightsToSelf(TMP_FontAsset font) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) TMP_FontWeightPair[] fontWeightTable = font.fontWeightTable; if (fontWeightTable != null) { for (int i = 0; i < fontWeightTable.Length; i++) { TMP_FontWeightPair val = fontWeightTable[i]; val.regularTypeface = font; val.italicTypeface = font; fontWeightTable[i] = val; } } } private static Material? GetPrimaryMaterial(CompositeFont composite, Material? styleSource) { if ((Object)(object)styleSource != (Object)null && MaterialUsesFontAtlas(styleSource, composite.PrimaryFont)) { return styleSource; } return composite.StyledPrimaryMaterial ?? ((TMP_Asset)composite.PrimaryFont).material; } private static bool MaterialUsesFontAtlas(Material material, TMP_FontAsset font) { if ((Object)(object)material == (Object)null || (Object)(object)font == (Object)null || (Object)(object)font.atlasTexture == (Object)null) { return false; } int iD_MainTex = ShaderUtilities.ID_MainTex; if (!material.HasProperty(iD_MainTex)) { return false; } Texture texture = material.GetTexture(iD_MainTex); if ((Object)(object)texture != (Object)null) { return ((Object)texture).GetInstanceID() == ((Object)font.atlasTexture).GetInstanceID(); } return false; } private static void RemoveLowercaseMappings(TMP_FontAsset font) { List<TMP_Character> characterTable = font.characterTable; for (int num = characterTable.Count - 1; num >= 0; num--) { if (IsCustomLowercaseUnicode(((TMP_TextElement)characterTable[num]).unicode)) { characterTable.RemoveAt(num); } } font.ReadFontAssetDefinition(); List<uint> list = new List<uint>(); foreach (uint key in font.characterLookupTable.Keys) { if (IsCustomLowercaseUnicode(key & 0xFFFFFF)) { list.Add(key); } } for (int i = 0; i < list.Count; i++) { font.characterLookupTable.Remove(list[i]); } } private static bool IsCustomLowercaseUnicode(uint unicode) { if ((unicode < 97 || unicode > 122) && unicode != 228 && unicode != 246 && unicode != 252 && unicode != 241) { return unicode == 231; } return true; } private unsafe static void ValidateCompositeOnce(TMP_FontAsset primaryFont, TMP_FontAsset lowercaseFont, AtlasPopulationMode originalPopulationMode) { if (!_validationLogged) { bool flag = primaryFont.characterLookupTable.ContainsKey(97u); bool flag2 = lowercaseFont.characterLookupTable.ContainsKey(97u); bool flag3 = default(bool); TMP_Character characterFromFontAsset = TMP_FontAssetUtilities.GetCharacterFromFontAsset(97u, primaryFont, true, (FontStyles)0, (FontWeight)400, ref flag3); bool flag4 = default(bool); TMP_Character characterFromFontAsset2 = TMP_FontAssetUtilities.GetCharacterFromFontAsset(97u, primaryFont, true, (FontStyles)1, (FontWeight)700, ref flag4); bool flag5 = default(bool); TMP_Character characterFromFontAsset3 = TMP_FontAssetUtilities.GetCharacterFromFontAsset(65u, primaryFont, true, (FontStyles)1, (FontWeight)700, ref flag5); string text = (((Object)(object)((characterFromFontAsset != null) ? ((TMP_TextElement)characterFromFontAsset).textAsset : null) != (Object)null) ? ((Object)((TMP_TextElement)characterFromFontAsset).textAsset).name : "<none>"); string text2 = (((Object)(object)((characterFromFontAsset2 != null) ? ((TMP_TextElement)characterFromFontAsset2).textAsset : null) != (Object)null) ? ((Object)((TMP_TextElement)characterFromFontAsset2).textAsset).name : "<none>"); string text3 = (((Object)(object)((characterFromFontAsset3 != null) ? ((TMP_TextElement)characterFromFontAsset3).textAsset : null) != (Object)null) ? ((Object)((TMP_TextElement)characterFromFontAsset3).textAsset).name : "<none>"); ManualLogSource? log = Plugin.Log; if (log != null) { log.LogDebug((object)("Composite routing check: Valheim primary population mode was " + ((object)(*(AtlasPopulationMode*)(&originalPopulationMode))/*cast due to .constrained prefix*/).ToString() + "; frozen clone contains lowercase 'a': " + flag + "; custom fallback contains 'a': " + flag2 + "; regular 'a' -> '" + text + "'; bold 'a' -> '" + text2 + "'; bold 'A' -> '" + text3 + "'.")); } _validationLogged = true; } } private static Material? CreateMatchedStyleMaterial(Material? targetFontMaterial, Material? styleSource, string name) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected O, but got Unknown //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Expected O, but got Unknown if ((Object)(object)targetFontMaterial == (Object)null) { return null; } if ((Object)(object)styleSource == (Object)null) { return new Material(targetFontMaterial) { name = name }; } try { Material fallbackMaterial = TMP_MaterialManager.GetFallbackMaterial(styleSource, targetFontMaterial); return new Material(fallbackMaterial) { name = name, shaderKeywords = fallbackMaterial.shaderKeywords }; } catch (Exception ex) { ManualLogSource? log = Plugin.Log; if (log != null) { log.LogWarning((object)("TMP material-preset matching failed; using the conservative style copier: " + ex.Message)); } return CreateStyledAtlasMaterial(targetFontMaterial, styleSource, name); } } private static Material? CreateStyledAtlasMaterial(Material? atlasMaterial, Material? styleSource, string name) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Expected O, but got Unknown if ((Object)(object)atlasMaterial == (Object)null) { return null; } Material val = new Material(atlasMaterial) { name = name }; if ((Object)(object)styleSource != (Object)null) { CopyColor(styleSource, val, "_FaceColor"); CopyFloat(styleSource, val, "_FaceDilate"); CopyColor(styleSource, val, "_OutlineColor"); CopyFloat(styleSource, val, "_OutlineWidth"); CopyFloat(styleSource, val, "_OutlineSoftness"); CopyColor(styleSource, val, "_UnderlayColor"); CopyFloat(styleSource, val, "_UnderlayOffsetX"); CopyFloat(styleSource, val, "_UnderlayOffsetY"); CopyFloat(styleSource, val, "_UnderlayDilate"); CopyFloat(styleSource, val, "_UnderlaySoftness"); CopyFloat(styleSource, val, "_StencilComp"); CopyFloat(styleSource, val, "_Stencil"); CopyFloat(styleSource, val, "_StencilOp"); CopyFloat(styleSource, val, "_StencilWriteMask"); CopyFloat(styleSource, val, "_StencilReadMask"); CopyFloat(styleSource, val, "_ColorMask"); CopyFloat(styleSource, val, "_MaskSoftnessX"); CopyFloat(styleSource, val, "_MaskSoftnessY"); CopyKeyword(styleSource, val, "OUTLINE_ON"); CopyKeyword(styleSource, val, "UNDERLAY_ON"); CopyKeyword(styleSource, val, "UNDERLAY_INNER"); CopyKeyword(styleSource, val, "GLOW_ON"); } return val; } private static void CopyColor(Material source, Material target, string propertyName) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) int num = Shader.PropertyToID(propertyName); if (source.HasProperty(num) && target.HasProperty(num)) { target.SetColor(num, source.GetColor(num)); } } private static void CopyFloat(Material source, Material target, string propertyName) { int num = Shader.PropertyToID(propertyName); if (source.HasProperty(num) && target.HasProperty(num)) { target.SetFloat(num, source.GetFloat(num)); } } private static void CopyKeyword(Material source, Material target, string keyword) { if (source.IsKeywordEnabled(keyword)) { target.EnableKeyword(keyword); } else { target.DisableKeyword(keyword); } } private static void RemoveCaseStyle(TMP_Text text) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) text.fontStyle = (FontStyles)(text.fontStyle & -57); TMP_Style textStyle = text.textStyle; if (textStyle != null && (ContainsCaseTag(textStyle.styleOpeningDefinition) || ContainsCaseTag(textStyle.styleClosingDefinition))) { text.textStyle = TMP_Style.NormalStyle; } } private static bool HasCaseStyle(TMP_Text text) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) if ((text.fontStyle & 0x38) != 0) { return true; } TMP_Style textStyle = text.textStyle; if (textStyle != null) { if (!ContainsCaseTag(textStyle.styleOpeningDefinition)) { return ContainsCaseTag(textStyle.styleClosingDefinition); } return true; } return false; } private static bool ContainsCaseTag(string? definition) { if (string.IsNullOrWhiteSpace(definition)) { return false; } if (definition.IndexOf("<uppercase>", StringComparison.OrdinalIgnoreCase) < 0 && definition.IndexOf("<allcaps>", StringComparison.OrdinalIgnoreCase) < 0 && definition.IndexOf("<lowercase>", StringComparison.OrdinalIgnoreCase) < 0) { return definition.IndexOf("<smallcaps>", StringComparison.OrdinalIgnoreCase) >= 0; } return true; } } internal static class RuntimeLowercaseFont { private const string FontFileName = "MixedCaseNorseLowercase-Regular.ttf"; private const string CustomLowercaseCharacters = "abcdefghijklmnopqrstuvwxyzäöüñç"; private static TMP_FontAsset? _font; private static bool _attempted; private static bool _errorLogged; public static TMP_FontAsset? Get() { if ((Object)(object)_font != (Object)null) { return _font; } if (_attempted) { return null; } _attempted = true; try { string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); if (string.IsNullOrWhiteSpace(directoryName)) { LogError("The plugin directory could not be determined."); return null; } string text = Path.Combine(directoryName, "MixedCaseNorseLowercase-Regular.ttf"); if (!File.Exists(text)) { LogError("Custom lowercase TTF was not found: " + text); return null; } TMP_FontAsset val = TMP_FontAsset.CreateFontAsset(text, 0, 90, 18, (GlyphRenderMode)4165, 512, 512); if ((Object)(object)val == (Object)null) { LogError("TextMeshPro returned null while creating the lowercase font from the TTF."); return null; } ((Object)val).name = "MixedCaseNorseLowercase (Runtime)"; string text2 = default(string); if (!val.TryAddCharacters("abcdefghijklmnopqrstuvwxyzäöüñç", ref text2, false)) { LogError("TextMeshPro could not add all custom lowercase characters. Missing: " + text2); return null; } val.atlasPopulationMode = (AtlasPopulationMode)0; val.ReadFontAssetDefinition(); Object.DontDestroyOnLoad((Object)(object)val); if ((Object)(object)((TMP_Asset)val).material != (Object)null) { Object.DontDestroyOnLoad((Object)(object)((TMP_Asset)val).material); } Texture2D[] atlasTextures = val.atlasTextures; if (atlasTextures != null) { for (int i = 0; i < atlasTextures.Length; i++) { if ((Object)(object)atlasTextures[i] != (Object)null) { Object.DontDestroyOnLoad((Object)(object)atlasTextures[i]); } } } _font = val; ManualLogSource? log = Plugin.Log; if (log != null) { log.LogDebug((object)"Created the lowercase TextMeshPro font directly from MixedCaseNorseLowercase-Regular.ttf at runtime."); } return _font; } catch (Exception ex) { LogError("Creating the lowercase runtime font failed: " + ex); return null; } } public static void Dispose() { _font = null; _attempted = false; _errorLogged = false; } private static void LogError(string message) { if (!_errorLogged) { ManualLogSource? log = Plugin.Log; if (log != null) { log.LogError((object)message); } _errorLogged = true; } } } }