The BepInEx console will not appear when launching like it does for other games on Thunderstore (you can turn it back on in your BepInEx.cfg file). If your PEAK crashes on startup, add -dx12 to your launch parameters.
Decompiled source of PeakTcnPatch v1.3.0
ue.Peak.TcnPatch.dll
Decompiled 4 days agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Net.Http; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Threading; using System.Threading.Tasks; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using TMPro; using UnityEngine; using ue.Peak.TcnPatch.API; using ue.Peak.TcnPatch.Adapters; using ue.Peak.TcnPatch.Patches; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("ue.Peak.TcnPatch")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+734d1f27f4e0fe1aad6bc8c3b209d4e56a0a1e72")] [assembly: AssemblyProduct("ue.Peak.TcnPatch")] [assembly: AssemblyTitle("ue.Peak.TcnPatch")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [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.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace ue.Peak.TcnPatch { [BepInPlugin("ue.Peak.TcnPatch", "ue.Peak.TcnPatch", "1.3.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { public const string ModGuid = "ue.Peak.TcnPatch"; public const string ModName = "ue.Peak.TcnPatch"; public const string ModVersion = "1.3.0"; private static FileSystemWatcher _watcher; public const string TcnTranslationFileName = "TcnTranslations.json"; private static SemaphoreSlim _lock = new SemaphoreSlim(1, 1); internal static Plugin Instance { get; private set; } internal static ManualLogSource Logger { get; private set; } internal static TranslationFile EmptyTranslationFile { get; set; } internal static TranslationFile CurrentTranslationFile { get; private set; } internal static PluginConfig ModConfig { get; private set; } internal static HashSet<string> EphemeralTranslationKeys { get; } = new HashSet<string>(); internal static bool HasOfficialTcn { get; private set; } internal static Dictionary<string, string> TcnTable { get; } = new Dictionary<string, string>(); internal static Dictionary<string, string> RegisteredTable { get; } = new Dictionary<string, string>(); internal static Dictionary<string, string> RegisteredOrigTable { get; } = new Dictionary<string, string>(); private void Awake() { Instance = this; ModConfig = new PluginConfig(((BaseUnityPlugin)this).Config); Logger = ((BaseUnityPlugin)this).Logger; Logger.LogInfo((object)"正在載入模組 - ue.Peak.TcnPatch"); if (Enum.GetValues(typeof(Language)).Cast<int>().Any((int values) => values == 10)) { HasOfficialTcn = true; } if (ModConfig.DownloadFromRemote.Value) { Task.Run(async delegate { string url = ModConfig.DownloadUrl.Value; Logger.LogInfo((object)"正在從遠端下載翻譯資料... (可以在模組設定停用)"); Logger.LogInfo((object)("網址:" + url)); HttpClient client = new HttpClient(); try { string content = await client.GetStringAsync(url); try { JObject.Parse(content); } catch (Exception ex) { Exception e2 = ex; Logger.LogWarning((object)"無效的遠端翻譯資料!將使用本機資料。"); Logger.LogWarning((object)e2); return; } string dir = Path.Combine(Paths.ConfigPath, "ue.Peak.TcnPatch"); Directory.CreateDirectory(dir); string path = Path.Combine(dir, "TcnTranslations.json"); await _lock.WaitAsync(); try { FileStream targetStream = File.Open(path, FileMode.Truncate, FileAccess.Write); try { StreamWriter writer = new StreamWriter(targetStream); try { await writer.WriteAsync(content); } finally { if (writer != null) { await writer.DisposeAsync(); } } } finally { if (targetStream != null) { await targetStream.DisposeAsync(); } } } finally { _lock.Release(); } Logger.LogInfo((object)"翻譯資料下載完成!"); } catch (Exception ex) { Exception e = ex; Logger.LogError((object)"翻譯資料下載失敗!將使用本機資料。"); Logger.LogError((object)e); } client.Dispose(); }); } Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), "ue.Peak.TcnPatch"); ue.Peak.TcnPatch.API.TcnPatch internalInstance = ue.Peak.TcnPatch.API.TcnPatch.InternalInstance; internalInstance.RegisterLocalizationKey("PeakTcnPatch.Passport.Crabland", "CRABLAND"); MoreAscentsSupport.RegisterLocalizations(); Logger.LogInfo((object)"已載入模組 - ue.Peak.TcnPatch"); Logger.LogInfo((object)" + 非官方繁體中文翻譯支援模組 -- by悠依"); } private void Start() { string path = Path.Combine(Paths.ConfigPath, "ue.Peak.TcnPatch"); Directory.CreateDirectory(path); _watcher = new FileSystemWatcher(path, "*.json"); _watcher.NotifyFilter = NotifyFilters.LastWrite; _watcher.Changed += delegate(object _, FileSystemEventArgs args) { if (args.Name == "TcnTranslations.json") { Logger.LogInfo((object)"正在更新遊戲內繁體中文翻譯資料..."); UpdateMainTable(); } }; UpdateMainTable(); _watcher.EnableRaisingEvents = true; } internal static bool TryGetVanilla(string id, out string result) { return TcnTable.TryGetValue(id.ToUpperInvariant(), out result); } internal static bool TryGetRegistered(string id, Language? language, out string result) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Invalid comparison between Unknown and I4 //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) Language valueOrDefault = language.GetValueOrDefault(); if (!language.HasValue) { valueOrDefault = LocalizedText.CURRENT_LANGUAGE; language = valueOrDefault; } if ((int)language.GetValueOrDefault() == 10 && RegisteredTable.TryGetValue(id, out result)) { return true; } return RegisteredOrigTable.TryGetValue(id, out result); } private static void UpdateMainTable() { _lock.Wait(); try { if (!TryReadFromJson<JObject>("TcnTranslations.json", out var result, () => new JObject())) { return; } CurrentTranslationFile = TranslationFile.Deserialize(result); } catch (TranslationParseException ex) { Logger.LogError((object)ex.UserMessage); Logger.LogError((object)"翻譯資料分析失敗!"); return; } catch (Exception ex2) { Logger.LogError((object)"翻譯資料分析失敗!"); Logger.LogError((object)ex2); return; } finally { _lock.Release(); } Dictionary<string, List<string>> mainTable = LocalizedText.mainTable; HashSet<string> hashSet = mainTable.Keys.ToHashSet(); if (CurrentTranslationFile.Authors.Count > 0) { Logger.LogInfo((object)("翻譯資料作者:" + string.Join("、", CurrentTranslationFile.Authors))); } else { Logger.LogInfo((object)"翻譯資料作者:未知"); } TcnTable.Clear(); RegisteredTable.Clear(); string key; string value; foreach (KeyValuePair<string, string> translation in CurrentTranslationFile.Translations) { translation.Deconstruct(out key, out value); string text = key; string value2 = value; if (!mainTable.ContainsKey(text.ToUpperInvariant())) { Logger.LogWarning((object)("已忽略未知的翻譯key:「" + text + "」!")); continue; } TcnTable[text] = value2; hashSet.Remove(text); } foreach (KeyValuePair<string, string> additionalTranslation in CurrentTranslationFile.AdditionalTranslations) { additionalTranslation.Deconstruct(out value, out key); string key2 = value; string value3 = key; RegisteredTable[key2] = value3; } HashSet<string> vanillaLocalizationKeys = LocalizedTextPatch.VanillaLocalizationKeys; foreach (string item in hashSet) { if (vanillaLocalizationKeys.Contains(item)) { Logger.LogWarning((object)("缺少「" + item + "」翻譯key,請更新翻譯資料!")); } else if (ModConfig.WarnMissingAdditionalKeys.Value) { Logger.LogWarning((object)("*附加翻譯* 缺少「" + item + "」翻譯key!")); } } } private static bool TryReadFromJson<T>(string fileName, out T result, Func<T> defaultContent) where T : class { string text = Path.Combine(Paths.ConfigPath, "ue.Peak.TcnPatch"); Directory.CreateDirectory(text); string path = Path.Combine(text, fileName); if (!File.Exists(path)) { string contents = JsonConvert.SerializeObject((object)defaultContent()); File.WriteAllText(path, contents); } try { using FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); using StreamReader streamReader = new StreamReader(stream); result = JsonConvert.DeserializeObject<T>(streamReader.ReadToEnd()); return true; } catch (Exception ex) { Logger.LogError((object)("無法讀取 JSON 設定:" + fileName)); Logger.LogError((object)ex); result = null; return false; } } } public class PluginConfig { public ConfigEntry<bool> DownloadFromRemote { get; } = config.Bind<bool>("Update", "DownloadFromRemote", true, "是否每次啟動都要從遠端下載最新的翻譯資料? (true: 是, false: 否)"); public ConfigEntry<string> DownloadUrl { get; } = config.Bind<string>("Update", "DownloadUrl", "https://raw.githubusercontent.com/Yuieii/ue.Peak.TcnPatch/refs/heads/master/TcnTranslations.json", "翻譯資料的 URL"); public ConfigEntry<Language> AutoDumpLanguage { get; } = config.Bind<Language>("Debug", "AutoDumpLanguage", (Language)9, "自動輸出官方參考翻譯時選擇的原始語言"); public ConfigEntry<bool> WarnMissingAdditionalKeys { get; } = config.Bind<bool>("Debug", "WarnMissingAdditionalKeys", false, "有缺失的附加翻譯key時是否觸發警告"); public ConfigEntry<LanguagePatchMode> LanguagePatchMode { get; } = config.Bind<LanguagePatchMode>("Patch", "LanguagePatchMode", ue.Peak.TcnPatch.LanguagePatchMode.InsertAfterSimplifiedChinese, "設定修正設定中的語言清單時,要放在簡體中文後面,或是取代簡體中文"); public ConfigEntry<bool> ShowPatchCredit { get; } = config.Bind<bool>("Patch", "ShowPatchCredit", true, "在主畫面版本文字後面顯示本模組作者?"); public ConfigEntry<bool> ShowTranslatorCredit { get; } = config.Bind<bool>("Patch", "ShowTranslatorCredit", true, "在主畫面版本文字後面交替顯示翻譯資料的作者?"); public ConfigEntry<bool> ShowModVersionInPatchCredit { get; } = config.Bind<bool>("Patch", "ShowModVersionInPatchCredit", true, "在主畫面版本文字後面顯示這個模組的版本? (v1.3.0)"); public PluginConfig(ConfigFile config) { } } public enum LanguagePatchMode { InsertAfterSimplifiedChinese, ReplaceSimplifiedChinese, Append } internal static class ReflectionExtensions { [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TValue GetReflectionFieldValue<TOwner, TValue>(this TOwner owner, ReflectionMembers.TypedFieldInfo<TOwner, TValue> fieldInfo) { return (TValue)fieldInfo.FieldInfo.GetValue(owner); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool TryGetReflectionFieldValue<TOwner, TValue>(this TOwner owner, ReflectionMembers.TypedFieldInfo<TOwner, TValue> fieldInfo, out TValue result) { if (fieldInfo == null) { result = default(TValue); return false; } result = (TValue)fieldInfo.FieldInfo.GetValue(owner); return true; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void SetReflectionFieldValue<TOwner, TValue>(this TOwner owner, ReflectionMembers.TypedFieldInfo<TOwner, TValue> fieldInfo, TValue value) { fieldInfo.FieldInfo.SetValue(owner, value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool TrySetReflectionFieldValue<TOwner, TValue>(this TOwner owner, ReflectionMembers.TypedFieldInfo<TOwner, TValue> fieldInfo, TValue value) { if (fieldInfo == null) { return false; } fieldInfo.FieldInfo.SetValue(owner, value); return true; } } public static class ReflectionMembers { public static class Fields { public static TypedFieldInfo<LocalizedText, Language> CurrentLanguage { get; } = AccessTools.Field(typeof(LocalizedText), "CURRENT_LANGUAGE"); public static TypedFieldInfo<VersionString, TextMeshProUGUI> VersionStringText { get; } = AccessTools.Field(typeof(VersionString), "m_text"); public static TypedFieldInfo<LoadingScreenAnimation, string> LoadingScreenString { get; } = AccessTools.Field(typeof(LoadingScreenAnimation), "loadingString"); public static TypedFieldInfo<LoadingScreenAnimation, float> LoadingScreenDefaultStringLength { get; } = AccessTools.Field(typeof(LoadingScreenAnimation), "defaultLoadingStringLength"); } public class TypedFieldInfo<TOwner, TValue> { public FieldInfo FieldInfo { get; } public TypedFieldInfo(FieldInfo fieldInfo) { FieldInfo = fieldInfo; base..ctor(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static implicit operator TypedFieldInfo<TOwner, TValue>(FieldInfo fieldInfo) { return (fieldInfo == null) ? null : new TypedFieldInfo<TOwner, TValue>(fieldInfo); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static implicit operator FieldInfo(TypedFieldInfo<TOwner, TValue> fieldInfo) { return fieldInfo?.FieldInfo; } } } public class TranslationParseException : Exception { public string UserMessage { get; } public TranslationParseException(string message, string userMessage) : base(message) { UserMessage = userMessage; } public TranslationParseException(string message) : base(message) { UserMessage = message; } } public class TranslationFile { public const int CurrentFormatVersion = 0; public const string FormatVersionKey = "FormatVersion"; public const string AuthorKey = "Authors"; public const string TranslationEntriesKey = "Translations"; public const string AdditionalTranslationEntriesKey = "AdditionalTranslations"; public List<string> Authors { get; } = new List<string>(); public Dictionary<string, string> Translations { get; } = new Dictionary<string, string>(); public Dictionary<string, string> AdditionalTranslations { get; } = new Dictionary<string, string>(); public static TranslationFile Deserialize(JObject obj) { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Invalid comparison between Unknown and I4 //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_0230: Unknown result type (might be due to invalid IL or missing references) string[] source = new string[2] { "FormatVersion", "Translations" }; if (!((IEnumerable<string>)source).All((Func<string, bool>)obj.ContainsKey)) { return InternalDeserializeFromLegacy(obj); } TranslationFile translationFile = new TranslationFile(); JToken val = obj["FormatVersion"]; if ((int)val.Type != 6) { throw new TranslationParseException($"Format version must be an integer value, found {val.Type}", "無效的格式版本!格式版本必須為一個整數!"); } int num = Extensions.Value<int>((IEnumerable<JToken>)val); if (num > 0) { Plugin.Logger.LogWarning((object)"正在讀取過新版本的翻譯資料!可能會無法正確讀取。"); } JToken val2 = default(JToken); if (obj.TryGetValue("Authors", ref val2)) { JArray val3 = (JArray)(object)((val2 is JArray) ? val2 : null); if (val3 != null) { foreach (JToken item in val3) { translationFile.Authors.Add(Extensions.Value<string>((IEnumerable<JToken>)item)); } } else { JValue val4 = (JValue)(object)((val2 is JValue) ? val2 : null); if (val4 != null) { translationFile.Authors.Add(Extensions.Value<string>((IEnumerable<JToken>)val4)); } else { Plugin.Logger.LogWarning((object)"無效的翻譯者資料! (Authors)"); } } } JToken val5 = obj["Translations"]; JObject val6 = (JObject)(object)((val5 is JObject) ? val5 : null); if (val6 == null) { throw new TranslationParseException($"Translation entries must be an object, found {val5.Type}", "無效的翻譯資料! (Translations)"); } string key; JToken value; foreach (KeyValuePair<string, JToken> item2 in val6) { item2.Deconstruct(out key, out value); string key2 = key; JToken val7 = value; translationFile.Translations[key2] = Extensions.Value<string>((IEnumerable<JToken>)val7); } JToken val8 = default(JToken); if (obj.TryGetValue("AdditionalTranslations", ref val8)) { JObject val9 = (JObject)(object)((val8 is JObject) ? val8 : null); if (val9 == null) { throw new TranslationParseException($"Additional translation entries must be an object, found {val8.Type}", "無效的附加翻譯資料! (AdditionalTranslations)"); } List<string> list = new List<string>(); foreach (KeyValuePair<string, JToken> item3 in val9) { item3.Deconstruct(out key, out value); string text = key; JToken val10 = value; if (list.Contains<string>(text, StringComparer.InvariantCultureIgnoreCase)) { Plugin.Logger.LogWarning((object)("翻譯資料出現已註冊過的附加翻譯key「" + text + "」!新的同名翻譯將會被忽略。")); continue; } list.Add(text); translationFile.AdditionalTranslations[text] = Extensions.Value<string>((IEnumerable<JToken>)val10); } } return translationFile; } private static TranslationFile InternalDeserializeFromLegacy(JObject obj) { TranslationFile translationFile = new TranslationFile(); foreach (var (key, val2) in obj) { translationFile.Translations[key] = Extensions.Value<string>((IEnumerable<JToken>)val2); } return translationFile; } } public static class Utils { [CompilerGenerated] private sealed class <DelayFramesCoroutine>d__2<T> : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public TaskCompletionSource<T> source; public int frames; public Func<T> resultGetter; private int <i>5__1; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <DelayFramesCoroutine>d__2(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <i>5__1 = 0; break; case 1: <>1__state = -1; <i>5__1++; break; } if (<i>5__1 < frames) { <>2__current = (object)new WaitForEndOfFrame(); <>1__state = 1; return true; } source.SetResult(resultGetter()); 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(); } } public static Task WaitForFramesAsync(int frames) { TaskCompletionSource<bool> taskCompletionSource = new TaskCompletionSource<bool>(); ((MonoBehaviour)Plugin.Instance).StartCoroutine(DelayFramesCoroutine(taskCompletionSource, frames, () => true)); return taskCompletionSource.Task; } public static async Task WaitForNextFrameAsync() { await WaitForFramesAsync(1); } [IteratorStateMachine(typeof(<DelayFramesCoroutine>d__2<>))] private static IEnumerator DelayFramesCoroutine<T>(TaskCompletionSource<T> source, int frames, Func<T> resultGetter) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <DelayFramesCoroutine>d__2<T>(0) { source = source, frames = frames, resultGetter = resultGetter }; } } } namespace ue.Peak.TcnPatch.Patches { [HarmonyPatch] public class LanguageSettingPatch { [HarmonyPatch(typeof(LanguageSetting), "GetCustomLocalizedChoices")] [HarmonyPostfix] private static void PatchLanguageChoices(LanguageSetting __instance, ref List<string> __result) { //IL_0050: Unknown result type (might be due to invalid IL or missing references) if (!Plugin.HasOfficialTcn) { switch (Plugin.ModConfig.LanguagePatchMode.Value) { case LanguagePatchMode.ReplaceSimplifiedChinese: break; case LanguagePatchMode.InsertAfterSimplifiedChinese: case LanguagePatchMode.Append: { int num = Enum.GetNames(typeof(Language)).Length; string text = LocalizedText.GetText("CURRENT_LANGUAGE", __instance.ValueToLanguage(num)); Plugin.Logger.LogInfo((object)"正在修正設定中的語言清單..."); __result.Add(text); break; } } } } [HarmonyPatch(typeof(LanguageSetting), "ValueToLanguage")] [HarmonyPostfix] private static void PatchValueToLanguage(int val, ref Language __result) { if (Plugin.HasOfficialTcn) { return; } switch (Plugin.ModConfig.LanguagePatchMode.Value) { case LanguagePatchMode.InsertAfterSimplifiedChinese: __result = (Language)val; break; case LanguagePatchMode.ReplaceSimplifiedChinese: if (val == 9) { __result = (Language)10; } break; case LanguagePatchMode.Append: { int length = Enum.GetValues(typeof(Language)).Length; if (val == length) { __result = (Language)10; } break; } } } } [HarmonyPatch] public class LoadingScreenAnimationPatch { private static bool _loadingScreenAnimationStartTranspiled; [HarmonyPatch(typeof(LoadingScreenAnimation), "Start")] [HarmonyTranspiler] private static IEnumerable<CodeInstruction> PatchLoadingAnimationSwitch(IEnumerable<CodeInstruction> instructions, ILGenerator generator) { //IL_0163: Unknown result type (might be due to invalid IL or missing references) //IL_016d: Expected O, but got Unknown //IL_017c: Unknown result type (might be due to invalid IL or missing references) //IL_0186: Expected O, but got Unknown if (Plugin.HasOfficialTcn) { _loadingScreenAnimationStartTranspiled = true; return instructions; } Plugin.Logger.LogInfo((object)"正在修補 LoadingScreenAnimation.Start() 的 IL code..."); List<CodeInstruction> list = instructions.ToList(); int num = -1; int num2 = -1; for (int i = 0; i < list.Count; i++) { CodeInstruction val = list[i]; if (CodeInstructionExtensions.LoadsField(val, (FieldInfo)ReflectionMembers.Fields.CurrentLanguage, false)) { CodeInstruction val2 = list[i + 1]; if (!(val2.opcode != OpCodes.Ldc_I4_S) && val2.operand.Equals((sbyte)9)) { num = i; num2 = i + 3; break; } } } if (num == -1 || num2 == -1) { Plugin.Logger.LogWarning((object)"沒有在 LoadingScreenAnimation.Start() 找到合適的對應 branch!"); Plugin.Logger.LogWarning((object)"無法以 IL 方式修補 LoadingScreenAnimation.Start()!將會使用 Postfix 方式修補文字。"); return list; } Plugin.Logger.LogInfo((object)"正在插入新的 IL code..."); CodeInstruction val3 = list[num2]; Label label = generator.DefineLabel(); val3.labels.Add(label); List<CodeInstruction> list2 = new List<CodeInstruction>(); list2.Add(CodeInstruction.LoadField(typeof(LocalizedText), "CURRENT_LANGUAGE", false)); list2.Add(new CodeInstruction(OpCodes.Ldc_I4_S, (object)10)); list2.Add(new CodeInstruction(OpCodes.Beq_S, (object)label)); List<CodeInstruction> collection = list2; list.InsertRange(num, collection); _loadingScreenAnimationStartTranspiled = true; return list; } [HarmonyPatch(typeof(LoadingScreenAnimation), "Start")] [HarmonyPostfix] private static void PatchLoadingAnimation(LoadingScreenAnimation __instance) { if (!_loadingScreenAnimationStartTranspiled) { string text = LocalizedText.GetText("LOADING", true); if (ReflectionMembers.Fields.LoadingScreenString == null) { Plugin.Logger.LogWarning((object)"Patcher 找不到欄位: LoadingScreenAnimation.loadingString"); return; } if (ReflectionMembers.Fields.LoadingScreenDefaultStringLength == null) { Plugin.Logger.LogWarning((object)"Patcher 找不到欄位: LoadingScreenAnimation.defaultLoadingStringLength"); return; } Plugin.Logger.LogInfo((object)"正在修正載入中動畫的文字 @ LoadingScreenAnimation.Start()..."); string text2 = text + "..." + text + "..." + text + "..." + text + "..."; __instance.SetReflectionFieldValue(ReflectionMembers.Fields.LoadingScreenString, text2); __instance.SetReflectionFieldValue(ReflectionMembers.Fields.LoadingScreenDefaultStringLength, text2.Length); } } } [HarmonyPatch] public class LocalizedTextPatch { private class AutoDumpRecord { public int FormatVersion { get; } = 0; public List<string> Authors { get; } = new List<string>(); public Dictionary<string, string> Translations { get; } public Dictionary<string, string> AdditionalTranslations { get; } public AutoDumpRecord(Dictionary<string, string> translations, Dictionary<string, string> additionalTranslations) { Translations = translations; AdditionalTranslations = additionalTranslations; base..ctor(); } } private static bool _writtenMainTable; public static HashSet<string> VanillaLocalizationKeys { get; } = new HashSet<string>(); [HarmonyPatch(typeof(LocalizedText), "GetText", new Type[] { typeof(string), typeof(bool) })] [HarmonyPrefix] private static void PatchGetText(string id, ref string __result, ref bool __runOriginal) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) PatchGetText(id, LocalizedText.CURRENT_LANGUAGE, ref __result, ref __runOriginal); } [HarmonyPatch(typeof(LocalizedText), "GetText", new Type[] { typeof(string), typeof(Language) })] [HarmonyPrefix] private static void PatchGetText(string id, Language language, ref string __result, ref bool __runOriginal) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Invalid comparison between Unknown and I4 if (Plugin.TryGetRegistered(id, language, out var result)) { __runOriginal = false; __result = result; } else if ((int)language == 10 && Plugin.TryGetVanilla(id, out result)) { __runOriginal = false; __result = result; } } [HarmonyPatch(typeof(LocalizedText), "LoadMainTable")] [HarmonyPriority(800)] [HarmonyPostfix] private static void PatchLoadMainTableFirstChance() { if (VanillaLocalizationKeys.Count > 0) { foreach (string item in LocalizedText.mainTable.Keys.Where((string key) => !VanillaLocalizationKeys.Contains(key))) { LocalizedText.mainTable.Remove(item); } return; } foreach (string key in LocalizedText.mainTable.Keys) { VanillaLocalizationKeys.Add(key); } } [HarmonyPatch(typeof(LocalizedText), "LoadMainTable")] [HarmonyPriority(0)] [HarmonyPostfix] private static void PatchLoadMainTable() { //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) if (_writtenMainTable) { return; } _writtenMainTable = true; string text = Path.Combine(Paths.ConfigPath, "ue.Peak.TcnPatch"); Directory.CreateDirectory(text); string path = Path.Combine(text, "_AutoTcnTranslations.json"); Language language = Plugin.ModConfig.AutoDumpLanguage.Value; Dictionary<string, string> translations = LocalizedText.mainTable.Where((KeyValuePair<string, List<string>> p) => VanillaLocalizationKeys.Contains(p.Key)).ToDictionary((KeyValuePair<string, List<string>> p) => p.Key, (KeyValuePair<string, List<string>> p) => p.Value[(int)language]); Dictionary<string, string> dictionary = LocalizedText.mainTable.Where((KeyValuePair<string, List<string>> p) => !VanillaLocalizationKeys.Contains(p.Key)).ToDictionary((KeyValuePair<string, List<string>> p) => p.Key, (KeyValuePair<string, List<string>> p) => p.Value[(int)language]); foreach (KeyValuePair<string, string> item in Plugin.RegisteredOrigTable) { item.Deconstruct(out var key, out var value); string key2 = key; string value2 = value; dictionary[key2] = value2; } string contents = JsonConvert.SerializeObject((object)new AutoDumpRecord(translations, dictionary), (Formatting)1); File.WriteAllText(path, contents); Plugin.EmptyTranslationFile = new TranslationFile(); foreach (string key3 in LocalizedText.mainTable.Keys) { if (VanillaLocalizationKeys.Contains(key3)) { Plugin.EmptyTranslationFile.Translations[key3] = ""; } else { Plugin.EmptyTranslationFile.AdditionalTranslations[key3] = ""; } } } } [HarmonyPatch] public class PassportManagerPatch { [HarmonyPatch(typeof(PassportManager), "Awake")] [HarmonyPostfix] private static void PatchCrabland(PassportManager __instance) { Transform val = ((Component)__instance).transform.Find("PassportUI/Canvas/Panel/Panel/BG/Text/Nationality/Text"); if ((Object)(object)val == (Object)null) { Plugin.Logger.LogDebug((object)"!!: Failed to find the Crabland text in passport UI"); return; } LocalizedText val2 = ((Component)val).gameObject.AddComponent<LocalizedText>(); val2.index = "PeakTcnPatch.Passport.Crabland"; val2.RefreshText(); } } [HarmonyPatch] public class VersionStringPatch { private static bool _versionTextMissingWarned; [HarmonyPatch(typeof(VersionString), "Start")] [HarmonyPostfix] private static void PatchVersionStringWarnOnStart() { if (ReflectionMembers.Fields.VersionStringText == null && !_versionTextMissingWarned) { _versionTextMissingWarned = true; Plugin.Logger.LogWarning((object)"VersionString: 找不到版本資訊的 m_text 欄位!"); } } [HarmonyPatch(typeof(VersionString), "Update")] [HarmonyPostfix] private static void PatchVersionString(VersionString __instance) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Invalid comparison between Unknown and I4 if ((int)LocalizedText.CURRENT_LANGUAGE != 10 || !Plugin.ModConfig.ShowPatchCredit.Value) { return; } ReflectionMembers.TypedFieldInfo<VersionString, TextMeshProUGUI> versionStringText = ReflectionMembers.Fields.VersionStringText; if (versionStringText != null) { string name = ((Object)((Component)((Component)__instance).transform.GetParent()).gameObject).name; string name2 = ((Object)((Component)__instance).gameObject).name; if (!(name != "Logo") && !(name2 != "Version")) { TextMeshProUGUI reflectionFieldValue = __instance.GetReflectionFieldValue(versionStringText); bool flag = Plugin.ModConfig.ShowTranslatorCredit.Value && Plugin.CurrentTranslationFile.Authors.Count > 0 && Math.Floor(Time.realtimeSinceStartup / 10f) % 2.0 != 0.0; string text = "繁中翻譯by: " + string.Join("、", Plugin.CurrentTranslationFile.Authors); string text2 = (Plugin.ModConfig.ShowModVersionInPatchCredit.Value ? "繁中支援v1.3.0 by悠依" : "繁中支援by悠依"); string text3 = (flag ? text : text2); ((TMP_Text)reflectionFieldValue).text = ((TMP_Text)reflectionFieldValue).text + " (" + text3 + ")"; } } } } } namespace ue.Peak.TcnPatch.API { public interface ITcnPatch { void RegisterLocalizationKey(string key, string unlocalized); } public class TcnPatch : ITcnPatch { public static ITcnPatch Instance => InternalInstance; internal static TcnPatch InternalInstance { get; } = new TcnPatch(); public void RegisterLocalizationKey(string key, string unlocalized) { Plugin.RegisteredOrigTable[key] = unlocalized; } } } namespace ue.Peak.TcnPatch.Adapters { public static class MoreAscentsSupport { private static readonly Lazy<Type> _gimmickHandlerType = new Lazy<Type>(() => Type.GetType("MoreAscents.AscentGimmickHandler")); private static bool _setupSupport; public static bool IsMoreAscentsInstalled => _gimmickHandlerType.Value != null; private static Type GimmickHandlerType => _gimmickHandlerType.Value; public static void RegisterLocalizations() { //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Expected O, but got Unknown if (!IsMoreAscentsInstalled) { return; } FieldInfo fieldInfo = AccessTools.Field(GimmickHandlerType, "HasInitialized"); if (fieldInfo == null) { return; } if (!(bool)fieldInfo.GetValue(null)) { Plugin.Logger.LogWarning((object)"MoreAscents 還沒初始化!?"); } else { if (_setupSupport) { return; } _setupSupport = true; Plugin.Logger.LogInfo((object)"偵測到 MoreAscents!正在為 MoreAscents 提供繁中支援..."); try { if (!TryFindTypeOrWarn("MoreAscents.AscentGimmick", out var type2) || !TryFindMethodOrWarn(type2, "GetTitle", out var gimmickGetTitleMethod) || !TryFindMethodOrWarn(type2, "GetDescription", out var gimmickGetDescriptionMethod) || !TryFindMethodOrWarn(type2, "GetTitleReward", out var method2) || !TryFindFieldOrWarn(GimmickHandlerType, "gimmicks", out var field2)) { return; } IReadOnlyList<object> readOnlyList = (IReadOnlyList<object>)field2.GetValue(null); MethodInfo methodInfo = AccessTools.PropertyGetter(typeof(AscentData), "Instance"); AscentData val = (AscentData)methodInfo.Invoke(null, Array.Empty<object>()); List<AscentInstanceData> ascents = val.ascents; foreach (object gimmick in readOnlyList) { AscentInstanceData val2 = ((IEnumerable<AscentInstanceData>)ascents).FirstOrDefault((Func<AscentInstanceData, bool>)((AscentInstanceData d) => d.title == (string)gimmickGetTitleMethod.Invoke(gimmick, Array.Empty<object>()) && d.description == (string)gimmickGetDescriptionMethod.Invoke(gimmick, Array.Empty<object>()))); if (val2 != null) { string text = (val2.title = "MoreAscent." + gimmick.GetType().Name); val2.titleReward = text + ".Reward"; ue.Peak.TcnPatch.API.TcnPatch.Instance.RegisterLocalizationKey(text ?? "", (string)gimmickGetTitleMethod.Invoke(gimmick, Array.Empty<object>())); ue.Peak.TcnPatch.API.TcnPatch.Instance.RegisterLocalizationKey(LocalizedText.GetDescriptionIndex(text), (string)gimmickGetDescriptionMethod.Invoke(gimmick, Array.Empty<object>())); ue.Peak.TcnPatch.API.TcnPatch.Instance.RegisterLocalizationKey(text + ".Reward", (string)method2.Invoke(gimmick, Array.Empty<object>())); } } } catch (Exception ex) { Plugin.Logger.LogError((object)"無法為 MoreAscents 提供繁中支援... x_x"); Plugin.Logger.LogError((object)ex); } } static bool TryFindFieldOrWarn(Type type, string fieldName, out FieldInfo field) { field = AccessTools.Field(type, fieldName); if (field != null) { return true; } Plugin.Logger.LogWarning((object)("無法找到 " + type.Name + "." + fieldName + " 欄位!無法為 MoreAscents 提供繁中支援... x_x")); return false; } static bool TryFindMethodOrWarn(Type type, string methodName, out MethodInfo method) { method = AccessTools.Method(type, methodName, (Type[])null, (Type[])null); if (method != null) { return true; } Plugin.Logger.LogWarning((object)("無法找到 " + type.Name + "." + methodName + "() 方法!無法為 MoreAscents 提供繁中支援... x_x")); return false; } static bool TryFindTypeOrWarn(string typeName, out Type type) { type = AccessTools.TypeByName(typeName); if (type != null) { return true; } Plugin.Logger.LogWarning((object)("無法找到 " + typeName + " 類型!無法為 MoreAscents 提供繁中支援... x_x")); return false; } } } }