using System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using BepInEx; using TMPro; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.TextCore; using UnityEngine.TextCore.LowLevel; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: AssemblyVersion("0.0.0.0")] namespace GWYF.KoreanTranslation; [BepInPlugin("codex.gwyf.koreantmpfontloader", "GWYF Korean TMP Font Loader", "0.4.2")] public sealed class GWYFKoreanTmpFontLoader : BaseUnityPlugin { private struct OriginalTextStyle { public TMP_FontAsset Font; public Material Material; public float LineSpacing; public float ParagraphSpacing; } private const string KoreanWarmupCharacters = "가각간갇갈감갑값갓강갖같갚갛개객거건걸검것게겠겨격결경계고곡곤골공과관광괴교구국군굴권그극근글금급기길김나난날남내냐너네노누느는니다더도돼되두드든들듯라락람랑래러려력로료루를리마만말맛매머메명모못무문물미바박반발방배버번벌범베보복본볼부분불브비사상새서선설성세소속손수순스습시식신실심아안않알앞애야약양어언얼엄없에여역연열염영예오온올와완왜외요용우운울워원위유육으은을음의이인일입있자작잔잘장재저적전절점정제조좋주준중즈지직진질집짜쪽차찬참찾처천체초추출치카커코크키타터토트파판패퍼페포표프피하한할함해했행향허현형호화확환회후흐희히"; private const string GeneratedAtlasFileName = "korean_tmp_atlas.png"; private const string GeneratedMetricsFileName = "korean_tmp_metrics.tsv"; private static TMP_FontAsset koreanFont; private static bool usingGeneratedAtlasFont; private static readonly Dictionary originalTextStyles = new Dictionary(); private static readonly List loadedBundles = new List(); private static readonly string[] FontBundleFileNames = new string[6] { "arialuni_sdf_u2019", "arialuni_sdf_u2018", "arialuni_sdf-u55to2017", "arialuni_sdf_u2022", "arialuni_sdf_u2021", "arialuni_sdf_u6000" }; private static readonly string[] RuntimeFontFileNames = new string[5] { "Maplestory Bold.ttf", "NotoSansKR-VF.ttf", "malgun.ttf", "malgunbd.ttf", "NGULIM.TTF" }; private void Awake() { LoadFontBundle(); SceneManager.sceneLoaded += OnSceneLoaded; ((MonoBehaviour)this).StartCoroutine(PatchRepeatedly("startup")); } private void OnDestroy() { SceneManager.sceneLoaded -= OnSceneLoaded; } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { ((MonoBehaviour)this).StartCoroutine(PatchRepeatedly("scene:" + ((Scene)(ref scene)).name)); } private IEnumerator PatchRepeatedly(string reason) { yield return null; PatchAllTmpTexts(reason + ":frame1"); yield return (object)new WaitForSecondsRealtime(0.5f); PatchAllTmpTexts(reason + ":delay0.5"); yield return (object)new WaitForSecondsRealtime(1.5f); PatchAllTmpTexts(reason + ":delay2.0"); } private void LoadFontBundle() { if ((Object)(object)koreanFont != (Object)null || TryLoadGeneratedAtlasFont() || TryLoadRuntimeTtfFont()) { return; } TMP_FontAsset val = null; string[] fontBundleFileNames = FontBundleFileNames; foreach (string text in fontBundleFileNames) { string text2 = Path.Combine(Paths.ConfigPath, text); if (!File.Exists(text2)) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("Korean TMP font bundle not found, skipping: " + text2)); continue; } AssetBundle val2 = AssetBundle.LoadFromFile(text2); if ((Object)(object)val2 == (Object)null) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Korean TMP font bundle could not be loaded: " + text2)); continue; } loadedBundles.Add(val2); Texture2D[] textures = val2.LoadAllAssets(); Material[] materials = val2.LoadAllAssets(); TMP_FontAsset[] array = val2.LoadAllAssets(); if (array == null || array.Length == 0) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("No TMP_FontAsset found in Korean font bundle: " + text2)); continue; } ((BaseUnityPlugin)this).Logger.LogInfo((object)("Scanning TMP font bundle " + text + " (" + array.Length + " font asset(s)).")); TMP_FontAsset[] array2 = array; foreach (TMP_FontAsset val3 in array2) { if (!((Object)(object)val3 == (Object)null)) { if ((Object)(object)val == (Object)null) { val = val3; } PrepareFontAsset(val3, textures, materials); bool flag = SupportsKorean(val3); ((BaseUnityPlugin)this).Logger.LogInfo((object)("TMP font candidate: " + ((Object)val3).name + " | KoreanGlyphs=" + flag + " | Atlas=" + GetObjectName((Object)(object)val3.atlasTexture) + " | Material=" + GetObjectName((Object)(object)((TMP_Asset)val3).material))); if (flag) { SelectKoreanFont(val3, text2); return; } } } } if ((Object)(object)val != (Object)null) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("No TMP font candidate reported Hangul glyph support. Falling back to first loaded candidate: " + ((Object)val).name)); SelectKoreanFont(val, "fallback candidate"); } else { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Could not find any Korean TMP font bundle in BepInEx/config."); } } private bool TryLoadGeneratedAtlasFont() { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Expected O, but got Unknown //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Expected O, but got Unknown //IL_01cc: Unknown result type (might be due to invalid IL or missing references) //IL_02c7: Unknown result type (might be due to invalid IL or missing references) //IL_02d4: Unknown result type (might be due to invalid IL or missing references) //IL_02df: Unknown result type (might be due to invalid IL or missing references) //IL_02e6: Expected O, but got Unknown //IL_02f7: Unknown result type (might be due to invalid IL or missing references) //IL_0301: Expected O, but got Unknown string text = FindGeneratedFile("korean_tmp_atlas.png"); string text2 = FindGeneratedFile("korean_tmp_metrics.tsv"); if (string.IsNullOrEmpty(text) || string.IsNullOrEmpty(text2)) { ((BaseUnityPlugin)this).Logger.LogInfo((object)"Generated Korean TMP atlas files not found; skipping bitmap atlas path."); return false; } try { Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false); if (!LoadImage(val, File.ReadAllBytes(text))) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Generated Korean TMP atlas could not be loaded: " + text)); return false; } ((Object)val).name = "GWYF Korean TMP Atlas"; ((Texture)val).wrapMode = (TextureWrapMode)1; ((Texture)val).filterMode = (FilterMode)1; Shader val2 = Shader.Find("TextMeshPro/Bitmap") ?? Shader.Find("TextMeshPro/Distance Field") ?? Shader.Find("UI/Default"); if ((Object)(object)val2 == (Object)null) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Could not find a shader for generated Korean TMP atlas."); return false; } Material val3 = new Material(val2); ((Object)val3).name = "GWYF Korean TMP Atlas Material"; Material val4 = val3; val4.mainTexture = (Texture)(object)val; if (val4.HasProperty("_MainTex")) { val4.SetTexture("_MainTex", (Texture)(object)val); } TMP_FontAsset val5 = ScriptableObject.CreateInstance(); ((Object)val5).name = "GWYF Korean Generated TMP Font"; val5.atlasPopulationMode = (AtlasPopulationMode)0; SetPrivateField(val5, "m_AtlasRenderMode", (object)(GlyphRenderMode)4118); SetPrivateField(val5, "m_AtlasWidth", ((Texture)val).width); SetPrivateField(val5, "m_AtlasHeight", ((Texture)val).height); SetPrivateField(val5, "m_AtlasPadding", 0); SetPrivateField(val5, "m_AtlasTexture", val); SetPrivateField(val5, "atlas", val); val5.atlasTextures = (Texture2D[])(object)new Texture2D[1] { val }; val5.isMultiAtlasTexturesEnabled = false; ((TMP_Asset)val5).material = val4; ((TMP_Asset)val5).materialHashCode = ((object)val4).GetHashCode(); ((TMP_Asset)val5).faceInfo = CreateGeneratedFaceInfo(); List list = new List(); List list2 = new List(); string[] array = File.ReadAllLines(text2); foreach (string text3 in array) { if (!string.IsNullOrWhiteSpace(text3) && text3[0] != '#') { string[] array2 = text3.Split(new char[1] { '\t' }); if (array2.Length >= 8) { uint num = uint.Parse(array2[0], CultureInfo.InvariantCulture); int num2 = int.Parse(array2[1], CultureInfo.InvariantCulture); int num3 = int.Parse(array2[2], CultureInfo.InvariantCulture); int num4 = int.Parse(array2[3], CultureInfo.InvariantCulture); int num5 = int.Parse(array2[4], CultureInfo.InvariantCulture); float num6 = float.Parse(array2[5], CultureInfo.InvariantCulture); float num7 = float.Parse(array2[6], CultureInfo.InvariantCulture); float num8 = float.Parse(array2[7], CultureInfo.InvariantCulture); Glyph val6 = new Glyph(num, new GlyphMetrics((float)num4, (float)num5, num6, num7, num8), new GlyphRect(num2, num3, num4, num5), 1f, 0); list.Add(val6); list2.Add(new TMP_Character(num, val5, val6)); } } } SetPrivateField(val5, "m_GlyphTable", list); SetPrivateField(val5, "m_CharacterTable", list2); val5.ReadFontAssetDefinition(); bool flag = SupportsKorean(val5) && !IsMissingImportantKorean(val5); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Generated Korean TMP atlas candidate: glyphs=" + list.Count + " | KoreanGlyphs=" + flag + " | Atlas=" + text)); if (!flag) { return false; } usingGeneratedAtlasFont = true; SelectKoreanFont(val5, text); return true; } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Generated Korean TMP atlas path failed: " + ex.GetType().Name + ": " + ex.Message)); return false; } } private static bool LoadImage(Texture2D texture, byte[] bytes) { Type type = Type.GetType("UnityEngine.ImageConversion, UnityEngine.ImageConversionModule"); if (type == null) { return false; } MethodInfo[] methods = type.GetMethods(BindingFlags.Static | BindingFlags.Public); foreach (MethodInfo methodInfo in methods) { if (!(methodInfo.Name != "LoadImage")) { ParameterInfo[] parameters = methodInfo.GetParameters(); if (parameters.Length == 2 && parameters[0].ParameterType == typeof(Texture2D) && parameters[1].ParameterType == typeof(byte[])) { return (bool)methodInfo.Invoke(null, new object[2] { texture, bytes }); } } } return false; } private static void SetPrivateField(object instance, string fieldName, object value) { FieldInfo field = instance.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { field.SetValue(instance, value); } } private static FaceInfo CreateGeneratedFaceInfo() { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) FaceInfo result = default(FaceInfo); ((FaceInfo)(ref result)).familyName = "Maplestory"; ((FaceInfo)(ref result)).styleName = "Bold"; ((FaceInfo)(ref result)).pointSize = 72f; ((FaceInfo)(ref result)).scale = 1f; ((FaceInfo)(ref result)).lineHeight = 132f; ((FaceInfo)(ref result)).ascentLine = 86f; ((FaceInfo)(ref result)).capLine = 58f; ((FaceInfo)(ref result)).meanLine = 42f; ((FaceInfo)(ref result)).baseline = 0f; ((FaceInfo)(ref result)).descentLine = -40f; ((FaceInfo)(ref result)).underlineOffset = -8f; ((FaceInfo)(ref result)).underlineThickness = 4f; ((FaceInfo)(ref result)).strikethroughOffset = 22f; ((FaceInfo)(ref result)).strikethroughThickness = 4f; ((FaceInfo)(ref result)).tabWidth = 72f; return result; } private static string FindGeneratedFile(string fileName) { string text = Path.Combine(Paths.ConfigPath, fileName); if (File.Exists(text)) { return text; } return FindFileUnder(Paths.PluginPath, fileName); } private bool TryLoadRuntimeTtfFont() { string text = default(string); foreach (string item in FindRuntimeFontPaths()) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("Trying runtime Korean TTF font: " + item)); TMP_FontAsset val = null; try { val = TMP_FontAsset.CreateFontAsset(item, 72, 8, 0, (GlyphRenderMode)4165, 4096, 4096); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("TMP rejected runtime Korean TTF path: " + item + " | " + ex.GetType().Name + ": " + ex.Message)); } if (!((Object)(object)val == (Object)null)) { ((Object)val).name = Path.GetFileNameWithoutExtension(item) + " Korean Runtime SDF"; val.atlasPopulationMode = (AtlasPopulationMode)1; val.isMultiAtlasTexturesEnabled = true; val.TryAddCharacters("가각간갇갈감갑값갓강갖같갚갛개객거건걸검것게겠겨격결경계고곡곤골공과관광괴교구국군굴권그극근글금급기길김나난날남내냐너네노누느는니다더도돼되두드든들듯라락람랑래러려력로료루를리마만말맛매머메명모못무문물미바박반발방배버번벌범베보복본볼부분불브비사상새서선설성세소속손수순스습시식신실심아안않알앞애야약양어언얼엄없에여역연열염영예오온올와완왜외요용우운울워원위유육으은을음의이인일입있자작잔잘장재저적전절점정제조좋주준중즈지직진질집짜쪽차찬참찾처천체초추출치카커코크키타터토트파판패퍼페포표프피하한할함해했행향허현형호화확환회후흐희히", ref text, true); PrepareFontAsset(val, (Texture2D[])(object)new Texture2D[1] { val.atlasTexture }, (Material[])(object)new Material[1] { ((TMP_Asset)val).material }); bool flag = SupportsKorean(val) && !IsMissingImportantKorean(val); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Runtime TMP font candidate: " + ((Object)val).name + " | KoreanGlyphs=" + flag + " | MissingWarmup=" + (string.IsNullOrEmpty(text) ? "" : text))); if (flag) { SelectKoreanFont(val, item); return true; } } } return false; } private static IEnumerable FindRuntimeFontPaths() { try { string[] runtimeFontFileNames = RuntimeFontFileNames; foreach (string fileName in runtimeFontFileNames) { string pluginPath = FindFileUnder(Paths.PluginPath, fileName); if (!string.IsNullOrEmpty(pluginPath)) { yield return pluginPath; } string configPath = FindFileUnder(Paths.ConfigPath, fileName); if (!string.IsNullOrEmpty(configPath)) { yield return configPath; } string windowsFontPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Fonts), fileName); if (File.Exists(windowsFontPath)) { yield return windowsFontPath; } } } finally { } } private static string FindFileUnder(string root, string fileName) { if (string.IsNullOrEmpty(root) || !Directory.Exists(root)) { return null; } try { string[] files = Directory.GetFiles(root, fileName, SearchOption.AllDirectories); int num = 0; if (num < files.Length) { return files[num]; } } catch { return null; } return null; } private void PrepareFontAsset(TMP_FontAsset font, Texture2D[] textures, Material[] materials) { //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Expected O, but got Unknown Texture2D val = FindBestAtlasTexture(font, textures); if ((Object)(object)val != (Object)null) { font.atlasTextures = (Texture2D[])(object)new Texture2D[1] { val }; font.isMultiAtlasTexturesEnabled = true; } Material val2 = FindBestMaterial(font, materials); if ((Object)(object)val2 == (Object)null) { Shader val3 = Shader.Find("TextMeshPro/Distance Field") ?? Shader.Find("TextMeshPro/Mobile/Distance Field"); if ((Object)(object)val3 != (Object)null) { Material val4 = new Material(val3); ((Object)val4).name = ((Object)font).name + " Korean Material"; val2 = val4; } } if (!((Object)(object)val2 != (Object)null)) { return; } if ((Object)(object)val != (Object)null) { val2.mainTexture = (Texture)(object)val; if (val2.HasProperty("_MainTex")) { val2.SetTexture("_MainTex", (Texture)(object)val); } } ((TMP_Asset)font).material = val2; ((TMP_Asset)font).materialHashCode = ((object)val2).GetHashCode(); } private static Texture2D FindBestAtlasTexture(TMP_FontAsset font, Texture2D[] textures) { if (textures == null || textures.Length == 0) { return font.atlasTexture; } foreach (Texture2D val in textures) { if ((Object)(object)val != (Object)null && ((Object)val).name.IndexOf(((Object)font).name, StringComparison.OrdinalIgnoreCase) >= 0) { return val; } } foreach (Texture2D val2 in textures) { if ((Object)(object)val2 != (Object)null && ((Object)val2).name.IndexOf("Atlas", StringComparison.OrdinalIgnoreCase) >= 0) { return val2; } } return textures[0]; } private static Material FindBestMaterial(TMP_FontAsset font, Material[] materials) { if (materials == null || materials.Length == 0) { return ((TMP_Asset)font).material; } foreach (Material val in materials) { if ((Object)(object)val != (Object)null && ((Object)val).name.IndexOf(((Object)font).name, StringComparison.OrdinalIgnoreCase) >= 0) { return val; } } return materials[0]; } private void SelectKoreanFont(TMP_FontAsset font, string source) { koreanFont = font; TMP_Settings.defaultFontAsset = koreanFont; if (TMP_Settings.fallbackFontAssets != null && !TMP_Settings.fallbackFontAssets.Contains(koreanFont)) { TMP_Settings.fallbackFontAssets.Add(koreanFont); } ((BaseUnityPlugin)this).Logger.LogInfo((object)("Selected Korean TMP font: " + ((Object)koreanFont).name + " from " + source)); } private static string GetObjectName(Object obj) { if (!(obj == (Object)null)) { return obj.name; } return ""; } private static bool SupportsKorean(TMP_FontAsset font) { if (!font.HasCharacter('한', false, false) && !font.HasCharacter('가', false, false)) { return font.HasCharacter('힣', false, false); } return true; } private static bool IsMissingImportantKorean(TMP_FontAsset font) { if (font.HasCharacter('한', false, false) && font.HasCharacter('가', false, false)) { return !font.HasCharacter('운', false, false); } return true; } private void PatchAllTmpTexts(string reason) { if ((Object)(object)koreanFont == (Object)null) { LoadFontBundle(); } if ((Object)(object)koreanFont == (Object)null) { return; } int num = 0; int num2 = 0; TMP_Text[] array = Resources.FindObjectsOfTypeAll(); TMP_Text[] array2 = array; foreach (TMP_Text val in array2) { if ((Object)(object)val == (Object)null || IsEditorOrAssetObject((Object)(object)val)) { continue; } RememberOriginalStyle(val); if (!ContainsKorean(val.text)) { if (RestoreOriginalStyle(val)) { num2++; } continue; } if ((Object)(object)val.font != (Object)(object)koreanFont) { val.font = koreanFont; num++; } if ((Object)(object)((TMP_Asset)koreanFont).material != (Object)null) { val.fontSharedMaterial = ((TMP_Asset)koreanFont).material; } if (usingGeneratedAtlasFont) { val.lineSpacing = Mathf.Max(val.lineSpacing, 24f); val.paragraphSpacing = Mathf.Max(val.paragraphSpacing, 6f); } val.UpdateMeshPadding(); val.ForceMeshUpdate(false, false); } ((BaseUnityPlugin)this).Logger.LogInfo((object)("Korean TMP font patch complete (" + reason + "): patched=" + num + ", restored=" + num2 + " text object(s).")); } private static void RememberOriginalStyle(TMP_Text text) { int instanceID = ((Object)text).GetInstanceID(); if (!originalTextStyles.ContainsKey(instanceID) && !((Object)(object)text.font == (Object)(object)koreanFont)) { originalTextStyles[instanceID] = new OriginalTextStyle { Font = text.font, Material = text.fontSharedMaterial, LineSpacing = text.lineSpacing, ParagraphSpacing = text.paragraphSpacing }; } } private static bool RestoreOriginalStyle(TMP_Text text) { if (!originalTextStyles.TryGetValue(((Object)text).GetInstanceID(), out var value)) { return false; } if ((Object)(object)text.font == (Object)(object)value.Font && (Object)(object)text.fontSharedMaterial == (Object)(object)value.Material) { return false; } text.font = value.Font; text.fontSharedMaterial = value.Material; text.lineSpacing = value.LineSpacing; text.paragraphSpacing = value.ParagraphSpacing; text.UpdateMeshPadding(); text.ForceMeshUpdate(false, false); return true; } private static bool ContainsKorean(string value) { if (string.IsNullOrEmpty(value)) { return false; } foreach (char c in value) { if (c >= '가' && c <= '힣') { return true; } } return false; } private static bool IsEditorOrAssetObject(Object obj) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Invalid comparison between Unknown and I4 if ((int)obj.hideFlags != 8) { return (int)obj.hideFlags == 61; } return true; } }