Decompiled source of GWYF Traditional Chinese Translation v1.1.9

BepInEx\plugins\GWYFTmpPatcher.dll

Decompiled 2 months ago
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<int> PatchedTextures { get; } = new HashSet<int>();


			public Dictionary<int, Sprite> SpriteCache { get; } = new Dictionary<int, Sprite>();

		}

		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 <PatchSceneTexturesBatched>d__40 : IEnumerator<object>, IDisposable, IEnumerator
		{
			private int <>1__state;

			private object <>2__current;

			public float delaySeconds;

			public TmpPatcherPlugin <>4__this;

			public string reason;

			private int <textures>5__2;

			private int <images>5__3;

			private int <spriteRenderers>5__4;

			private int <materials>5__5;

			private int <processedThisFrame>5__6;

			private int <batchSize>5__7;

			private Texture2D[] <>7__wrap7;

			private int <>7__wrap8;

			private Image[] <>7__wrap9;

			private SpriteRenderer[] <>7__wrap10;

			private Renderer[] <>7__wrap11;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <PatchSceneTexturesBatched>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)
						{
							<materials>5__5 += tmpPatcherPlugin.PatchRendererMaterials(val);
						}
						if (++<processedThisFrame>5__6 >= <batchSize>5__7)
						{
							<processedThisFrame>5__6 = 0;
							<>2__current = null;
							<>1__state = 5;
							return true;
						}
						goto IL_0333;
					}
					<>7__wrap11 = null;
					if (tmpPatcherPlugin._logChanges.Value && (<textures>5__2 > 0 || <images>5__3 > 0 || <spriteRenderers>5__4 > 0 || <materials>5__5 > 0))
					{
						((BaseUnityPlugin)tmpPatcherPlugin).Logger.LogInfo((object)$"Texture patch complete ({reason}). Textures={<textures>5__2}, images={<images>5__3}, spriteRenderers={<spriteRenderers>5__4}, materials={<materials>5__5}.");
					}
					return false;
					IL_0063:
					if (!tmpPatcherPlugin.LoadEmbeddedTextureReplacements())
					{
						return false;
					}
					<textures>5__2 = 0;
					<images>5__3 = 0;
					<spriteRenderers>5__4 = 0;
					<materials>5__5 = 0;
					<processedThisFrame>5__6 = 0;
					<batchSize>5__7 = Mathf.Max(1, tmpPatcherPlugin._textureBatchSize.Value);
					<>7__wrap7 = Resources.FindObjectsOfTypeAll<Texture2D>();
					<>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))
						{
							<textures>5__2++;
						}
						if (++<processedThisFrame>5__6 >= <batchSize>5__7)
						{
							<processedThisFrame>5__6 = 0;
							<>2__current = null;
							<>1__state = 2;
							return true;
						}
						goto IL_0122;
					}
					<>7__wrap7 = null;
					<>7__wrap9 = Resources.FindObjectsOfTypeAll<Image>();
					<>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))
						{
							<images>5__3++;
						}
						if (++<processedThisFrame>5__6 >= <batchSize>5__7)
						{
							<processedThisFrame>5__6 = 0;
							<>2__current = null;
							<>1__state = 3;
							return true;
						}
						goto IL_01da;
					}
					<>7__wrap9 = null;
					<>7__wrap10 = Resources.FindObjectsOfTypeAll<SpriteRenderer>();
					<>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))
						{
							<spriteRenderers>5__4++;
						}
						if (++<processedThisFrame>5__6 >= <batchSize>5__7)
						{
							<processedThisFrame>5__6 = 0;
							<>2__current = null;
							<>1__state = 4;
							return true;
						}
						goto IL_0292;
					}
					<>7__wrap10 = null;
					<>7__wrap11 = Resources.FindObjectsOfTypeAll<Renderer>();
					<>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 <PatchSceneTmpObjectsBatched>d__38 : IEnumerator<object>, IDisposable, IEnumerator
		{
			private int <>1__state;

			private object <>2__current;

			public float delaySeconds;

			public TmpPatcherPlugin <>4__this;

			public string reason;

			private TMP_Text[] <texts>5__2;

			private HashSet<int> <seen>5__3;

			private int <fontPatched>5__4;

			private int <translated>5__5;

			private int <processedThisFrame>5__6;

			private int <batchSize>5__7;

			private TMP_FontAsset[] <>7__wrap7;

			private int <>7__wrap8;

			private TMP_Text[] <>7__wrap9;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <PatchSceneTmpObjectsBatched>d__38(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<texts>5__2 = null;
				<seen>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:
					<texts>5__2 = Resources.FindObjectsOfTypeAll<TMP_Text>();
					<seen>5__3 = new HashSet<int>();
					<fontPatched>5__4 = 0;
					<translated>5__5 = 0;
					<processedThisFrame>5__6 = 0;
					<batchSize>5__7 = Mathf.Max(1, tmpPatcherPlugin._fontBatchSize.Value);
					<>7__wrap7 = Resources.FindObjectsOfTypeAll<TMP_FontAsset>();
					<>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))
						{
							<fontPatched>5__4++;
						}
						if (++<processedThisFrame>5__6 >= <batchSize>5__7)
						{
							<processedThisFrame>5__6 = 0;
							<>2__current = null;
							<>1__state = 2;
							return true;
						}
						goto IL_0118;
					}
					<>7__wrap7 = null;
					<>7__wrap9 = <texts>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) && <seen>5__3.Add(((Object)val2).GetInstanceID()))
						{
							tmpPatcherPlugin.PatchTmpText(val2, reason, allowTranslate: false, allowFont: true, out var fontChanged, out var textTranslated);
							<fontPatched>5__4 += (fontChanged ? 1 : 0);
							<translated>5__5 += (textTranslated ? 1 : 0);
							if (textTranslated && tmpPatcherPlugin._forceMeshUpdate.Value)
							{
								((Graphic)val2).SetAllDirty();
								val2.ForceMeshUpdate(true, true);
							}
							<processedThisFrame>5__6++;
							if (<processedThisFrame>5__6 >= <batchSize>5__7)
							{
								<processedThisFrame>5__6 = 0;
								<>2__current = null;
								<>1__state = 3;
								return true;
							}
						}
						goto IL_022d;
					}
					<>7__wrap9 = null;
					tmpPatcherPlugin.LogPatchSummary(reason, <fontPatched>5__4, <translated>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<bool> _enabled;

		private ConfigEntry<bool> _patchOnlyUi;

		private ConfigEntry<bool> _translateTmpText;

		private ConfigEntry<bool> _forceMeshUpdate;

		private ConfigEntry<bool> _logChanges;

		private ConfigEntry<int> _fontBatchSize;

		private ConfigEntry<bool> _replaceFontWithExternalFile;

		private ConfigEntry<string> _externalFontPath;

		private ConfigEntry<string> _assetBundleFontAssetName;

		private ConfigEntry<bool> _replaceBuiltInTextures;

		private ConfigEntry<int> _textureBatchSize;

		private ConfigEntry<string> _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<int> _patchedFontAssetIds = new HashSet<int>();

		private readonly List<AssetBundle> _loadedFontBundles = new List<AssetBundle>();

		private readonly Dictionary<string, TextureReplacement> _textureReplacements = new Dictionary<string, TextureReplacement>(StringComparer.OrdinalIgnoreCase);

		private readonly Dictionary<string, string> _exactRules = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

		private readonly Dictionary<string, string> _postRules = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

		private readonly List<RegexRule> _regexRules = new List<RegexRule>();

		private readonly Dictionary<int, string> _lastAppliedText = new Dictionary<int, string>();

		private readonly Dictionary<string, DateTime> _translationFileTimes = new Dictionary<string, DateTime>(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<bool>("General", "Enabled", true, "Enable TMP patching.");
			_patchOnlyUi = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "PatchOnlyUi", true, "Only patch UI text under Canvas/TextMeshProUGUI.");
			_translateTmpText = ((BaseUnityPlugin)this).Config.Bind<bool>("Translation", "TranslateTmpText", true, "Apply XUnity translation files to TMP_Text objects missed by XUnity hooks.");
			_forceMeshUpdate = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ForceMeshUpdate", false, "Force TMP meshes to redraw after text replacement.");
			_logChanges = ((BaseUnityPlugin)this).Config.Bind<bool>("Diagnostics", "LogChanges", true, "Log applied font and translation changes.");
			_fontBatchSize = ((BaseUnityPlugin)this).Config.Bind<int>("Performance", "FontBatchSize", 32, "How many TMP objects to font patch per frame during scene scans.");
			_replaceFontWithExternalFile = ((BaseUnityPlugin)this).Config.Bind<bool>("Font", "ReplaceFontWithExternalFile", true, "Patch only the game's ChineseFont TMP font asset.");
			_externalFontPath = ((BaseUnityPlugin)this).Config.Bind<string>("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<string>("Font", "AssetBundleFontAssetName", "", "TMP_FontAsset name inside the AssetBundle. Leave blank to use the first TMP_FontAsset.");
			_replaceBuiltInTextures = ((BaseUnityPlugin)this).Config.Bind<bool>("Texture", "ReplaceBuiltInTextures", true, "Replace selected game textures from resources embedded in this patcher.");
			_textureBatchSize = ((BaseUnityPlugin)this).Config.Bind<int>("Performance", "TextureBatchSize", 64, "How many texture related objects to inspect per frame during texture replacement scans.");
			_translationTextPath = ((BaseUnityPlugin)this).Config.Bind<string>("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<Object>)OnTextChanged);
			QueueSceneFontScans("startup");
			QueueTextureScans("startup");
		}

		private void OnDestroy()
		{
			SceneManager.sceneLoaded -= OnSceneLoaded;
			TMPro_EventManager.TEXT_CHANGED_EVENT.Remove((Action<Object>)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(<PatchSceneTmpObjectsBatched>d__38))]
		private IEnumerator PatchSceneTmpObjectsBatched(string reason, float delaySeconds)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <PatchSceneTmpObjectsBatched>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(<PatchSceneTexturesBatched>d__40))]
		private IEnumerator PatchSceneTexturesBatched(string reason, float delaySeconds)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <PatchSceneTexturesBatched>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<Material>();
			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<string> 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<Canvas>(true) != (Object)null;
			}
			return true;
		}

		private static string GetHierarchyPath(Transform transform)
		{
			Stack<string> stack = new Stack<string>();
			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<string> 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<string, string> item in _postRules.OrderByDescending<KeyValuePair<string, string>, int>((KeyValuePair<string, string> 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<TMP_FontAsset>(text);
			}
			if (val == null)
			{
				val = bundle.LoadAllAssets<TMP_FontAsset>().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<Texture2D>().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("<noparse></noparse>", string.Empty).Replace("<noparse><\\/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;
		}
	}
}

BepInEx\plugins\GWYFTranslationUpdater.dll

Decompiled 2 months ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Configuration;
using Microsoft.CodeAnalysis;
using UnityEngine;

[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("GWYFTranslationUpdater")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+a9d3046b810a7bd0dd1013e2f8e0683442b198ae")]
[assembly: AssemblyProduct("GWYFTranslationUpdater")]
[assembly: AssemblyTitle("GWYFTranslationUpdater")]
[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 GWYFTranslationUpdater
{
	[BepInPlugin("codex.gwyf.translationupdater", "GWYF Translation Updater", "0.1.2")]
	public sealed class TranslationUpdaterPlugin : BaseUnityPlugin
	{
		private sealed class ManifestEntry
		{
			public string RelativePath { get; }

			public string Sha256 { get; }

			public long Size { get; }

			public ManifestEntry(string relativePath, string sha256, long size)
			{
				RelativePath = relativePath;
				Sha256 = sha256;
				Size = size;
			}
		}

		private ConfigEntry<bool> _enabled;

		private ConfigEntry<bool> _autoUpdateOnStartup;

		private ConfigEntry<float> _startupDelaySeconds;

		private ConfigEntry<float> _commandPollSeconds;

		private ConfigEntry<bool> _deleteMissingFiles;

		private ConfigEntry<string> _translationRootPath;

		private string _translationRoot;

		private string _dataDirectory;

		private string _commandsPath;

		private string _statusPath;

		private string _manifestCachePath;

		private string _readmePath;

		private float _nextCommandPollAt;

		private float _startupCheckAt;

		private bool _startupUpdateTriggered;

		private DateTime _lastCommandWriteTimeUtc = DateTime.MinValue;

		private Task? _updateTask;

		private const string ManifestUrl = "https://raw.githubusercontent.com/XoF-eLtTiL/GWYF-zhtw-Translation/refs/heads/main/manifest.txt";

		private const string RawBaseUrl = "https://raw.githubusercontent.com/XoF-eLtTiL/GWYF-zhtw-Translation/refs/heads/main/translations";

		private const long MaxDownloadBytes = 26214400L;

		private static readonly string[] AllowedPathPrefixes = new string[2] { "zh-TW/Text/", "zh-TW/Texture/" };

		private static readonly HashSet<string> AllowedExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".txt", ".png" };

		private static readonly HttpClient HttpClient = new HttpClient
		{
			Timeout = TimeSpan.FromSeconds(45.0),
			MaxResponseContentBufferSize = 26214400L
		};

		private void Awake()
		{
			_enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Enable automatic translation updates.");
			_autoUpdateOnStartup = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "AutoUpdateOnStartup", true, "Check the remote translation repository shortly after the game starts.");
			_startupDelaySeconds = ((BaseUnityPlugin)this).Config.Bind<float>("General", "StartupDelaySeconds", 1.5f, "Delay before the startup update check.");
			_commandPollSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("General", "CommandPollSeconds", 1.5f, "How often to poll commands.txt.");
			_deleteMissingFiles = ((BaseUnityPlugin)this).Config.Bind<bool>("Sync", "DeleteMissingFiles", false, "Delete local translation files that are not listed in the remote manifest.");
			_translationRootPath = ((BaseUnityPlugin)this).Config.Bind<string>("Paths", "TranslationRoot", "Translation", "Translation root relative to BepInEx. Use config\\Translation for Thunderstore profiles.");
			_translationRoot = ResolveBepInExRelativePath(_translationRootPath.Value);
			_dataDirectory = Path.Combine(Paths.ConfigPath, "GWYF.TranslationUpdater");
			_commandsPath = Path.Combine(_dataDirectory, "commands.txt");
			_statusPath = Path.Combine(_dataDirectory, "status.txt");
			_manifestCachePath = Path.Combine(_dataDirectory, "last_manifest.txt");
			_readmePath = Path.Combine(_dataDirectory, "README.txt");
			Directory.CreateDirectory(_dataDirectory);
			EnsureFileExists(_commandsPath, "# commands: update, force, status" + Environment.NewLine + "# update -> update only changed files" + Environment.NewLine + "# force  -> re-download all files listed in manifest");
			EnsureFileExists(_readmePath, "GWYF Translation Updater" + Environment.NewLine + "1. Remote source is locked to XoF-eLtTiL/GWYF-zhtw-Translation." + Environment.NewLine + "2. Put 'update' or 'force' into commands.txt to trigger a sync." + Environment.NewLine + "3. Only zh-TW/Text/*.txt and zh-TW/Texture/*.png are downloaded." + Environment.NewLine + "4. Files are written to " + _translationRoot + "." + Environment.NewLine + "5. The updater only writes files; it never executes downloaded content.");
			DeleteAutoGeneratedTranslationFiles();
			WriteStatus("Translation updater ready.");
		}

		private void Start()
		{
			if (_enabled.Value && _autoUpdateOnStartup.Value)
			{
				float num = Mathf.Max(0f, _startupDelaySeconds.Value);
				if (num <= 0.01f)
				{
					_startupUpdateTriggered = true;
					WriteStatus("Startup translation sync requested immediately.");
					TriggerUpdate(force: false, "startup");
				}
				else
				{
					_startupCheckAt = Time.unscaledTime + num;
					WriteStatus($"Startup translation sync scheduled in {num:0.##}s.");
				}
			}
		}

		private void Update()
		{
			if (_enabled.Value)
			{
				if (!_startupUpdateTriggered && _autoUpdateOnStartup.Value && Time.unscaledTime >= _startupCheckAt)
				{
					_startupUpdateTriggered = true;
					TriggerUpdate(force: false, "startup");
				}
				if (Time.unscaledTime >= _nextCommandPollAt)
				{
					_nextCommandPollAt = Time.unscaledTime + Mathf.Max(0.25f, _commandPollSeconds.Value);
					ProcessCommandsIfChanged();
				}
			}
		}

		private void ProcessCommandsIfChanged()
		{
			if (!File.Exists(_commandsPath))
			{
				return;
			}
			DateTime lastWriteTimeUtc = File.GetLastWriteTimeUtc(_commandsPath);
			if (lastWriteTimeUtc <= _lastCommandWriteTimeUtc)
			{
				return;
			}
			_lastCommandWriteTimeUtc = lastWriteTimeUtc;
			string[] array = File.ReadAllLines(_commandsPath);
			if (!array.Any((string line) => !string.IsNullOrWhiteSpace(line) && !line.TrimStart(Array.Empty<char>()).StartsWith("#", StringComparison.Ordinal)))
			{
				return;
			}
			string[] array2 = array;
			for (int i = 0; i < array2.Length; i++)
			{
				string text = array2[i].Trim();
				if (!string.IsNullOrWhiteSpace(text) && !text.StartsWith("#", StringComparison.Ordinal))
				{
					switch (text.ToLowerInvariant())
					{
					case "update":
						TriggerUpdate(force: false, "manual");
						break;
					case "force":
						TriggerUpdate(force: true, "manual-force");
						break;
					case "status":
						WriteStatus("Updater is idle.");
						break;
					default:
						WriteStatus("Unknown updater command: " + text);
						break;
					}
				}
			}
			File.WriteAllText(_commandsPath, "# commands processed" + Environment.NewLine);
		}

		private void TriggerUpdate(bool force, string reason)
		{
			if (_updateTask != null && !_updateTask.IsCompleted)
			{
				WriteStatus("Update already running. Ignored " + reason + " request.");
			}
			else
			{
				_updateTask = RunUpdateAsync(force, reason);
			}
		}

		private async Task RunUpdateAsync(bool force, string reason)
		{
			_ = 1;
			try
			{
				WriteStatus("Checking remote translations (" + reason + ")...");
				string manifestText = await HttpClient.GetStringAsync("https://raw.githubusercontent.com/XoF-eLtTiL/GWYF-zhtw-Translation/refs/heads/main/manifest.txt");
				List<ManifestEntry> remoteEntries = ParseManifest(manifestText);
				if (remoteEntries.Count == 0)
				{
					WriteStatus("Remote manifest is empty.");
					return;
				}
				int downloaded = 0;
				foreach (ManifestEntry entry in remoteEntries)
				{
					if (!IsSafeManifestEntry(entry, out string reason2))
					{
						((BaseUnityPlugin)this).Logger.LogWarning((object)("Skipped unsafe manifest entry '" + entry.RelativePath + "': " + reason2));
						continue;
					}
					string localPath = GetLocalPath(entry);
					if (!IsPathUnderRoot(localPath, _translationRoot))
					{
						((BaseUnityPlugin)this).Logger.LogWarning((object)("Skipped manifest entry outside translation root: " + entry.RelativePath));
						continue;
					}
					string a = (File.Exists(localPath) ? ComputeFileSha256(localPath) : string.Empty);
					if (force || !string.Equals(a, entry.Sha256, StringComparison.OrdinalIgnoreCase))
					{
						string text = BuildRemoteFileUrl(entry);
						byte[] array = await HttpClient.GetByteArrayAsync(text);
						if (array.LongLength != entry.Size)
						{
							WriteStatus("Size mismatch for " + entry.RelativePath + ". Skipped.");
							continue;
						}
						if (!string.Equals(ComputeSha256(array), entry.Sha256, StringComparison.OrdinalIgnoreCase))
						{
							WriteStatus("Hash mismatch for " + entry.RelativePath + ". Skipped.");
							continue;
						}
						Directory.CreateDirectory(Path.GetDirectoryName(localPath) ?? _translationRoot);
						File.WriteAllBytes(localPath, array);
						downloaded++;
						((BaseUnityPlugin)this).Logger.LogInfo((object)("Updated translation file: " + entry.RelativePath));
					}
				}
				if (_deleteMissingFiles.Value)
				{
					DeleteFilesMissingFromManifest(remoteEntries);
				}
				DeleteAutoGeneratedTranslationFiles();
				File.WriteAllText(_manifestCachePath, manifestText, Encoding.UTF8);
				if (downloaded > 0)
				{
					TouchTranslationFiles();
					TryReloadXUnityTranslations();
				}
				WriteStatus($"Translation sync complete. Updated {downloaded} file(s) from {remoteEntries.Count} manifest entries.");
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)ex);
				WriteStatus("Translation sync failed: " + ex.Message);
			}
		}

		private string GetLocalPath(ManifestEntry entry)
		{
			string text = NormalizeRelativePath(entry.RelativePath);
			return Path.GetFullPath(Path.Combine(_translationRoot, text.Replace('/', Path.DirectorySeparatorChar)));
		}

		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 = NormalizeRelativePath(text);
			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 string BuildRemoteFileUrl(ManifestEntry entry)
		{
			string relativePath = NormalizeRelativePath(entry.RelativePath);
			return BuildRawFileUrl("https://raw.githubusercontent.com/XoF-eLtTiL/GWYF-zhtw-Translation/refs/heads/main/translations", relativePath);
		}

		private void DeleteFilesMissingFromManifest(List<ManifestEntry> remoteEntries)
		{
			HashSet<string> hashSet = new HashSet<string>(remoteEntries.Select((ManifestEntry entry) => NormalizeRelativePath(entry.RelativePath)), StringComparer.OrdinalIgnoreCase);
			if (!Directory.Exists(_translationRoot))
			{
				return;
			}
			string[] files = Directory.GetFiles(_translationRoot, "*", SearchOption.AllDirectories);
			foreach (string text in files)
			{
				string text2 = NormalizeRelativePath(GetRelativePathCompat(_translationRoot, text));
				if (IsSafeRelativeDownloadPath(text2) && !hashSet.Contains(text2))
				{
					File.Delete(text);
					((BaseUnityPlugin)this).Logger.LogInfo((object)("Deleted translation file not listed in manifest: " + text2));
				}
			}
		}

		private void DeleteAutoGeneratedTranslationFiles()
		{
			if (Directory.Exists(_translationRoot))
			{
				string[] files = Directory.GetFiles(_translationRoot, "_AutoGeneratedTranslations.txt", SearchOption.AllDirectories);
				foreach (string text in files)
				{
					File.Delete(text);
					((BaseUnityPlugin)this).Logger.LogInfo((object)("Deleted auto-generated translation file: " + NormalizeRelativePath(GetRelativePathCompat(_translationRoot, text))));
				}
			}
		}

		private void TouchTranslationFiles()
		{
			if (!Directory.Exists(_translationRoot))
			{
				return;
			}
			DateTime utcNow = DateTime.UtcNow;
			string[] files = Directory.GetFiles(_translationRoot, "*", SearchOption.AllDirectories);
			foreach (string text in files)
			{
				string text2 = NormalizeRelativePath(GetRelativePathCompat(_translationRoot, text));
				if (IsSafeRelativeDownloadPath(text2))
				{
					try
					{
						File.SetLastWriteTimeUtc(text, utcNow);
					}
					catch (Exception ex)
					{
						((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not touch translation file for reload: " + text2 + ". " + ex.Message));
					}
				}
			}
		}

		private void TryReloadXUnityTranslations()
		{
			try
			{
				MethodInfo methodInfo = Type.GetType("XUnity.AutoTranslator.Plugin.Core.SettingsTranslationsInitializer, XUnity.AutoTranslator.Plugin.Core")?.GetMethod("LoadTranslations", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
				if (methodInfo == null)
				{
					((BaseUnityPlugin)this).Logger.LogInfo((object)"XUnity translation reload hook was not found; updated translations may apply after text refresh or game restart.");
					return;
				}
				methodInfo.Invoke(null, null);
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Requested XUnity translation reload after update.");
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not request XUnity translation reload; updated translations may apply after text refresh or game restart. " + ex.Message));
			}
		}

		private static List<ManifestEntry> ParseManifest(string manifestText)
		{
			List<ManifestEntry> list = new List<ManifestEntry>();
			string[] array = manifestText.Split(new string[2] { "\r\n", "\n" }, StringSplitOptions.None);
			for (int i = 0; i < array.Length; i++)
			{
				string text = array[i].Trim();
				if (string.IsNullOrWhiteSpace(text) || text.StartsWith("#", StringComparison.Ordinal))
				{
					continue;
				}
				string[] array2 = text.Split(new char[1] { '|' });
				if (array2.Length >= 2)
				{
					string relativePath = NormalizeRelativePath(array2[0]);
					string sha = array2[1].Trim();
					long result = 0L;
					if (array2.Length >= 3)
					{
						long.TryParse(array2[2].Trim(), out result);
					}
					list.Add(new ManifestEntry(relativePath, sha, result));
				}
			}
			return list;
		}

		private static string NormalizeRelativePath(string path)
		{
			return (path ?? string.Empty).Trim().Replace('\\', '/');
		}

		private static bool IsSafeManifestEntry(ManifestEntry entry, out string reason)
		{
			if (!IsSafeRelativeDownloadPath(NormalizeRelativePath(entry.RelativePath)))
			{
				reason = "path is outside the locked translation file allowlist";
				return false;
			}
			if (!IsSha256Hex(entry.Sha256))
			{
				reason = "sha256 is invalid";
				return false;
			}
			if (entry.Size < 0 || entry.Size > 26214400)
			{
				reason = "size is invalid";
				return false;
			}
			reason = string.Empty;
			return true;
		}

		private static bool IsSafeRelativeDownloadPath(string relativePath)
		{
			string relativePath2 = relativePath;
			if (string.IsNullOrWhiteSpace(relativePath2) || relativePath2.StartsWith("/", StringComparison.Ordinal) || relativePath2.StartsWith("\\", StringComparison.Ordinal) || relativePath2.IndexOf(':') >= 0 || relativePath2.Split(new char[1] { '/' }).Any((string part) => part == "." || part == ".." || string.IsNullOrWhiteSpace(part)))
			{
				return false;
			}
			if (!AllowedPathPrefixes.Any((string prefix) => relativePath2.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)))
			{
				return false;
			}
			return AllowedExtensions.Contains(Path.GetExtension(relativePath2));
		}

		private static bool IsPathUnderRoot(string candidatePath, string rootPath)
		{
			string fullPath = Path.GetFullPath(candidatePath);
			string value = AppendDirectorySeparator(Path.GetFullPath(rootPath));
			return fullPath.StartsWith(value, StringComparison.OrdinalIgnoreCase);
		}

		private static bool IsSha256Hex(string value)
		{
			if (value.Length == 64)
			{
				return value.All((char ch) => (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F'));
			}
			return false;
		}

		private static string BuildRawFileUrl(string baseUrl, string relativePath)
		{
			string text = baseUrl.TrimEnd(new char[1] { '/' });
			string[] value = NormalizeRelativePath(relativePath).Split(new char[1] { '/' }, StringSplitOptions.RemoveEmptyEntries).Select(Uri.EscapeDataString).ToArray();
			return text + "/" + string.Join("/", value);
		}

		private static string ComputeFileSha256(string filePath)
		{
			using FileStream inputStream = File.OpenRead(filePath);
			using SHA256 sHA = SHA256.Create();
			return ConvertToHex(sHA.ComputeHash(inputStream));
		}

		private static string ComputeSha256(byte[] content)
		{
			using SHA256 sHA = SHA256.Create();
			return ConvertToHex(sHA.ComputeHash(content));
		}

		private static string ConvertToHex(byte[] bytes)
		{
			StringBuilder stringBuilder = new StringBuilder(bytes.Length * 2);
			foreach (byte b in bytes)
			{
				stringBuilder.Append(b.ToString("x2"));
			}
			return stringBuilder.ToString();
		}

		private static string GetRelativePathCompat(string basePath, string targetPath)
		{
			Uri uri = new Uri(AppendDirectorySeparator(basePath));
			Uri uri2 = new Uri(targetPath);
			return Uri.UnescapeDataString(uri.MakeRelativeUri(uri2).ToString()).Replace('/', Path.DirectorySeparatorChar);
		}

		private static string AppendDirectorySeparator(string path)
		{
			char directorySeparatorChar;
			if (string.IsNullOrEmpty(path))
			{
				directorySeparatorChar = Path.DirectorySeparatorChar;
				return directorySeparatorChar.ToString();
			}
			directorySeparatorChar = Path.DirectorySeparatorChar;
			if (!path.EndsWith(directorySeparatorChar.ToString(), StringComparison.Ordinal))
			{
				directorySeparatorChar = Path.DirectorySeparatorChar;
				return path + directorySeparatorChar;
			}
			return path;
		}

		private void WriteStatus(string message)
		{
			((BaseUnityPlugin)this).Logger.LogInfo((object)message);
			File.WriteAllText(_statusPath, $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {message}{Environment.NewLine}", Encoding.UTF8);
		}

		private static void EnsureFileExists(string path, string content)
		{
			if (!File.Exists(path))
			{
				File.WriteAllText(path, content + Environment.NewLine, Encoding.UTF8);
			}
		}
	}
}