using System; using System.Collections; 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 System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Configuration; using Microsoft.CodeAnalysis; using TMPro; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("GWYFTmpPatcher")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+a9d3046b810a7bd0dd1013e2f8e0683442b198ae")] [assembly: AssemblyProduct("GWYFTmpPatcher")] [assembly: AssemblyTitle("GWYFTmpPatcher")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.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] [Microsoft.CodeAnalysis.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] [Microsoft.CodeAnalysis.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 GWYFTmpPatcher { [BepInPlugin("codex.gwyf.tmppatcher", "GWYF TMP Patcher", "0.1.15")] public sealed class TmpPatcherPlugin : BaseUnityPlugin { private sealed class TextureReplacement { public string Name { get; set; } = string.Empty; public Texture2D Texture { get; set; } public HashSet PatchedTextures { get; } = new HashSet(); public Dictionary SpriteCache { get; } = new Dictionary(); } private sealed class RegexRule { public Regex Regex { get; } public string Replacement { get; } public RegexRule(Regex regex, string replacement) { Regex = regex; Replacement = replacement; } } [CompilerGenerated] private sealed class d__40 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public float delaySeconds; public TmpPatcherPlugin <>4__this; public string reason; private int 5__2; private int 5__3; private int 5__4; private int 5__5; private int 5__6; private int 5__7; private Texture2D[] <>7__wrap7; private int <>7__wrap8; private Image[] <>7__wrap9; private SpriteRenderer[] <>7__wrap10; private Renderer[] <>7__wrap11; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__40(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>7__wrap7 = null; <>7__wrap9 = null; <>7__wrap10 = null; <>7__wrap11 = null; <>1__state = -2; } private bool MoveNext() { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Expected O, but got Unknown int num = <>1__state; TmpPatcherPlugin tmpPatcherPlugin = <>4__this; switch (num) { default: return false; case 0: <>1__state = -1; if (delaySeconds > 0f) { <>2__current = (object)new WaitForSecondsRealtime(delaySeconds); <>1__state = 1; return true; } goto IL_0063; case 1: <>1__state = -1; goto IL_0063; case 2: <>1__state = -1; goto IL_0122; case 3: <>1__state = -1; goto IL_01da; case 4: <>1__state = -1; goto IL_0292; case 5: { <>1__state = -1; goto IL_0333; } IL_0341: if (<>7__wrap8 < <>7__wrap11.Length) { Renderer val = <>7__wrap11[<>7__wrap8]; if ((Object)(object)val != (Object)null) { 5__5 += tmpPatcherPlugin.PatchRendererMaterials(val); } if (++5__6 >= 5__7) { 5__6 = 0; <>2__current = null; <>1__state = 5; return true; } goto IL_0333; } <>7__wrap11 = null; if (tmpPatcherPlugin._logChanges.Value && (5__2 > 0 || 5__3 > 0 || 5__4 > 0 || 5__5 > 0)) { ((BaseUnityPlugin)tmpPatcherPlugin).Logger.LogInfo((object)$"Texture patch complete ({reason}). Textures={5__2}, images={5__3}, spriteRenderers={5__4}, materials={5__5}."); } return false; IL_0063: if (!tmpPatcherPlugin.LoadEmbeddedTextureReplacements()) { return false; } 5__2 = 0; 5__3 = 0; 5__4 = 0; 5__5 = 0; 5__6 = 0; 5__7 = Mathf.Max(1, tmpPatcherPlugin._textureBatchSize.Value); <>7__wrap7 = Resources.FindObjectsOfTypeAll(); <>7__wrap8 = 0; goto IL_0130; IL_0292: <>7__wrap8++; goto IL_02a0; IL_01da: <>7__wrap8++; goto IL_01e8; IL_0130: if (<>7__wrap8 < <>7__wrap7.Length) { Texture2D val2 = <>7__wrap7[<>7__wrap8]; if ((Object)(object)val2 != (Object)null && tmpPatcherPlugin.PatchLoadedTextureObject(val2)) { 5__2++; } if (++5__6 >= 5__7) { 5__6 = 0; <>2__current = null; <>1__state = 2; return true; } goto IL_0122; } <>7__wrap7 = null; <>7__wrap9 = Resources.FindObjectsOfTypeAll(); <>7__wrap8 = 0; goto IL_01e8; IL_01e8: if (<>7__wrap8 < <>7__wrap9.Length) { Image val3 = <>7__wrap9[<>7__wrap8]; if ((Object)(object)val3 != (Object)null && (Object)(object)val3.sprite != (Object)null && tmpPatcherPlugin.PatchImage(val3)) { 5__3++; } if (++5__6 >= 5__7) { 5__6 = 0; <>2__current = null; <>1__state = 3; return true; } goto IL_01da; } <>7__wrap9 = null; <>7__wrap10 = Resources.FindObjectsOfTypeAll(); <>7__wrap8 = 0; goto IL_02a0; IL_0122: <>7__wrap8++; goto IL_0130; IL_02a0: if (<>7__wrap8 < <>7__wrap10.Length) { SpriteRenderer val4 = <>7__wrap10[<>7__wrap8]; if ((Object)(object)val4 != (Object)null && (Object)(object)val4.sprite != (Object)null && tmpPatcherPlugin.PatchSpriteRenderer(val4)) { 5__4++; } if (++5__6 >= 5__7) { 5__6 = 0; <>2__current = null; <>1__state = 4; return true; } goto IL_0292; } <>7__wrap10 = null; <>7__wrap11 = Resources.FindObjectsOfTypeAll(); <>7__wrap8 = 0; goto IL_0341; IL_0333: <>7__wrap8++; goto IL_0341; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class d__38 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public float delaySeconds; public TmpPatcherPlugin <>4__this; public string reason; private TMP_Text[] 5__2; private HashSet 5__3; private int 5__4; private int 5__5; private int 5__6; private int 5__7; private TMP_FontAsset[] <>7__wrap7; private int <>7__wrap8; private TMP_Text[] <>7__wrap9; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__38(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { 5__2 = null; 5__3 = null; <>7__wrap7 = null; <>7__wrap9 = null; <>1__state = -2; } private bool MoveNext() { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Expected O, but got Unknown int num = <>1__state; TmpPatcherPlugin tmpPatcherPlugin = <>4__this; switch (num) { default: return false; case 0: <>1__state = -1; if (delaySeconds > 0f) { <>2__current = (object)new WaitForSecondsRealtime(delaySeconds); <>1__state = 1; return true; } goto IL_005b; case 1: <>1__state = -1; goto IL_005b; case 2: <>1__state = -1; goto IL_0118; case 3: { <>1__state = -1; goto IL_022d; } IL_022d: <>7__wrap8++; goto IL_023b; IL_005b: 5__2 = Resources.FindObjectsOfTypeAll(); 5__3 = new HashSet(); 5__4 = 0; 5__5 = 0; 5__6 = 0; 5__7 = Mathf.Max(1, tmpPatcherPlugin._fontBatchSize.Value); <>7__wrap7 = Resources.FindObjectsOfTypeAll(); <>7__wrap8 = 0; goto IL_0126; IL_0126: if (<>7__wrap8 < <>7__wrap7.Length) { TMP_FontAsset val = <>7__wrap7[<>7__wrap8]; if ((Object)(object)val != (Object)null && tmpPatcherPlugin.PatchChineseFontAsset(val)) { 5__4++; } if (++5__6 >= 5__7) { 5__6 = 0; <>2__current = null; <>1__state = 2; return true; } goto IL_0118; } <>7__wrap7 = null; <>7__wrap9 = 5__2; <>7__wrap8 = 0; goto IL_023b; IL_0118: <>7__wrap8++; goto IL_0126; IL_023b: if (<>7__wrap8 < <>7__wrap9.Length) { TMP_Text val2 = <>7__wrap9[<>7__wrap8]; if (!((Object)(object)val2 == (Object)null) && !IsEditorOrAssetObject((Component)(object)val2) && 5__3.Add(((Object)val2).GetInstanceID())) { tmpPatcherPlugin.PatchTmpText(val2, reason, allowTranslate: false, allowFont: true, out var fontChanged, out var textTranslated); 5__4 += (fontChanged ? 1 : 0); 5__5 += (textTranslated ? 1 : 0); if (textTranslated && tmpPatcherPlugin._forceMeshUpdate.Value) { ((Graphic)val2).SetAllDirty(); val2.ForceMeshUpdate(true, true); } 5__6++; if (5__6 >= 5__7) { 5__6 = 0; <>2__current = null; <>1__state = 3; return true; } } goto IL_022d; } <>7__wrap9 = null; tmpPatcherPlugin.LogPatchSummary(reason, 5__4, 5__5); return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private ConfigEntry _enabled; private ConfigEntry _patchOnlyUi; private ConfigEntry _translateTmpText; private ConfigEntry _forceMeshUpdate; private ConfigEntry _logChanges; private ConfigEntry _fontBatchSize; private ConfigEntry _replaceFontWithExternalFile; private ConfigEntry _externalFontPath; private ConfigEntry _assetBundleFontAssetName; private ConfigEntry _replaceBuiltInTextures; private ConfigEntry _textureBatchSize; private ConfigEntry _translationTextPath; private const string TargetTmpFontName = "ChineseFont"; private const string LogoTextureName = "GWYF_LOGO_1280x720"; private const string MainTexPropertyName = "_MainTex"; private const string ReplacerTargetPath = "/LocalManager/UI/InteractionUIPanel/TextPanel/ItemNameText"; private const string EmbeddedFontResourceName = "GWYFTmpPatcher.Assets.notosanstc-medium-sdf.bundle"; private const string EmbeddedLogoResourceName = "GWYFTmpPatcher.Assets.GWYFTW_LOGO.png"; private static readonly string[] TmpFontAssetCopyFields = new string[24] { "m_Version", "m_FaceInfo", "m_GlyphTable", "m_CharacterTable", "m_AtlasTexture", "m_AtlasTextures", "m_AtlasTextureIndex", "m_UsedGlyphRects", "m_FreeGlyphRects", "m_GlyphIndexList", "m_GlyphIndexListNewlyAdded", "m_GlyphsToRender", "m_GlyphsRendered", "m_AtlasWidth", "m_AtlasHeight", "m_AtlasPadding", "m_AtlasRenderMode", "m_AtlasPopulationMode", "m_FontFeatureTable", "m_fontInfo", "m_glyphInfoList", "m_KerningTable", "m_kerningInfo", "fallbackFontAssets" }; private readonly HashSet _patchedFontAssetIds = new HashSet(); private readonly List _loadedFontBundles = new List(); private readonly Dictionary _textureReplacements = new Dictionary(StringComparer.OrdinalIgnoreCase); private readonly Dictionary _exactRules = new Dictionary(StringComparer.OrdinalIgnoreCase); private readonly Dictionary _postRules = new Dictionary(StringComparer.OrdinalIgnoreCase); private readonly List _regexRules = new List(); private readonly Dictionary _lastAppliedText = new Dictionary(); private readonly Dictionary _translationFileTimes = new Dictionary(StringComparer.OrdinalIgnoreCase); private string _translationTextDirectory; private TMP_FontAsset? _externalFontAsset; private bool _embeddedFontLoadAttempted; private bool _textureReplacementsLoaded; private static readonly MethodInfo? LoadImageMethod = Type.GetType("UnityEngine.ImageConversion, UnityEngine.ImageConversionModule")?.GetMethod("LoadImage", BindingFlags.Static | BindingFlags.Public, null, new Type[3] { typeof(Texture2D), typeof(byte[]), typeof(bool) }, null); private void Awake() { _enabled = ((BaseUnityPlugin)this).Config.Bind("General", "Enabled", true, "Enable TMP patching."); _patchOnlyUi = ((BaseUnityPlugin)this).Config.Bind("General", "PatchOnlyUi", true, "Only patch UI text under Canvas/TextMeshProUGUI."); _translateTmpText = ((BaseUnityPlugin)this).Config.Bind("Translation", "TranslateTmpText", true, "Apply XUnity translation files to TMP_Text objects missed by XUnity hooks."); _forceMeshUpdate = ((BaseUnityPlugin)this).Config.Bind("General", "ForceMeshUpdate", false, "Force TMP meshes to redraw after text replacement."); _logChanges = ((BaseUnityPlugin)this).Config.Bind("Diagnostics", "LogChanges", true, "Log applied font and translation changes."); _fontBatchSize = ((BaseUnityPlugin)this).Config.Bind("Performance", "FontBatchSize", 32, "How many TMP objects to font patch per frame during scene scans."); _replaceFontWithExternalFile = ((BaseUnityPlugin)this).Config.Bind("Font", "ReplaceFontWithExternalFile", true, "Patch only the game's ChineseFont TMP font asset."); _externalFontPath = ((BaseUnityPlugin)this).Config.Bind("Font", "ExternalFontPath", "notosanstc-medium sdf", "External TMP font AssetBundle path. Relative paths are searched from the game root and BepInEx/config."); _assetBundleFontAssetName = ((BaseUnityPlugin)this).Config.Bind("Font", "AssetBundleFontAssetName", "", "TMP_FontAsset name inside the AssetBundle. Leave blank to use the first TMP_FontAsset."); _replaceBuiltInTextures = ((BaseUnityPlugin)this).Config.Bind("Texture", "ReplaceBuiltInTextures", true, "Replace selected game textures from resources embedded in this patcher."); _textureBatchSize = ((BaseUnityPlugin)this).Config.Bind("Performance", "TextureBatchSize", 64, "How many texture related objects to inspect per frame during texture replacement scans."); _translationTextPath = ((BaseUnityPlugin)this).Config.Bind("Paths", "TranslationTextPath", "Translation\\zh-TW\\Text", "Translation text directory relative to BepInEx. Use config\\Translation\\zh-TW\\Text for Thunderstore profiles."); _translationTextDirectory = ResolveBepInExRelativePath(_translationTextPath.Value); ReloadTranslationsIfNeeded(force: true); TryLoadExternalFont(); LoadEmbeddedTextureReplacements(); SceneManager.sceneLoaded += OnSceneLoaded; TMPro_EventManager.TEXT_CHANGED_EVENT.Add((Action)OnTextChanged); QueueSceneFontScans("startup"); QueueTextureScans("startup"); } private void OnDestroy() { SceneManager.sceneLoaded -= OnSceneLoaded; TMPro_EventManager.TEXT_CHANGED_EVENT.Remove((Action)OnTextChanged); foreach (AssetBundle loadedFontBundle in _loadedFontBundles) { loadedFontBundle.Unload(false); } _loadedFontBundles.Clear(); foreach (TextureReplacement item in _textureReplacements.Values.Distinct()) { if ((Object)(object)item.Texture != (Object)null) { Object.Destroy((Object)(object)item.Texture); } } _textureReplacements.Clear(); } private void OnTextChanged(Object changedObject) { if (!_enabled.Value) { return; } TMP_Text val = (TMP_Text)(object)((changedObject is TMP_Text) ? changedObject : null); if (val != null && !((Object)(object)val == (Object)null) && !IsEditorOrAssetObject((Component)(object)val)) { ReloadTranslationsIfNeeded(force: false); PatchTmpText(val, "text-changed", allowTranslate: true, allowFont: true, out var _, out var textTranslated); if (textTranslated && _forceMeshUpdate.Value) { ((Graphic)val).SetAllDirty(); val.ForceMeshUpdate(true, true); } } } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { if (_enabled.Value) { ReloadTranslationsIfNeeded(force: false); QueueSceneFontScans("scene:" + ((Scene)(ref scene)).name); QueueTextureScans("scene:" + ((Scene)(ref scene)).name); } } private void QueueSceneFontScans(string reason) { if (_replaceFontWithExternalFile.Value) { ((MonoBehaviour)this).StartCoroutine(PatchSceneTmpObjectsBatched(reason, 0f)); ((MonoBehaviour)this).StartCoroutine(PatchSceneTmpObjectsBatched(reason + ":delayed1", 1f)); ((MonoBehaviour)this).StartCoroutine(PatchSceneTmpObjectsBatched(reason + ":delayed3", 3f)); } } [IteratorStateMachine(typeof(d__38))] private IEnumerator PatchSceneTmpObjectsBatched(string reason, float delaySeconds) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__38(0) { <>4__this = this, reason = reason, delaySeconds = delaySeconds }; } private void QueueTextureScans(string reason) { if (_replaceBuiltInTextures.Value) { ((MonoBehaviour)this).StartCoroutine(PatchSceneTexturesBatched(reason, 0f)); ((MonoBehaviour)this).StartCoroutine(PatchSceneTexturesBatched(reason + ":delayed1", 1f)); ((MonoBehaviour)this).StartCoroutine(PatchSceneTexturesBatched(reason + ":delayed3", 3f)); } } [IteratorStateMachine(typeof(d__40))] private IEnumerator PatchSceneTexturesBatched(string reason, float delaySeconds) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__40(0) { <>4__this = this, reason = reason, delaySeconds = delaySeconds }; } private bool PatchTmpText(TMP_Text text, string reason, bool allowTranslate, bool allowFont, out bool fontChanged, out bool textTranslated) { TMP_Text text2 = text; fontChanged = false; textTranslated = false; bool result = false; if (allowTranslate && _translateTmpText.Value && ShouldPatchTextComponent((Component)(object)text2) && IsReplacerTarget(text2) && TryTranslateText((Component)(object)text2, text2.text, delegate(string value) { text2.text = value; }, reason, "TMP")) { result = true; textTranslated = true; } if (allowFont && _replaceFontWithExternalFile.Value) { TryLoadExternalFont(); if (PatchChineseFontAsset(text2.font)) { result = true; fontChanged = true; } } return result; } private bool PatchChineseFontAsset(TMP_FontAsset? targetFont) { TMP_FontAsset externalFontAsset = _externalFontAsset; if (!_replaceFontWithExternalFile.Value || (Object)(object)externalFontAsset == (Object)null) { return false; } if ((Object)(object)targetFont == (Object)null || !IsTargetTmpFontAsset(targetFont) || targetFont == externalFontAsset) { return false; } int instanceID = ((Object)targetFont).GetInstanceID(); if (_patchedFontAssetIds.Contains(instanceID)) { return false; } PatchTmpFontAssetInPlace(targetFont, externalFontAsset); _patchedFontAssetIds.Add(instanceID); return true; } private void LogPatchSummary(string reason, int fontPatched, int translated) { if (_logChanges.Value && (fontPatched > 0 || translated > 0)) { ((BaseUnityPlugin)this).Logger.LogInfo((object)$"TMP patch complete ({reason}). Fonts={fontPatched}, translated={translated}."); } } private bool LoadEmbeddedTextureReplacements() { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Expected O, but got Unknown if (_textureReplacementsLoaded) { return _textureReplacements.Count > 0; } _textureReplacementsLoaded = true; if (!_replaceBuiltInTextures.Value) { return false; } byte[] array = ReadEmbeddedResourceBytes("GWYFTmpPatcher.Assets.GWYFTW_LOGO.png"); if (array == null || array.Length == 0) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Embedded logo PNG was not found in patcher resources."); return false; } Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false); if (!TryLoadPng(val, array)) { Object.Destroy((Object)(object)val); ((BaseUnityPlugin)this).Logger.LogWarning((object)"Embedded logo PNG could not be decoded."); return false; } ((Object)val).name = "GWYF_LOGO_1280x720"; ((Texture)val).wrapMode = (TextureWrapMode)1; ((Texture)val).filterMode = (FilterMode)1; TextureReplacement value = new TextureReplacement { Name = "GWYF_LOGO_1280x720", Texture = val }; _textureReplacements["GWYF_LOGO_1280x720"] = value; if (_logChanges.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)$"Loaded embedded texture replacements: {_textureReplacements.Count} aliases, logo={((Texture)val).width}x{((Texture)val).height}"); } return _textureReplacements.Count > 0; } private bool PatchLoadedTextureObject(Texture2D texture) { if (!TryGetTextureReplacement(((Object)texture).name, out TextureReplacement replacement)) { return false; } int instanceID = ((Object)texture).GetInstanceID(); if (replacement.PatchedTextures.Contains(instanceID) || texture == replacement.Texture || ((Texture)texture).width != ((Texture)replacement.Texture).width || ((Texture)texture).height != ((Texture)replacement.Texture).height) { return false; } try { Graphics.CopyTexture((Texture)(object)replacement.Texture, (Texture)(object)texture); replacement.PatchedTextures.Add(instanceID); return true; } catch (Exception ex) { replacement.PatchedTextures.Add(instanceID); ((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not copy replacement into Texture2D '" + ((Object)texture).name + "'; sprite/material replacement may still work. " + ex.Message)); return false; } } private bool PatchImage(Image image) { TextureReplacement textureReplacement = FindReplacementForSprite(image.sprite); if (textureReplacement == null) { return false; } Sprite replacementSprite = GetReplacementSprite(textureReplacement, image.sprite); if (image.sprite == replacementSprite) { return false; } image.sprite = replacementSprite; ((Graphic)image).SetAllDirty(); return true; } private bool PatchSpriteRenderer(SpriteRenderer renderer) { TextureReplacement textureReplacement = FindReplacementForSprite(renderer.sprite); if (textureReplacement == null) { return false; } Sprite replacementSprite = GetReplacementSprite(textureReplacement, renderer.sprite); if (renderer.sprite == replacementSprite) { return false; } renderer.sprite = replacementSprite; return true; } private int PatchRendererMaterials(Renderer renderer) { int num = 0; Material[] array = renderer.sharedMaterials ?? Array.Empty(); foreach (Material val in array) { if (!((Object)(object)val == (Object)null) && HasMainTextureProperty(val)) { Texture mainTexture = val.mainTexture; if (!((Object)(object)mainTexture == (Object)null) && TryGetTextureReplacement(((Object)mainTexture).name, out TextureReplacement replacement) && mainTexture != replacement.Texture) { val.mainTexture = (Texture)(object)replacement.Texture; num++; } } } return num; } private TextureReplacement? FindReplacementForSprite(Sprite sprite) { if (TryGetTextureReplacement(((Object)sprite).name, out TextureReplacement replacement)) { return replacement; } Texture2D texture = sprite.texture; if ((Object)(object)texture != (Object)null && TryGetTextureReplacement(((Object)texture).name, out replacement)) { return replacement; } return null; } private static bool HasMainTextureProperty(Material material) { return material.HasProperty("_MainTex"); } private bool TryGetTextureReplacement(string objectName, out TextureReplacement replacement) { if (_textureReplacements.TryGetValue(objectName, out replacement)) { return true; } string text = NormalizeUnityObjectName(objectName); if (!string.Equals(text, objectName, StringComparison.Ordinal) && _textureReplacements.TryGetValue(text, out replacement)) { return true; } replacement = null; return false; } private static string NormalizeUnityObjectName(string objectName) { string text = (objectName ?? string.Empty).Trim(); string[] array = new string[2] { "(Clone)", "(Instance)" }; foreach (string value in array) { int num = text.IndexOf(value, StringComparison.OrdinalIgnoreCase); if (num >= 0) { text = text.Substring(0, num).Trim(); } } return text; } private static Sprite GetReplacementSprite(TextureReplacement replacement, Sprite source) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) int instanceID = ((Object)source).GetInstanceID(); if (replacement.SpriteCache.TryGetValue(instanceID, out Sprite value)) { return value; } Rect rect = source.rect; if (((Rect)(ref rect)).xMax > (float)((Texture)replacement.Texture).width || ((Rect)(ref rect)).yMax > (float)((Texture)replacement.Texture).height) { ((Rect)(ref rect))..ctor(0f, 0f, (float)((Texture)replacement.Texture).width, (float)((Texture)replacement.Texture).height); } Vector2 val = default(Vector2); ((Vector2)(ref val))..ctor(0.5f, 0.5f); Rect rect2 = source.rect; if (((Rect)(ref rect2)).width > 0f) { rect2 = source.rect; if (((Rect)(ref rect2)).height > 0f) { float x = source.pivot.x; rect2 = source.rect; float num = x / ((Rect)(ref rect2)).width; float y = source.pivot.y; rect2 = source.rect; ((Vector2)(ref val))..ctor(num, y / ((Rect)(ref rect2)).height); } } Sprite val2 = Sprite.Create(replacement.Texture, rect, val, source.pixelsPerUnit, 0u, (SpriteMeshType)0, source.border); ((Object)val2).name = replacement.Name; replacement.SpriteCache[instanceID] = val2; return val2; } private static IEnumerable SplitAliases(string aliases) { return from alias in (aliases ?? string.Empty).Split(new char[5] { '|', ',', ';', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries) select alias.Trim() into alias where alias.Length > 0 select alias; } private static byte[]? ReadEmbeddedResourceBytes(string resourceName) { using Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName); if (stream == null) { return null; } using MemoryStream memoryStream = new MemoryStream(); stream.CopyTo(memoryStream); return memoryStream.ToArray(); } private static bool TryLoadPng(Texture2D texture, byte[] bytes) { if (LoadImageMethod == null) { return false; } object obj = LoadImageMethod.Invoke(null, new object[3] { texture, bytes, false }); bool flag = default(bool); int num; if (obj is bool) { flag = (bool)obj; num = 1; } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } private void TryLoadExternalFont() { if (!_replaceFontWithExternalFile.Value || (Object)(object)_externalFontAsset != (Object)null || TryLoadEmbeddedFont()) { return; } string text = ResolveExternalFontPath(_externalFontPath.Value); if (text == null) { if (_logChanges.Value) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("External TMP font file was not found: " + _externalFontPath.Value)); } return; } try { AssetBundle val = AssetBundle.LoadFromFile(text); if ((Object)(object)val == (Object)null) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("External TMP font file is not a readable AssetBundle: " + text)); return; } _loadedFontBundles.Add(val); TryLoadFontFromBundle(val, text); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Failed to load external TMP font '" + text + "': " + ex.Message)); } } private bool TryLoadEmbeddedFont() { if (_embeddedFontLoadAttempted) { return (Object)(object)_externalFontAsset != (Object)null; } _embeddedFontLoadAttempted = true; try { byte[] array = ReadEmbeddedResourceBytes("GWYFTmpPatcher.Assets.notosanstc-medium-sdf.bundle"); if (array == null || array.Length == 0) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Embedded TMP font AssetBundle was not found in patcher resources."); return false; } AssetBundle val = AssetBundle.LoadFromMemory(array); if ((Object)(object)val == (Object)null) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Embedded TMP font AssetBundle could not be loaded."); return false; } _loadedFontBundles.Add(val); if (!TryLoadFontFromBundle(val, "embedded patcher resource")) { return false; } return true; } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Failed to load embedded TMP font AssetBundle: " + ex.Message)); return false; } } private static string? ResolveExternalFontPath(string configuredPath) { string text = (configuredPath ?? string.Empty).Trim(); if (string.IsNullOrWhiteSpace(text)) { return null; } if (Path.IsPathRooted(text) && File.Exists(text)) { return Path.GetFullPath(text); } string[] array = new string[5] { Paths.GameRootPath, Paths.ConfigPath, Path.Combine(Paths.ConfigPath, "TMPFonts"), Path.Combine(Paths.ConfigPath, "ConfigTMP"), Paths.PluginPath }; for (int i = 0; i < array.Length; i++) { string path = Path.Combine(array[i], text); if (File.Exists(path)) { return Path.GetFullPath(path); } } return null; } private static string ResolveBepInExRelativePath(string configuredPath) { string text = (configuredPath ?? string.Empty).Trim(); if (string.IsNullOrWhiteSpace(text)) { text = "Translation"; } if (Path.IsPathRooted(text)) { return Path.GetFullPath(text); } string text2 = text.Replace('\\', '/'); if (text2.Equals("config", StringComparison.OrdinalIgnoreCase)) { return Paths.ConfigPath; } if (text2.StartsWith("config/", StringComparison.OrdinalIgnoreCase)) { string path = text2.Substring("config/".Length).Replace('/', Path.DirectorySeparatorChar); return Path.GetFullPath(Path.Combine(Paths.ConfigPath, path)); } return Path.GetFullPath(Path.Combine(Paths.GameRootPath, "BepInEx", text2.Replace('/', Path.DirectorySeparatorChar))); } private static bool IsTargetTmpFontAsset(TMP_FontAsset? fontAsset) { if ((Object)(object)fontAsset != (Object)null) { return string.Equals(NormalizeUnityObjectName(((Object)fontAsset).name), "ChineseFont", StringComparison.OrdinalIgnoreCase); } return false; } private void PatchTmpFontAssetInPlace(TMP_FontAsset targetFont, TMP_FontAsset sourceFont) { Material material = ((TMP_Asset)targetFont).material; Material material2 = ((TMP_Asset)sourceFont).material; string[] tmpFontAssetCopyFields = TmpFontAssetCopyFields; foreach (string fieldName in tmpFontAssetCopyFields) { CopyFieldValue(sourceFont, targetFont, fieldName); } if ((Object)(object)material != (Object)null && (Object)(object)material2 != (Object)null) { string name = ((Object)material).name; material.CopyPropertiesFromMaterial(material2); ((Object)material).name = name; CopyFieldValue(sourceFont, targetFont, "m_Material", material); } else if ((Object)(object)material2 != (Object)null) { CopyFieldValue(sourceFont, targetFont, "m_Material"); } RefreshTmpFontAsset(targetFont); if (_logChanges.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("Patched TMP font asset 'ChineseFont' with '" + ((Object)sourceFont).name + "'.")); } } private static void CopyFieldValue(TMP_FontAsset source, TMP_FontAsset target, string fieldName) { FieldInfo field = typeof(TMP_FontAsset).GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (!(field == null)) { field.SetValue(target, field.GetValue(source)); } } private static void CopyFieldValue(TMP_FontAsset source, TMP_FontAsset target, string fieldName, object value) { typeof(TMP_FontAsset).GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.SetValue(target, value); } private static void RefreshTmpFontAsset(TMP_FontAsset fontAsset) { MethodInfo method = typeof(TMP_FontAsset).GetMethod("ReadFontAssetDefinition", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (method != null) { method.Invoke(fontAsset, null); } else { typeof(TMP_FontAsset).GetMethod("InitializeDictionaryLookupTables", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.Invoke(fontAsset, null); } } private static bool IsReplacerTarget(TMP_Text text) { string hierarchyPath = GetHierarchyPath(text.transform); string text2 = NormalizeHierarchyPath("/LocalManager/UI/InteractionUIPanel/TextPanel/ItemNameText"); if (!string.Equals(hierarchyPath, text2, StringComparison.OrdinalIgnoreCase)) { return hierarchyPath.EndsWith(text2, StringComparison.OrdinalIgnoreCase); } return true; } private bool ShouldPatchTextComponent(Component component) { if (!_patchOnlyUi.Value) { return true; } if (!(component is TextMeshProUGUI)) { return (Object)(object)component.GetComponentInParent(true) != (Object)null; } return true; } private static string GetHierarchyPath(Transform transform) { Stack stack = new Stack(); Transform val = transform; while ((Object)(object)val != (Object)null) { stack.Push(((Object)val).name); val = val.parent; } return "/" + string.Join("/", stack.Select(NormalizePathSegment)); } private static string NormalizeHierarchyPath(string path) { string text = path.Replace('\\', '/').Trim(); if (!text.StartsWith("/", StringComparison.Ordinal)) { text = "/" + text; } return string.Join("/", text.Split(new char[1] { '/' }, StringSplitOptions.RemoveEmptyEntries).Select(NormalizePathSegment).Prepend(string.Empty)); } private static string NormalizePathSegment(string segment) { int num = segment.IndexOf("(Clone)", StringComparison.OrdinalIgnoreCase); if (num >= 0) { segment = segment.Remove(num); } return segment.Trim(); } private bool TryTranslateText(Component component, string original, Action setText, string reason, string kind) { if (string.IsNullOrWhiteSpace(original) || IsMostlyCjk(original)) { return false; } int instanceID = ((Object)component).GetInstanceID(); if (_lastAppliedText.TryGetValue(instanceID, out string value) && string.Equals(value, original, StringComparison.Ordinal)) { return false; } string text = Translate(original); if (string.Equals(text, original, StringComparison.Ordinal)) { return false; } setText(text); _lastAppliedText[instanceID] = text; if (_logChanges.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)(kind + " translated (" + reason + "): " + EscapeForLog(original) + " -> " + EscapeForLog(text))); } return true; } private string Translate(string input) { string key = NormalizeText(input); if (_exactRules.TryGetValue(key, out string value)) { return value; } string text = input; foreach (RegexRule regexRule in _regexRules) { if (regexRule.Regex.IsMatch(text)) { text = regexRule.Regex.Replace(text, regexRule.Replacement); } } foreach (KeyValuePair item in _postRules.OrderByDescending, int>((KeyValuePair rule) => rule.Key.Length)) { text = Regex.Replace(text, Regex.Escape(item.Key), EscapeReplacement(item.Value), RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); } return text; } private void ReloadTranslationsIfNeeded(bool force) { if (!Directory.Exists(_translationTextDirectory)) { return; } string[] array = new string[2] { Path.Combine(_translationTextDirectory, "Complate.txt"), Path.Combine(_translationTextDirectory, "_Postprocessors.txt") }; bool flag = force; string[] array2 = array; foreach (string text in array2) { if (File.Exists(text)) { DateTime lastWriteTimeUtc = File.GetLastWriteTimeUtc(text); if (!_translationFileTimes.TryGetValue(text, out var value) || lastWriteTimeUtc > value) { flag = true; } } } if (!flag) { return; } _exactRules.Clear(); _regexRules.Clear(); _postRules.Clear(); array2 = array; foreach (string text2 in array2) { if (File.Exists(text2)) { LoadTranslationFile(text2, Path.GetFileName(text2).Equals("_Postprocessors.txt", StringComparison.OrdinalIgnoreCase)); _translationFileTimes[text2] = File.GetLastWriteTimeUtc(text2); } } ((BaseUnityPlugin)this).Logger.LogInfo((object)$"Loaded XUnity translations: exact={_exactRules.Count}, regex={_regexRules.Count}, post={_postRules.Count}"); } private void LoadTranslationFile(string path, bool isPostprocessor) { string[] array = File.ReadAllLines(path, Encoding.UTF8); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (string.IsNullOrWhiteSpace(text) || text.StartsWith("#", StringComparison.Ordinal)) { continue; } int num = text.IndexOf('='); if (num <= 0) { continue; } string text2 = StripNoParse(text.Substring(0, num).Trim()); string text3 = StripNoParse(DecodeEscapes(text.Substring(num + 1).Trim())); if (!string.IsNullOrWhiteSpace(text2) && !string.IsNullOrWhiteSpace(text3)) { if (text2.StartsWith("r:\"", StringComparison.Ordinal) && text2.EndsWith("\"", StringComparison.Ordinal)) { string pattern = text2.Substring(3, text2.Length - 4); _regexRules.Add(new RegexRule(new Regex(pattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant), text3)); } else if (!isPostprocessor) { _exactRules[NormalizeText(text2)] = text3; } else if (IsSafePostprocessorSource(text2)) { _postRules[text2] = text3; } } } } private bool TryLoadFontFromBundle(AssetBundle bundle, string source) { string text = _assetBundleFontAssetName.Value.Trim(); TMP_FontAsset val = null; if (!string.IsNullOrWhiteSpace(text)) { val = bundle.LoadAsset(text); } if (val == null) { val = bundle.LoadAllAssets().FirstOrDefault(); } if ((Object)(object)val == (Object)null) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("No TMP_FontAsset found in AssetBundle: " + source)); return false; } Texture2D val2 = bundle.LoadAllAssets().FirstOrDefault(); if ((Object)(object)val2 != (Object)null) { typeof(TMP_FontAsset).GetField("m_AtlasTexture", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(val, val2); FieldInfo field = typeof(TMP_FontAsset).GetField("m_AtlasTextures", BindingFlags.Instance | BindingFlags.NonPublic); if (field?.GetValue(val) is Texture2D[] array && array.Length != 0) { array[0] = val2; } else { field?.SetValue(val, new Texture2D[1] { val2 }); } if ((Object)(object)((TMP_Asset)val).material != (Object)null && HasMainTextureProperty(((TMP_Asset)val).material)) { ((TMP_Asset)val).material.SetTexture(ShaderUtilities.ID_MainTex, (Texture)(object)val2); } if (_logChanges.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)$"Bound atlas texture: {((Object)val2).name} ({((Texture)val2).width}x{((Texture)val2).height})"); } } else { ((BaseUnityPlugin)this).Logger.LogWarning((object)("No Texture2D found in AssetBundle: " + source + "; glyphs may render incorrectly.")); } _externalFontAsset = val; ((BaseUnityPlugin)this).Logger.LogInfo((object)("Loaded TMP font: " + ((Object)_externalFontAsset).name + " from " + source)); return true; } private static bool IsSafePostprocessorSource(string source) { if (source.Length < 3 || source.Any((char ch) => char.IsDigit(ch) || ch == '$')) { return false; } return source.Any(char.IsLetter); } private static string StripNoParse(string value) { return value.Replace("", string.Empty).Replace("<\\/noparse>", string.Empty).Trim(); } private static string NormalizeText(string value) { return Regex.Replace(StripNoParse(value).Replace('\u00a0', ' ').Replace('\u200b', ' ').Replace('\u200c', ' ') .Replace('\u200d', ' ') .Replace("\\n", " ") .Replace("\r", " ") .Replace("\n", " "), "\\s+", " ").Trim(); } private static string DecodeEscapes(string value) { return value.Replace("\\n", Environment.NewLine).Replace("\\t", "\t"); } private static string EscapeReplacement(string value) { return value.Replace("$", "$$"); } private static string EscapeForLog(string value) { return value.Replace("\r", "\\r").Replace("\n", "\\n"); } private static bool IsEditorOrAssetObject(Component component) { //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) GameObject gameObject = component.gameObject; if (!((Object)(object)gameObject == (Object)null)) { Scene scene = gameObject.scene; return !((Scene)(ref scene)).IsValid(); } return true; } private static bool IsMostlyCjk(string value) { int num = 0; int num2 = 0; foreach (char c in value) { if (IsCjk(c)) { num++; } else if (char.IsLetter(c)) { num2++; } } if (num > 0) { return num >= num2; } return false; } private static bool IsCjk(char ch) { if ((ch < '一' || ch > '\u9fff') && (ch < '㐀' || ch > '䶿')) { if (ch >= '豈') { return ch <= '\ufaff'; } return false; } return true; } } }